// ==UserScript==
// @name 游戏盈亏监控
// @namespace https://greasyfork.org/users/your-id
// @version 2.5.95
// @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 = `
`;
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 tables = [...dlg.querySelectorAll('table')];
if (!tables.length) {
console.warn(`⚠️ [${user}] 对话框内无表格`);
closeAndContinue();
return;
}
// 找包含用户的行所在的表格和行
let targetRow = null;
let targetTable = null;
for (const table of tables) {
const rows = [...table.querySelectorAll('tbody tr')];
for (const row of rows) {
if (row.textContent.includes(user)) {
targetRow = row;
targetTable = table;
break;
}
}
if (targetRow) break;
}
if (!targetRow) {
console.warn(`⚠️ [${user}] 未找到包含用户名的表格行`);
closeAndContinue();
return;
}
// 在目标行中找 class 包含 el-table_4_column_26 的单元格,就是余额列
const balanceCell = targetRow.querySelector('td.el-table_4_column_26');
if (!balanceCell) {
console.warn(`⚠️ [${user}] 未找到余额单元格`);
closeAndContinue();
return;
}
// 解析余额数字
const val = parseFloat(balanceCell.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);
})();