// ==UserScript== // @name 游戏盈亏监控 // @namespace https://greasyfork.org/users/your-id // @version 2.5.91 // @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: 0, // 每页记录数 batchSize: 5, // 每批处理条数 maxParallel: 3, // 最大并发检查数 activeRequests: 0, // 当前并发数 processedItems: 0, // 已处理记录数 monitoringDuration: 40, // 监控时长(分钟) startTime: 0, // 监控开始时间戳 panelCollapsed: false, // 面板折叠状态 profitAlerts: 0, // 盈利报警累计数 lossAlerts: 0, // 亏损报警累计数 dialogIdCounter: 0, // 对话框ID计数器 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 = '已停止'; } } /** 安全点击,尝试多种Event */ 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; goToFirstPage(() => initMonitoring()); } /** 跳转到第一页 */ function goToFirstPage(cb) { const first = document.querySelector('.el-pager .number:first-child'); if (first && !first.classList.contains('active')) { safeClick(first); setTimeout(cb, 500); } 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, 500); } ]; (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.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), 300); } else cb(false); }, 300); } /** 设置时间范围 */ function setTimeRange(cb) { const now = new Date(); const start = new Date(now - 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,300); } function formatDate(d) { const z=n=>String(d['get'+d? '' :'']).padStart(2,'0'); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`; } /** 设置每页数量 */ 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.activeRequests--; config.processedItems++; updateProgress(); processBatch(rows); }); } else { config.processedItems++; updateProgress(); processBatch(rows); } } else if (config.currentIndexprocessBatch(rows),100); 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{ updatePaginationInfo(); checkUsers(); },config.checkInterval); } else { config.processedItems=0; setTimeout(startMonitoring,config.checkInterval); } } /** 打开用户对话框并检查余额 */ function processUser(elem, done) { const username = elem.textContent.trim(); const id = `dlg${++config.dialogIdCounter}`; // 关闭其他对话框 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(); done(); } }); mo.observe(document.body,{ childList:true, subtree:true }); function parseDialog(dlg,user) { const ths=[...dlg.querySelectorAll('th')]; const idx=ths.findIndex(th=>/余额|Balance/.test(th.textContent)); const tr=[...dlg.querySelectorAll('tr')].find(r=>r.textContent.includes(user.split('_')[1])); if (tr && idx>-1) { const cell=tr.querySelector(`td:nth-child(${idx+1})`); const val=parseFloat(cell.textContent.replace(/[^\d.-]/g,'')); if (!isNaN(val)) { if (config.profitThreshold!=null && val>=config.profitThreshold) alertUser(user,val,'profit'); if (config.lossThreshold!=null && val<=config.lossThreshold) alertUser(user,val,'loss'); } } // 关闭对话框后继续 safeClick(dlg.querySelector('.el-dialog__headerbtn')); 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); })();