diff --git a/flows/openai/background/steps/create-plus-checkout.js b/flows/openai/background/steps/create-plus-checkout.js index 30fb819..585b36e 100644 --- a/flows/openai/background/steps/create-plus-checkout.js +++ b/flows/openai/background/steps/create-plus-checkout.js @@ -1423,16 +1423,51 @@ function FindProxyForURL(url, host) { ].join(' '); return /\b(active|selected|current|checked|on|true)\b/i.test(markerText); }; - const candidates = Array.from(document.querySelectorAll('button, [role="button"], .design-mode-card, .mode-card, [class*="mode"]')); - const cardModeButton = candidates.find((element) => /卡密充值/.test(textOf(element))); - const freeModeButton = candidates.find((element) => /免费充值/.test(textOf(element))); + const modeSelector = 'button, [role="button"], .design-mode-card, .mode-card, [class*="mode-card"], [class*="recharge-card"], [class*="tab"], [class*="option"]'; + const findModeButton = (label, oppositeLabel) => { + const directCandidates = Array.from(document.querySelectorAll(modeSelector)); + const allCandidates = directCandidates.length + ? directCandidates + : Array.from(document.querySelectorAll('body *')); + const candidates = allCandidates + .filter(isVisible) + .map((element) => ({ element, text: textOf(element) })) + .filter(({ text }) => text.includes(label) && !text.includes(oppositeLabel) && text.length <= 120) + .sort((left, right) => left.text.length - right.text.length); + const target = candidates[0]?.element || null; + return target?.closest?.(modeSelector) || target; + }; + const clickElement = (element) => { + element.scrollIntoView?.({ block: 'center', inline: 'center' }); + element.focus?.(); + const rect = typeof element.getBoundingClientRect === 'function' ? element.getBoundingClientRect() : null; + const centerTarget = rect && document.elementFromPoint + ? document.elementFromPoint(rect.left + (rect.width / 2), rect.top + (rect.height / 2)) + : null; + const target = centerTarget && element.contains?.(centerTarget) ? centerTarget : element; + const fire = (type, EventCtor) => { + try { + target.dispatchEvent?.(new EventCtor(type, { bubbles: true, cancelable: true, view: window })); + } catch { + target.dispatchEvent?.(new Event(type, { bubbles: true, cancelable: true })); + } + }; + const pointerEventCtor = window.PointerEvent || window.MouseEvent || Event; + const mouseEventCtor = window.MouseEvent || Event; + fire('pointerdown', pointerEventCtor); + fire('mousedown', mouseEventCtor); + fire('pointerup', pointerEventCtor); + fire('mouseup', mouseEventCtor); + target.click?.(); + }; + const cardModeButton = findModeButton('卡密充值', '免费充值'); + const freeModeButton = findModeButton('免费充值', '卡密充值'); const visibleCardInputs = Array.from(document.querySelectorAll('input.card-key-seg, input[placeholder*="XXXXXXXX"], input[maxlength="8"]')) .filter(isVisible); const isCardModeActive = Boolean(cardModeButton && hasActiveMarker(cardModeButton)) || (visibleCardInputs.length > 0 && !(freeModeButton && hasActiveMarker(freeModeButton))); if (cardModeButton && !isCardModeActive) { - cardModeButton.scrollIntoView?.({ block: 'center', inline: 'center' }); - cardModeButton.click?.(); + clickElement(cardModeButton); return { hasCardMode: true, clickedCardMode: true, @@ -1451,12 +1486,22 @@ function FindProxyForURL(url, host) { return results?.[0]?.result || {}; }; - const modeResult = await ensureCardMode(); + let modeResult = null; + for (let attempt = 1; attempt <= 8; attempt += 1) { + modeResult = await ensureCardMode(); + if (!modeResult.hasCardMode) { + throw new Error('步骤 6:未找到 GPC“卡密充值”模式入口。'); + } + if (modeResult.isCardModeActive) { + break; + } + await sleepWithStop(modeResult.clickedCardMode ? 800 : 500); + } if (!modeResult.hasCardMode) { throw new Error('步骤 6:未找到 GPC“卡密充值”模式入口。'); } - if (modeResult.clickedCardMode) { - await sleepWithStop(600); + if (!modeResult.isCardModeActive) { + throw new Error('步骤 6:GPC 页面切换到卡密充值模式超时。'); } const results = await chrome.scripting.executeScript({ diff --git a/flows/openai/background/steps/fill-plus-checkout.js b/flows/openai/background/steps/fill-plus-checkout.js index 1a9bf85..75dcf65 100644 --- a/flows/openai/background/steps/fill-plus-checkout.js +++ b/flows/openai/background/steps/fill-plus-checkout.js @@ -283,9 +283,45 @@ ].join(' '); return /\b(active|selected|current|checked|on|true)\b/i.test(markerText); }; - const candidates = Array.from(document.querySelectorAll('button, [role="button"], .design-mode-card, .mode-card, [class*="mode"]')); - const cardModeButton = candidates.find((element) => /卡密充值/.test(textOf(element))); - const freeModeButton = candidates.find((element) => /免费充值/.test(textOf(element))); + const modeSelector = 'button, [role="button"], .design-mode-card, .mode-card, [class*="mode-card"], [class*="recharge-card"], [class*="tab"], [class*="option"]'; + const findModeButton = (label, oppositeLabel) => { + const directCandidates = Array.from(document.querySelectorAll(modeSelector)); + const allCandidates = directCandidates.length + ? directCandidates + : Array.from(document.querySelectorAll('body *')); + const candidates = allCandidates + .filter(isVisible) + .map((element) => ({ element, text: textOf(element) })) + .filter(({ text }) => text.includes(label) && !text.includes(oppositeLabel) && text.length <= 120) + .sort((left, right) => left.text.length - right.text.length); + const target = candidates[0]?.element || null; + return target?.closest?.(modeSelector) || target; + }; + const clickElement = (element) => { + element.scrollIntoView?.({ block: 'center', inline: 'center' }); + element.focus?.(); + const rect = typeof element.getBoundingClientRect === 'function' ? element.getBoundingClientRect() : null; + const centerTarget = rect && document.elementFromPoint + ? document.elementFromPoint(rect.left + (rect.width / 2), rect.top + (rect.height / 2)) + : null; + const target = centerTarget && element.contains?.(centerTarget) ? centerTarget : element; + const fire = (type, EventCtor) => { + try { + target.dispatchEvent?.(new EventCtor(type, { bubbles: true, cancelable: true, view: window })); + } catch { + target.dispatchEvent?.(new Event(type, { bubbles: true, cancelable: true })); + } + }; + const pointerEventCtor = window.PointerEvent || window.MouseEvent || Event; + const mouseEventCtor = window.MouseEvent || Event; + fire('pointerdown', pointerEventCtor); + fire('mousedown', mouseEventCtor); + fire('pointerup', pointerEventCtor); + fire('mouseup', mouseEventCtor); + target.click?.(); + }; + const cardModeButton = findModeButton('卡密充值', '免费充值'); + const freeModeButton = findModeButton('免费充值', '卡密充值'); const cardInputs = Array.from(document.querySelectorAll('input.card-key-seg, input[placeholder*="XXXXXXXX"], input[maxlength="8"]')) .filter(isVisible); const isCardModeActive = Boolean(cardModeButton && hasActiveMarker(cardModeButton)) @@ -305,8 +341,7 @@ activeModeText: textOf(cardModeButton), }; } - cardModeButton.scrollIntoView?.({ block: 'center', inline: 'center' }); - cardModeButton.click?.(); + clickElement(cardModeButton); return { ok: true, clicked: true, @@ -414,19 +449,27 @@ } if (!pageState.isCardModeActive) { - const modeResult = await ensureGpcCardMode(tabId); - if (!modeResult.ok) { - throw new Error('GPC_PAGE_FLOW_ENDED::步骤 7:未找到 GPC“卡密充值”模式入口,无法启动 Plus 充值。'); + let modeResult = null; + for (let attempt = 1; attempt <= 8; attempt += 1) { + modeResult = await ensureGpcCardMode(tabId); + if (!modeResult.ok) { + throw new Error('GPC_PAGE_FLOW_ENDED::步骤 7:未找到 GPC“卡密充值”模式入口,无法启动 Plus 充值。'); + } + if (modeResult.isCardModeActive) { + break; + } + if (attempt === 1 && modeResult.clicked) { + await addLog('步骤 7:已切换到 GPC 卡密充值模式,等待页面完成渲染。', 'info'); + } + await sleepWithStop(modeResult.clicked ? 800 : 500); + } + if (!modeResult?.isCardModeActive) { + throw new Error('GPC_PAGE_FLOW_ENDED::步骤 7:GPC 页面切换到卡密充值模式超时,无法启动 Plus 充值。'); } await addLog( - modeResult.clicked - ? '步骤 7:已切换到 GPC 卡密充值模式,准备启动。' - : '步骤 7:GPC 卡密充值模式已就绪。', + '步骤 7:GPC 卡密充值模式已就绪,准备启动。', 'info' ); - if (modeResult.clicked) { - await sleepWithStop(800); - } continue; } diff --git a/tests/plus-checkout-billing-tab-resolution.test.js b/tests/plus-checkout-billing-tab-resolution.test.js index e119dad..f4ef460 100644 --- a/tests/plus-checkout-billing-tab-resolution.test.js +++ b/tests/plus-checkout-billing-tab-resolution.test.js @@ -904,18 +904,30 @@ function createGpcPageHarness(states) { const pageStates = Array.isArray(states) ? [...states] : []; const clicks = []; const modeClicks = []; + let cardModeReady = false; return { clicks, modeClicks, run(details) { const source = String(details.func || ''); - if (source.includes('cardModeButton.click')) { - modeClicks.push({ target: details.target?.tabId }); + if (source.includes("findModeButton('卡密充值'")) { + if (!cardModeReady) { + modeClicks.push({ target: details.target?.tabId }); + cardModeReady = true; + return [{ + result: { + ok: true, + clicked: true, + isCardModeActive: false, + activeModeText: '卡密充值 使用付费卡密扣次充值', + }, + }]; + } return [{ result: { ok: true, - clicked: true, - isCardModeActive: false, + clicked: false, + isCardModeActive: true, activeModeText: '卡密充值 使用付费卡密扣次充值', }, }];