// ==UserScript== // @name 游戏盈亏监控 // @namespace https://greasyfork.org/users/your-id // @version 2.5.93 // @description 高效监控游戏平台用户盈亏数据,支持自动选择状态和分页,优化大数据处理 // @author Cisco // @match https://7777m.topcms.org/* // @match https://*.topcms.org/* // @icon https://7777m.topcms.org/favicon.ico // @license MIT // @grant GM_notification // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @run-at document-end // @downloadURL none // ==/UserScript== (function() { 'use strict'; const config = { checkInterval: 2000, profitThreshold: null, lossThreshold: null, monitoring: false, currentIndex: 0, currentPage: 1, totalPages: 1, totalItems: 0, itemsPerPage: 0, batchSize: 5, maxParallel: 3, activeRequests: 0, processedItems: 0, monitoringDuration: 40, startTime: 0, panelCollapsed: false, profitAlerts: 0, lossAlerts: 0, dialogIdCounter: 0, activeDialogs: {} }; const storage = { get(key, defaultValue) { try { if (typeof GM_getValue === 'function') { return GM_getValue(key, defaultValue); } const raw = localStorage.getItem(`monitor_${key}`); return raw != null ? JSON.parse(raw) : defaultValue; } catch (e) { console.error('Storage.get 错误:', e); return defaultValue; } }, set(key, value) { try { if (typeof GM_setValue === 'function') { GM_setValue(key, value); } else { localStorage.setItem(`monitor_${key}`, JSON.stringify(value)); } } catch (e) { console.error('Storage.set 错误:', e); } } }; function addStyles() { const css = ` .monitor-panel { position:fixed;top:20px;right:20px;z-index:9999;background:#fff;padding:15px;border:1px solid #ddd;border-radius:5px;box-shadow:0 2px 10px rgba(0,0,0,0.1);font-family:Arial, sans-serif;width:320px;max-height:90vh;overflow-y:auto;transition:all .3s; } .monitor-panel.collapsed { width:40px;height:40px;overflow:hidden;padding:5px; } .toggle-panel { position:absolute;top:5px;right:5px;width:30px;height:30px;border:none;background:#f0f0f0;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px; } .toggle-panel:hover { background:#e0e0e0; } .collapsed .panel-content { display:none; } .monitor-header { margin:0 0 15px;color:#333;font-size:16px;font-weight:bold;border-bottom:1px solid #eee;padding-bottom:10px; } .monitor-input-group { margin-bottom:12px; } .monitor-label { display:block;margin-bottom:5px;color:#666;font-size:13px; } .monitor-input { width:100%;padding:8px;border:1px solid #ddd;border-radius:4px;box-sizing:border-box; } .monitor-button { width:100%;padding:10px;background:#409EFF;color:#fff;border:none;border-radius:4px;font-weight:bold;cursor:pointer;transition:background .3s; } .monitor-button.stop { background:#F56C6C; } .monitor-stats { margin-top:15px;font-size:12px;color:#666;border-top:1px solid #eee;padding-top:10px; } .monitor-stat-row { display:flex;justify-content:space-between;margin-bottom:5px; } .monitor-progress-container { margin:10px 0;height:10px;background:#f0f0f0;border-radius:5px;overflow:hidden; } .monitor-progress-bar { height:100%;background:linear-gradient(to right,#67C23A,#409EFF);transition:width .3s; } .monitor-speed { font-size:11px;color:#999;text-align:right; } .monitor-alert-count { display:flex;justify-content:space-between;margin-top:5px; } .monitor-alert-badge { display:inline-block;padding:2px 6px;border-radius:10px;font-size:11px;font-weight:bold; } .profit-badge { background:#f0f9eb;color:#67C23A; } .loss-badge { background:#fef0f0;color:#F56C6C; } `; const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); } function createControlPanel() { addStyles(); const panel = document.createElement('div'); panel.id = 'monitorPanel'; panel.className = 'monitor-panel'; panel.innerHTML = `

游戏盈亏监控

状态:未启动
进度:0/0
页数:1/1
速度:0 条/分钟
预计剩余时间: 计算中...
盈利超标: 0 亏损超标: 0
`; document.body.appendChild(panel); config.panelCollapsed = storage.get('panelCollapsed', false); if (config.panelCollapsed) panel.classList.add('collapsed'); const savedP = storage.get('profitThreshold', ''); const savedL = storage.get('lossThreshold', ''); if (savedP !== null) document.getElementById('profitThresholdInput').value = savedP; if (savedL !== null) document.getElementById('lossThresholdInput').value = savedL; document.getElementById('profitAlerts').textContent = storage.get('profitAlerts', 0); document.getElementById('lossAlerts').textContent = storage.get('lossAlerts', 0); document.getElementById('togglePanelBtn').addEventListener('click', togglePanel); document.getElementById('toggleMonitor').addEventListener('click', toggleMonitoring); } function togglePanel() { const panel = document.getElementById('monitorPanel'); config.panelCollapsed = !config.panelCollapsed; panel.classList.toggle('collapsed', config.panelCollapsed); storage.set('panelCollapsed', config.panelCollapsed); } function toggleMonitoring() { const p = parseFloat(document.getElementById('profitThresholdInput').value); const l = parseFloat(document.getElementById('lossThresholdInput').value); const m = parseInt(document.getElementById('minutesInput').value) || 40; const par = Math.min(Math.max(parseInt(document.getElementById('parallelInput').value) || 3, 1), 10); if (isNaN(p) && isNaN(l)) { alert('请至少设置一个阈值'); return; } config.profitThreshold = isNaN(p) ? null : p; config.lossThreshold = isNaN(l) ? null : l; config.monitoringDuration = m; config.maxParallel = par; storage.set('profitThreshold', config.profitThreshold); storage.set('lossThreshold', config.lossThreshold); config.monitoring = !config.monitoring; const btn = document.getElementById('toggleMonitor'); const stat = document.getElementById('statusText'); if (config.monitoring) { btn.textContent = '停止监控'; btn.classList.add('stop'); stat.textContent = '监控中'; config.startTime = Date.now(); config.processedItems = 0; startMonitoring(); } else { btn.textContent = '开始监控'; btn.classList.remove('stop'); stat.textContent = '已停止'; } } function safeClick(el) { if (!el) return; ['mousedown','mouseup','click'].forEach(e => el.dispatchEvent(new MouseEvent(e, { bubbles: true, cancelable: true }))); } function startMonitoring() { if (!config.monitoring) return; console.log('开始监控,跳转第一页'); goToFirstPage(() => initMonitoring()); } function goToFirstPage(cb) { const first = document.querySelector('.el-pagination .number:first-child'); if (first && !first.classList.contains('active')) { safeClick(first); setTimeout(cb, 800); } else cb(); } function initMonitoring() { config.currentPage = 1; config.currentIndex = 0; const steps = [ next => selectDropdownOption('.el-select.filter-item','已支付', () => next()), next => setTimeRange(() => next()), next => setPageSize(200, () => next()), () => { updatePaginationInfo(); setTimeout(checkUsers, 800); } ]; (function run() { if (steps.length) steps.shift()(run); })(); } function selectDropdownOption(sel,text,cb) { const dd = document.querySelector(sel); if (!dd) return cb(false); const input = dd.querySelector('.el-input__inner'); if (input && input.value === text) return cb(true); safeClick(dd); setTimeout(() => { const items = [...document.querySelectorAll('.el-select-dropdown__item')].filter(i=>i.offsetParent); const opt = items.find(i=>i.textContent.trim()===text); if (opt) { safeClick(opt); setTimeout(() => cb(true), 400); } else cb(false); }, 400); } function setTimeRange(cb) { const now = new Date(); const start = new Date(now.getTime() - config.monitoringDuration*60000); const inputs = document.querySelectorAll('.el-range-input'); if (inputs.length>=2) { inputs[0].value = formatDate(start); inputs[1].value = formatDate(now); inputs.forEach(i=>['input','change'].forEach(e=>i.dispatchEvent(new Event(e,{ bubbles:true })))); } setTimeout(cb, 400); } function formatDate(d) { const pad = n => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; } function setPageSize(size, cb) { selectDropdownOption('.el-pagination__sizes .el-select', `${size}条/页`, success=>{ if (success) { config.itemsPerPage = size; setTimeout(() => { updatePaginationInfo(); cb(); }, 500); } else cb(); }); } function updatePaginationInfo() { const pag = document.querySelector('.el-pagination__total')?.textContent || ''; const total = parseInt((pag.match(/(\d+)/) || [0,0])[1]) || 0; config.totalItems = total; config.totalPages = config.itemsPerPage ? Math.ceil(total/config.itemsPerPage) : 1; document.getElementById('totalItems').textContent = config.totalItems; document.getElementById('totalPages').textContent = config.totalPages; } function checkUsers() { if (!config.monitoring) return; const rows = document.querySelectorAll('.el-table__row:not(.el-table__row--level)'); if (!rows.length) return setTimeout(startMonitoring, config.checkInterval); config.currentIndex = 0; processBatch(rows); } function processBatch(rows) { if (config.activeRequests < config.maxParallel && config.currentIndex < rows.length) { const row = rows[config.currentIndex++]; const ue = row.querySelector('.el-tooltip[style*="color: rgb(24, 144, 255)"]'); if (ue) { config.activeRequests++; processUser(ue, () => { config.activeRequests--; config.processedItems++; updateProgress(); processBatch(rows); }); } else { config.processedItems++; updateProgress(); processBatch(rows); } } else if (config.currentIndex < rows.length) { setTimeout(() => processBatch(rows), 150); } else { nextPageOrRestart(); } } function updateProgress() { const pos = document.getElementById('currentPosition'); pos.textContent = config.processedItems; const speed = config.processedItems / Math.max((Date.now() - config.startTime) / 60000, 1); document.getElementById('speedText').textContent = `${Math.round(speed)} 条/分钟`; } function nextPageOrRestart() { if (config.currentPage < config.totalPages) { config.currentPage++; safeClick(document.querySelector('.el-pagination .btn-next:not([disabled])')); setTimeout(() => { updatePaginationInfo(); checkUsers(); }, config.checkInterval); } else { config.processedItems = 0; setTimeout(startMonitoring, config.checkInterval); } } function processUser(elem, done) { const username = elem.textContent.trim(); // 关闭其他对话框 document.querySelectorAll('.el-dialog__wrapper:not([style*="none"]) .el-dialog__headerbtn').forEach(btn => safeClick(btn)); if (elem?.href?.includes?.('?')) { elem.href = elem.href.split('?')[0] + `?t=${Date.now()}`; } safeClick(elem); const start = Date.now(); const mo = new MutationObserver(() => { const dlg = document.querySelector('.el-dialog__wrapper:not([style*="none"])'); if (dlg && dlg.textContent.match(/余额|Balance/)) { mo.disconnect(); parseDialog(dlg, username); } if (Date.now() - start > 3000) { mo.disconnect(); console.warn(`⚠️ [${username}] 对话框加载超时`); done(); } }); mo.observe(document.body, { childList: true, subtree: true }); function parseDialog(dlg, user) { try { const ths = [...dlg.querySelectorAll('th')]; const idx = ths.findIndex(th => /余额|Balance/.test(th.textContent)); if (idx === -1) { console.warn(`⚠️ [${user}] 未找到余额列`); closeAndContinue(); return; } // 这里改为模糊匹配用户名字部分,防止拆分错误 const tr = [...dlg.querySelectorAll('tr')].find(r => r.textContent.includes(user)); if (!tr) { console.warn(`⚠️ [${user}] 未找到对应的表格行`); closeAndContinue(); return; } const cell = tr.querySelector(`td:nth-child(${idx + 1})`); if (!cell) { console.warn(`⚠️ [${user}] 未找到余额单元格`); closeAndContinue(); return; } const val = parseFloat(cell.textContent.replace(/[^\d.-]/g, '')); if (isNaN(val)) { console.warn(`⚠️ [${user}] 余额无法解析`); closeAndContinue(); return; } if (config.profitThreshold != null && val >= config.profitThreshold) alertUser(user, val, 'profit'); if (config.lossThreshold != null && val <= config.lossThreshold) alertUser(user, val, 'loss'); } catch (err) { console.error(`❌ [${user}] 解析对话框异常:`, err); } finally { closeAndContinue(); } } function closeAndContinue() { const closeBtn = document.querySelector('.el-dialog__wrapper:not([style*="none"]) .el-dialog__headerbtn'); if (closeBtn) safeClick(closeBtn); done(); } } function alertUser(user,val,type) { const msg = `用户 ${user} 余额 ${type==='profit'?'超标':'不足'}: ${val}`; console.warn(msg); if (typeof GM_notification === 'function') { GM_notification({ title:type==='profit'?'盈利报警':'亏损报警', text:msg, timeout:5000 }); } else alert(msg); const el = document.getElementById(type === 'profit' ? 'profitAlerts' : 'lossAlerts'); const count = parseInt(el.textContent) || 0; el.textContent = count + 1; storage.set(type === 'profit' ? 'profitAlerts' : 'lossAlerts', count + 1); } function init() { const timer = setInterval(() => { if (document.querySelector('.el-table')) { clearInterval(timer); createControlPanel(); } }, 500); } if (document.readyState === 'complete') init(); else window.addEventListener('load', init); })();