机器人控制中心
打开机器人实例
机器人BotId:
Url
打开数量:
(当前还可打开:4 个)
打开机器人
当前运行的机器人实例(共 1 个)
ID
机器人BotId
URL
端口
启动时间
操作
inst_6911acff1f3c99.67983549
101
http://localhost/tel?botId=101
9222
2025-11-10 10:14:39
关闭
关闭所有浏览器
插件管理
插件代码:
// 配置参数 const FETCH_INTERVAL = 10000; // 10秒,单位:毫秒 const REQUEST_TIMEOUT = 15000; // 请求超时时间,15秒 const BET_HANDLER_API = 'http://localhost/Tel/API/betHandler.php'; // 本地投注记录API(无需代理) const PROXY_URL = 'proxy.php'; // 本地PHP代理脚本路径(关键新增:目标网站请求走这里) // 账号列表 const accounts = [ { username: 'TelSms101', password: 'Abab7878' } ]; let currentAccountIndex = 0; // 当前使用的账号索引 let fetchTimer = null; // 定时器实例 let isProcessing = false; // 流程状态锁,防止并发执行 let chDomainUid = null; // _CHDomain.uid // 初始化:启动定时器 init(); /** * 获取目标网站的基础API URL(修改为目标网站实际地址,而非当前页面域名) * @returns {string} 目标网站的基础API URL */ function getBaseApiUrl() { const protocol = 'https:'; // 目标网站协议(http/https,根据实际修改) const hostname = 'hga035.com'; // 目标网站域名(根据实际修改,如原直接请求的域名) return `${protocol}//${hostname}/transform.php`; } /** * 生成当前日期的版本字符串 * @returns {string} 动态生成的版本字符串 */ function getCurrentVersionString() { const today = new Date(); const year = today.getFullYear(); const month = String(today.getMonth() + 1).padStart(2, '0'); const day = String(today.getDate()).padStart(2, '0'); return `${year}-${month}-${day}-Glive_112`; } /** * 初始化:启动定时任务 */ function init() { // 等待主页面和iframe加载完成 waitForPageAndIframeReady().then(() => { // 加载完成后尝试获取_CHDomain.uid(原逻辑保留) getCHDomainUid().then(uid => { if (uid) { chDomainUid = uid; console.log('从页面提取到_CHDomain.uid:', chDomainUid); } else { console.log('未找到_CHDomain.uid'); } }); }); // 清除旧定时器 if (fetchTimer) { clearInterval(fetchTimer); fetchTimer = null; console.log('已清除旧的数据抓取定时器'); } // 立即执行一次,然后定时执行 runCycle(); fetchTimer = setInterval(runCycle, FETCH_INTERVAL); console.log(`=== 数据抓取定时器已启动(间隔${FETCH_INTERVAL/1000}秒) ===`); console.log(`目标网站基础域名: ${getBaseApiUrl()}`); console.log(`PHP代理路径: ${PROXY_URL}`); // 30分钟页面刷新 setInterval(() => window.location.reload(), 1800000); } /** * 执行完整周期:登录 → 获取未投注记录 → 执行投注 → 更新状态 */ async function runCycle() { if (isProcessing) { console.log('上一次处理尚未完成,跳过本次执行'); return; } const cycleStartTime = new Date().toLocaleTimeString(); console.log(`\n=== [${cycleStartTime}] 开始新的处理周期 ===`); console.log(`目标网站API域名:${getBaseApiUrl()}`); try { // 1. 获取最新未投注记录(本地API,无需代理) console.log('=== 获取最新未投注记录 ==='); const betRecord = await getLatestUnbetRecord(); if (betRecord) { isProcessing = true; console.log('找到未投注记录:', betRecord); // 2. 登录(通过PHP代理) console.log('=== 开始登录流程(通过代理) ==='); const uid = await loginWithRetry(); console.log(`=== 登录成功,uid: ${uid} ===`); // 3. 获取当前版本号(通过PHP代理) console.log('=== 获取当前版本号(通过代理) ==='); const version = await getCurrentVersion(uid); console.log('当前版本号:', version); // 4. 读取订单数据(通过PHP代理) console.log('=== 读取订单数据(通过代理) ==='); const orderData = await readOrderData(betRecord, uid, version); console.log('获取订单数据:', orderData.length > 0 ? orderData : '无订单数据'); // 5. 执行投注(通过PHP代理) if (orderData.length > 0) { console.log('=== 执行投注操作(通过代理) ==='); const betResult = await executeBet(betRecord, uid, version, orderData); console.log('投注结果:', betResult); // 6. 更新投注状态(本地API,无需代理) console.log('=== 更新投注状态 ==='); const updateResult = await updateBetStatus(betRecord.id, betResult.success); console.log('状态更新结果:', updateResult); } else { // 订单数据为空,更新状态为失败 const updateResult = await updateBetStatus(betRecord.id, false); console.log('订单数据为空,更新状态为失败:', updateResult); } } else { console.log('没有找到需要处理的未投注记录'); } console.log(`=== [${new Date().toLocaleTimeString()}] 处理周期完成 ===`); } catch (error) { console.error(`=== [${new Date().toLocaleTimeString()}] 处理周期失败 ===`, error.message); } finally { isProcessing = false; } } /** * 获取最新的未投注记录(本地API,无需代理) */ async function getLatestUnbetRecord() { try { // 原逻辑保留:向本地API传递账号信息 const requestUrl = `${BET_HANDLER_API}?hgname=${encodeURIComponent(accounts[0].username)}&hgpass=${encodeURIComponent(accounts[0].password)}`; const res = await Promise.race([ fetch(requestUrl, { method: 'GET' }), timeoutPromise(REQUEST_TIMEOUT, '获取未投注记录超时') ]); if (!res.ok) throw new Error(`获取未投注记录失败 [HTTP ${res.status}]`); const result = await res.json(); if (!result.success) throw new Error(result.message || '获取未投注记录失败'); return result.data; } catch (error) { console.error('获取未投注记录出错:', error.message); throw error; } } /** * 读取订单数据(通过PHP代理请求目标网站) * @param {Object} betRecord - 投注记录 * @param {string} uid - 当前登录用户ID * @param {string} version - 当前版本号 */ async function readOrderData(betRecord, uid, version) { try { let actionP = 'FT_order_view'; let gtype = 'FT'; if(betRecord.betType === 'bk'){ actionP = 'Other_order_view'; gtype = 'BK'; } // 1. 构建目标网站需要的订单请求参数(原逻辑参数不变) const orderParams = { p: actionP, uid: uid, ver: version, langx: 'zh-cn', odd_f_type: 'H', // H=盘口/大小球 gid: betRecord.game_id, gtype: gtype, wtype: betRecord.wtype, // OU=大小球,R=让球 chose_team: betRecord.chose_team // C=大球/受让,H=小球/让球 }; // 2. 构建发送给PHP代理的请求数据(包含目标地址、方法、参数、头信息) const proxyRequest = { targetUrl: `${getBaseApiUrl()}?ver=${encodeURIComponent(version)}`, // 目标网站订单接口 method: 'POST', // 请求方法 params: orderParams, // 目标接口参数 headers: { 'Content-Type': 'application/x-www-form-urlencoded', // 模拟表单提交 'User-Agent': navigator.userAgent, // 传递浏览器UA(避免反爬) 'Referer': getBaseApiUrl() // 模拟目标网站Referer } }; // 3. 请求PHP代理(同源,无跨域) const res = await requestUtils.fetchWithRetry(PROXY_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, // 告诉代理请求体是JSON body: JSON.stringify(proxyRequest), credentials: 'include' // 让代理维持Cookie会话(登录状态) }); if (!res.ok) throw new Error(`代理响应异常 [HTTP ${res.status}]`); // 4. 解析目标网站返回的XML数据(原逻辑保留) const xmlStr = await res.text(); const xmlDoc = parseXml(xmlStr); // 检测错误响应 const errorCode = xmlDoc.querySelector('code'); if (errorCode?.textContent === 'error') { const errorMsg = xmlDoc.querySelector('msg')?.textContent || '未知错误'; throw new Error(`获取注单错误: ${errorMsg}`); } // 订单数据成功返回(code=501) if (xmlDoc.querySelector('code')?.textContent === '501') { console.log('=== 读取订单数据成功 ==='); return [{ code: '501', game_sc: xmlDoc.querySelector('game_sc')?.textContent || '', game_so: xmlDoc.querySelector('game_so')?.textContent || '', mem_sc: xmlDoc.querySelector('mem_sc')?.textContent || '', mem_so: xmlDoc.querySelector('mem_so')?.textContent || '', gold_gmin: xmlDoc.querySelector('gold_gmin')?.textContent || '', gold_gmax: xmlDoc.querySelector('gold_gmax')?.textContent || '', maxcredit: xmlDoc.querySelector('maxcredit')?.textContent || '', con: xmlDoc.querySelector('con')?.textContent || '', ratio: xmlDoc.querySelector('ratio')?.textContent || '', ioratio: xmlDoc.querySelector('ioratio')?.textContent || '', spread: xmlDoc.querySelector('spread')?.textContent || '', strong: xmlDoc.querySelector('strong')?.textContent || '', restsinglecredit: xmlDoc.querySelector('restsinglecredit')?.textContent || '', league_id: xmlDoc.querySelector('league_id')?.textContent || '', dates: xmlDoc.querySelector('dates')?.textContent || '', times: xmlDoc.querySelector('times')?.textContent || '', num_c: xmlDoc.querySelector('num_c')?.textContent || '', num_h: xmlDoc.querySelector('num_h')?.textContent || '', team_id_c: xmlDoc.querySelector('team_id_c')?.textContent || '', team_id_h: xmlDoc.querySelector('team_id_h')?.textContent || '', currency: xmlDoc.querySelector('currency')?.textContent || '', currency_value: xmlDoc.querySelector('currency_value')?.textContent || '', ltype: xmlDoc.querySelector('ltype')?.textContent || '', pay_type: xmlDoc.querySelector('pay_type')?.textContent || '', username: xmlDoc.querySelector('username')?.textContent || '', aid: xmlDoc.querySelector('aid')?.textContent || '', ms: xmlDoc.querySelector('ms')?.textContent || '', league_name: xmlDoc.querySelector('league_name')?.textContent || '', team_name_c: xmlDoc.querySelector('team_name_c')?.textContent || '', team_name_h: xmlDoc.querySelector('team_name_h')?.textContent || '', max_gold: xmlDoc.querySelector('max_gold')?.textContent || '', dg: xmlDoc.querySelector('dg')?.textContent || '', ts: xmlDoc.querySelector('ts')?.textContent || '', ptype: xmlDoc.querySelector('ptype')?.textContent || '', important: xmlDoc.querySelector('important')?.textContent || '', systime: xmlDoc.querySelector('systime')?.textContent || '', fast_check: xmlDoc.querySelector('fast_check')?.textContent || '' }]; } console.log('=== 读取订单数据失败(无501状态) ==='); return []; } catch (error) { console.error('读取订单数据失败:', error.message); return []; } } /** * 执行投注操作(通过PHP代理请求目标网站) * @param {Object} betRecord - 投注记录 * @param {string} uid - 当前登录用户ID * @param {string} version - 当前版本号 * @param {Array} orderData - 订单数据 */ async function executeBet(betRecord, uid, version, orderData) { try { let actionP = 'FT_bet'; let gtype = 'FT'; if(betRecord.betType === 'bk'){ actionP = 'Other_bet'; gtype = 'BK'; } // 1. 构建目标网站需要的投注参数(原逻辑参数不变) const betParams = { p: actionP, uid: uid, ver: version, langx: 'zh-cn', odd_f_type: 'H',//H 盘口 大小球 golds: Math.abs(Math.floor(Number(betRecord.bet_money))), // 确保为正整数格式 gid: betRecord.game_id, gtype: gtype, wtype: betRecord.wtype,// OU 大小球 R 让球 rtype: betRecord.rtype,// OUC 大球 OUH 小球 RH 让球 RC 受让 chose_team: betRecord.chose_team,// C 大球 H 小球 H让球 C受让 让球H表示主队 ioratio: orderData[0].ioratio, con: orderData[0].con, //2 大球 -2 小球 ratio: orderData[0].ratio,//-50 大球 50 小球 autoOdd: 'Y', timestamp: new Date().getTime(), // 添加时间戳 timestamp2: '', // 添加空时间戳2 isRB: 'N', imp: 'N', ptype: '', isYesterday: 'N', f: '1R' }; // 2. 构建发送给PHP代理的请求数据 const proxyRequest = { targetUrl: `${getBaseApiUrl()}?ver=${encodeURIComponent(version)}`, // 目标网站投注接口 method: 'POST', params: betParams, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': navigator.userAgent, 'Referer': getBaseApiUrl() } }; // 3. 请求PHP代理 const res = await requestUtils.fetchWithRetry(PROXY_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(proxyRequest), credentials: 'include' }); if (!res.ok) throw new Error(`代理响应异常 [HTTP ${res.status}]`); // 4. 解析投注结果(原逻辑保留) const xmlStr = await res.text(); const xmlDoc = parseXml(xmlStr); const code = xmlDoc.querySelector('code')?.textContent; if (code === '560') { console.log('=== 投注成功 ==='); return { success: true, message: '投注成功', code: '560' }; } else { console.log(`=== 投注失败(状态码:${code}) ===`); return { success: false, message: `投注失败,状态码:${code}`, code: code }; } } catch (error) { console.error('执行投注出错:', error.message); return { success: false, message: '投注失败:' + error.message }; } } /** * 请求工具类(适配代理请求,区分本地/目标网站接口) */ const requestUtils = { delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }, /** * 带重试的请求(自动适配本地/代理请求) * @param {string} url - 请求地址(本地API或代理地址) * @param {Object} options - 请求配置 * @param {number} timeout - 超时时间 * @param {number} maxRetries - 最大重试次数 */ async fetchWithRetry(url, options = {}, timeout = 10000, maxRetries = 1) { let retries = 0; while (retries <= maxRetries) { const controller = new AbortController(); const signal = controller.signal; const timeoutId = setTimeout(() => controller.abort(), timeout); try { // 基础请求头:代理请求用JSON格式,本地请求加Referer const defaultHeaders = url === PROXY_URL ? {} // 代理请求已指定Content-Type: application/json : { 'Referer': new URL(url).origin + '/' }; // 发送请求 const response = await fetch(url, { ...options, signal, headers: { ...defaultHeaders, ...options.headers } }); clearTimeout(timeoutId); // 状态码处理(401/403表示登录失效,直接抛出) if (!response.ok) { if ([401, 403].includes(response.status)) { throw new Error(`HTTP ${response.status}:登录状态失效,请重新登录`); } throw new Error(`HTTP错误: ${response.status}`); } return response; } catch (error) { clearTimeout(timeoutId); // 仅对504超时或请求中断进行重试(代理转发常见问题) if ((error.message.includes('504') || error.name === 'AbortError') && retries < maxRetries) { retries++; const delay = 1000 * Math.pow(2, retries); // 指数退避重试 console.log(`[${new Date().toLocaleTimeString()}] 请求重试(${retries}/${maxRetries}),等待${delay}ms`); await this.delay(delay); } else { throw error; // 其他错误直接抛出 } } } throw new Error(`达到最大重试次数(${maxRetries}),请求失败`); }, async fetchAPIData(url) { const response = await this.fetchWithRetry(url); const data = await response.json(); if (data.error) throw new Error(`API错误: ${data.error}`); return data; } }; /** * 更新投注状态(本地API,无需代理) * @param {number} betId - 投注记录ID * @param {boolean} success - 投注是否成功 */ async function updateBetStatus(betId, success) { try { const res = await Promise.race([ fetch(BET_HANDLER_API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: betId, success: success }) }), timeoutPromise(REQUEST_TIMEOUT, '更新投注状态超时') ]); if (!res.ok) throw new Error(`更新投注状态失败 [HTTP ${res.status}]`); return await res.json(); } catch (error) { console.error('更新投注状态出错:', error.message); throw error; } } /** * 带重试的登录(通过PHP代理) * @returns {string} 成功登录的uid * @throws {Error} 所有账号登录失败 */ async function loginWithRetry() { let lastError = null; const initialIndex = currentAccountIndex; for (let i = 0; i < accounts.length; i++) { const currentAccount = accounts[currentAccountIndex]; console.log(`尝试登录账号: ${currentAccount.username} (第${i+1}/${accounts.length}次尝试)`); try { const uid = await login(currentAccount); currentAccountIndex = (currentAccountIndex + 1) % accounts.length; return uid; } catch (error) { lastError = error; console.error(`账号 ${currentAccount.username} 登录失败:`, error.message); currentAccountIndex = (currentAccountIndex + 1) % accounts.length; if (currentAccountIndex === initialIndex) break; } } throw new Error(`所有账号登录失败: ${lastError?.message || '未知错误'}`); } /** * 登录函数(通过PHP代理请求目标网站) * @param {Object} account - 账号信息 {username, password} * @returns {string} 登录成功的uid * @throws {Error} 登录失败 */ async function login(account) { try { // 1. 构建目标网站登录参数(原FormData参数转为普通对象) const loginParams = { p: 'chk_login', langx: 'zh-cn', ver: getCurrentVersionString(), username: account.username, password: account.password, app: 'N', auto: 'AZZCFC', userAgent: '', blackbox: '' }; // 2. 构建代理请求数据 const proxyRequest = { targetUrl: `${getBaseApiUrl()}?ver=${encodeURIComponent(getCurrentVersionString())}`, // 目标网站登录接口 method: 'POST', params: loginParams, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': navigator.userAgent } }; // 3. 请求代理 const res = await Promise.race([ requestUtils.fetchWithRetry(PROXY_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(proxyRequest), credentials: 'include' }), timeoutPromise(REQUEST_TIMEOUT, '登录请求超时') ]); if (!res.ok) throw new Error(`代理响应异常 [HTTP ${res.status}]`); // 4. 解析登录结果(原逻辑保留) const xmlStr = await res.text(); const xmlDoc = parseXml(xmlStr); const errorCode = xmlDoc.querySelector('code'); if (errorCode?.textContent === 'error') { const errorMsg = xmlDoc.querySelector('msg')?.textContent || '未知错误'; throw new Error(`登录错误: ${errorMsg}`); } if (xmlDoc.querySelector('status')?.textContent !== '200') { throw new Error('登录状态码异常'); } const newUid = xmlDoc.querySelector('uid')?.textContent; if (!newUid) throw new Error('登录响应中未找到uid'); return newUid; } catch (error) { throw error; } } /** * 获取当前版本号(通过PHP代理请求目标网站) * @param {string} uid - 登录用户uid * @returns {string} 目标网站当前版本号 * @throws {Error} 获取失败 */ async function getCurrentVersion(uid) { try { // 1. 构建目标网站版本请求参数(原FormData参数转为普通对象) const versionParams = { p: 'get_version', langx: 'zh-cn', uid: uid }; // 2. 构建代理请求数据 const proxyRequest = { targetUrl: getBaseApiUrl(), // 目标网站版本接口 method: 'POST', params: versionParams, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': navigator.userAgent } }; // 3. 请求代理 const res = await requestUtils.fetchWithRetry(PROXY_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(proxyRequest), credentials: 'include' }); if (!res.ok) throw new Error(`代理响应异常 [HTTP ${res.status}]`); // 4. 解析版本号(原逻辑保留) const xmlStr = await res.text(); const xmlDoc = parseXml(xmlStr); const errorCode = xmlDoc.querySelector('code'); if (errorCode?.textContent === 'error') { const errorMsg = xmlDoc.querySelector('msg')?.textContent || '未知错误'; throw new Error(`获取版本号错误: ${errorMsg}`); } const version = xmlDoc.querySelector('ver')?.textContent; if (!version) throw new Error('响应中未找到版本号'); return version; } catch (error) { throw error; } } /** * 等待页面和iframe加载完成 * @returns {Promise} 加载完成Promise */ async function waitForPageAndIframeReady() { // 等待主页面加载完成 if (document.readyState !== 'complete') { await new Promise(resolve => window.addEventListener('load', resolve)); } // 等待所有iframe加载完成(如有需要) const iframes = document.querySelectorAll('iframe'); if (iframes.length > 0) { await Promise.all( Array.from(iframes).map(iframe => new Promise(resolve => { if (iframe.contentDocument?.readyState === 'complete') { resolve(); } else { iframe.addEventListener('load', resolve); } }) ) ); } console.log('页面和iframe加载完成'); return Promise.resolve(); } /** * XML解析工具(增加解析错误检测) * @param {string} xmlStr - XML字符串 * @returns {Document} XML DOM对象 */ function parseXml(xmlStr) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlStr, 'text/xml'); // 检测XML解析错误 const parseError = xmlDoc.querySelector('parsererror'); if (parseError) { const errorMsg = parseError.textContent.trim().replace(/\s+/g, ' ').substring(0, 100); throw new Error(`XML解析失败: ${errorMsg}...`); } return xmlDoc; } /** * 超时Promise工具 * @param {number} ms - 超时毫秒数 * @param {string} message - 超时错误信息 * @returns {Promise} 超时Promise */ function timeoutPromise(ms, message) { return new Promise((_, reject) => { setTimeout(() => reject(new Error(message)), ms); }); } /** * 注入脚本获取页面全局变量_CHDomain.uid(原逻辑保留,无跨域) * @returns {Promise<string|null>} _CHDomain.uid的值或null */ function getCHDomainUid() { return new Promise((resolve) => { try { const timeoutId = setTimeout(() => { console.warn('获取_CHDomain.uid超时'); resolve(null); }, 5000); // 注入脚本获取全局变量 const script = document.createElement('script'); script.textContent = ` (function() { const uid = window._CHDomain?.uid || null; const event = new CustomEvent('chDomainUidEvent', { detail: { uid: uid } }); window.dispatchEvent(event); })(); `; // 监听结果事件 const handler = (event) => { clearTimeout(timeoutId); resolve(event.detail.uid); window.removeEventListener('chDomainUidEvent', handler); document.body.removeChild(script); }; window.addEventListener('chDomainUidEvent', handler, { once: true }); document.body.appendChild(script); } catch (e) { console.error('获取_CHDomain.uid失败:', e); resolve(null); } }); }
更新并重新加载插件
操作日志