fix(desktop-client): stop false manual-verification states and auto-revive probes after sleep/offline
Challenge detection previously regex-matched the entire page innerText, so chat transcripts or console copy containing words like 验证码/安全验证/verify flagged healthy accounts as challenge_required. Detection is now scoped to real evidence only (visible captcha vendor widgets, short modal copy, sparse interstitial pages) via a shared challenge-signals module used by the generic AI probe, doubao/kimi/qwen auth rules, and the deepseek runtime snapshot. Platform API error codes (e.g. baijiahao_challenge_required) keep working. Account health also could not recover after lid-close sleep or a network drop: offline probes misread cached console pages as expired, and expired records null out nextProbeAt so they were never re-probed. Probing is now skipped while offline (network_error backoff instead), and powerMonitor resume/unlock plus an offline-to-online transition watch schedule jittered recovery probes for all tracked accounts, including expired ones. Challenge rechecks wait 8s so transient widgets during page load no longer stick. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2379,20 +2379,72 @@ async function readDeepSeekPageSnapshot(
|
||||
}
|
||||
|
||||
const bodyText = normalize(document.body?.innerText || '')
|
||||
// 登录/验证判定不能直接对整页 innerText 匹配:聊天问题与回答里出现
|
||||
// "登录/验证码/安全验证" 这类词会把正常会话误判成需要人工处理。
|
||||
// 只认三类证据:sign_in 路由、可见弹层内的短文案、稀疏拦截页。
|
||||
const loginPhrasePattern =
|
||||
/(log in|send code|scan with wechat to login|phone number|continue with deepseek|和 deepseek 继续聊|登录|发送验证码|手机号|验证码)/i
|
||||
const challengePhrasePattern =
|
||||
/(人机验证|安全验证|请完成验证|完成下方验证|拖动滑块|滑动验证|向右滑动|安全检测|访问异常|环境异常|verification required|security check|unusual traffic|not a robot|请输入(?:图形)?验证码)/i
|
||||
const findVisibleSurfaceText = (
|
||||
selectors: string,
|
||||
pattern: RegExp,
|
||||
maxTextLength: number,
|
||||
): boolean => {
|
||||
for (const node of Array.from(document.querySelectorAll(selectors)).slice(0, 50)) {
|
||||
if (!isVisible(node)) {
|
||||
continue
|
||||
}
|
||||
const text = normalize(node.innerText || node.textContent)
|
||||
if (text && text.length <= maxTextLength && pattern.test(text)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
const hasChallengeVendorWidget = (() => {
|
||||
const vendorSelectors = [
|
||||
'[class*="geetest_"]',
|
||||
'[id^="nc_"][id$="_wrapper"]',
|
||||
'.nc-container',
|
||||
'[class*="nocaptcha"]',
|
||||
'[class*="yidun_modal"]',
|
||||
'[class*="yidun_popup"]',
|
||||
'[class*="vaptcha"]',
|
||||
'[class*="tcaptcha"]',
|
||||
'#tcaptcha_iframe',
|
||||
'[class*="captcha_verify"]',
|
||||
'[class*="secsdk-captcha"]',
|
||||
'iframe[src*="captcha"]',
|
||||
'iframe[src*="geetest"]',
|
||||
'iframe[src*="turnstile"]',
|
||||
'iframe[src*="hcaptcha"]',
|
||||
'iframe[src*="recaptcha"]',
|
||||
'iframe[title*="验证"]',
|
||||
].join(',')
|
||||
for (const node of Array.from(document.querySelectorAll(vendorSelectors)).slice(0, 50)) {
|
||||
if (isVisible(node)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})()
|
||||
const loginRequired =
|
||||
window.location.pathname.includes('/sign_in') ||
|
||||
Boolean(
|
||||
bodyText &&
|
||||
/(log in|send code|scan with wechat to login|phone number|continue with deepseek|和 deepseek 继续聊|登录|发送验证码|手机号|验证码)/i.test(
|
||||
bodyText,
|
||||
),
|
||||
)
|
||||
const challengeRequired = Boolean(
|
||||
bodyText &&
|
||||
/(verification required|captcha|security check|verify|challenge|人机验证|安全验证|请完成验证|验证码|风控|环境异常)/i.test(
|
||||
bodyText,
|
||||
),
|
||||
)
|
||||
findVisibleSurfaceText(
|
||||
'[role="dialog"], [aria-modal="true"], [class*="modal"], [class*="login"], [class*="sign"], [class*="auth"]',
|
||||
loginPhrasePattern,
|
||||
300,
|
||||
) ||
|
||||
Boolean(bodyText && bodyText.length <= 600 && loginPhrasePattern.test(bodyText))
|
||||
const challengeRequired =
|
||||
hasChallengeVendorWidget ||
|
||||
findVisibleSurfaceText(
|
||||
'[role="dialog"], [aria-modal="true"], [class*="captcha"], [id*="captcha"], [class*="verify"], [class*="Verify"]',
|
||||
challengePhrasePattern,
|
||||
240,
|
||||
) ||
|
||||
Boolean(bodyText && bodyText.length <= 400 && challengePhrasePattern.test(bodyText))
|
||||
|
||||
const busyDescriptors: string[] = []
|
||||
for (const node of Array.from(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Cookie, Session, WebContents } from 'electron/main'
|
||||
|
||||
import type { AccountHealthProfile, AuthProbeResult } from '../../auth-types'
|
||||
import { PAGE_CHALLENGE_SIGNAL_COUNT_SNIPPET } from '../../challenge-signals'
|
||||
import type { GenericAIPageState } from '../../generic-ai-auth'
|
||||
import { normalizeRemoteUrl } from '../common'
|
||||
|
||||
@@ -269,9 +270,7 @@ export async function readDoubaoAuthPageState(
|
||||
const loggedOutSignalCount = /(未登录|登录后继续|请先登录|扫码登录|手机号登录)/.test(bodyText)
|
||||
? 1
|
||||
: 0;
|
||||
const challengeSignalCount = /(验证码|滑块|人机验证|安全验证|短信验证|captcha|verification)/i.test(bodyText)
|
||||
? 1
|
||||
: 0;
|
||||
const challengeSignalCount = ${PAGE_CHALLENGE_SIGNAL_COUNT_SNIPPET};
|
||||
const account = pickAccountTrigger();
|
||||
const displayName = account?.displayName || null;
|
||||
const avatarUrl = isAccountAvatarUrl(account?.avatarUrl || null)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Cookie, Session, WebContents } from 'electron/main'
|
||||
|
||||
import type { AccountHealthProfile, AuthProbeResult } from '../../auth-types'
|
||||
import { PAGE_CHALLENGE_SIGNAL_COUNT_SNIPPET } from '../../challenge-signals'
|
||||
import type { GenericAIPageState } from '../../generic-ai-auth'
|
||||
import { normalizeRemoteUrl } from '../common'
|
||||
|
||||
@@ -195,9 +196,7 @@ export async function readKimiAuthPageState(
|
||||
const loggedOutSignalCount = /(登录以同步历史会话|微信扫码登录|手机号快捷登录|请输入手机号|请输入验证码|扫码登录|未登录|登录后继续|请先登录)/.test(bodyText)
|
||||
? 1
|
||||
: 0;
|
||||
const challengeSignalCount = /(验证码|滑块|人机验证|安全验证|短信验证|captcha|verification|NECAPTCHA)/i.test(bodyText)
|
||||
? 1
|
||||
: 0;
|
||||
const challengeSignalCount = ${PAGE_CHALLENGE_SIGNAL_COUNT_SNIPPET};
|
||||
const stopWords = /^(Kimi|Kimi Code|Kimi Claw|历史会话|新建会话|查看全部|设置|会员计划|关于我们|Language|用户反馈|获取应用程序|升级|升级套餐|登录|注册|虚拟用户)$/i;
|
||||
const isKimiAvatarUrl = (src) => {
|
||||
if (!src) return false;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Cookie, Session, WebContents } from 'electron/main'
|
||||
|
||||
import type { AccountHealthProfile, AuthProbeResult } from '../../auth-types'
|
||||
import { PAGE_CHALLENGE_SIGNAL_COUNT_SNIPPET } from '../../challenge-signals'
|
||||
import type { GenericAIPageState } from '../../generic-ai-auth'
|
||||
import { normalizeRemoteUrl } from '../common'
|
||||
|
||||
@@ -370,9 +371,7 @@ export async function readQwenAuthPageState(
|
||||
const loggedOutSignalCount = /(未登录|登录后继续|请先登录|扫码登录|手机号登录|登录可同步历史对话)/.test(bodyText)
|
||||
? 1
|
||||
: 0;
|
||||
const challengeSignalCount = /(验证码|滑块|人机验证|安全验证|短信验证|captcha|verification)/i.test(bodyText)
|
||||
? 1
|
||||
: 0;
|
||||
const challengeSignalCount = ${PAGE_CHALLENGE_SIGNAL_COUNT_SNIPPET};
|
||||
return {
|
||||
displayName: sidebarAccount?.displayName || user?.displayName || null,
|
||||
avatarUrl: sidebarAccount?.avatarUrl || user?.avatarUrl || null,
|
||||
|
||||
Reference in New Issue
Block a user