// ==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);
}
})();