From fa2bcbb64f9456361bd98eed03279bb2dba8fc5a Mon Sep 17 00:00:00 2001 From: germondai Date: Fri, 24 Jul 2026 16:51:07 +0200 Subject: [PATCH] refactor(solvers): simplify optional results --- packages/tiers/src/solvers/geetest.ts | 4 ++-- packages/tiers/src/solvers/index.ts | 8 ++++---- packages/tiers/src/solvers/recaptcha.ts | 6 +++--- packages/tiers/src/solvers/stt.ts | 20 +++++++++--------- packages/tiers/src/solvers/turnstile.ts | 4 ++-- packages/tiers/src/utils/challengeWait.ts | 25 ++++++++++------------- packages/tiers/src/utils/impervaWait.ts | 4 ++-- 7 files changed, 34 insertions(+), 37 deletions(-) diff --git a/packages/tiers/src/solvers/geetest.ts b/packages/tiers/src/solvers/geetest.ts index bf4df00..04d5aec 100644 --- a/packages/tiers/src/solvers/geetest.ts +++ b/packages/tiers/src/solvers/geetest.ts @@ -42,7 +42,7 @@ export async function solveGeetestSlide(page: Page, timeoutMs = 30_000): Promise console.log("[geetest] clicking initial verify button") // The div has tabindex and aria-label — use page.mouse.click at its actual coordinates // so the browser dispatches the click to the div (not forced through a covering element). - const verifyBox = await verifyBtn.boundingBox().catch(() => null) + const verifyBox = await verifyBtn.boundingBox().catch(() => undefined) if (verifyBox) { await page.mouse.click(verifyBox.x + verifyBox.width / 2, verifyBox.y + verifyBox.height / 2) } else { @@ -127,7 +127,7 @@ export async function solveGeetestSlide(page: Page, timeoutMs = 30_000): Promise .locator(DRAG_HANDLE_V3) .first() .boundingBox() - .catch(() => null) + .catch(() => undefined) if (v3Handle) { sliderBox = v3Handle console.log( diff --git a/packages/tiers/src/solvers/index.ts b/packages/tiers/src/solvers/index.ts index 57875ed..21d6f3e 100644 --- a/packages/tiers/src/solvers/index.ts +++ b/packages/tiers/src/solvers/index.ts @@ -59,11 +59,11 @@ async function detectTurnstile(page: Page, timeoutMs: number): Promise ) ) return "iframe" - const inp = document.querySelector('input[name="cf-turnstile-response"]') as HTMLInputElement | null - if (inp && inp.value.length > 10) return "token" - return null + const input = document.querySelector('input[name="cf-turnstile-response"]') + if (input instanceof HTMLInputElement && input.value.length > 10) return "token" + return }) - .catch(() => null) + .catch(() => undefined) if (viaDOM === "iframe") return true if (viaDOM === "token") { diff --git a/packages/tiers/src/solvers/recaptcha.ts b/packages/tiers/src/solvers/recaptcha.ts index 91d9772..5888032 100644 --- a/packages/tiers/src/solvers/recaptcha.ts +++ b/packages/tiers/src/solvers/recaptcha.ts @@ -134,14 +134,14 @@ export async function solveRecaptchaV2(page: Page, timeoutMs = 30_000): Promise< (await bframe .locator(".rc-audiochallenge-tdownload-link") .getAttribute("href", { timeout: 1000 }) - .catch(() => null)) || + .catch(() => undefined)) || "" // Reject blob: URLs — they're browser-internal and can't be fetched from outside - const audioHref = rawHref && !rawHref.startsWith("blob:") ? rawHref : null + const audioHref = rawHref && !rawHref.startsWith("blob:") ? rawHref : undefined console.log("[recaptcha] raw audio href:", rawHref?.slice(0, 100) ?? "none") - console.log("[recaptcha] audio URL:", audioHref?.slice(0, 80) ?? "null") + console.log("[recaptcha] audio URL:", audioHref?.slice(0, 80) ?? "unavailable") if (!audioHref) { console.log("[recaptcha] audio URL not found, retry", attempt) diff --git a/packages/tiers/src/solvers/stt.ts b/packages/tiers/src/solvers/stt.ts index 0f945c7..5e0a33e 100644 --- a/packages/tiers/src/solvers/stt.ts +++ b/packages/tiers/src/solvers/stt.ts @@ -26,17 +26,17 @@ const FFMPEG = process.env.FFMPEG_PATH ?? "ffmpeg" const GOOGLE_STT = "https://www.google.com/speech-api/v2/recognize?output=json&lang=en-US&key=AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw" -export async function transcribeAudio(audioUrl: string, signal?: AbortSignal): Promise { +export async function transcribeAudio(audioUrl: string, signal?: AbortSignal): Promise { return STT_URL ? transcribeWhisper(audioUrl, signal) : transcribeGoogle(audioUrl, signal) } -async function transcribeWhisper(audioUrl: string, signal?: AbortSignal): Promise { +async function transcribeWhisper(audioUrl: string, signal?: AbortSignal): Promise { try { const res = await fetch(audioUrl, { signal, headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/149" }, }) - if (!res.ok) return null + if (!res.ok) return const form = new FormData() form.append("file", await res.blob(), "audio.mp3") @@ -48,16 +48,16 @@ async function transcribeWhisper(audioUrl: string, signal?: AbortSignal): Promis if (STT_KEY) headers.Authorization = `Bearer ${STT_KEY}` const sttRes = await fetch(STT_URL, { method: "POST", headers, body: form, signal }) - if (!sttRes.ok) return null + if (!sttRes.ok) return return clean(await sttRes.text()) } catch { - return null + return } } // Converts MP3 → FLAC via ffmpeg, sends to Google's free Speech API. // Tries 8000 Hz first (reCAPTCHA audio is typically low-bitrate), then 16000 Hz. -async function transcribeGoogle(audioUrl: string, signal?: AbortSignal): Promise { +async function transcribeGoogle(audioUrl: string, signal?: AbortSignal): Promise { const id = randomUUID().slice(0, 8) const mp3 = `/tmp/trawl-${id}.mp3` const flac8 = `/tmp/trawl-${id}-8k.flac` @@ -75,13 +75,13 @@ async function transcribeGoogle(audioUrl: string, signal?: AbortSignal): Promise }) if (!res.ok) { console.log("[stt] audio download failed:", res.status) - return null + return } const audioBytes = await res.arrayBuffer() console.log("[stt] audio downloaded:", audioBytes.byteLength, "bytes, type:", res.headers.get("content-type")) if (audioBytes.byteLength < 1000) { console.log("[stt] audio too small") - return null + return } await Bun.write(mp3, audioBytes) @@ -127,10 +127,10 @@ async function transcribeGoogle(audioUrl: string, signal?: AbortSignal): Promise } catch {} } } - return null + return } catch (err) { console.log("[stt] error:", err instanceof Error ? err.message : err) - return null + return } finally { await $`rm -f ${mp3} ${flac8} ${flac16}`.nothrow().catch(() => {}) } diff --git a/packages/tiers/src/solvers/turnstile.ts b/packages/tiers/src/solvers/turnstile.ts index 6a104d5..3750671 100644 --- a/packages/tiers/src/solvers/turnstile.ts +++ b/packages/tiers/src/solvers/turnstile.ts @@ -68,8 +68,8 @@ export async function solveTurnstile(page: Page, timeoutMs = 25_000): Promise element in the parent page, so we can // get its bounding box and click at the checkbox position via page mouse coordinates. - const frameEl = await cfFrame.frameElement().catch(() => null) - const box = frameEl ? await frameEl.boundingBox().catch(() => null) : null + const frameEl = await cfFrame.frameElement().catch(() => undefined) + const box = frameEl ? await frameEl.boundingBox().catch(() => undefined) : undefined if (box && box.width > 20) { // Checkbox is in the left portion of the Turnstile iframe (approx x+24, vertically centered). diff --git a/packages/tiers/src/utils/challengeWait.ts b/packages/tiers/src/utils/challengeWait.ts index f3f2e9a..704529c 100644 --- a/packages/tiers/src/utils/challengeWait.ts +++ b/packages/tiers/src/utils/challengeWait.ts @@ -16,7 +16,7 @@ export async function waitForChallengeResolution( ): Promise<"ok" | "ip-blocked" | "timeout"> { const deadline = Date.now() + Math.max(timeoutMs, 30_000) let lastClickAttempt = 0 - let cfClearanceAt: number | null = null + let cfClearanceAt: number | undefined // Only count cf_clearance for the current domain — warm browser may have cookies from prior domains const targetHost = (() => { @@ -61,7 +61,7 @@ export async function waitForChallengeResolution( targetHost.endsWith(c.domain.replace(/^\./, ""))), ) if (hasDomainClearance) { - if (cfClearanceAt === null) { + if (cfClearanceAt === undefined) { cfClearanceAt = Date.now() console.log("[challenge] cf_clearance obtained") } @@ -103,8 +103,8 @@ async function attemptTurnstileClick(page: Page): Promise { if (clicked) return true // C: page-coordinate click — bypasses Fission by clicking on page instead of inside frame - const frameEl = await frame.frameElement().catch(() => null) - const box = frameEl ? await frameEl.boundingBox().catch(() => null) : null + const frameEl = await frame.frameElement().catch(() => undefined) + const box = frameEl ? await frameEl.boundingBox().catch(() => undefined) : undefined if (box && box.width > 20) { const cx = box.x + Math.min(24, box.width * 0.15) const cy = box.y + box.height / 2 @@ -138,11 +138,9 @@ async function clickShadowCheckbox(_page: Page, frame: Frame): Promise try { const handle = await frame .evaluateHandle(() => { - // biome-ignore lint/suspicious/noExplicitAny: shadow DOM traversal - const roots: any[] = [] - // biome-ignore lint/suspicious/noExplicitAny: shadow DOM traversal - function collect(node: any) { - if (!node) return + type ShadowHost = ParentNode & { shadowRootUnl?: ShadowRoot } + const roots: ShadowRoot[] = [] + function collect(node: ShadowHost) { if (node.shadowRootUnl) { roots.push(node.shadowRootUnl) collect(node.shadowRootUnl) @@ -157,20 +155,19 @@ async function clickShadowCheckbox(_page: Page, frame: Frame): Promise collect(document) return roots }) - .catch(() => null) + .catch(() => undefined) if (!handle) return false - const props = await handle.getProperties().catch(() => null) + const props = await handle.getProperties().catch(() => undefined) if (!props) return false for (const [, shadowHandle] of props) { const el = shadowHandle.asElement() if (!el) continue const checkboxHandle = await el - // biome-ignore lint/suspicious/noExplicitAny: shadow root handle - .evaluateHandle((root: any) => root.querySelector('input[type="checkbox"]')) - .catch(() => null) + .evaluateHandle((root: ParentNode) => root.querySelector('input[type="checkbox"]')) + .catch(() => undefined) if (!checkboxHandle) continue const checkbox = checkboxHandle.asElement() if (!checkbox) continue diff --git a/packages/tiers/src/utils/impervaWait.ts b/packages/tiers/src/utils/impervaWait.ts index f4c06a4..503a089 100644 --- a/packages/tiers/src/utils/impervaWait.ts +++ b/packages/tiers/src/utils/impervaWait.ts @@ -16,7 +16,7 @@ export async function waitForImpervaResolution( originalUrl?: string, ): Promise<"ok" | "ip-blocked" | "timeout"> { const deadline = Date.now() + Math.max(timeoutMs, 30_000) - let sensorCookieAt: number | null = null + let sensorCookieAt: number | undefined const targetHost = (() => { try { @@ -51,7 +51,7 @@ export async function waitForImpervaResolution( ) if (hasSensorCookie) { - if (sensorCookieAt === null) { + if (sensorCookieAt === undefined) { sensorCookieAt = Date.now() console.log("[imperva] sensor cookie obtained") }