diff --git a/_php_code/.well-known/acme-challenge/47demv6l5lvam1gzqdkzrih64pb0tzqd b/_php_code/.well-known/acme-challenge/47demv6l5lvam1gzqdkzrih64pb0tzqd new file mode 100755 index 0000000..e30782b --- /dev/null +++ b/_php_code/.well-known/acme-challenge/47demv6l5lvam1gzqdkzrih64pb0tzqd @@ -0,0 +1 @@ +47demv6l5lvam1gzqdkzrih64pb0tzqd.gddJCefh0gXT5ZBsRHlHGniOhraU34wwA4o-jjmdTFw \ No newline at end of file diff --git a/_php_code/1.txt b/_php_code/1.txt new file mode 100755 index 0000000..847dd18 --- /dev/null +++ b/_php_code/1.txt @@ -0,0 +1 @@ +1213 \ No newline at end of file diff --git a/_php_code/Chatknow.php b/_php_code/Chatknow.php old mode 100644 new mode 100755 diff --git a/_php_code/SplitPageUrlResolver.php b/_php_code/SplitPageUrlResolver.php old mode 100644 new mode 100755 diff --git a/_php_code/Whatshub.php b/_php_code/Whatshub.php old mode 100644 new mode 100755 diff --git a/_php_code/Xinghe2.php b/_php_code/Xinghe2.php old mode 100644 new mode 100755 diff --git a/_php_code/test.php b/_php_code/test.php new file mode 100755 index 0000000..2c15f05 --- /dev/null +++ b/_php_code/test.php @@ -0,0 +1,210 @@ +抓取任务 (多引擎智能调度)...\n\r"; +// $pageUrl = 'https://web.whatshub.cc/m/iTYsWKQH5030/1'; // PageUrl 入口授权页 +// $username = ""; // 登录账号 +// $password = "745030"; // 登录密码 +// $spider = new Whatshub($pageUrl, $username, $password); +// $finalData = $spider->run(); + +// echo "✅ 任务完成!统一数据如下:\n\r"; +// echo "----------------------------------------\n\r"; +// echo "当日新增:{$finalData->todayNewCount} 人\n\r"; +// echo "在线号码:{$finalData->totalOnline} 个\n\r"; +// echo "离线号码:{$finalData->totalOffline} 个\n\r"; +// echo "Total:" . $finalData->total . " 个号码\n\r"; +// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r"; +// echo "号码列表:\n\r"; +// echo dd($finalData->numbers); +// } catch (Exception $e) { +// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r"; +// } + + +// try { +// echo "🚀 开始执行抓取任务 (多引擎智能调度)...\n\r"; +// $pageUrl = 'https://user.chatknow.com/child/workorder-share?shareKey=jf5t6MNrJ2mC76N'; // PageUrl 入口授权页 +// $username = ""; // 登录账号 +// $password = ""; // 登录密码 +// $spider = new Chatknow($pageUrl, $username, $password); +// $finalData = $spider->run(); + +// echo "✅ 任务完成!统一数据如下:\n\r"; +// echo "----------------------------------------\n\r"; +// echo "当日新增:{$finalData->todayNewCount} 人\n\r"; +// echo "在线号码:{$finalData->totalOnline} 个\n\r"; +// echo "离线号码:{$finalData->totalOffline} 个\n\r"; +// echo "Total:" . $finalData->total . " 个号码\n\r"; +// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r"; +// echo "号码列表:\n\r"; +// echo dd($finalData->numbers); +// } catch (Exception $e) { +// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r"; +// } + +// try { +// echo "🚀 开始执行抓取任务 (多引擎智能调度)...\n\r"; +// $pageUrl = 'https://yyk.ink/52oXZRG'; // PageUrl 入口授权页 +// $username = ""; // 登录账号 +// $password = ""; // 登录密码 +// $spider = new A2c($pageUrl, $username, $password); +// $finalData = $spider->run(); + +// echo "✅ 任务完成!统一数据如下:\n\r"; +// echo "----------------------------------------\n\r"; +// echo "当日新增:{$finalData->todayNewCount} 人\n\r"; +// echo "在线号码:{$finalData->totalOnline} 个\n\r"; +// echo "离线号码:{$finalData->totalOffline} 个\n\r"; +// echo "Total:" . $finalData->total . " 个号码\n\r"; +// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r"; +// echo "号码列表:\n\r"; +// echo dd($finalData->numbers); + +// } catch (Exception $e) { +// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r"; +// } + +// try { +// echo "🚀 开始执行<星河云控>抓取任务 (多引擎智能调度)...\n\r"; +// $pageUrl = 'http://8.218.14.51/share/share/index.html?token=hn6z3egq4nnnebkv4063pcdouzr4ug5js902mlqo2n3yp9gzhe'; // PageUrl 入口授权页 +// $username = ""; // 登录账号 +// $password = ""; // 登录密码 +// $spider = new Xinghe($pageUrl, $username, $password); +// $finalData = $spider->run(); + +// echo "✅ 任务完成!统一数据如下:\n\r"; +// echo "----------------------------------------\n\r"; +// echo "当日新增:{$finalData->todayNewCount} 人\n\r"; +// echo "在线号码:{$finalData->totalOnline} 个\n\r"; +// echo "离线号码:{$finalData->totalOffline} 个\n\r"; +// echo "Total:" . $finalData->total . " 个号码\n\r"; +// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r"; +// echo "号码列表:\n\r"; +// echo dd($finalData->numbers); +// } catch (Exception $e) { +// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r"; +// } + +try { + echo "🚀 开始执行<火箭工单>抓取任务 (多引擎智能调度)...\n\r"; + $pageUrl = 'https://s.url99.me/50q3622j'; // PageUrl 入口授权页 + $username = ""; // 登录账号 + $password = "666666"; // 登录密码 + $spider = new Huojian($pageUrl, $username, $password); + $finalData = $spider->run(); + + echo "✅ 任务完成!统一数据如下:\n\r"; + echo "----------------------------------------\n\r"; + echo "当日新增:{$finalData->todayNewCount} 人\n\r"; + echo "在线号码:{$finalData->totalOnline} 个\n\r"; + echo "离线号码:{$finalData->totalOffline} 个\n\r"; + echo "Total:" . $finalData->total . " 个号码\n\r"; + echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r"; + echo "号码列表:\n\r"; + echo dd($finalData->numbers); + +} catch (Exception $e) { + echo "🚨 抓取异常:" . $e->getMessage() . "\n\r"; +} + +// try { +// echo "🚀 开始执行抓取任务 (多引擎智能调度)...\n\r"; +// $pageUrl = 'https://app.salesmartly.vip/next/share/reports/verify/customer/fwt7cg'; // PageUrl 入口授权页 +// $username = ""; // 登录账号 +// $password = "123456"; // 登录密码 +// $spider = new SsCustomer($pageUrl, $username, $password); +// $finalData = $spider->run(); + +// echo "✅ 任务完成!统一数据如下:\n\r"; +// echo "----------------------------------------\n\r"; +// echo "当日新增:{$finalData->todayNewCount} 人\n\r"; +// echo "在线号码:{$finalData->totalOnline} 个\n\r"; +// echo "离线号码:{$finalData->totalOffline} 个\n\r"; +// echo "Total:" . $finalData->total . " 个号码\n\r"; +// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r"; +// echo "号码列表:\n\r"; +// echo dd($finalData->numbers); + + +// } catch (Exception $e) { +// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r"; +// } + +// try { +// echo "🚀 开始执行<海王>抓取任务 (多引擎智能调度)...\n\r"; +// $pageUrl = 'https://admin.haiwangweb.com/web#/accountshow/pZsEulYrb'; // PageUrl 入口授权页 +// $username = ""; // 登录账号 +// $password = "9999"; // 登录密码 +// $spider = new Haiwang($pageUrl, $username, $password); +// $finalData = $spider->run(); + +// echo "✅ 任务完成!统一数据如下:\n\r"; +// echo "----------------------------------------\n\r"; +// echo "当日新增:{$finalData->todayNewCount} 人\n\r"; +// echo "在线号码:{$finalData->totalOnline} 个\n\r"; +// echo "离线号码:{$finalData->totalOffline} 个\n\r"; +// echo "Total:" . $finalData->total . " 个号码\n\r"; +// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r"; +// echo "号码列表:\n\r"; +// echo dd($finalData->numbers); + +// } catch (Exception $e) { +// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r"; +// } + +// try { +// echo "🚀 开始执行抓取任务 (多引擎智能调度)...\n\r"; +// $pageUrl = 'https://admin.scrmceo.com/#/workShareDetail?code=XgGTK5yN'; // PageUrl 入口授权页 +// $username = ""; // 登录账号 +// $password = "a2222"; // 登录密码 +// $spider = new SsCustomer($pageUrl, $username, $password); +// $finalData = $spider->run(); + +// echo "✅ 任务完成!统一数据如下:\n\r"; +// echo "----------------------------------------\n\r"; +// echo "当日新增:{$finalData->todayNewCount} 人\n\r"; +// echo "在线号码:{$finalData->totalOnline} 个\n\r"; +// echo "离线号码:{$finalData->totalOffline} 个\n\r"; +// echo "Total:" . $finalData->total . " 个号码\n\r"; +// echo "实际总共抓取:" . count($finalData->numbers) . " 个号码\n\r"; +// echo "号码列表:\n\r"; +// echo dd($finalData->numbers); +// } catch (Exception $e) { +// echo "🚨 抓取异常:" . $e->getMessage() . "\n\r"; +// } \ No newline at end of file diff --git a/application/common/library/scrm/AntiBotConfigBuilder.php b/application/common/library/scrm/AntiBotConfigBuilder.php old mode 100644 new mode 100755 diff --git a/application/common/library/scrm/spider/WhatshubSpider.php b/application/common/library/scrm/spider/WhatshubSpider.php old mode 100644 new mode 100755 diff --git a/application/common/service/SplitNumberRatioDeferredService.php b/application/common/service/SplitNumberRatioDeferredService.php old mode 100644 new mode 100755 diff --git a/application/common/service/SplitScrmSpiderFactory.php b/application/common/service/SplitScrmSpiderFactory.php old mode 100644 new mode 100755 diff --git a/application/extra/site.php b/application/extra/site.php index 33bf3ab..dd4ca89 100755 --- a/application/extra/site.php +++ b/application/extra/site.php @@ -4,7 +4,7 @@ return array ( 'name' => 'link管理系统', 'beian' => '', 'cdnurl' => '', - 'version' => '1.0.9', + 'version' => '1.0.10', 'timezone' => 'Asia/Shanghai', 'forbiddenip' => '', 'languages' => diff --git a/patches/application/common/library/scrm/AntiBotConfigBuilder.php b/patches/application/common/library/scrm/AntiBotConfigBuilder.php old mode 100644 new mode 100755 diff --git a/patches/application/common/library/scrm/spider/WhatshubSpider.php b/patches/application/common/library/scrm/spider/WhatshubSpider.php old mode 100644 new mode 100755 index e6a0afd..2b12f7f --- a/patches/application/common/library/scrm/spider/WhatshubSpider.php +++ b/patches/application/common/library/scrm/spider/WhatshubSpider.php @@ -114,10 +114,10 @@ class WhatshubSpider extends AbstractScrmSpider return parent::run(); } - /** Whatshub vxe 密码表单:placeholder 含「密码」+ 确认按钮,与业务页搜索框区分 */ - private const AUTH_PASSWORD_INPUT = '.vxe-form .el-input__inner[placeholder*="密码"]'; + /** Whatshub vxe 密码表单内 input / 确认按钮(避免误填业务页搜索框) */ + private const AUTH_PASSWORD_INPUT = '.vxe-form .el-input__inner'; - private const AUTH_CONFIRM_BUTTON = '.vxe-form .vxe-button-group button.theme--primary[type="submit"]'; + private const AUTH_CONFIRM_BUTTON = '.vxe-form button.theme--primary[type="submit"]'; /** * Node 监听 statistics API,返回 dayNewFans + reDayNewFans 总和;失败返回 null diff --git a/patches/application/common/service/SplitScrmSpiderFactory.php b/patches/application/common/service/SplitScrmSpiderFactory.php old mode 100644 new mode 100755 diff --git a/patches/puppeteer-api/server.js b/patches/puppeteer-api/server.js index f75d548..46942b4 100755 --- a/patches/puppeteer-api/server.js +++ b/patches/puppeteer-api/server.js @@ -213,30 +213,6 @@ async function waitForUrlSettled(page, { maxAttempts = 15, stableMs = 500, redir return currentUrl; } -/** PHP → Node 透传的 Whatshub/auth 扩展字段(normalizeAntiBot 不含这些项) */ -const WHATSHUB_AUTH_EXTRA_KEYS = [ - 'authSkipIfNoPasswordDialog', - 'authSelectorTimeoutMs', - 'earlyApiIntercept', - 'skipPostCfReadyApi', - 'purgeSession', - 'postCfReadySelectorOptional', - 'skipShortLinkCfPurgeOnReuse', -]; - -function pickAntiBotExtraFields(rawAntiBot, keys) { - const extras = {}; - if (!rawAntiBot || typeof rawAntiBot !== 'object') { - return extras; - } - for (const key of keys) { - if (rawAntiBot[key] !== undefined) { - extras[key] = rawAntiBot[key]; - } - } - return extras; -} - /** 合并 antiBot 扩展字段(postCfReady* 由 PHP 按工单类型传入,未配置则跳过) */ function resolveAntiBot(rawAntiBot) { const base = normalizeAntiBot(rawAntiBot); @@ -245,7 +221,6 @@ function resolveAntiBot(rawAntiBot) { } return { ...base, - ...pickAntiBotExtraFields(rawAntiBot, WHATSHUB_AUTH_EXTRA_KEYS), postCfReadyUrlContains: rawAntiBot.postCfReadyUrlContains || '', postCfReadySelector: rawAntiBot.postCfReadySelector || '', postCfReadyApiUrl: rawAntiBot.postCfReadyApiUrl || '', @@ -291,7 +266,6 @@ function applyWhatshubPostCfDefaults(antiBot, pageUrl) { postCfSettleMs: antiBot.postCfSettleMs || 500, postCfSpaWaitMs: antiBot.postCfSpaWaitMs || 4000, skipShortLinkCfPurgeOnReuse: antiBot.skipShortLinkCfPurgeOnReuse !== false, - authSkipIfNoPasswordDialog: antiBot.authSkipIfNoPasswordDialog !== false, }; } catch (_) { return antiBot; @@ -481,11 +455,7 @@ function isWhatshubHost(url) { } } -/** Whatshub 密码弹窗 DOM 选择器(与 PHP WhatshubSpider authActions 保持一致) */ -const WHATSHUB_PASSWORD_INPUT_SEL = '.vxe-form .el-input__inner[placeholder*="密码"]'; -const WHATSHUB_PASSWORD_CONFIRM_SEL = '.vxe-form .vxe-button-group button.theme--primary[type="submit"]'; - -/** Whatshub 密码 UI 是否可见(必须密码 input + 确认按钮同框,排除搜索区 vxe-form) */ +/** Whatshub 密码 UI 是否可见(vxe-form / vxe-modal + Element dialog) */ async function isWhatshubPasswordDialogVisible(page) { return page.evaluate(() => { const isVisible = (el) => { @@ -495,53 +465,26 @@ async function isWhatshubPasswordDialogVisible(page) { const style = window.getComputedStyle(el); return style.display !== 'none' && style.visibility !== 'hidden'; }; - const isPasswordInput = (input) => { - if (!input || !isVisible(input)) return false; - const placeholder = (input.getAttribute('placeholder') || '').trim(); - if (placeholder.includes('密码')) return true; - return input.getAttribute('maxlength') === '8'; - }; - const rootHasPasswordForm = (root) => { - if (!root || !isVisible(root)) return false; - const pwd = root.querySelector('.el-input__inner[placeholder*="密码"], .el-input__inner[maxlength="8"]'); - if (!isPasswordInput(pwd)) return false; - const confirm = root.querySelector( - '.vxe-button-group button.theme--primary[type="submit"], button.theme--primary[type="submit"]' - ); - return !!(confirm && isVisible(confirm)); + const hasVisiblePasswordInput = (root) => { + const pwd = root.querySelector('.vxe-form .el-input__inner, .el-input__inner[placeholder*="密码"]'); + return pwd && isVisible(pwd); }; const roots = document.querySelectorAll( 'form.vxe-form, .vxe-modal--wrapper, .vxe-modal, ' + '.el-message-box, .el-overlay-message-box, .el-dialog, .el-overlay-dialog' ); for (const root of roots) { - if (rootHasPasswordForm(root)) return true; + if (!isVisible(root)) continue; + if (hasVisiblePasswordInput(root)) return true; + } + const standaloneForm = document.querySelector('form.vxe-form'); + if (standaloneForm && isVisible(standaloneForm) && hasVisiblePasswordInput(standaloneForm)) { + return true; } return false; }).catch(() => false); } -/** 业务页表格已就绪且页面上无密码确认按钮 → 视为 session 已登录 */ -async function isWhatshubBusinessLoggedIn(page) { - return page.evaluate(() => { - const isVisible = (el) => { - if (!el) return false; - const rect = el.getBoundingClientRect(); - if (rect.width <= 0 || rect.height <= 0) return false; - const style = window.getComputedStyle(el); - return style.display !== 'none' && style.visibility !== 'hidden'; - }; - const tableReady = !!document.querySelector('.vxe-table, .vxe-grid'); - const pwdDialog = document.querySelector('.vxe-form .el-input__inner[placeholder*="密码"]'); - const confirmBtn = document.querySelector( - '.vxe-form .vxe-button-group button.theme--primary[type="submit"]' - ); - const hasPasswordUi = (pwdDialog && isVisible(pwdDialog)) - && (confirmBtn && isVisible(confirmBtn)); - return tableReady && !hasPasswordUi; - }).catch(() => false); -} - /** statistics-only 等场景:导航后立即挂 API 拦截器,避免 session 复用时响应早于监听 */ function shouldUseEarlyApiIntercept(antiBot, apiUrls) { if (antiBot?.earlyApiIntercept === true) { @@ -1450,8 +1393,8 @@ async function collectPageDiagnostics(page) { bodyText: (document.body && document.body.innerText ? document.body.innerText : '').slice(0, 500), elInputInner: document.querySelectorAll('.el-input__inner').length, vxeForm: document.querySelectorAll('form.vxe-form').length, - vxePasswordInput: document.querySelectorAll('.vxe-form .el-input__inner[placeholder*="密码"]').length, - vxeConfirmBtn: document.querySelectorAll('.vxe-form .vxe-button-group button.theme--primary[type="submit"]').length, + vxePasswordInput: document.querySelectorAll('.vxe-form .el-input__inner').length, + vxeConfirmBtn: document.querySelectorAll('.vxe-form button.theme--primary').length, elTable: document.querySelectorAll('.el-table').length, vxeTable: document.querySelectorAll('.vxe-table, .vxe-grid').length, btnNext: document.querySelectorAll('.btn-next').length, @@ -1518,11 +1461,6 @@ async function awaitPostCfBusinessReady(page, antiBot, waitForUrlSettledFn, stor } if (!skipApiWait) { await waitForPostCfListApi(page, antiBot, timeoutMs); - } else if (antiBot?.authSkipIfNoPasswordDialog && isStatisticsOnlyApiUrls(apiUrls)) { - console.log( - '[CF] 跳过 pre-auth statistics 等待' - + '(sid 直进业务页:auth 跳过后 reload 补捕获;需填密码:auth 提交后 waitForApis)' - ); } else { console.log('[CF] 跳过 pre-auth 业务 API 等待(statistics 在密码验证后触发)'); } @@ -1607,180 +1545,54 @@ async function awaitPostCfBusinessReady(page, antiBot, waitForUrlSettledFn, stor async function executeAuthActionsWithDiagnostics(page, authActions, antiBot = null) { try { - return await executeAuthActions(page, authActions, antiBot); + await executeAuthActions(page, authActions, antiBot); } catch (err) { err.pageDiagnostics = await collectPageDiagnostics(page); throw err; } } -/** 采集 Whatshub auth 决策所需的 DOM 计数(与 collectPageDiagnostics 字段对齐) */ -async function collectWhatshubAuthDomCounts(page) { - return page.evaluate(() => ({ - vxeTable: document.querySelectorAll('.vxe-table, .vxe-grid').length, - vxePasswordInput: document.querySelectorAll( - '.vxe-form .el-input__inner[placeholder*="密码"]' - ).length, - vxeConfirmBtn: document.querySelectorAll( - '.vxe-form .vxe-button-group button.theme--primary[type="submit"]' - ).length, - })).catch(() => ({ vxeTable: 0, vxePasswordInput: 0, vxeConfirmBtn: 0 })); -} - -/** - * Whatshub skip-auth 决策:URL 仅确认路由,登录/业务态靠 DOM - * @returns {Promise<{skip: boolean, reason: string, pwdVisible?: boolean, businessLoggedIn?: boolean, dom?: object, url?: string}>} - */ -async function resolveWhatshubAuthSkipDecision(page, antiBot) { - if (!antiBot?.authSkipIfNoPasswordDialog) { - return { skip: false, reason: 'authSkipIfNoPasswordDialog_disabled' }; - } - const urlContains = (antiBot.postCfReadyUrlContains || 'work-order-sharing').trim(); - let currentUrl = ''; - try { - currentUrl = page.url(); - } catch (_) { - return { skip: false, reason: 'page_url_unavailable' }; - } - const onBusinessRoute = !urlContains || currentUrl.includes(urlContains); - if (!onBusinessRoute) { - return { skip: false, reason: 'not_on_business_route', url: currentUrl }; - } - - const pwdVisible = await isWhatshubPasswordDialogVisible(page); - const businessLoggedIn = await isWhatshubBusinessLoggedIn(page); - const dom = await collectWhatshubAuthDomCounts(page); - - if (pwdVisible) { - return { - skip: false, - reason: 'password_dialog_visible', - pwdVisible, - businessLoggedIn, - dom, - url: currentUrl, - }; - } - const tableReadyNoPassword = dom.vxeTable > 0 - && dom.vxePasswordInput === 0 - && dom.vxeConfirmBtn === 0; - if (businessLoggedIn || tableReadyNoPassword) { - return { - skip: true, - reason: businessLoggedIn ? 'business_logged_in' : 'table_ready_no_password_ui', - pwdVisible, - businessLoggedIn, - dom, - url: currentUrl, - }; - } - return { - skip: true, - reason: 'no_password_dialog_on_business_route', - pwdVisible, - businessLoggedIn, - dom, - url: currentUrl, - }; -} - -function logWhatshubAuthSkipDecision(decision) { - const dom = decision.dom || {}; - console.log( - '[Whatshub] auth skip 决策' - + ` skip=${decision.skip}` - + ` reason=${decision.reason}` - + ` pwdVisible=${decision.pwdVisible}` - + ` businessLoggedIn=${decision.businessLoggedIn}` - + ` vxeTable=${dom.vxeTable ?? '?'}` - + ` vxePasswordInput=${dom.vxePasswordInput ?? '?'}` - + ` vxeConfirmBtn=${dom.vxeConfirmBtn ?? '?'}` - + ` url=${decision.url || ''}` - ); -} - async function shouldSkipWhatshubAuthActions(page, antiBot) { - const decision = await resolveWhatshubAuthSkipDecision(page, antiBot); - logWhatshubAuthSkipDecision(decision); - return decision.skip; -} - -/** - * Loading 结束后才会出现密码框或业务表格;先等二者之一再决定是否需要填密码 - * @returns {Promise} true=应跳过 authActions - */ -async function waitForWhatshubPasswordFormIfNeeded(page, antiBot) { - if (!antiBot || !(isWhatshubHost(page.url()) || page.url().includes('work-order-sharing'))) { + if (!antiBot?.authSkipIfNoPasswordDialog) { return false; } - const initial = await resolveWhatshubAuthSkipDecision(page, antiBot); - logWhatshubAuthSkipDecision({ ...initial, reason: `pre_wait_${initial.reason}` }); - if (initial.skip) { - return true; - } - - const timeout = resolveAuthSelectorTimeoutMs(antiBot); - const surfaceTimeout = Math.min(timeout, 10000); - console.log('[Whatshub] 等待密码框或业务表格渲染(Loading 结束后)...'); - await page.waitForFunction( - () => { - const isVisible = (el) => { - if (!el) return false; - const rect = el.getBoundingClientRect(); - if (rect.width <= 0 || rect.height <= 0) return false; - const style = window.getComputedStyle(el); - return style.display !== 'none' && style.visibility !== 'hidden'; - }; - if (document.querySelector('.vxe-table, .vxe-grid')) { - return true; - } - const roots = document.querySelectorAll( - 'form.vxe-form, .vxe-modal--wrapper, .vxe-modal, .el-dialog, .el-overlay-dialog' - ); - for (const root of roots) { - const pwd = root.querySelector('.el-input__inner[placeholder*="密码"], .el-input__inner[maxlength="8"]'); - const confirm = root.querySelector( - '.vxe-button-group button.theme--primary[type="submit"], button.theme--primary[type="submit"]' - ); - if (isVisible(pwd) && isVisible(confirm)) { - return true; - } - } + const urlContains = (antiBot.postCfReadyUrlContains || 'work-order-sharing').trim(); + try { + const onBusiness = !urlContains || page.url().includes(urlContains); + if (!onBusiness) { return false; - }, - { timeout: surfaceTimeout, polling: 200 } - ).catch(() => null); - - const afterSurface = await resolveWhatshubAuthSkipDecision(page, antiBot); - logWhatshubAuthSkipDecision({ ...afterSurface, reason: `after_surface_${afterSurface.reason}` }); - if (afterSurface.skip) { - return true; + } + const pwdVisible = await isWhatshubPasswordDialogVisible(page); + if (!pwdVisible) { + return true; + } + } catch (_) { + /* ignore */ } + return false; +} - console.log('[Whatshub] 等待 vxe 密码表单出现(password input + confirm)...'); - await page.waitForSelector(WHATSHUB_PASSWORD_INPUT_SEL, { visible: true, timeout }).catch(() => null); - await page.waitForSelector(WHATSHUB_PASSWORD_CONFIRM_SEL, { visible: true, timeout }).catch(() => null); - - const afterWait = await resolveWhatshubAuthSkipDecision(page, antiBot); - logWhatshubAuthSkipDecision({ ...afterWait, reason: `after_password_wait_${afterWait.reason}` }); - return afterWait.skip; +async function waitForWhatshubPasswordFormIfNeeded(page, antiBot) { + if (!antiBot || !(isWhatshubHost(page.url()) || page.url().includes('work-order-sharing'))) { + return; + } + if (await shouldSkipWhatshubAuthActions(page, antiBot)) { + return; + } + const timeout = resolveAuthSelectorTimeoutMs(antiBot); + console.log('[Whatshub] 等待 vxe 密码表单出现...'); + await page.waitForSelector('.vxe-form .el-input__inner', { visible: true, timeout }).catch(() => null); } async function executeAuthActions(page, authActions, antiBot = null) { - if (!Array.isArray(authActions)) { - return { skipped: false, ran: false }; - } + if (!Array.isArray(authActions)) return; if (await shouldSkipWhatshubAuthActions(page, antiBot)) { console.log('[Whatshub] Session 已登录且无密码弹窗,跳过 authActions'); - return { skipped: true, ran: false }; + return; } - const skipAfterWait = await waitForWhatshubPasswordFormIfNeeded(page, antiBot); - if (skipAfterWait) { - console.log('[Whatshub] 密码等待后复检:业务页已就绪,跳过 authActions'); - return { skipped: true, ran: false }; - } + await waitForWhatshubPasswordFormIfNeeded(page, antiBot); const selectorTimeout = resolveAuthSelectorTimeoutMs(antiBot); @@ -1859,78 +1671,6 @@ async function executeAuthActions(page, authActions, antiBot = null) { }, action.selector, action.text); } } - return { skipped: false, ran: true }; -} - -/** 是否 statistics-only 拦截(Whatshub 完成量) */ -function isStatisticsOnlyApiUrls(apiUrls) { - if (!Array.isArray(apiUrls) || apiUrls.length === 0) { - return false; - } - return apiUrls.every((u) => String(u).includes('/open/statistics')); -} - -function areAllApisIntercepted(interceptor, apiUrls) { - if (!interceptor || !Array.isArray(apiUrls)) { - return false; - } - return apiUrls.every((api) => !!interceptor.interceptedApis[api]); -} - -function createApiInterceptorHandle() { - return { interceptor: null, boundPage: null, cleanup: null }; -} - -function attachApiInterceptorToPage(handle, page, apiUrls, timeoutMs, label = '') { - if (handle.interceptor && handle.boundPage === page) { - return handle.interceptor; - } - if (handle.cleanup) { - handle.cleanup(); - handle.cleanup = null; - } - handle.interceptor = createApiInterceptor(page, apiUrls, timeoutMs); - handle.boundPage = page; - handle.cleanup = handle.interceptor.cleanup; - const tag = label ? ` (${label})` : ''; - console.log(`[API] 拦截器已挂载${tag} apis=${apiUrls.join(',')} url=${page.url()}`); - return handle.interceptor; -} - -async function ensureWhatshubStatisticsAfterAuthSkip(page, interceptor, apiUrls, authSkipped, interceptTimeout) { - if (!authSkipped || !isStatisticsOnlyApiUrls(apiUrls)) { - return; - } - if (areAllApisIntercepted(interceptor, apiUrls)) { - console.log('[Whatshub] statistics 已在页面加载时捕获'); - return; - } - - console.log( - '[Whatshub] auth 已跳过但 statistics 未捕获' - + '(可能 CF 换页后漏包或 sid 直进业务页时 pre-auth 未等待),reload 补触发' - ); - - await new Promise((resolve) => setTimeout(resolve, 1500)); - if (areAllApisIntercepted(interceptor, apiUrls)) { - console.log('[Whatshub] statistics 延迟响应已捕获'); - return; - } - - const reloadTimeout = Math.min(60000, interceptTimeout); - try { - await page.reload({ waitUntil: 'domcontentloaded', timeout: reloadTimeout }); - await page.waitForSelector('.vxe-table, .vxe-grid', { timeout: 30000 }).catch(() => null); - await new Promise((resolve) => setTimeout(resolve, 2000)); - } catch (err) { - console.warn(`[Whatshub] reload 触发 statistics 失败: ${err.message}`); - } - - if (areAllApisIntercepted(interceptor, apiUrls)) { - console.log('[Whatshub] reload 后 statistics 已捕获'); - } else { - console.warn('[Whatshub] reload 后 statistics 仍未捕获,继续 waitForApis'); - } } function createApiInterceptor(page, apiUrls, defaultTimeoutMs = API_INTERCEPT_TIMEOUT_MS) { @@ -2188,13 +1928,12 @@ app.post('/api/auth-and-intercept', async (req, res) => { await prepareWhatshubShortLinkBeforeNav(page, navigationUrl, antiBot, storedSession); - const apiHandle = createApiInterceptorHandle(); let interceptor = null; const useEarlyIntercept = shouldUseEarlyApiIntercept(antiBot, apiUrls); if (useEarlyIntercept) { - interceptor = attachApiInterceptorToPage( - apiHandle, page, apiUrls, interceptTimeout, 'pre-nav' - ); + interceptor = createApiInterceptor(page, apiUrls, interceptTimeout); + interceptorCleanup = interceptor.cleanup; + console.log('[Whatshub] statistics 早挂 API 拦截器(session 复用防漏包)'); } await navigateToPage(page, navigationUrl); @@ -2222,12 +1961,6 @@ app.post('/api/auth-and-intercept', async (req, res) => { browserSession = browserCtx.browserSession; page = browserCtx.page; - if (useEarlyIntercept) { - interceptor = attachApiInterceptorToPage( - apiHandle, page, apiUrls, interceptTimeout, 'post-cf-proxy' - ); - } - if (antiBot.enabled) { const cfResult = await runCloudflareWithBusinessGuard( page, antiBot, waitForUrlSettled, storedSession, pageUrl, browserCtx @@ -2265,37 +1998,14 @@ app.post('/api/auth-and-intercept', async (req, res) => { await seedShareTokenCookie(page, finalPageUrl); } - if (useEarlyIntercept) { - interceptor = attachApiInterceptorToPage( - apiHandle, page, apiUrls, interceptTimeout, 'post-cf-ready' - ); - } else { - interceptor = attachApiInterceptorToPage( - apiHandle, page, apiUrls, interceptTimeout, 'pre-auth' - ); + if (!interceptor) { + interceptor = createApiInterceptor(page, apiUrls, interceptTimeout); + interceptorCleanup = interceptor.cleanup; } - interceptorCleanup = apiHandle.cleanup; - await attachHttpIpRewriteInterceptor(page, 'auth-and-intercept', { shareToken }); - const authResult = await executeAuthActionsWithDiagnostics(page, authActions, antiBot); - await ensureWhatshubStatisticsAfterAuthSkip( - page, - interceptor, - apiUrls, - !!authResult?.skipped, - interceptTimeout - ); - - const capturedBeforeWait = Object.keys(interceptor.interceptedApis).join(',') || 'none'; - console.log( - `[API] 等待 intercept apis=${apiUrls.join(',')} timeout=${interceptTimeout}ms` - + ` already=${capturedBeforeWait}` - ); + await executeAuthActionsWithDiagnostics(page, authActions, antiBot); await interceptor.waitForApis(interceptTimeout); - console.log( - `[API] intercept 完成 captured=${Object.keys(interceptor.interceptedApis).join(',') || 'none'}` - ); const rawCookies = await page.cookies(); const cookiePayload = rawCookies.map((c) => ({