// ==UserScript== // @name 游戏盈亏监控 // @namespace https://greasyfork.org/users/your-id // @version 2.6.2 // @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, // 轮询间隔 ms profitThreshold: null, // 盈利阈值 lossThreshold: null, // 亏损阈值 monitoring: false, // 监控状态 currentIndex: 0, // 当前行索引 currentPage: 1, // 当前页 totalPages: 1, // 总页数 totalItems: 0, // 总条数 itemsPerPage: 10, // 每页条数 batchSize: 5, // 每批处理条数(非并发限制,只是批次) maxParallel: 3, // 最大并发请求数 activeRequests: 0, // 当前活跃请求数 processedItems: 0, // 已处理条数 monitoringDuration: 40, // 监控时长 分钟 lastCheckTime: 0, // 上次检查时间戳 startTime: 0, // 启动时间戳 panelCollapsed: false, // 面板收起状态 profitAlerts: 0, // 盈利报警计数 lossAlerts: 0, // 亏损报警计数 dialogQueue: [], // 弹窗队列:存放待处理的弹窗任务函数 isDialogProcessing: false, // 是否正在处理弹窗(保证串行) activeDialogs: {}, // 当前活跃弹窗信息,格式 {dialogId: {userName, callback}} dialogCounter: 0 // 弹窗唯一ID计数器 }; // ========== 本地存储辅助 ========== const storage = { get: function(key, defaultValue) { try { if(typeof GM_getValue !== 'undefined') { return GM_getValue(key, defaultValue); } const value = localStorage.getItem(`monitor_${key}`); return value !== null ? JSON.parse(value) : defaultValue; } catch(e) { console.error('Storage get error:', e); return defaultValue; } }, set: function(key, value) { try { if(typeof GM_setValue !== 'undefined') { GM_setValue(key, value); } else { localStorage.setItem(`monitor_${key}`, JSON.stringify(value)); } } catch(e) { console.error('Storage set error:', e); } } }; // ========== 样式插入 ========== function addStyles(){ const css = ` .monitor-panel { position: fixed; top: 20px; right: 20px; z-index: 9999; background: white; 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 0.3s ease; } .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; z-index: 10000; } .toggle-panel:hover { background: #e0e0e0; } .collapsed .panel-content { display: none; } .monitor-header { margin: 0 0 15px 0; 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: white; border: none; border-radius: 4px; font-weight: bold; cursor: pointer; transition: background 0.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 0.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.type = 'text/css'; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); } // ========== 创建控制面板 ========== function createControlPanel() { addStyles(); const panel = document.createElement('div'); panel.className = 'monitor-panel'; panel.id = 'monitorPanel'; const toggleBtn = document.createElement('button'); toggleBtn.className = 'toggle-panel'; toggleBtn.innerHTML = '×'; toggleBtn.title = '收起/展开控制面板'; toggleBtn.addEventListener('click', togglePanel); const panelContent = document.createElement('div'); panelContent.className = 'panel-content'; panelContent.innerHTML = `

游戏盈亏监控

状态:未启动
进度:0/0
页数:1/1
速度:0条/分钟
预计剩余时间:计算中...
盈利超标:0 亏损超标:0
`; panel.appendChild(toggleBtn); panel.appendChild(panelContent); document.body.appendChild(panel); // 初始化保存的面板状态 config.panelCollapsed = storage.get('panelCollapsed', false); if(config.panelCollapsed) { panel.classList.add('collapsed'); toggleBtn.innerHTML = '≡'; } // 恢复输入框和报警数 const savedProfit = storage.get('profitThreshold', null); const savedLoss = storage.get('lossThreshold', null); const savedMinutes = storage.get('monitoringDuration', 40); const savedParallel = storage.get('parallelCount', 3); config.profitAlerts = storage.get('profitAlerts', 0); config.lossAlerts = storage.get('lossAlerts', 0); if(savedProfit !== null) document.getElementById('profitThresholdInput').value = savedProfit; if(savedLoss !== null) document.getElementById('lossThresholdInput').value = savedLoss; document.getElementById('minutesInput').value = savedMinutes; document.getElementById('parallelInput').value = savedParallel; document.getElementById('profitAlerts').textContent = config.profitAlerts; document.getElementById('lossAlerts').textContent = config.lossAlerts; document.getElementById('toggleMonitor').addEventListener('click', toggleMonitoring); } // ========== 控制面板收起/展开 ========== function togglePanel() { const panel = document.getElementById('monitorPanel'); config.panelCollapsed = !panel.classList.contains('collapsed'); if(config.panelCollapsed){ panel.classList.add('collapsed'); this.innerHTML = '≡'; } else { panel.classList.remove('collapsed'); this.innerHTML = '×'; } storage.set('panelCollapsed', config.panelCollapsed); } // ========== 开始/停止监控 ========== function toggleMonitoring() { const profitVal = parseFloat(document.getElementById('profitThresholdInput').value); const lossVal = parseFloat(document.getElementById('lossThresholdInput').value); const minutes = parseInt(document.getElementById('minutesInput').value) || 40; const parallel = parseInt(document.getElementById('parallelInput').value) || 3; if(isNaN(profitVal) && isNaN(lossVal)) { alert('请至少设置一个阈值'); return; } // 保存配置 storage.set('profitThreshold', isNaN(profitVal) ? null : profitVal); storage.set('lossThreshold', isNaN(lossVal) ? null : Math.abs(lossVal)); storage.set('monitoringDuration', minutes); storage.set('parallelCount', parallel); config.profitThreshold = isNaN(profitVal) ? null : profitVal; config.lossThreshold = isNaN(lossVal) ? null : Math.abs(lossVal); config.monitoringDuration = minutes; config.maxParallel = Math.min(Math.max(parallel, 1), 10); config.monitoring = !config.monitoring; const btn = document.getElementById('toggleMonitor'); const status = document.getElementById('statusText'); if(config.monitoring){ btn.textContent = '停止监控'; btn.classList.add('stop'); let statusMsg = '监控中 ('; if(config.profitThreshold) statusMsg += `盈>${config.profitThreshold}`; if(config.lossThreshold) statusMsg += `${config.profitThreshold ? ' ' : ''}亏>${config.lossThreshold}`; status.textContent = statusMsg + ')'; config.startTime = Date.now(); config.processedItems = 0; config.lastCheckTime = Date.now(); startMonitoring(); } else { btn.textContent = '开始监控'; btn.classList.remove('stop'); status.textContent = '已停止'; } } // ========== 启动监控主流程 ========== function startMonitoring() { if(!config.monitoring) return; const firstPageBtn = document.querySelector('.el-pager .number:first-child'); if(firstPageBtn && !firstPageBtn.classList.contains('active')) { safeClick(firstPageBtn, () => { setTimeout(() => initMonitoring(), 2000); }); } else { initMonitoring(); } } // ========== 模拟点击事件 ========== function safeClick(element, callback) { if(simulateClick(element)) { setTimeout(() => { if(callback) callback(); }, 500); } else if(callback) { callback(); } } function simulateClick(element) { if(!element) { console.log('模拟点击失败:元素不存在'); return false; } try { element.click(); return true; } catch(e) { // 尝试MouseEvent } try { const mouseDown = new MouseEvent('mousedown', { bubbles:true, cancelable:true, view:window }); element.dispatchEvent(mouseDown); const mouseUp = new MouseEvent('mouseup', { bubbles:true, cancelable:true, view:window }); element.dispatchEvent(mouseUp); const clickEvent = new MouseEvent('click', { bubbles:true, cancelable:true, view:window }); element.dispatchEvent(clickEvent); return true; } catch(e) {} try { const event = document.createEvent('Event'); event.initEvent('click', true, true); element.dispatchEvent(event); return true; } catch(e) {} console.log('所有点击模拟方法均失败'); return false; } // ========== 初始化监控设置 ========== function initMonitoring() { config.currentPage = 1; config.currentIndex = 0; const initSteps = [ (next) => selectOrderStatus('已支付', (success) => { console.log(success ? '状态设置成功' : '状态设置失败'); next(); }), (next) => setTimeRange(() => { console.log('时间设置完成'); next(); }), (next) => setPageSize(200, (success) => { console.log(success ? '分页设置成功' : '分页设置失败'); next(); }), () => { console.log('开始查询'); updatePaginationInfo(); setTimeout(() => clickQueryButton(), 1000); } ]; function executeStep() { if(initSteps.length > 0) { const step = initSteps.shift(); step(executeStep); } } executeStep(); } // ========== 更新分页信息 ========== function updatePaginationInfo() { const pagination = document.querySelector('.el-pagination'); if(!pagination) { // 无分页时,默认全部数据一页 const rows = document.querySelectorAll('.el-table__row:not(.el-table__row--level)'); config.totalItems = rows.length; config.itemsPerPage = rows.length || config.itemsPerPage; config.totalPages = 1; } else { const totalText = pagination.querySelector('.el-pagination__total')?.textContent || ''; const totalMatch = totalText.match(/(\d+)(?=\s*条)/) || [null, 0]; config.totalItems = parseInt(totalMatch[1]) || 0; config.itemsPerPage = config.itemsPerPage || 200; config.totalPages = Math.ceil(config.totalItems / config.itemsPerPage); } document.getElementById('totalItems').textContent = config.totalItems; document.getElementById('totalPages').textContent = config.totalPages; updateProgressDisplay(); console.log('更新分页信息 - 总条数:', config.totalItems, '每页:', config.itemsPerPage, '总页数:', config.totalPages); } // ========== 点击查询按钮 ========== function clickQueryButton() { const queryBtn = [...document.querySelectorAll('.filter-container button.el-button')] .find(btn => !btn.classList.contains('is-disabled') && btn.textContent.includes('查询')); if(queryBtn) { safeClick(queryBtn, () => { setTimeout(() => checkUsers(), 3000); }); } else { console.log('未找到查询按钮'); setTimeout(() => { if(config.monitoring) clickQueryButton(); }, 1000); } } // ========== 更新进度条和状态 ========== function updateProgressDisplay() { const currentPos = (config.currentPage - 1) * config.itemsPerPage + config.currentIndex + 1; document.getElementById('currentPosition').textContent = Math.min(currentPos, config.totalItems); document.getElementById('displayPage').textContent = config.currentPage; const progressPercent = config.totalItems ? (currentPos / config.totalItems) * 100 : 0; document.getElementById('progressBar').style.width = `${progressPercent.toFixed(2)}%`; // 速度计算 const elapsedMinutes = Math.max((Date.now() - config.startTime) / 60000, 0.01); const speed = config.processedItems / elapsedMinutes; document.getElementById('speedText').textContent = `${speed.toFixed(1)} 条/分钟`; // 预计剩余时间 const remainItems = config.totalItems - config.processedItems; const remainMinutes = speed > 0 ? (remainItems / speed) : 0; const remainText = remainMinutes > 60 ? `${(remainMinutes/60).toFixed(2)} 小时` : `${Math.ceil(remainMinutes)} 分钟`; document.getElementById('timeRemaining').textContent = `预计剩余时间: ${remainText}`; } // ========== 查询用户列表 ========== function checkUsers() { if(!config.monitoring) return; // 当前页面所有数据行 const rows = [...document.querySelectorAll('.el-table__row:not(.el-table__row--level)')]; if(rows.length === 0) { console.log('当前页无数据,尝试翻页或结束'); if(config.currentPage < config.totalPages){ config.currentPage++; jumpToPage(config.currentPage, () => { setTimeout(() => { updatePaginationInfo(); config.currentIndex = 0; checkUsers(); }, 1500); }); } else { console.log('已处理完所有页,停止监控'); stopMonitoring(); } return; } // 分批处理,控制并发 processBatch(rows, config.currentIndex); } // ========== 跳转到指定页 ========== function jumpToPage(pageNum, callback) { const pageBtns = [...document.querySelectorAll('.el-pagination .number')]; const targetBtn = pageBtns.find(btn => btn.textContent == pageNum); if(targetBtn && !targetBtn.classList.contains('active')) { safeClick(targetBtn, () => { setTimeout(() => { if(callback) callback(); }, 1500); }); } else if(callback) { callback(); } } // ========== 批量处理条目,控制最大并发 ========== function processBatch(rows, startIndex) { if(!config.monitoring) return; // 计算本次处理数量,最多batchSize条 const batch = rows.slice(startIndex, startIndex + config.batchSize); if(batch.length === 0) { // 本页处理完,翻页 if(config.currentPage < config.totalPages){ config.currentPage++; config.currentIndex = 0; jumpToPage(config.currentPage, () => { setTimeout(() => { updatePaginationInfo(); checkUsers(); }, 1500); }); } else { stopMonitoring(); } return; } // 并发请求限制 let i = 0; function nextRequest() { if(i >= batch.length) return; if(config.activeRequests < config.maxParallel) { const row = batch[i]; i++; config.activeRequests++; processRow(row) .then(() => { config.activeRequests--; config.processedItems++; config.currentIndex++; updateProgressDisplay(); // 继续发起下一个请求 nextRequest(); }) .catch(err => { console.error('处理用户异常:', err); config.activeRequests--; config.processedItems++; config.currentIndex++; updateProgressDisplay(); nextRequest(); }); // 继续尝试发起并发请求,直到达到maxParallel或无剩余 nextRequest(); } else { // 达到最大并发,等待下一个请求完成后调用 nextRequest } } nextRequest(); } // ========== 处理单行数据 ========== async function processRow(row) { return new Promise(async (resolve) => { try { // 正确获取用户名 const userNameElement = row.querySelector('td.el-table_3_column_21 .el-tooltip'); const userName = userNameElement ? userNameElement.textContent.trim() : null; if(!userName){ console.warn('该行找不到用户名元素'); resolve(); return; } userNameElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); const balance = await fetchUserBalance(userName); if(balance !== null) { console.log(`[${userName}] 余额: ${balance}`); if(config.profitThreshold !== null && balance >= config.profitThreshold) { const exceed = (balance - config.profitThreshold).toFixed(2); await queueDialog({ username: userName, type: 'profit', balance, message: `用户${userName}余额超标: ${balance} (超过${exceed})` }); incrementAlertCount('profit'); } else if(config.lossThreshold !== null && balance <= config.lossThreshold) { const below = (config.lossThreshold - balance).toFixed(2); await queueDialog({ username: userName, type: 'loss', balance, message: `用户${userName}余额不足: ${balance} (低于${below})` }); incrementAlertCount('loss'); } } else { console.warn(`[${userName}] 余额读取失败`); } } catch(e) { console.error('processRow 异常:', e); } finally { resolve(); } }); } // ========== fetchUserBalance 里的定位用户行也改 ========== function fetchUserBalance(username) { return new Promise((resolve) => { try { const rows = [...document.querySelectorAll('.el-table__row:not(.el-table__row--level)')]; const targetRow = rows.find(row => { const userNameElement = row.querySelector('td.el-table_3_column_21 .el-tooltip'); const userName = userNameElement ? userNameElement.textContent.trim() : null; return userName === username; }); if(!targetRow) { console.warn(`找不到用户名为${username}的行`); resolve(null); return; } let btn = targetRow.querySelector('button.btn-view-balance'); if(!btn) { const buttons = targetRow.querySelectorAll('button,a'); btn = [...buttons].find(b => /余额|查看/.test(b.textContent)); } if(!btn) { console.warn(`找不到${username}对应的余额查看按钮`); resolve(null); return; } if(!simulateClick(btn)){ console.warn(`无法点击${username}余额按钮`); resolve(null); return; } const maxWaitTime = 3000; const interval = 100; let waited = 0; const intervalId = setInterval(() => { waited += interval; const dialog = document.querySelector('.modal-balance, .balance-dialog, .balance-popup'); if(dialog){ const balanceEl = dialog.querySelector('.balance-amount, .amount, .balance-value'); if(balanceEl){ let balanceText = balanceEl.textContent.trim(); balanceText = balanceText.replace(/[^\d\.\-]/g, ''); clearInterval(intervalId); const closeBtn = dialog.querySelector('button.btn-close, button.close, .close-btn, .dialog-close'); if(closeBtn){ simulateClick(closeBtn); } else { document.dispatchEvent(new KeyboardEvent('keydown', {key:'Escape'})); } resolve(parseFloat(balanceText)); } } if(waited >= maxWaitTime){ clearInterval(intervalId); console.warn(`等待余额弹窗超时 - 用户:${username}`); resolve(null); } }, interval); } catch(e) { console.error('fetchUserBalance 异常', e); resolve(null); } }); } // ========== 弹窗队列管理 ========== function queueDialog(dialogInfo) { config.dialogQueue.push(dialogInfo); if(!config.isDialogProcessing) { processDialogQueue(); } } // ========== 串行处理弹窗 ========== function processDialogQueue() { if(config.dialogQueue.length === 0) { config.isDialogProcessing = false; return; } config.isDialogProcessing = true; const dialogInfo = config.dialogQueue.shift(); showDialog(dialogInfo).then(() => { setTimeout(() => { processDialogQueue(); }, 300); // 弹窗关闭间隔 }); } // ========== 弹窗显示实现 ========== function showDialog({username, type, balance, message}) { return new Promise((resolve) => { const dialogId = `monitorDialog_${++config.dialogCounter}`; // 创建遮罩层 const mask = document.createElement('div'); mask.style.position = 'fixed'; mask.style.top = 0; mask.style.left = 0; mask.style.width = '100vw'; mask.style.height = '100vh'; mask.style.backgroundColor = 'rgba(0,0,0,0.3)'; mask.style.zIndex = 99999; // 创建弹窗容器 const dialog = document.createElement('div'); dialog.style.position = 'fixed'; dialog.style.top = '50%'; dialog.style.left = '50%'; dialog.style.transform = 'translate(-50%, -50%)'; dialog.style.background = 'white'; dialog.style.borderRadius = '8px'; dialog.style.padding = '20px 30px'; dialog.style.boxShadow = '0 2px 15px rgba(0,0,0,0.3)'; dialog.style.zIndex = 100000; dialog.style.minWidth = '320px'; dialog.style.fontFamily = 'Arial,sans-serif'; dialog.style.color = '#333'; const title = document.createElement('h3'); title.textContent = type === 'profit' ? '盈利警告' : '亏损警告'; title.style.marginTop = '0'; title.style.marginBottom = '15px'; title.style.color = type === 'profit' ? '#67C23A' : '#F56C6C'; const msg = document.createElement('p'); msg.textContent = message; msg.style.marginBottom = '20px'; const closeBtn = document.createElement('button'); closeBtn.textContent = '确定'; closeBtn.style.padding = '8px 20px'; closeBtn.style.border = 'none'; closeBtn.style.backgroundColor = '#409EFF'; closeBtn.style.color = 'white'; closeBtn.style.borderRadius = '4px'; closeBtn.style.cursor = 'pointer'; closeBtn.addEventListener('click', () => { document.body.removeChild(dialog); document.body.removeChild(mask); resolve(); }); dialog.appendChild(title); dialog.appendChild(msg); dialog.appendChild(closeBtn); document.body.appendChild(mask); document.body.appendChild(dialog); }); } // ========== 停止监控 ========== function stopMonitoring() { config.monitoring = false; const btn = document.getElementById('toggleMonitor'); btn.textContent = '开始监控'; btn.classList.remove('stop'); document.getElementById('statusText').textContent = '已停止'; alert('监控已停止'); } // ========== 主入口 ========== function main() { createControlPanel(); } main(); })();