您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
根据多重条件自动审核大额订单(完整重构版)
// ==UserScript== // @name 智能订单审核 // @namespace http://tampermonkey.net/ // @version 1.4.5 // @description 根据多重条件自动审核大额订单(完整重构版) // @author Cisco // @match https://7777m.topcms.org/* // @match https://111bet22.topcms.org/* // @match https://888bet.topcms.org/* // @match https://hkgame.topcms.org/* // @match https://666bet.topcms.org/* // @match https://111bet.topcms.org/* // @match https://k9.topcms.org/* // @match https://34jogo.topcms.org/* // @icon https://7777m.topcms.org/favicon.ico // @license MIT // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // ==/UserScript== (function () { "use strict"; // 配置参数 const rawConfig = { currentPage: 1, // 当前页码 totalPages: 1, // 总页数 pageSize: 200, // 每页订单数量 isLastPage: false, // 是否是最后一页 maxWithdrawAmount: 80, // 最大提现金额 betToBonusRatio: 20, // 总投注额/赠送总额最小比率 betToRechargeRatio: 3, // 总投注额/订单充值最小比率 profitToRechargeRatio: 3, // 游戏盈亏/订单充值最大比率 maxSameIPUsers: 2, // 最大相同IP用户数 minBalance: 9, // 最小余额要求 maxWithdrawTimes: 3, // 最大提现次数 password: "", // 充值密码 processedOrders: GM_getValue("processedOrders", {}), // 已处理订单记录 payOutOrders: GM_getValue("payOutOrders", {}), // 已出款订单记录 currentOrderId: null, // 当前处理的订单ID isProcessing: false, // 是否正在处理中 isReturning: false, // 是否正在返回订单页面 panelCollapsed: false, // 面板是否收起 completedOneRound: false, // 是否完成了一轮处理 processingOrderId: null, // 正在处理的订单ID totalBetAmount: 0, // 当前订单的总投注额 profitAmount: 0, // 当前订单的游戏盈亏 }; // 使用 Proxy 监听 config 变化 const config = new Proxy(rawConfig, { set(target, prop, value) { target[prop] = value; if (prop === "currentOrderId") { const el = document.getElementById("currentOrderId"); if (el) el.textContent = value || ""; } return true; } }); // 添加控制面板样式 GM_addStyle(` .monitor-panel-order { position: fixed; top: 20px; right: 80px; 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-order.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: #409EFF; font-size: 16px; font-weight: bold; border-bottom: 1px solid #eee; padding-bottom: 10px; } .config-group { margin-bottom: 12px; position: relative; } .config-label { display: block; margin-bottom: 5px; color: #666; font-size: 13px; font-weight: bold; } .config-label.required:after { content: " *"; color: #F56C6C; } .config-input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } .config-input.error { border-color: #F56C6C; } .error-message { color: #F56C6C; font-size: 12px; margin-top: 3px; display: none; } .monitor-button { width: 100%; padding: 10px; background: #409EFF; color: white; border: none; border-radius: 4px; font-weight: bold; cursor: pointer; transition: background 0.3s; margin-bottom: 10px; } .monitor-button:disabled { background: #C0C4CC; cursor: not-allowed; } .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; } #statusText { font-weight: bold; color: #409EFF; } #processedCount { font-weight: bold; color: #67C23A; } .button-container { display: flex; flex-direction: column; } .monitor-button.hidden { display: none; } `); // ==================== 工具函数 ==================== /** * 延迟执行 * @param {number} ms 毫秒数 * @returns {Promise<void>} */ function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * 等待指定元素出现(可选检测其文本内容),超时返回 null 而不是抛异常 * @param {string} selector - CSS 选择器 * @param {number} [timeout=10000] - 等待的最长时间(毫秒) * @param {ParentNode} [parent=document] - 查找的父容器(默认 document) * @param {AbortSignal|null} [signal=null] - 可选的 AbortSignal,用于提前中断等待 * @param {Object} [options={}] - 额外选项 * @param {boolean} [options.requireText=false] - 是否要求元素有非空文本才算找到 * @returns {Promise<Element|null>} - 找到则返回元素,没找到或超时返回 null */ function waitForElement( selector, timeout = 10000, parent = document, signal = null, options = {} ) { // 如果 parent 为 null,则兜底使用 document if (!parent) parent = document; // 解析选项 const { requireText = false } = options; return new Promise(resolve => { // 如果调用时就已经中断,直接返回 null if (signal?.aborted) return resolve(null); // 计算超时时间点 const endTime = Date.now() + timeout; // 定时检测函数 const check = () => { // 如果外部中断,直接返回 null if (signal?.aborted) return resolve(null); // 查找元素 const element = parent.querySelector(selector); if (element) { // 如果要求有文本内容,但当前元素文本为空,继续等待 if (requireText && !element.textContent.trim()) { if (Date.now() < endTime) { setTimeout(check, 200); // 每 200ms 再检测一次 return; } // 超时依然没内容 return resolve(null); } // 找到符合条件的元素 return resolve(element); } // 元素还没出现,检查是否超时 if (Date.now() >= endTime) { return resolve(null); // 超时返回 null } // 继续等待 setTimeout(check, 200); }; check(); // 启动首次检测 }); } /** * 等待页面跳转完成 * @param {string} targetHash 目标页面hash * @param {number} timeout 超时时间(ms) * @returns {Promise<void>} */ function waitForPageChange(targetHash, timeout = 10000) { return new Promise((resolve, reject) => { if (window.location.hash.includes(targetHash)) { return resolve(); } const timer = setTimeout(() => { window.removeEventListener('hashchange', handler); reject(new Error(`Timeout waiting for page change to ${targetHash}`)); }, timeout); const handler = () => { if (window.location.hash.includes(targetHash)) { clearTimeout(timer); window.removeEventListener('hashchange', handler); resolve(); } }; window.addEventListener('hashchange', handler); }); } /** * 带重试的操作 * @param {Function} operation 操作函数 * @param {number} retries 重试次数 * @param {number} delayMs 重试间隔(ms) * @param {AbortSignal} signal 取消信号 * @returns {Promise<any>} */ async function retryOperation(operation, retries = 3, delayMs = 1000, signal = null) { let lastError; for (let i = 0; i < retries; i++) { if (signal?.aborted) { throw new DOMException('Aborted', 'AbortError'); } try { return await operation(); } catch (err) { lastError = err; if (i < retries - 1) await delay(delayMs); } } throw lastError; } /** * 检查是否在订单页面 * @returns {boolean} */ function isOrderPage() { return window.location.hash.includes("#/order/unread-withdraw"); } /** * 检查是否在代理页面 * @returns {boolean} */ function isAgentPage() { return window.location.hash.includes("#/agent/agent-list"); } /** * 检查是否在提现记录页面 * @returns {boolean} */ function isWithdrawPage() { return window.location.hash.includes("#/order/order-withdraw"); } /** * 检查是否在充值记录页面 * @returns {boolean} */ function isTopupPage() { return window.location.hash.includes("#/order/order-topup"); } /** * 更新状态显示 * @param {string} text 状态文本 */ function updateStatus(text) { const statusEl = document.getElementById("statusText"); if (statusEl) { statusEl.textContent = text; } } /** * 更新按钮显示状态 */ function updateButtonVisibility() { const startBtn = document.getElementById("startBtn"); const stopBtn = document.getElementById("stopBtn"); if (config.isProcessing) { startBtn?.classList.add("hidden"); stopBtn?.classList.remove("hidden"); } else { startBtn?.classList.remove("hidden"); stopBtn?.classList.add("hidden"); } } /** * 验证输入参数 * @returns {boolean} */ function validateInputs() { let isValid = true; const inputs = [ { id: "maxAmount", name: "最大提现金额" }, { id: "betBonusRatio", name: "总投注额大于赠送总额指定倍数" }, { id: "betRechargeRatio", name: "总投注额大于订单充值指定倍数" }, { id: "profitRatio", name: "游戏盈亏大于订单充值指定倍数" }, { id: "maxSameIPUsers", name: "最大相同IP用户数" }, { id: "minBalance", name: "最大余额要求" }, { id: "withdrawPassword", name: "充值密码" }, ]; inputs.forEach((input) => { const element = document.getElementById(input.id); const errorElement = element.parentElement.querySelector(".error-message") || createErrorElement(element.parentElement); if (input.id === "withdrawPassword") { if (!element.value.trim()) { element.classList.add("error"); errorElement.textContent = `${input.name}不能为空`; errorElement.style.display = "block"; isValid = false; } else { element.classList.remove("error"); errorElement.style.display = "none"; } } else { if (!element.value.trim()) { element.classList.add("error"); errorElement.textContent = `${input.name}不能为空`; errorElement.style.display = "block"; isValid = false; } else if (isNaN(element.value)) { element.classList.add("error"); errorElement.textContent = `${input.name}必须是数字`; errorElement.style.display = "block"; isValid = false; } else { element.classList.remove("error"); errorElement.style.display = "none"; } } }); return isValid; } /** * 创建错误提示元素 * @param {HTMLElement} parent 父元素 * @returns {HTMLElement} */ function createErrorElement(parent) { const errorElement = document.createElement("div"); errorElement.className = "error-message"; parent.appendChild(errorElement); return errorElement; } /** * 更新配置 */ function updateConfig() { config.maxWithdrawAmount = parseFloat( document.getElementById("maxAmount").value ); config.betToBonusRatio = parseFloat( document.getElementById("betBonusRatio").value ); config.betToRechargeRatio = parseFloat( document.getElementById("betRechargeRatio").value ); config.profitToRechargeRatio = parseFloat( document.getElementById("profitRatio").value ); config.maxSameIPUsers = parseInt( document.getElementById("maxSameIPUsers").value ); config.minBalance = parseFloat(document.getElementById("minBalance").value); config.password = document.getElementById("withdrawPassword").value; } /** * 标记订单为已处理 * @param {string} orderId 订单ID * @param {string} reason 原因 */ function markOrderAsProcessed(orderId, reason = "") { if (!orderId) return; config.processedOrders[orderId] = { time: new Date().toISOString(), reason: reason || "已处理", }; GM_setValue("processedOrders", config.processedOrders); document.getElementById("processedCount").textContent = Object.keys( config.processedOrders ).length; console.log(`订单 ${orderId} 已标记为已处理`, reason ? `原因: ${reason}` : ""); } /** * 从行中获取订单ID * @param {HTMLElement} row 表格行 * @returns {string} */ function getOrderIdFromRow(row) { // 1. 先找到包含"订单号"的表头单元格 const headerCells = document.querySelectorAll(".el-table__header .cell"); const orderHeader = Array.from(headerCells).find( (cell) => cell.textContent.trim() === "订单号" ); if (orderHeader) { // 2. 获取列索引 const colIndex = orderHeader.closest("th").getAttribute("aria-colindex"); if (colIndex) { // 3. 使用列索引找到对应数据单元格 const orderIdEl = row.querySelector( `td:nth-child(${colIndex}) .cell > span` ); if (orderIdEl) { return orderIdEl.textContent.replace(/[\u200B-\u200D\uFEFF]/g, "").trim(); } } } // 备用方案:通过内容特征匹配 const cells = Array.from(row.querySelectorAll(".cell > span")); const idCell = cells.find((cell) => { const text = cell.textContent.trim(); return /^tx_\d+_\d{8}\w+$/.test(text); // 匹配订单号格式 }); return idCell ? idCell.textContent.replace(/[\u200B-\u200D\uFEFF]/g, "").trim() : ""; } /** * 更新分页信息 */ function updatePaginationInfo() { const pagination = document.querySelector(".el-pagination"); if (!pagination) { console.error("无法找到分页元素"); // 默认值 config.currentPage = 1; config.totalPages = 1; config.isLastPage = true; return; } // 当前页 const activePage = pagination.querySelector(".el-pager .number.active"); config.currentPage = activePage ? parseInt(activePage.textContent) : 1; // 每页数量 const pageSizeSelect = pagination.querySelector( ".el-select-dropdown__item.selected" ); config.pageSize = pageSizeSelect ? parseInt(pageSizeSelect.textContent) : null; // 总条数 let totalItems = null; const totalTextEl = pagination.querySelector(".el-pagination__total"); if (totalTextEl) { const totalMatch = totalTextEl.textContent.match(/共\s*(\d+)\s*条/); if (totalMatch) { totalItems = parseInt(totalMatch[1]); } } // 计算总页数 if (totalItems !== null && config.pageSize) { config.totalPages = Math.ceil(totalItems / config.pageSize); } else { config.totalPages = config.currentPage || 1; } // 是否最后一页 const nextBtn = pagination.querySelector(".btn-next"); if (config.totalPages === 1) { config.isLastPage = true; } else if (nextBtn) { config.isLastPage = nextBtn.disabled; } else { config.isLastPage = config.currentPage >= config.totalPages; } console.log( `当前页码: ${config.currentPage}/${config.totalPages}`, `每页: ${config.pageSize || "-"}条`, `是否末页: ${config.isLastPage}` ); } /** * 清空所有搜索输入框 */ function clearAllSearchInputs() { // 清空订单号输入框 const orderInput = document.querySelector( '.filter-container input[placeholder="请输入订单号"]' ); if (orderInput) { orderInput.value = ""; orderInput.dispatchEvent(new Event("input", { bubbles: true })); } // 清空用户ID输入框 const userIdInput = document.querySelector( '.filter-container input[placeholder="请输入用户ID"]' ); if (userIdInput) { userIdInput.value = ""; userIdInput.dispatchEvent(new Event("input", { bubbles: true })); } // 重置第三方选择器 const thirdPartySelect = document.querySelector( '.el-select input[placeholder="全部"]' ); if (thirdPartySelect && thirdPartySelect.value !== "全部") { thirdPartySelect.value = "全部"; thirdPartySelect.dispatchEvent(new Event("input", { bubbles: true })); } // 清空日期范围选择器 const dateInputs = document.querySelectorAll(".el-range-input"); dateInputs.forEach((input) => { if (input.value) { input.value = ""; input.dispatchEvent(new Event("input", { bubbles: true })); } }); } /** * 添加控制面板 */ function addControlPanel() { const panel = document.createElement("div"); panel.className = "monitor-panel-order"; panel.id = "autoWithdrawPanel"; // 添加收起/展开按钮 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 = ` <h3 class="monitor-header">智能自动出款系统</h3> <div class="config-group"> <label class="config-label required">最大提现金额</label> <input type="number" class="config-input" id="maxAmount" value="${ config.maxWithdrawAmount }" required> <div class="error-message"></div> </div> <div class="config-group"> <label class="config-label required">总投注额大于赠送总额指定倍数</label> <input type="number" class="config-input" id="betBonusRatio" value="${ config.betToBonusRatio }" step="0.1" required> <div class="error-message"></div> </div> <div class="config-group"> <label class="config-label required">总投注额大于订单充值指定倍数</label> <input type="number" class="config-input" id="betRechargeRatio" value="${ config.betToRechargeRatio }" step="0.1" required> <div class="error-message"></div> </div> <div class="config-group"> <label class="config-label required">游戏盈亏小于订单充值指定倍数</label> <input type="number" class="config-input" id="profitRatio" value="${ config.profitToRechargeRatio }" step="0.1" required> <div class="error-message"></div> </div> <div class="config-group"> <label class="config-label required">最大相同IP用户数</label> <input type="number" class="config-input" id="maxSameIPUsers" value="${ config.maxSameIPUsers }" required> <div class="error-message"></div> </div> <div class="config-group"> <label class="config-label required">最大余额要求</label> <input type="number" class="config-input" id="minBalance" value="${ config.minBalance }" required> <div class="error-message"></div> </div> <div class="config-group"> <label class="config-label required">充值密码</label> <input type="text" class="config-input" id="withdrawPassword" value="${ config.password }" required> <div class="error-message"></div> </div> <div class="button-container"> <button id="startBtn" class="monitor-button ${ config.isProcessing ? "hidden" : "" }">开始处理</button> <button id="stopBtn" class="monitor-button stop ${ !config.isProcessing ? "hidden" : "" }">停止处理</button> </div> <div class="button-container"> <button id="clearCacheBtn" class="monitor-button">清理缓存</button> </div> <div class="monitor-stats"> <div class="monitor-stat-row"> <span>状态:</span> <span id="statusText">待命</span> </div> <div class="monitor-stat-row"> <span>当前处理:</span> <span id="currentOrderId">${config.currentOrderId || ""}</span> </div> <div class="monitor-stat-row"> <span>已处理:</span> <span id="processedCount">${ Object.keys(config.processedOrders).length }</span> 单 </div> <div class="monitor-stat-row"> <span>已出款:</span> <span id="payOutOrders">${Object.keys( config.payOutOrders ).join(", ")}</span> </div> </div> `; panel.appendChild(toggleBtn); panel.appendChild(panelContent); document.body.appendChild(panel); // 恢复面板状态 config.panelCollapsed = GM_getValue("panelCollapsed", false); if (config.panelCollapsed) { panel.classList.add("collapsed"); toggleBtn.innerHTML = "≡"; } // 事件监听 document.getElementById("startBtn").addEventListener("click", function () { if (validateInputs()) { updateConfig(); processor.start(); } }); document.getElementById("stopBtn").addEventListener("click", function () { processor.stop(); }); // 为所有输入框添加实时验证 document.querySelectorAll(".config-input").forEach((input) => { input.addEventListener("input", function () { validateInputs(); }); }); // 绑定清理缓存事件 document.getElementById("clearCacheBtn").addEventListener("click", function () { if (confirm("确定要清理缓存数据吗?这会清空已处理订单记录。")) { config.processedOrders = {}; config.payOutOrders = {}; config.currentOrderId = null; GM_setValue("processedOrders", {}); GM_setValue("payOutOrders", {}); GM_setValue("currentOrderId", null); document.getElementById("processedCount").textContent = "0"; document.getElementById("payOutOrders").textContent = ""; document.getElementById("currentOrderId").textContent = ""; updateStatus("缓存已清理"); } }); } /** * 收起/展开面板 */ function togglePanel() { const panel = document.getElementById("autoWithdrawPanel"); config.panelCollapsed = !panel.classList.contains("collapsed"); if (config.panelCollapsed) { panel.classList.add("collapsed"); this.innerHTML = "≡"; } else { panel.classList.remove("collapsed"); this.innerHTML = "×"; } GM_setValue("panelCollapsed", config.panelCollapsed); } // ==================== 主流程控制器 ==================== class OrderProcessor { constructor(config) { this.config = config; this.abortController = new AbortController(); this.currentTask = null; } /** * 开始处理流程 */ async start() { if (this.currentTask) return; this.config.isProcessing = true; updateButtonVisibility(); updateStatus("开始处理订单..."); this.currentTask = this.runProcessingLoop() .catch(err => { console.error('Processing error:', err); updateStatus(`处理出错: ${err.message}`); }) .finally(() => { this.currentTask = null; updateButtonVisibility(); }); } /** * 停止处理流程 */ stop() { this.abortController.abort(); this.config.isProcessing = false; this.abortController = new AbortController(); updateStatus("处理已停止"); } /** * 主处理循环 */ async runProcessingLoop() { const { signal } = this.abortController; while (this.config.isProcessing && !signal.aborted) { try { if (isOrderPage()) { console.log('当前在订单页面'); await this.processOrderPage(signal); } else { console.log('当前在订单页面'); await this.navigateToOrderPage(); } } catch (err) { if (err.name === 'AbortError') break; console.error('Loop error:', err); await delay(3000); } } } /** * 处理订单页面 * @param {AbortSignal} signal 取消信号 */ async processOrderPage(signal) { if (!isOrderPage()) { // 如果当前不是订单页面就跳转过来 processor.navigateToOrderPage(); return; } if (signal.aborted) return; // 1. 确保页面完全加载 const el = await waitForElement('.el-table--scrollable-x .el-table__body .el-table__row .cell > span', 10000, null, signal, { requireText: true }); if (!el) { console.warn('没找到元素或超时'); await this.processOrderPage(signal); return; } // 4. 设置订单状态为"已支付" //await this.selectDropdownOption("请选择", "200条/页", signal); // 2. 更新分页信息 updatePaginationInfo(); // 3. 如果不是最后一页,先跳转到最后一页 if (!this.config.isLastPage && this.config.currentPage < this.config.totalPages) { await this.goToLastPage(signal); return; } // 5. 点击查询按钮 // const queryBtn = await this.findQueryButton(signal); // queryBtn.click(); // // 6. 等待查询结果 // await delay(1500); // 4. 获取当前页订单行 const rows = await this.getOrderRows(signal); console.log("当前页订单所有行", rows); if (rows.length === 0) { if (this.config.currentPage > 1) { await this.goToPrevPage(signal); } else { await this.startNewRound(signal); } return; } // 5. 从最后一行开始处理 for (let i = rows.length - 1; i >= 0; i--) { if (signal.aborted || !this.config.isProcessing) break; try { console.log("当前页订单行", rows[i]); await this.processSingleOrder(rows[i], signal); } catch (err) { console.error(`处理订单失败:`, err); const orderId = getOrderIdFromRow(rows[i]); markOrderAsProcessed(orderId, `处理失败: ${err.message}`); } } // 6. 当前页处理完成后翻页或开始新一轮 if (this.config.currentPage > 1) { await this.goToPrevPage(signal); } else { await this.startNewRound(signal); } } /** * 加载已处理订单 */ async loadProcessedOrders() { const stored = await GM_getValue("processedOrders"); if (stored && typeof stored === "object" && !Array.isArray(stored)) { // 如果是 Proxy 或特殊对象,用 Object.assign 复制到空对象 config.processedOrders = Object.assign(Object.create(null), stored); } else { config.processedOrders = Object.create(null); } console.log("加载后的 processedOrders:", config.processedOrders); } /** * 处理单个订单 * @param {HTMLElement} row 订单行 * @param {AbortSignal} signal 取消信号 */ async processSingleOrder(row, signal) { const orderId = getOrderIdFromRow(row); console.log("当前订单ID", orderId); if (config.processedOrders[orderId]) { console.log(`订单 ${orderId} 已处理,跳过`); return; } this.config.currentOrderId = orderId; updateStatus(`处理订单: ${orderId}`); // 1. 检查提现金额 const amount = await this.getWithdrawAmount(row, signal); console.log("订单号:",orderId,"提现金额:",amount); if (amount == 0 || amount > this.config.maxWithdrawAmount) { return markOrderAsProcessed(orderId, "金额超限"); } // 2. 检查用户详情 const userDetails = await this.getUserDetails(row, signal); if (!userDetails) { return markOrderAsProcessed(orderId, "获取用户详情失败"); } // 3. 检查财务条件 if (!this.checkFinancialConditions(userDetails)) { return markOrderAsProcessed(orderId, "财务条件不满足"); } // 4. 检查提现记录 // const withdrawOk = await this.checkWithdrawRecords(userDetails.userId, signal); // if (!withdrawOk) { // return markOrderAsProcessed(orderId, "提现记录不满足"); // } // 5. 检查代理条件 await this.processAgentPage(userDetails.userId, signal); const agentOk = await this.checkAgentConditions(signal); if (!agentOk) { return markOrderAsProcessed(orderId, "代理条件不满足"); } // 6. 返回订单页面 await this.goBackToOrderPage(signal); console.log("返回订单页面"); // 重新查找订单行(不依赖旧的row) const freshRow = await this.findOrderRowById(orderId, signal); if (!freshRow) { return markOrderAsProcessed(orderId, "返回后找不到订单"); } // 7. 批准订单 await this.approveOrder(freshRow, signal); // 8. 标记为已处理 markOrderAsProcessed(orderId, "已批准"); this.config.payOutOrders[orderId] = true; GM_setValue("payOutOrders", this.config.payOutOrders); document.getElementById("payOutOrders").textContent = Object.keys( this.config.payOutOrders ).join(", "); } /** * 返回到订单页面 * @param {AbortSignal} signal 取消信号 */ async goBackToOrderPage(signal) { updateStatus("返回订单页面..."); // 跳转到订单页面 await this.navigateTo("#/order/unread-withdraw", signal); // 等待查询结果 await delay(1500); } /** * 在订单列表页中根据 orderId 重新查找订单行 * @param {string} orderId 订单ID * @param {AbortSignal} signal 取消信号 * @returns {Promise<HTMLElement|null>} 返回找到的行,如果找不到则返回 null */ async findOrderRowById(orderId, signal) { updateStatus(`正在查找订单 ${orderId}...`); const maxAttempts = 5; let attempts = 0; while (attempts < maxAttempts && !signal.aborted) { // 方法1:使用列位置(第7列的单元格) const cells = Array.from(document.querySelectorAll('.el-table__body tr td:nth-child(7) .cell')); const targetCell = cells.find(cell => cell.textContent.trim() === orderId); if (targetCell) { const row = targetCell.closest('tr'); if (row) return row; } // 方法2:如果方法1失效,改用遍历所有行,检查第7列的文本 const rows = document.querySelectorAll('.el-table__body tr'); for (const row of rows) { const orderCell = row.querySelector('td:nth-child(7) .cell'); if (orderCell?.textContent.trim() === orderId) { return row; } } // 如果没有找到,等待后重试 attempts++; await delay(1000); } console.error(`找不到订单 ${orderId} 的行`); return null; } /** * 处理提现记录页面 * @param {AbortSignal} signal 取消信号 */ async processWithdrawPage(signal) { if (signal.aborted) return; updateStatus(`检查用户 ${this.config.currentUserId} 的提现记录...`); try { // 1. 确保页面完全加载 const el = await waitForElement('.el-table__body', 10000, null, signal); if (!el) { console.warn('没找到元素或超时'); return; } // 2. 设置用户ID搜索条件 const idInput = await waitForElement( 'input[placeholder="请输入用户ID"].el-input__inner', 5000, null, signal ); // 清空并设置用户ID await this.setInputValue(idInput, this.config.currentUserId, signal); // 3. 调整日期范围(当天GMT+0时间) await this.adjustWithdrawDateRange(signal); // 4. 设置订单状态为"已支付" await this.selectDropdownOption("全部状态", "已支付", signal); // 5. 点击查询按钮 const queryBtn = await this.findQueryButton(signal); queryBtn.click(); // 6. 等待查询结果 await delay(2000); // 7. 检查提现记录 const todayCount = await this.checkWithdrawTimes(signal); if (todayCount > this.config.maxWithdrawTimes) { throw new Error(`当天提现次数已达 ${todayCount} 次(限制: ${this.config.maxWithdrawTimes} 次)`); } // 8. 检查CoinPay记录 const hasCoinPay = await this.checkCoinPayWithdraw(signal); if (hasCoinPay) { throw new Error("检测到CoinPay提现记录"); } } catch (err) { console.error('处理提现页面出错:', err); markOrderAsProcessed(this.config.currentOrderId, `提现记录检查失败: ${err.message}`); await this.navigateToOrderPage(); throw err; } } /** * 调整提现记录查询日期范围(GMT+0当天) */ async adjustWithdrawDateRange(signal) { const dateInputs = await waitForElement( ".el-range-input", 5000, null, signal ); if (!dateInputs || dateInputs.length < 2) { throw new Error("找不到日期范围输入框"); } const now = new Date(); const startDate = new Date(now); startDate.setHours(0, 0, 0, 0); startDate.setHours(startDate.getHours() - 8); // 转为GMT+0 const endDate = new Date(now); endDate.setHours(23, 59, 59, 999); endDate.setHours(endDate.getHours() - 8); // 转为GMT+0 const formatDate = (date) => { const pad = (num) => num.toString().padStart(2, '0'); return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; }; const setter = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, "value" ).set; setter.call(dateInputs[0], formatDate(startDate)); dateInputs[0].dispatchEvent(new Event("input", { bubbles: true })); setter.call(dateInputs[1], formatDate(endDate)); dateInputs[1].dispatchEvent(new Event("input", { bubbles: true })); } /** * 处理充值记录页面 * @param {AbortSignal} signal 取消信号 */ async processTopupPage(signal) { if (signal.aborted) return; updateStatus(`检查用户 ${this.config.currentUserId} 的充值记录...`); try { // 1. 确保页面完全加载 await waitForElement('.el-table__body', 10000, null, signal); // 2. 设置用户ID搜索条件 const idInput = await waitForElement( 'input[placeholder="请输入用户ID"].el-input__inner', 5000, null, signal ); // 清空并设置用户ID await this.setInputValue(idInput, this.config.currentUserId, signal); // 3. 调整日期范围(最近7天) await this.adjustTopupDateRange(signal); // 4. 设置订单状态为"已支付" await this.selectDropdownOption("全部状态", "已支付", signal); // 5. 点击查询按钮 const queryBtn = await this.findQueryButton(signal); queryBtn.click(); // 6. 等待查询结果 await delay(2000); // 7. 获取最新充值记录 const latestRecharge = await this.getLatestRecharge(signal); if (!latestRecharge) { throw new Error("没有找到充值记录"); } // 8. 验证投注额与充值额比率 if (this.config.totalBetAmount <= latestRecharge.amount * this.config.betToRechargeRatio) { throw new Error(`投注额 ${this.config.totalBetAmount} 不满足大于充值额 ${latestRecharge.amount} 的 ${this.config.betToRechargeRatio} 倍要求`); } // 9. 验证盈亏与充值额比率 if (Math.abs(this.config.profitAmount) < latestRecharge.amount * this.config.profitToRechargeRatio) { throw new Error(`盈亏 ${this.config.profitAmount} 低于充值额 ${latestRecharge.amount} 的 ${this.config.profitToRechargeRatio} 倍限制`); } } catch (err) { console.error('处理充值页面出错:', err); markOrderAsProcessed(this.config.currentOrderId, `充值记录检查失败: ${err.message}`); await this.navigateToOrderPage(); throw err; } } /** * 调整充值记录查询日期范围(最近7天) */ async adjustTopupDateRange(signal) { const dateInputs = await waitForElement( ".el-range-input", 5000, null, signal ); if (!dateInputs || dateInputs.length < 2) { throw new Error("找不到日期范围输入框"); } const now = new Date(); const endDate = new Date(now); endDate.setHours(23, 59, 59, 999); endDate.setHours(endDate.getHours() - 8); // 转为GMT+0 const startDate = new Date(endDate); startDate.setDate(startDate.getDate() - 6); // 7天范围 startDate.setHours(0, 0, 0, 0); const formatDate = (date) => { const pad = (num) => num.toString().padStart(2, '0'); return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; }; const setter = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, "value" ).set; setter.call(dateInputs[0], formatDate(startDate)); dateInputs[0].dispatchEvent(new Event("input", { bubbles: true })); setter.call(dateInputs[1], formatDate(endDate)); dateInputs[1].dispatchEvent(new Event("input", { bubbles: true })); } /** * 获取最新充值记录 */ async getLatestRecharge(signal) { const rows = document.querySelectorAll('.el-table__body .el-table__row'); if (rows.length <= 1) return null; // 第一行是表头 // 取第二行(第一行数据),第5列是金额(根据实际结构调整) const amountCell = rows[1].querySelector('td:nth-child(5) .cell'); if (!amountCell) return null; const amount = parseFloat(amountCell.textContent.replace(/[^\d.-]/g, '')) || 0; return { amount, time: new Date().toISOString() }; } /** * 获取提现金额 * @param {HTMLElement} row 订单行 * @param {AbortSignal} signal 取消信号 * @returns {Promise<number>} */ async getWithdrawAmount(row, signal) { // 第8列通常是金额列 console.log("当前订单row", row); // 数值解析函数 const parseValue = (text) => { // 移除所有非数字字符(保留小数点和负号) const numStr = text.replace(/[^\d.-]/g, ""); return parseFloat(numStr) || 0; }; const amountNode = row.querySelector("td:nth-child(8) .cell > span"); if (!amountNode) { // 如果第8列找不到,尝试其他可能的位置 const amountNodes = row.querySelectorAll("td .cell > span"); let foundNode = null; for (const node of amountNodes) { // 检查是否是金额(包含数字和小数点) if (/^\d+\.\d{2}$/.test(node.textContent.trim())) { foundNode = node; break; } } if (!foundNode) { throw new Error("提取提现金额失败"); } return parseValue(foundNode.textContent); } return parseValue(amountNode.textContent); } /** * 查找查询按钮(带重试机制) */ async findQueryButton(signal) { return await retryOperation( async () => { const buttons = document.querySelectorAll('.filter-container button.el-button'); const btn = Array.from(buttons).find(b => b.textContent.includes('查询') || (b.querySelector('span')?.textContent.includes('查询')) ); if (!btn) throw new Error('找不到查询按钮'); if (btn.disabled) throw new Error('查询按钮不可用'); return btn; }, 3, // 重试3次 1000, // 间隔1秒 signal ); } /** * 获取用户详情 * @param {HTMLElement} row 订单行 * @param {AbortSignal} signal 取消信号 * @returns {Promise<Object>} */ async getUserDetails(row, signal) { // 使用更稳定的选择器定位用户名元素 let usernameEl = row.querySelector("td:nth-child(2) .el-tooltip"); // 用户名通常在第二列 if (!usernameEl) { // 如果第二列找不到,尝试其他方式 const usernameEls = row.querySelectorAll(".el-tooltip"); if (usernameEls.length > 0) { usernameEl = usernameEls[0]; // 取第一个el-tooltip元素 } else { // throw new Error("找不到用户名元素"); return null; } } // 点击用户名打开详情弹窗 usernameEl.click(); // 等待弹窗出现 const dialog = await waitForElement( '.el-dialog__wrapper:not([style*="display: none"])', 5000, document, signal ); if (!dialog) { // throw new Error("用户详情弹窗格式异常"); return null; } // 等待财务表格渲染并有内容 const el = await waitForElement( ".el-table__body .cell > span", 5000, dialog, signal, { requireText: true } ); if (!el) { // throw new Error("用户详情弹窗格式异常"); return null; } // 获取弹窗中的所有表格 const tables = dialog.querySelectorAll(".el-table"); if (tables.length < 2) { //throw new Error("用户详情弹窗格式异常"); return null; } console.log("用户详情弹窗第一个表格", tables[0]); console.log("用户详情弹窗第二个表格", tables[1]); console.log("用户详情弹窗第三个表格", tables[2]); console.log("用户详情弹窗第四个表格", tables[3]); console.log("用户详情弹窗第五个表格", tables[4]); console.log("用户详情弹窗第六个表格", tables[5]); // 第二个表格包含所需的财务数据 const financialTable = tables[1]; const cells = financialTable.querySelectorAll(".el-table__body .cell > span"); // 按固定位置提取数值(索引从0开始) // 列顺序:余额, 误差, 订单充值, 订单提现, 提现手续费, 公积金购买, 通行证购买, 充提差, 游戏盈亏, 总投注额, 总打码量, 赠送总额 const balanceText = cells[0]?.textContent || "0"; // 余额 const totalRechargeText = cells[2]?.textContent || "0"; // 订单充值 const profitText = cells[8]?.textContent || "0"; // 游戏盈亏 const totalBetText = cells[9]?.textContent || "0"; // 总投注额 const totalBonusText = cells[11]?.textContent || "0"; // 赠送总额 // 第一个表格包含相同IP用户数 const sameIPTable = tables[0]; const sameIPUsersText = sameIPTable?.querySelector(".el-table__body tr td:nth-child(4) .cell > span")?.textContent || "0"; // 数值解析函数 const parseValue = (text) => { // 移除所有非数字字符(保留小数点和负号) const numStr = text.replace(/[^\d.-]/g, ""); return parseFloat(numStr) || 0; }; // 解析各数值 const totalBet = parseValue(totalBetText); // 总投注额 const totalBonus = parseValue(totalBonusText); // 赠送总额 const totalRecharge = parseValue(totalRechargeText); // 订单充值 const profit = parseValue(profitText); // 游戏盈亏 const sameIPUsers = parseInt(sameIPUsersText) || 0; // 相同IP用户数 const balance = parseValue(balanceText); // 余额 this.config.totalBetAmount = totalBet; // 记录当前订单的总投注额 this.config.profitAmount = profit; // 记录当前订单的游戏盈亏 // 从用户信息区域提取用户ID const userIdEl = dialog.querySelector( ".el-table__row .cell div div:nth-child(3)" ); const userId = userIdEl ? userIdEl.textContent.replace("ID:", "").trim() : null; console.log("订单号:",this.config.currentOrderId,"用户ID:",userId); console.log("订单号:",this.config.currentOrderId,"相同IP用户数:",sameIPUsers); console.log("订单号:",this.config.currentOrderId,"余额:",balance); console.log("订单号:",this.config.currentOrderId,"订单充值:",totalRecharge); console.log("订单号:",this.config.currentOrderId,"游戏盈亏:",profit); console.log("订单号:",this.config.currentOrderId,"总投注额:",totalBet); console.log("订单号:",this.config.currentOrderId,"赠送总额:",totalBonus); if (!userId) { // throw new Error("无法获取用户ID"); return null; } // 关闭弹窗 this.closeDialog(dialog); return { userId, totalBet, totalBonus, totalRecharge, profit, sameIPUsers, balance }; } /** * 检查财务条件 * @param {Object} details 用户详情 * @returns {boolean} */ checkFinancialConditions(details) { const { totalBet, totalBonus, totalRecharge, profit, sameIPUsers, balance } = details; // 1.2 检查总投注额 > 赠送总额 × 指定倍数 if (totalBet <= totalBonus * this.config.betToBonusRatio) { updateStatus( `订单 ${this.config.currentOrderId} 投注额不满足大于赠送额指定倍数条件` ); return false; } // 1.3 检查总投注额 > 订单充值 × 指定倍数 if (totalBet <= totalRecharge * this.config.betToRechargeRatio) { updateStatus( `订单 ${this.config.currentOrderId} 投注额不满足大于充值额指定倍数条件` ); return false; } // 1.4 检查游戏盈亏绝对值 < 订单充值 × 指定倍数 if (Math.abs(profit) >= totalRecharge * this.config.profitToRechargeRatio) { updateStatus( `订单 ${this.config.currentOrderId} 盈亏不满足小于充值额指定倍数条件` ); return false; } // 检查相同IP用户数是否超过限制 if (sameIPUsers > this.config.maxSameIPUsers) { updateStatus(`订单 ${this.config.currentOrderId} 相同IP用户数超过限制`); return false; } // 检查用户余额是否满足最低要求 if (balance > this.config.minBalance) { updateStatus( `订单 ${this.config.currentOrderId} 余额过多 (${balance} > ${this.config.minBalance})` ); return false; } return true; } /** * 检查提现记录 * @param {string} userId 用户ID * @param {AbortSignal} signal 取消信号 * @returns {Promise<boolean>} */ async checkWithdrawRecords(userId, signal) { // 跳转到提现记录页面 await this.navigateTo("#/order/order-withdraw", signal); // 1. 查找用户ID输入框 const idInput = await waitForElement( 'input[placeholder="请输入用户ID"].el-input__inner', 5000, null, signal ); // 2. 设置用户ID值 await this.setInputValue(idInput, userId, signal); // 3. 调整日期范围 await this.adjustDateTimeRange(signal); // 4. 设置订单状态为"已支付" await this.selectDropdownOption("全部状态", "已支付", signal); // 5. 点击查询按钮 const queryBtn = await this.findQueryButton(signal); if (!queryBtn) { updateStatus(`用户 ${userId} 找不到查询按钮`); return false; } queryBtn.click(); // 6. 等待查询结果 await delay(2000); // 7. 检查提现次数 const todayCount = await this.checkWithdrawTimes(signal); if (todayCount > this.config.maxWithdrawTimes) { updateStatus(`用户 ${userId} 当天提现次数超过限制`); return false; } // 8. 检查CoinPay提现记录 const hasCoinPay = await this.checkCoinPayWithdraw(signal); if (hasCoinPay) { updateStatus(`用户 ${userId} 有CoinPay提现记录`); return false; } return true; } /** * 处理代理页面 * @param {string} userId 用户ID * @param {AbortSignal} signal 取消信号 */ async processAgentPage(userId, signal) { // 跳转到代理页面 await this.navigateTo("#/agent/agent-list", signal); await delay(1000); // 1. 查找用户ID输入框 const idInput = await waitForElement( 'input[placeholder="请输入用户ID"].el-input__inner', 5000, null, signal ); if (!idInput) { return false; } // 2. 设置用户ID值 await this.setInputValue(idInput, userId, signal); // 3. 点击查询按钮 const queryBtn = await this.findQueryButton(signal); if (!queryBtn) { updateStatus(`用户 ${userId} 找不到查询按钮`); return false; } queryBtn.click(); // 4. 等待查询结果 await delay(1500); return true; } /** * 检查代理条件 * @param {AbortSignal} signal 取消信号 * @returns {Promise<boolean>} */ async checkAgentConditions(signal) { // 获取代理信息 const rows = await this.getAgentRows(signal); if (rows.length === 0) { return false; // 根据用户id没有查询到对应的用户数据 } if (rows.length > 1) { return false; // 查询到多个用户数据说明没有根据用户id查询 } // 取第一行数据进行判断 const row = rows[0]; const cells = row.querySelectorAll("td"); if (cells.length < 4) { throw new Error("代理表格结构异常"); } // 获取上级代理(第2列) let superiorAgent = ""; const agentCell = cells[1]; if (agentCell) { const span = agentCell.querySelector(".el-tooltip") || agentCell.querySelector("span"); if (span) { superiorAgent = span.textContent.trim(); } } // 获取直推成员数量(第4列) let directMembers = 0; const directMembersSpan = cells[3]?.querySelector(".cell > span"); if (directMembersSpan) { directMembers = parseInt(directMembersSpan.textContent.trim()) || 0; } console.log(`上级代理: ${superiorAgent}, 直推成员: ${directMembers}`); // 判断代理条件:存在上级代理或直推成员>=1则不满足 if (superiorAgent || directMembers >= 1) { updateStatus(`代理条件不满足`); return false; } return true; } /** * 批准订单 * @param {HTMLElement} row 订单行 * @param {AbortSignal} signal 取消信号 */ async approveOrder(row, signal) { // 点击"待审核大额"按钮 const cells = row.querySelectorAll("td"); const statusCell = cells[10]; // 第11列单元格(订单状态) if (!statusCell) { throw new Error("找不到订单状态单元格"); } const statusSpan = statusCell.querySelector(".cell > span"); if (!statusSpan?.textContent.includes("待审核")) { throw new Error("订单状态不是待审核"); } statusSpan.click(); // 等待弹窗出现 const dialog = await waitForElement( '.el-dialog__wrapper:not([style*="display: none"])', 5000, document, signal ); if (!dialog) { throw new Error("同意出款弹窗格式异常"); } // 查找密码输入框 const passwordInput = await waitForElement( '.myDialog input.el-input__inner', 3000, dialog, signal ); // 设置密码 await this.setInputValue(passwordInput, this.config.password, signal); // 点击批准按钮 const approveBtn = await waitForElement( '.el-button--success', 3000, dialog, signal ); approveBtn.click(); // 等待确认弹窗 const confirmDialog = await waitForElement( '.el-message-box__wrapper:not([style*="display: none"])', 5000, document, signal ); // 点击确认按钮 const confirmBtn = await waitForElement( '.el-button--primary', 3000, confirmDialog, signal ); confirmBtn.click(); // 等待操作完成 await delay(1000); } /** * 跳转到指定页面 * @param {string} hash 页面hash * @param {AbortSignal} signal 取消信号 */ async navigateTo(hash, signal) { if (window.location.hash.includes(hash)) { await delay(1000); return; } window.location.hash = hash; await waitForPageChange(hash, 10000); await delay(1000); // 额外等待确保页面稳定 } /** * 跳转到订单页面 */ async navigateToOrderPage() { await this.navigateTo("#/order/unread-withdraw"); } /** * 跳转到最后一页 * @param {AbortSignal} signal 取消信号 */ async goToLastPage(signal) { const pageInput = await waitForElement( ".el-pagination__editor input", 5000, null, signal ); const jumpBtn = await waitForElement( ".el-pagination__jump", 5000, null, signal ); // 设置页码为总页数 const setter = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, "value" ).set; setter.call(pageInput, config.totalPages); pageInput.dispatchEvent(new Event("input", { bubbles: true })); pageInput.dispatchEvent(new Event("change", { bubbles: true })); // 模拟回车键触发跳转 const enterEvent = new KeyboardEvent("keydown", { key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true, }); pageInput.dispatchEvent(enterEvent); // 等待页面加载 await delay(2000); await this.processOrderPage(signal); } /** * 跳转到上一页 * @param {AbortSignal} signal 取消信号 */ async goToPrevPage(signal) { const prevBtn = await waitForElement( ".el-pagination .btn-prev:not([disabled])", 5000, null, signal ); prevBtn.click(); config.currentPage--; await delay(2000); await this.processOrderPage(signal); } /** * 开始新一轮处理 */ async startNewRound(signal) { updateStatus("所有订单已处理完成"); config.completedOneRound = true; GM_setValue("completedOneRound", true); // 点击查询按钮重新开始 await this.clickSearchButton(signal); } /** * 点击查询按钮 */ async clickSearchButton(signal) { // 获取所有filter-container元素 const filterContainers = document.querySelectorAll(".filter-container"); // 第三个filter-container包含查询按钮 if (filterContainers.length >= 3) { const searchContainer = filterContainers[2]; // 查找查询按钮 const searchBtn = Array.from( searchContainer.querySelectorAll("button.el-button") ).find((btn) => !btn.disabled && btn.textContent.includes("查询")); if (searchBtn) { updateStatus("点击查询按钮重新开始处理..."); // 先清空所有搜索条件 clearAllSearchInputs(); // 点击查询按钮 searchBtn.click(); // 等待查询完成 await delay(1500); config.completedOneRound = false; GM_setValue("completedOneRound", false); // 重新开始处理 await delay(500); await this.processOrderPage(signal); return; } } // 如果没找到按钮,尝试其他方式 updateStatus("未找到查询按钮,3秒后尝试重新开始"); await delay(3000); await this.processOrderPage(signal); } /** * 获取订单行 * @param {AbortSignal} signal 取消信号 * @returns {Promise<NodeListOf<HTMLElement>>} */ async getOrderRows(signal) { return await retryOperation( () => { // 找到包含“提现用户”表头的表格 const orderTable = Array.from(document.querySelectorAll(".el-table--scrollable-x")) .find(table => table.textContent.includes("提现用户")); if (!orderTable) { throw new Error("未找到包含 '提现用户' 的订单表格"); } const rows = orderTable.querySelectorAll(".el-table__body .el-table__row"); if (rows.length === 0) { throw new Error("订单表格没有行数据"); } return rows; }, 3, 1000, signal ); } /** * 获取代理行 * @param {AbortSignal} signal 取消信号 * @returns {Promise<NodeListOf<HTMLElement>>} */ async getAgentRows(signal) { return await retryOperation( () => { const rows = document.querySelectorAll(".el-table__body .el-table__row"); return rows; }, 3, 1000, signal ); } /** * 检查提现次数 * @param {AbortSignal} signal 取消信号 * @returns {Promise<number>} */ async checkWithdrawTimes(signal) { // 检查是否有"暂无数据"提示 const emptyText = document.querySelector(".el-table__empty-text"); if (emptyText?.textContent.includes("暂无数据")) { return 0; } // 获取表格的所有行 const rows = await this.getOrderRows(signal); if (rows.length <= 1) { return 0; // 只有统计行,没有实际数据 } // 计算当天的 GMT+0 日期(系统时间默认是 GMT+8,所以减去 8 小时得到 GMT+0) const now = new Date(); now.setHours(now.getHours() - 8); const todayGMT0Year = now.getUTCFullYear(); const todayGMT0Month = now.getUTCMonth(); const todayGMT0Date = now.getUTCDate(); let todayCount = 0; // 遍历数据行(跳过第一行统计数据) for (let i = 1; i < rows.length; i++) { const cells = rows[i].querySelectorAll("td"); // 固定位置第 6 列(索引 5)为 "回调时间(GMT+0:00)" const timeCell = cells[5]?.querySelector(".cell"); if (!timeCell) continue; const timeText = timeCell.textContent.trim(); if (!timeText) continue; // 解析日期字符串 const recordDate = new Date(timeText); // 假设 timeText 是 GMT+0 时间 if ( recordDate.getUTCFullYear() === todayGMT0Year && recordDate.getUTCMonth() === todayGMT0Month && recordDate.getUTCDate() === todayGMT0Date ) { todayCount++; } } return todayCount; } /** * 检查CoinPay提现记录 * @param {AbortSignal} signal 取消信号 * @returns {Promise<boolean>} */ async checkCoinPayWithdraw(signal) { // 找到第三方下拉选择框 const thirdPartySelect = await waitForElement( '.el-select input[placeholder="全部"]', 5000, null, signal ); // 点击下拉框 thirdPartySelect.click(); // 等待下拉菜单出现 const dropdown = await waitForElement( '.el-select-dropdown.el-popper:not([style*="display: none"])', 5000, null, signal ); // 查找CoinPay选项 const items = dropdown.querySelectorAll(".el-select-dropdown__item"); let coinPayItem = null; for (const item of items) { if (item.textContent.trim().toLowerCase() === "coinpay") { coinPayItem = item; break; } } if (!coinPayItem) { throw new Error("找不到CoinPay选项"); } // 点击选项 coinPayItem.click(); // 点击查询按钮 const queryBtn = await this.findQueryButton(signal); if (!queryBtn) { return false; } queryBtn.click(); // 等待查询结果 await delay(2000); // 检查是否有数据 const emptyText = document.querySelector(".el-table__empty-text"); if (emptyText?.textContent.includes("暂无数据")) { return false; } // 检查表格行数(减1是因为第一行是统计数据) const rows = document.querySelectorAll(".el-table__body .el-table__row"); return rows.length > 1; } /** * 调整日期时间范围(减8小时) * @param {AbortSignal} signal 取消信号 */ async adjustDateTimeRange(signal) { const dateRangeInputs = await waitForElement( ".el-range-input", 5000, null, signal ); if (!dateRangeInputs || dateRangeInputs.length < 2) { throw new Error("找不到日期范围输入框"); } const startInput = dateRangeInputs[0]; const endInput = dateRangeInputs[1]; // 获取当前日期时间 const now = new Date(); const endDate = new Date( now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59 ); const startDate = new Date( now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0 ); // 减8小时 startDate.setHours(startDate.getHours() - 8); endDate.setHours(endDate.getHours() - 8); // 格式化日期时间 const formatDate = (date) => { const pad = (num) => num.toString().padStart(2, "0"); return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad( date.getDate() )} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad( date.getSeconds() )}`; }; // 设置日期时间 const setter = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, "value" ).set; setter.call(startInput, formatDate(startDate)); startInput.dispatchEvent(new Event("input", { bubbles: true })); setter.call(endInput, formatDate(endDate)); endInput.dispatchEvent(new Event("input", { bubbles: true })); } /** * 设置输入框值 * @param {HTMLInputElement} input 输入框 * @param {string} value 值 * @param {AbortSignal} signal 取消信号 */ async setInputValue(input, value, signal = null) { if (signal?.aborted) return; input.focus(); input.value = ""; const nativeInputValueSetter = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, "value" ).set; nativeInputValueSetter.call(input, value); ["input", "change", "blur"].forEach((eventType) => { input.dispatchEvent(new Event(eventType, { bubbles: true })); }); await delay(300); // 确保值已设置 } /** * 选择下拉选项 * @param {string} placeholder 下拉框placeholder * @param {string} optionText 选项文本 * @param {AbortSignal} signal 取消信号 */ async selectDropdownOption(placeholder, optionText, signal) { const selectInput = await waitForElement( `.el-select input[placeholder="${placeholder}"]`, 5000, null, signal ); selectInput.click(); const dropdown = await waitForElement( '.el-select-dropdown.el-popper:not([style*="display: none"])', 5000, null, signal ); const options = dropdown.querySelectorAll(".el-select-dropdown__item"); const targetOption = Array.from(options).find(option => option.textContent.trim().toLowerCase() === optionText.toLowerCase() ); if (!targetOption) { throw new Error(`找不到选项: ${optionText}`); } targetOption.click(); await delay(500); // 等待选择生效 } /** * 关闭弹窗 * @param {HTMLElement} dialog 弹窗元素 */ closeDialog(dialog) { const closeBtn = dialog.querySelector(".el-dialog__headerbtn"); if (closeBtn) closeBtn.click(); } /** * 关闭所有弹窗 */ async closeAllDialogs(signal) { const dialogs = document.querySelectorAll('.el-dialog__wrapper:not([style*="display: none"])'); if (!dialogs) { processor.processOrderPage(signal); return; } for (const dialog of dialogs) { const closeBtn = dialog.querySelector('.el-dialog__headerbtn'); if (closeBtn) closeBtn.click(); await delay(300); } } } // 创建处理器实例 const processor = new OrderProcessor(config); // 初始化 function init() { // 初始化控制面板 addControlPanel(); // 检查是否已完成一轮处理 config.completedOneRound = GM_getValue("completedOneRound", false); if (config.completedOneRound) { updateStatus("检测到已完成一轮处理,开始新一轮处理"); GM_setValue("completedOneRound", false); // config.processedOrders = {}; // GM_setValue("processedOrders", {}); // document.getElementById("processedCount").textContent = "0"; } updateStatus("准备就绪"); // 监听hash变化 // window.addEventListener("hashchange", () => { // if (!config.isProcessing) return; // if (isOrderPage()) { // processor.processOrderPage(); // } else if (isAgentPage()) { // processor.checkAgentConditions(); // } else if (isWithdrawPage()) { // processor.processWithdrawPage(); // } else if (isTopupPage()) { // processor.processTopupPage(); // } else { // // 其他所有页面都返回订单页 // processor.navigateToOrderPage(); // } // }); // 如果当前已经在订单页面且正在处理中,直接开始处理 if (config.isProcessing) { updateStatus("恢复处理中..."); processor.processOrderPage(processor.abortController.signal); } } // 页面加载完成后执行 if (document.readyState === "complete") { init(); } else { window.addEventListener("load", init); } })();