您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
根据多重条件自动审核大额订单
// ==UserScript== // @name 智能订单CP审核 // @namespace http://tampermonkey.net/ // @version 1.0.0 // @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 NS = "cpAuto"; // 唯一前缀,多个脚本用不同的值即可 const gmGet = (k, def) => GM_getValue(`${NS}_${k}`, def); const gmSet = (k, v) => GM_setValue(`${NS}_${k}`, v); // 配置参数 const rawConfigCp = { currentPage: 1, // 当前页码 totalPages: 1, // 总页数 pageSize: 200, // 每页订单数量 isLastPage: false, // 是否是最后一页 minSameIPUsers: 2, // 最大相同IP用户数 minTotalRecharge: 10, // 总充值金额 >= maxTotalRecharge: 100, // 总充值金额 <= rechargeToProfitRatio: 10, // 总充值金额 > 游戏盈亏指定倍数 depositWithdrawDiffRatio: 50, // 设置的充提差比例 actualDepositWithdrawDiffRatio: 0, // 实际的充提差比例 password: "", // 充值密码 processedOrders: gmGet("processedOrders", {}), // 已处理订单记录 payOutOrders: gmGet("payOutOrders", {}), // 已出款订单记录 cancelledOrders: gmGet("cancelledOrders", {}), // 已取消出款订单记录 currentOrderId: null, // 当前处理的订单ID isProcessing: false, // 是否正在处理中 isReturning: false, // 是否正在返回订单页面 panelCollapsed: false, // 面板是否收起 completedOneRound: false, // 是否完成了一轮处理 processingOrderId: null, // 正在处理的订单ID totalBetAmount: 0, // 当前订单的总投注额 profitAmount: 0, // 当前订单的游戏盈亏 startedProcessing: false, // 新的一轮处理 }; // 使用 Proxy 监听 config 变化 const config = new Proxy(rawConfigCp, { set(target, prop, value) { target[prop] = value; if (prop === "currentOrderId") { const el = document.getElementById(`${NS}_currentOrderId`); if (el) el.textContent = value || ""; } return true; } }); // 控制面板样式 GM_addStyle(` .${NS}-monitor-panel { position: fixed; top: 20px; right: 140px; 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: 400px; max-height: 90vh; overflow-y: auto; transition: all 0.3s ease; } .${NS}-monitor-panel.${NS}_collapsed { width: 40px; height: 40px; overflow: hidden; padding: 5px; } .${NS}-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; } .${NS}-toggle-panel:hover { background: #e0e0e0; } .${NS}_collapsed .${NS}-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; } .${NS}-monitor-panel .rule-block { border: 1px solid #ddd; border-radius: 5px; padding: 10px; margin-bottom: 12px; background: #fafafa; } .${NS}-monitor-panel .rule-title { font-weight: bold; font-size: 14px; margin-bottom: 8px; color: #409EFF; border-bottom: 1px solid #eee; padding-bottom: 4px; } .config-group { margin-bottom: 12px; position: relative; } .${NS}-config-label { display: block; margin-bottom: 5px; color: #666; font-size: 13px; font-weight: bold; } .${NS}-config-label.required:after { content: " *"; color: #F56C6C; } .${NS}-config-input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } .${NS}-config-input.error { border-color: #F56C6C; } .unit-wrapper { display: flex; align-items: center; flex-wrap: nowrap; } .unit-text { margin-left: 4px; color: #999; font-size: 12px; white-space: nowrap; } .error-message { color: #F56C6C; font-size: 12px; margin-top: 3px; display: none; } .${NS}-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; } .${NS}-monitor-button:disabled { background: #C0C4CC; cursor: not-allowed; } .${NS}-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; } #${NS}_statusText { font-weight: bold; color: #409EFF; } #${NS}_processedCount { font-weight: bold; color: #67C23A; } .button-container { display: flex; flex-direction: column; } .${NS}-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(`${NS}_statusText`); if (statusEl) { statusEl.textContent = text; } } /** * 更新按钮显示状态 */ function updateButtonVisibility() { const startBtn = document.getElementById(`${NS}_startBtn`); const stopBtn = document.getElementById(`${NS}_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: "minSameIPUsers", name: "大于相同IP用户数" }, { id: "minTotalRecharge", name: "总充值金额 >=" }, { id: "maxTotalRecharge", name: "总充值金额 <=" }, { id: "rechargeToProfitRatio", name: "充值金额大于游戏盈亏倍数" }, { id: "depositWithdrawDiffRatio", name: "充提差比例" }, { id: "withdrawPassword", name: "充值密码" } ]; inputs.forEach((input) => { const element = document.getElementById(`${NS}_${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"; } return; // 跳过后续数值验证 } // 数值字段验证 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() { // 最大相同IP用户数 config.minSameIPUsers = parseInt( document.getElementById(`${NS}_minSameIPUsers`).value ); // 总充值金额 >= config.minTotalRecharge = parseFloat( document.getElementById(`${NS}_minTotalRecharge`).value ) || 0; // 总充值金额 <= config.maxTotalRecharge = parseFloat( document.getElementById(`${NS}_maxTotalRecharge`).value ) || 0; // 总充值金额大于游戏盈亏指定倍数 config.rechargeToProfitRatio = parseFloat( document.getElementById(`${NS}_rechargeToProfitRatio`).value ) || 0; // 充提差比例 config.depositWithdrawDiffRatio = parseFloat( document.getElementById(`${NS}_depositWithdrawDiffRatio`).value ) || 0; // 充值密码 config.password = document.getElementById(`${NS}_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 || "已处理", }; gmSet("processedOrders", config.processedOrders); document.getElementById(`${NS}_processedCount`).textContent = Object.keys( config.processedOrders ).length; console.log(`订单 ${orderId} 已标记为已处理`, reason ? `原因: ${reason}` : ""); processor.closeAllDialogs(processor.abortController.signal); } /** * 从行中获取订单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 = `${NS}-monitor-panel`; panel.id = `${NS}_autoWithdrawPanel`; // 添加收起/展开按钮 const toggleBtn = document.createElement("button"); toggleBtn.className = `${NS}-toggle-panel`; toggleBtn.innerHTML = "×"; toggleBtn.title = "收起/展开控制面板"; toggleBtn.addEventListener("click", togglePanel); // 面板内容 const panelContent = document.createElement("div"); panelContent.className = `${NS}-panel-content`; panelContent.innerHTML = ` <h3 class="monitor-header">智能CP出款系统</h3> <!-- 规则一模块 --> <div class="rule-block"> <div class="rule-title">规则一</div> <div class="config-group"> <label class="${NS}-config-label">提现金额区间</label> <div class="unit-wrapper" style="gap: 5px;"> <input type="number" class="${NS}-config-input" id="${NS}_minTotalRecharge" value="${config.minTotalRecharge}" step="0.01" style="flex: 1;"> <span style="color: #999;">—</span> <input type="number" class="${NS}-config-input" id="${NS}_maxTotalRecharge" value="${config.maxTotalRecharge}" step="0.01" style="flex: 1;"> <span class="unit-text">元</span> </div> </div> <div class="config-group"> <label class="${NS}-config-label">提现金额 ≥ 充值金额倍数</label> <div class="unit-wrapper"> <input type="number" class="${NS}-config-input" id="${NS}_rechargeToProfitRatio" value="${config.rechargeToProfitRatio}" step="0.1"> <span class="unit-text">倍</span> </div> </div> </div> <!-- 规则二模块 --> <div class="rule-block"> <div class="rule-title">规则二</div> <div class="config-group"> <label class="${NS}-config-label required">大于相同IP用户数</label> <input type="number" class="${NS}-config-input" id="${NS}_minSameIPUsers" value="${config.minSameIPUsers}" required> <div class="error-message"></div> </div> </div> <div class="config-group"> <label class="${NS}-config-label">充提差比例</label> <div class="unit-wrapper"> <input type="number" class="${NS}-config-input" id="${NS}_depositWithdrawDiffRatio" value="${config.depositWithdrawDiffRatio}" step="1"> <span class="unit-text">%</span> </div> </div> <div class="config-group"> <label class="${NS}-config-label required">充值密码</label> <input type="text" class="${NS}-config-input" id="${NS}_withdrawPassword" value="${config.password}" required> <div class="error-message"></div> </div> <div class="button-container"> <button id="${NS}_startBtn" class="${NS}-monitor-button ${config.isProcessing ? "hidden" : ""}">开始处理</button> <button id="${NS}_stopBtn" class="${NS}-monitor-button stop ${!config.isProcessing ? "hidden" : ""}">停止处理</button> </div> <div class="button-container"> <button id="${NS}_clearCacheBtn" class="${NS}-monitor-button">清理缓存</button> </div> <div class="monitor-stats"> <div class="monitor-stat-row"> <span>状态:</span> <span id="${NS}_statusText">待命</span> </div> <div class="monitor-stat-row"> <span>当前处理:</span> <span id="${NS}_currentOrderId">${config.currentOrderId || ""}</span> </div> <div class="monitor-stat-row"> <span>已处理:</span> <span id="${NS}_processedCount">${ Object.keys(config.processedOrders).length }</span> 单 </div> <div class="monitor-stat-row"> <span>已出款:</span> <span id="${NS}_payOutOrders">${Object.keys(config.payOutOrders).join(", ")}</span> </div> <div class="monitor-stat-row"> <span>已取消出款:</span> <span id="${NS}_cancelledOrders">${Object.keys(config.cancelledOrders).join(", ")}</span> </div> </div> `; panel.appendChild(toggleBtn); panel.appendChild(panelContent); document.body.appendChild(panel); // 恢复面板状态 config.panelCollapsed = gmGet("panelCollapsed", false); if (config.panelCollapsed) { panel.classList.add(`${NS}_collapsed`); toggleBtn.innerHTML = "≡"; } // 事件监听 document.getElementById(`${NS}_startBtn`).addEventListener("click", function () { if (validateInputs()) { updateConfig(); processor.start(); } }); document.getElementById(`${NS}_stopBtn`).addEventListener("click", function () { processor.stop(); }); // 为所有输入框添加实时验证 document.querySelectorAll(`.${NS}-config-input`).forEach((input) => { input.addEventListener("input", function () { validateInputs(); }); }); // 绑定清理缓存事件 document.getElementById(`${NS}_clearCacheBtn`).addEventListener("click", function () { if (confirm("确定要清理缓存数据吗?这会清空已处理订单记录。")) { config.processedOrders = {}; config.payOutOrders = {}; config.cancelledOrders = {}; config.currentOrderId = null; gmSet("processedOrders", {}); gmSet("payOutOrders", {}); gmSet("cancelledOrders", {}); gmSet("currentOrderId", null); document.getElementById(`${NS}_processedCount`).textContent = "0"; document.getElementById(`${NS}_payOutOrders`).textContent = ""; document.getElementById(`${NS}_cancelledOrders`).textContent = ""; document.getElementById(`${NS}_currentOrderId`).textContent = ""; updateStatus("缓存已清理"); } }); } /** * 收起/展开面板 */ function togglePanel() { const panel = document.getElementById(`${NS}_autoWithdrawPanel`); config.panelCollapsed = !panel.classList.contains(`${NS}_collapsed`); if (config.panelCollapsed) { panel.classList.add(`${NS}_collapsed`); this.innerHTML = "≡"; } else { panel.classList.remove(`${NS}_collapsed`); this.innerHTML = "×"; } gmSet("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.startedProcessing = true; // 开始新的轮次 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() { if (!this.config.isProcessing) return; this.abortController.abort(); // 中止当前任务 this.config.isProcessing = false; this.closeAllDialogs(this.abortController.signal); // 先执行需要旧 signal 的方法 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; this.closeAllDialogs(signal); const emptyText = document.querySelector(".el-table__empty-text"); if (emptyText?.textContent.includes("暂无数据")) { console.log('暂无数据'); // 无数据隔5秒重开 await delay(5000); await this.startNewRound(signal); 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); if (rows.length === 0) { if (this.config.currentPage > 1) { await this.goToPrevPage(signal); } else { await this.startNewRound(signal); } return; } updateStatus(`开始处理订单列表`); // 5. 从最后一行开始处理 for (let i = rows.length - 1; i >= 0; i--) { if (signal.aborted || !this.config.isProcessing) break; try { 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 { this.config.startedProcessing = false; // 重置标记 await this.startNewRound(signal); } } /** * 加载已处理订单 */ async loadProcessedOrders() { const stored = await gmGet("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]) { updateStatus(`订单 ${orderId} 已处理,跳过`); return; } this.config.currentOrderId = orderId; updateStatus(`处理订单: ${orderId}`); // 获取订单提现金额 const amount = await this.getWithdrawAmount(row, signal); // 2. 检查用户详情 const userDetails = await this.getUserDetails(row, signal); if (!userDetails) { return markOrderAsProcessed(orderId, "获取用户详情失败"); } const { count: successCount, failedRules } = this.checkFinancialConditions(userDetails, amount); // 3. 检查财务条件 if (successCount == 1) { const reasonSoFar = failedRules.join(";"); console.log("订单 ${orderId} 满足1个条件,计算充提差校验是否需要coinpay出款") // 4. 获取当天提现差 const withdrawAmount = await this.getTotalWithdrawAmountRecords(userDetails.userId, signal); if (withdrawAmount < 1) { this.addCancelledOrder(orderId, `${reasonSoFar}`); return markOrderAsProcessed(orderId, "提现记录不满足"); } // 5. 获取当天充值总额 const rechargeAmount = await this.getTodayRecharge(userDetails.userId, signal); if (rechargeAmount < 1) { this.addCancelledOrder(orderId, `${reasonSoFar}`); return markOrderAsProcessed(orderId, "充值记录不满足"); } // 计算实际充提差比率 const actualDiffRatio = withdrawAmount / rechargeAmount; console.log("当天提现差:", withdrawAmount); console.log("当天充值总额:", rechargeAmount); console.log("计算实际充提差比率:", actualDiffRatio); if (actualDiffRatio > this.config.actualDepositWithdrawDiffRatio) { this.addCancelledOrder(orderId, `${reasonSoFar}`); return markOrderAsProcessed(orderId, "充提差比率超过设定,要求:"+this.config.actualDepositWithdrawDiffRatio+"%,实际:"+actualDiffRatio+"%"); } // 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; gmSet("payOutOrders", this.config.payOutOrders); document.getElementById(`${NS}_payOutOrders`).textContent = Object.keys( this.config.payOutOrders ).join(", "); } else if (successCount == 2) { console.log("订单 ${orderId} 满足2个条件,直接coinpay出款") // 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; gmSet("payOutOrders", this.config.payOutOrders); document.getElementById(`${NS}_payOutOrders`).textContent = Object.keys( this.config.payOutOrders ).join(", "); } else { updateStatus(`处理订单: ${orderId} 两个规则都不满足`); // 8. 标记为已处理 markOrderAsProcessed(orderId, "两个规则都不满足"); } return; } async addCancelledOrder(orderId, reason) { this.config.cancelledOrders[orderId] = reason || "未提供理由"; gmSet("cancelledOrders", this.config.cancelledOrders); // 显示成 “订单号(理由)” 的形式 document.getElementById(`${NS}_cancelledOrders`).textContent = Object.entries(this.config.cancelledOrders) .map(([id, r]) => `${id}(${r})`) .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; } /** * 调整提现记录查询日期范围(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 getTodayRecharge(signal) { this.closeAllDialogs(signal); if (signal.aborted) return; // 跳转到充值记录页面 await this.navigateTo("#/order/order-topup", signal); updateStatus(`检查用户 ${this.config.currentUserId} 的充值记录...`); // 等待网页加载 await delay(1500); 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 totalTopupAmount = await this.getTodayRechargeTotalAmount(signal); if (totalTopupAmount < 1) { return 0; } } 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 })); } /** * 获取当天已支付的充值总金额 * @param {AbortSignal} signal 取消信号 * @returns {Promise<number>} 当天充值总金额 */ async getTodayRechargeTotalAmount(signal) { const rows = document.querySelectorAll(".el-table__body .el-table__row"); if (rows.length <= 1) return 0; // 没有数据 // 计算当天 GMT+0 日期 const now = new Date(); now.setHours(now.getHours() - 8); const todayYear = now.getUTCFullYear(); const todayMonth = now.getUTCMonth() + 1; // getUTCMonth()返回的是0-11,要得到正常的月份(1-12),需要加 1 const todayDate = now.getUTCDate(); let totalAmount = 0; for (let i = 1; i < rows.length; i++) { const cells = rows[i].querySelectorAll("td"); // 假设时间在第 6 列(索引 5),金额在第 9 列(索引 8),状态在第 12 列(索引 11) const timeCell = cells[5]?.querySelector(".cell"); const amountCell = cells[8]?.querySelector(".cell"); const statusCell = cells[11]?.querySelector(".cell"); if (!timeCell || !amountCell || !statusCell) continue; const timeText = timeCell.textContent.trim(); const amountText = amountCell.textContent.trim(); const statusText = statusCell.textContent.trim(); if (!timeText || !amountText) continue; if (statusText !== "已支付") continue; // 只统计已支付充值 const recordDate = new Date(timeText); if ( recordDate.getUTCFullYear() === todayYear && recordDate.getUTCMonth() + 1 === todayMonth && recordDate.getUTCDate() === todayDate ) { const amount = parseFloat(amountText.replace(/[^\d.-]/g, "")); if (!isNaN(amount)) { totalAmount += amount; } } } return totalAmount; } /** * 获取提现金额 * @param {HTMLElement} row 订单行 * @param {AbortSignal} signal 取消信号 * @returns {Promise<number>} */ async getWithdrawAmount(row, signal) { // 第8列通常是金额列 console.log("当前订单row", row); 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 parseFloat(foundNode.textContent); } return parseFloat(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 ); } /** * 查找 操作选中订单 (带重试机制) */ async findOperateOrderButton(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; } // 第二个表格包含所需的财务数据 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, amount) { const { totalBet, totalBonus, totalRecharge, profit, sameIPUsers, balance } = details; let count = 2; let failedRules = []; let ruleOne = 0; // 1.1 检查提现金额是否在指定范围内 if (this.config.minTotalRecharge > amount || amount > this.config.maxTotalRecharge) { updateStatus(`订单 ${this.config.currentOrderId} 提现金额不在指定范围内`); ruleOne += 1; } // 1.2 检查总充值金额 > 充值金额 × 指定倍数 if (amount < totalRecharge * this.config.rechargeToProfitRatio) { updateStatus( `订单 ${this.config.currentOrderId} 提现金额不满足大于充值金额指定倍数` ); ruleOne += 1; } if (ruleOne > 0) { failedRules.push('不满足规则一'); // 规则1未满足 count -= 1; } // 2. 检查相同IP用户数是否小于限制 if (sameIPUsers <= this.config.minSameIPUsers) { updateStatus(`订单 ${this.config.currentOrderId} 相同IP用户数小于限制`); failedRules.push('不满足规则二'); // 规则2未满足 count -= 1; } return { count, failedRules }; } /** * 获取当日提现金额差 * @description 当日总提现金额-当日coinPay提现金额 * @param {string} userId 用户ID * @param {AbortSignal} signal 取消信号 * @returns {Promise<boolean>} */ async getTotalWithdrawAmountRecords(userId, signal) { this.closeAllDialogs(signal); // 跳转到提现记录页面 await this.navigateTo("#/order/order-withdraw", signal); // 等待页面跳转成功 await delay(1500); // 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 0; } queryBtn.click(); // 6. 等待查询结果 await delay(2000); // 7. 获取当日总提现金额 const todayCount = await this.getTodayWithdrawTotalAmount(signal); if (todayCount < 1) { updateStatus(`用户 ${userId} 当天没有提现金额`); return 0; } // 8. 获取当日coinPay提现金额 const coinPayCount = await this.getTodayCoinPayWithdrawAmount(signal); // 计算 当日总提现金额-当日coinPay提现金额 const count = todayCount - coinPayCount; return count; } /** * 批准coinpay订单 * @param {HTMLElement} row 订单行 * @param {AbortSignal} signal 取消信号 */ async approveOrder(row, signal) { // 点击"选择框" const cells = row.querySelectorAll("td"); const statusCell = cells[0]; // 第1列单元格(选择框) if (!statusCell) { throw new Error("找不到订单选择框单元格"); } // 点击勾选框 const checkboxInner = statusCell.querySelector('.el-checkbox__inner'); if (!checkboxInner) { throw new Error("找不到订单选择框"); } checkboxInner.click(); // 5. 点击操作订单按钮 const operateOrderBtn = await this.findOperateOrderButton(signal); if (!operateOrderBtn) { updateStatus(`用户 ${userId} 找不到操作订单按钮`); return 0; } operateOrderBtn.click(); // 等待弹窗出现 const dialog = await waitForElement( '.el-dialog__wrapper:not([style*="display: none"])', 5000, document, signal ); if (!dialog) { throw new Error("操作订单出款弹窗格式异常"); } // 选择操作类型 await this.selectDropdownOptionInDialog(dialog, "操作类型", "指定三方出款", signal); // 等待第三方下拉渲染 await delay(300); // 选择第三方 await this.selectDropdownOptionInDialog(dialog, "第三方", "coinpay", signal); // 查找密码输入框 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-dialog__footer .el-button--primary', 3000, dialog, signal ); approveBtn.click(); // 等待确认弹窗 const confirmDialog = await waitForElement( '.el-message-box__wrapper:not([style*="display: none"])', 5000, document, signal ); if (!confirmDialog) throw new Error("确认弹窗未出现"); // 点击确认按钮 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; gmSet("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; gmSet("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) { console.log("未找到包含 '提现用户' 的订单表格"); return null; } const rows = orderTable.querySelectorAll(".el-table__body .el-table__row"); if (rows.length === 0) { console.log("订单表格没有行数据"); return null; } return rows; }, 3, 1000, signal ); } /** * 统计当天已支付提现总金额 * @param {AbortSignal} signal 取消信号 * @returns {Promise<number>} */ async getTodayWithdrawTotalAmount(signal) { // 检查是否有"暂无数据"提示 const emptyText = document.querySelector(".el-table__empty-text"); if (emptyText?.textContent.includes("暂无数据")) { console.log("暂无提现数据") return 0; } // 获取表格的所有行 const rows = await this.getOrderRows(signal); if (!rows || rows.length <= 1) { console.log("暂无提现数据") 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() + 1; // getUTCMonth()返回的是0-11,要得到正常的月份(1-12),需要加 1 const todayGMT0Date = now.getUTCDate(); let totalAmount = 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; // 固定位置第 9 列(索引8)为提现金额 const amountCell = cells[8]?.querySelector(".cell"); if (!amountCell) continue; const amountText = amountCell.textContent.trim(); if (!amountText) continue; // 固定位置第 12 列(索引11)为状态,需要判断是否已支付 const statusCell = cells[11]?.querySelector(".cell"); if (!statusCell) continue; const statusText = statusCell.textContent.trim(); if (statusText !== "已支付") continue; // 只统计已支付的 // 解析日期字符串 const recordDate = new Date(timeText); // 假设 timeText 是 GMT+0 时间 console.log(recordDate.getUTCFullYear()) console.log(recordDate.getUTCMonth()) console.log(recordDate.getUTCDate()) if ( recordDate.getUTCFullYear() === todayGMT0Year && recordDate.getUTCMonth() + 1 === todayGMT0Month && recordDate.getUTCDate() === todayGMT0Date ) { // 解析金额 const amount = parseFloat(amountText); if (!isNaN(amount)) { totalAmount += amount; } } } return totalAmount; } /** * 统计当天 CoinPay 已支付提现总金额 * @param {AbortSignal} signal 取消信号 * @returns {Promise<boolean>} */ async getTodayCoinPayWithdrawAmount(signal) { await this.selectDropdownOption("全部", "CoinPay", signal); // 点击查询按钮 const queryBtn = await this.findQueryButton(signal); if (!queryBtn) { return 0; } queryBtn.click(); // 等待查询结果 await delay(1500); // 检查是否有数据 const emptyText = document.querySelector(".el-table__empty-text"); if (emptyText?.textContent.includes("暂无数据")) { console.log("暂无CoinPay提现数据") return 0; } // 获取表格行 const rows = document.querySelectorAll(".el-table__body .el-table__row"); if (!rows || rows.length <= 1) { console.log("暂无CoinPay提现数据") return 0; } // 计算当天的 GMT+0 日期 const now = new Date(); now.setHours(now.getHours() - 8); const todayYear = now.getUTCFullYear(); const todayMonth = now.getUTCMonth() + 1; // getUTCMonth()返回的是0-11,要得到正常的月份(1-12),需要加 1 const todayDate = now.getUTCDate(); let totalAmount = 0; for (let i = 1; i < rows.length; i++) { const cells = rows[i].querySelectorAll("td"); const timeCell = cells[5]?.querySelector(".cell"); if (!timeCell) continue; const timeText = timeCell.textContent.trim(); if (!timeText) continue; const amountCell = cells[8]?.querySelector(".cell"); if (!amountCell) continue; const amountText = amountCell.textContent.trim(); if (!amountText) continue; const statusCell = cells[11]?.querySelector(".cell"); if (!statusCell) continue; const statusText = statusCell.textContent.trim(); if (statusText !== "已支付") continue; const recordDate = new Date(timeText); if ( recordDate.getUTCFullYear() === todayYear && recordDate.getUTCMonth() + 1 === todayMonth && recordDate.getUTCDate() === todayDate ) { const amount = parseFloat(amountText); if (!isNaN(amount)) { totalAmount += amount; } } } return totalAmount; } /** * 调整日期时间范围(减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 弹窗容器 * @param {string} label 下拉框左侧文本 * @param {string} optionText 要选择的文本 * @param {AbortSignal} signal 取消信号 */ async selectDropdownOptionInDialog(dialog, label, optionText, signal) { // 1. 找到 label 对应的容器(模糊匹配,只匹配直接文本节点) const labelDiv = Array.from(dialog.querySelectorAll('div')) .find(div => { const text = Array.from(div.childNodes) .filter(node => node.nodeType === Node.TEXT_NODE) .map(node => node.textContent.trim()) .join(''); return text.replace(/\s|:/g, '').includes(label.replace(/\s|:/g, '')); }); if (!labelDiv) throw new Error(`未找到 ${label} 标签`); // 2. 找到同一个父容器里的 .el-select const selectContainer = labelDiv.parentElement.querySelector('.el-select'); if (!selectContainer) throw new Error(`未找到 ${label} 下拉框容器`); // 3. 再找到 input 并点击 const input = selectContainer.querySelector('input.el-input__inner'); if (!input) throw new Error(`未找到 ${label} 下拉框输入`); input.click(); await delay(100); // 等待浮层渲染 // 4. 等待至少一个浮层出现 await waitForElement( '.el-select-dropdown.el-popper:not([style*="display: none"])', 5000, document, signal ); // 5. 精准匹配浮层位置 const rect = input.getBoundingClientRect(); const matchedDropdown = Array.from(document.querySelectorAll('.el-select-dropdown.el-popper:not([style*="display: none"])')) .find(d => { const dr = d.getBoundingClientRect(); // 浮层 top 应在 input bottom 下方,left 与 input 左对齐 return dr.top > rect.bottom - 5 && Math.abs(dr.left - rect.left) < 5; }); if (!matchedDropdown) throw new Error(`未找到 ${label} 下拉浮层`); // 6. 查找下拉选项并点击 const options = matchedDropdown.querySelectorAll(".el-select-dropdown__item"); const targetOption = Array.from(options) .find(opt => opt.textContent.trim().toLowerCase() === optionText.toLowerCase()); if (!targetOption) throw new Error(`找不到 ${label} 下拉选项: ${optionText}`); targetOption.click(); await delay(300); // 等待选择生效 } /** * 关闭弹窗 * @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 = gmGet("completedOneRound", false); if (config.completedOneRound) { updateStatus("检测到已完成一轮处理,开始新一轮处理"); gmSet("completedOneRound", false); config.processedOrders = {}; gmSet("processedOrders", {}); document.getElementById(`${NS}_processedCount`).textContent = "0"; } updateStatus("准备就绪"); // 监听hash变化 // window.addEventListener("hashchange", () => { // if (!config.isProcessing) return; // if (isOrderPage()) { // processor.processOrderPage(processor.abortController.signal); // } else { // // 其他所有页面都返回订单页 // processor.navigateToOrderPage(); // } // }); // 如果当前已经在订单页面且正在处理中,直接开始处理 if (config.isProcessing) { updateStatus("开始处理中..."); processor.processOrderPage(processor.abortController.signal); } } // 页面加载完成后执行 if (document.readyState === "complete") { init(); } else { window.addEventListener("load", init); } })();