// ==UserScript== // @name MT菠菜数据分析 // @namespace http://tampermonkey.net/ // @version 2.4 // @description 等级分布表只显示前3行,其余折叠可点击展开 // @author YourName // @match https://kp.m-team.cc/* // @grant GM_addStyle // @grant GM_notification // @require https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js // ==/UserScript== (function() { 'use strict'; // ======================= 配置 ======================= const CONFIG = { CHART_COLORS: ['#4CAF50', '#2196F3', '#FF9800', '#9C27B0', '#E53935', '#8BC34A', '#3ad', '#d8a520', '#8B008B', '#B8860B'], DATA_XPATH: '/html/body/div[1]/div/div/div[3]/div/div/div[2]/div[2]/div[2]/div/div/div[2]/div/ul/div/div/div', PANEL_WIDTH: 450, AUTO_CHECK_INTERVAL: 1000, PANEL_OFFSET: 50, // 修改order字段即可更改等级排序(高到低,数字越大越高) USER_LEVELS: [ { color: 'rgb(0, 159, 0)', name: 'Vip', order: 9 }, { color: 'rgb(236, 56, 78)', name: '大臣/mTorrent Master', order: 8 }, { color: 'rgb(0, 100, 0)', name: '總督/Ultimate User', order: 7 }, { color: 'rgb(255, 140, 0)', name: '府尹/Extreme User', order: 6 }, { color: 'rgb(72, 61, 139)', name: '府丞/Veteran User', order: 5 }, { color: 'rgb(139, 0, 139)', name: '知州/Insane User', order: 4 }, { color: 'rgb(0, 191, 255)', name: '通判/Crazy User', order: 3 }, { color: 'rgb(0, 139, 139)', name: '知縣/Elite User', order: 2 }, { color: 'rgb(218, 165, 32)', name: '捕頭/Power User', order: 1 }, { color: 'rgb(51, 51, 51)', name: '小卒/User', order: 0 } ], VISIBLE_LEVELS: 3 // 前几名等级常显 }; // ======================= 样式 ======================= GM_addStyle(` .bet-stats-container { position: fixed; top: 50%; right: 20px; transform: translateY(-50%); width: ${CONFIG.PANEL_WIDTH}px; background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; padding: 15px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 9999; font-family: 'Segoe UI', Arial, sans-serif; max-height: 85vh; overflow-y: auto; transition: all 0.3s ease; opacity: 0; visibility: hidden; } .bet-stats-container.visible { opacity: 1; visibility: visible; } .bet-stats-title { font-weight: 600; margin-bottom: 12px; font-size: 18px; color: #2c3e50; text-align: center; padding-bottom: 8px; border-bottom: 2px solid #f0f3f6; } .bet-stats-table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 14px; } .bet-stats-table th { background-color: #f8f9fa; padding: 8px 12px; border-bottom: 2px solid #e9ecef; color: #495057; } .bet-stats-table td { padding: 8px 12px; border-bottom: 1px solid #e9ecef; color: #6c757d; } .bet-stats-table tr:hover td { background-color: #f8f9fa; } .chart-container { margin: 16px 0; position: relative; height: 220px; } .bet-stats-summary { padding: 12px; background-color: #f8f9fa; border-radius: 6px; margin-top: 16px; font-size: 13px; } .highlight { color: #2c3e50; font-weight: 600; } .analyze-btn { position: fixed; top: 50%; right: 20px; transform: translateY(-50%); z-index: 10000; padding: 10px 20px; background: #4CAF50; color: white; border: none; border-radius: 25px; cursor: pointer; font-size: 14px; box-shadow: 0 3px 8px rgba(76,175,80,0.3); transition: all 0.3s ease; } .analyze-btn:hover { transform: translateY(-50%) translateY(-2px); box-shadow: 0 5px 12px rgba(76,175,80,0.4); } .analyze-btn:disabled { background: #bdc3c7; cursor: not-allowed; } .analyze-btn.hidden { opacity: 0; visibility: hidden; transform: translateY(-50%) translateX(20px); } .close-btn { position: absolute; top: 8px; right: 8px; width: 28px; height: 28px; border-radius: 50%; background: #f8f9fa; border: none; color: #6c757d; cursor: pointer; transition: all 0.2s; } .close-btn:hover { background: #e9ecef; transform: rotate(90deg); } .top-bettors-panel { margin-top: 16px; border: 1px solid #e0e0e0; border-radius: 6px; overflow: hidden; } .top-bettors-header { padding: 10px 15px; background-color: #f8f9fa; cursor: pointer; font-weight: 600; display: flex; justify-content: space-between; align-items: center; } .top-bettors-header:hover { background-color: #e9ecef; } .top-bettors-content { padding: 0; max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; } .top-bettors-content.expanded { max-height: 1000px; padding: 10px; } .top-bettors-option { margin-bottom: 10px; } .top-bettors-option-title { font-weight: 600; margin-bottom: 5px; color: #2c3e50; } .top-bettors-list { list-style-type: none; padding-left: 15px; margin: 0; } .top-bettors-list li { margin-bottom: 3px; display: flex; justify-content: space-between; } .toggle-icon { transition: transform 0.2s; margin-left: 8px; } .toggle-icon.expanded { transform: rotate(180deg); } .color-sample { display: inline-block; width: 12px; height: 12px; border-radius: 2px; margin-right: 6px; vertical-align: middle; } .color-stats-table { margin-top: 16px; width: 100%; border-collapse: collapse; font-size: 13px; } .color-stats-table th, .color-stats-table td { padding: 8px 12px; border-bottom: 1px solid #e9ecef; } .color-stats-table th { background-color: #f8f9fa; text-align: left; } .color-stats-table tr:hover td { background-color: #f8f9fa; } .option-header { background-color: #e9ecef !important; font-weight: bold; } .level-name { white-space: nowrap; } /*新增,用户等级统计表折叠部分*/ .collapsed-extra-levels-row {display: none;} .collapsed-extra-levels-row.show {display: table-row;} .collapse-toggle-row { cursor: pointer; background-color: #fff7e0 !important; font-weight: 600; user-select: none; } .collapse-toggle-arrow { display: inline-block; width: 15px; margin-right: 6px; color: #f39c12; transition: transform 0.25s; } .collapse-toggle-arrow.opened { transform: rotate(90deg); } `); // ======================= 功能代码 ======================= const BetAnalyzer = { getUserLevel: (color) => { if (!color) return { name: '未知', order: -1 }; const level = CONFIG.USER_LEVELS.find(l => l.color.toLowerCase() === color.toLowerCase()); return level || { name: '未知', order: -1 }; }, parseData: () => { const result = document.evaluate(CONFIG.DATA_XPATH, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); return Array.from({length: result.snapshotLength}, (_, i) => { const node = result.snapshotItem(i); const text = node.textContent.trim(); if (text === '') return null; // 查找用户名的span元素 const userSpan = node.querySelector('span[style*="color"]'); let color = null; if (userSpan) { color = userSpan.style.color || null; } return { text, color }; }) .filter(item => item !== null) .reduce((acc, item, index) => { if (index % 4 === 0) acc.push([]); acc[acc.length-1].push(item); return acc; }, []) .map(([time, user, option, magic]) => ({ time: time.text, user: user.text, userColor: user.color, userLevel: BetAnalyzer.getUserLevel(user.color), option: option.text.replace(/^选项/, '').trim(), magic: parseInt(magic.text.replace(/,/g, ''), 10) || 0 })); }, calculateStats: (data) => { const stats = data.reduce((acc, bet) => { const opt = bet.option; if (!acc[opt]) { acc[opt] = { count: 0, total: 0, topBettors: [], levelStats: {} }; } acc[opt].count++; acc[opt].total += bet.magic; // 记录每个选项的投注者 acc[opt].topBettors.push({ user: bet.user, magic: bet.magic, color: bet.userColor, level: bet.userLevel }); // 记录每个选项的等级统计 const levelName = bet.userLevel.name; if (!acc[opt].levelStats[levelName]) { acc[opt].levelStats[levelName] = { count: 0, order: bet.userLevel.order }; } acc[opt].levelStats[levelName].count++; return acc; }, {}); // 对每个选项的投注者按魔力排序,取前三名 Object.keys(stats).forEach(opt => { stats[opt].topBettors.sort((a, b) => b.magic - a.magic); stats[opt].topBettors = stats[opt].topBettors.slice(0, 3); }); return { stats, totalBets: data.length, totalMagic: data.reduce((sum, bet) => sum + bet.magic, 0) }; } }; // ======================= UI代码 ======================= const UIComponents = { createChart: (container, stats, totalBets) => { const canvas = document.createElement('canvas'); container.appendChild(canvas); const ctx = canvas.getContext('2d'); new window.Chart(ctx, { type: 'doughnut', data: { labels: Object.keys(stats), datasets: [{ data: Object.values(stats).map(s => s.count), backgroundColor: CONFIG.CHART_COLORS, borderWidth: 0, hoverOffset: 8 }] }, options: { cutout: '60%', responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { boxWidth: 12, padding: 16, font: { size: 12 } } }, tooltip: { backgroundColor: 'rgba(0,0,0,0.85)', bodyFont: { size: 12 }, callbacks: { label: (context) => { const value = context.parsed; const percentage = ((value / totalBets) * 100).toFixed(1); return `${context.label}: ${value}人 (${percentage}%)`; } } } } } }); }, createTopBettorsPanel: (stats) => { const panel = document.createElement('div'); panel.className = 'top-bettors-panel'; const header = document.createElement('div'); header.className = 'top-bettors-header'; header.innerHTML = ` 📊 各选项投注大佬 (点击展开) `; const content = document.createElement('div'); content.className = 'top-bettors-content'; Object.entries(stats).forEach(([opt, data]) => { if (data.topBettors.length > 0) { const optionDiv = document.createElement('div'); optionDiv.className = 'top-bettors-option'; const title = document.createElement('div'); title.className = 'top-bettors-option-title'; title.textContent = `选项 ${opt}`; const list = document.createElement('ul'); list.className = 'top-bettors-list'; data.topBettors.forEach(bettor => { const li = document.createElement('li'); const colorSpan = bettor.color ? `` : ''; li.innerHTML = `${colorSpan}${bettor.user} (${bettor.level.name.split('/')[0]}) ${bettor.magic.toLocaleString()} 魔力`; list.appendChild(li); }); optionDiv.appendChild(title); optionDiv.appendChild(list); content.appendChild(optionDiv); } }); header.addEventListener('click', () => { content.classList.toggle('expanded'); header.querySelector('.toggle-icon').classList.toggle('expanded'); }); panel.appendChild(header); panel.appendChild(content); return panel; }, createColorStatsTable: (stats) => { const table = document.createElement('table'); table.className = 'color-stats-table'; const thead = document.createElement('thead'); thead.innerHTML = `
| 选项 | 人数 | 占比 | 总魔力 | 人均 |
|---|---|---|---|---|
| ${opt} | ${data.count} | ${((data.count / total.totalBets) * 100).toFixed(1)}% | ${data.total.toLocaleString()} | ${Math.round(data.total/data.count).toLocaleString()} |