From 16499ce2fc13c731f13d2c7b51750fca3a799f4d Mon Sep 17 00:00:00 2001 From: germondai Date: Wed, 8 Jul 2026 19:40:24 +0200 Subject: [PATCH 1/3] feat(tiers): add audio STT fallback to hCaptcha solver --- packages/tiers/src/solvers/hcaptcha.ts | 162 ++++++++++++++++++++++--- 1 file changed, 144 insertions(+), 18 deletions(-) diff --git a/packages/tiers/src/solvers/hcaptcha.ts b/packages/tiers/src/solvers/hcaptcha.ts index 579d61f..1aafa68 100644 --- a/packages/tiers/src/solvers/hcaptcha.ts +++ b/packages/tiers/src/solvers/hcaptcha.ts @@ -1,20 +1,33 @@ -// hCaptcha solver — browser-only, no external services. +// hCaptcha solver — checkbox auto-pass + audio STT fallback. // -// With a real Chrome fingerprint and a non-datacenter IP, hCaptcha's risk -// scoring sometimes auto-passes the checkbox without showing an image challenge. -// Success rate varies by site sitekey difficulty and IP reputation. +// Flow: +// 1. Click the hCaptcha checkbox. With a good IP and a Camoufox Firefox fingerprint, +// hCaptcha's risk scoring sometimes auto-passes without showing an image challenge. +// 2. If a visual image challenge appears, switch to the audio challenge and solve via +// speech-to-text (Google's free API or a configured Whisper-compatible endpoint). +// 3. Submit the transcribed digit string and verify. // -// There is no fully free, reliable way to solve hCaptcha image grids without -// an AI/ML model or a paid solving service. We attempt the checkbox and return -// whether it auto-passed. +// Site owners can disable the audio option per sitekey — when that happens the solver +// gives up cleanly and returns false. There is no fully free, reliable way to solve +// hCaptcha image grids without an AI/ML model or a paid solving service. -import type { Page } from "patchright" +import type { FrameLocator, Page } from "patchright" +import { transcribeAudio } from "./stt" // hCaptcha widget iframe. newassets.hcaptcha.com is their CDN; don't filter by title // since the title attribute may not be set yet or may vary across versions. const WIDGET_FRAME = 'iframe[src*="hcaptcha.com"]' -export async function solveHcaptcha(page: Page, timeoutMs = 15_000): Promise { +// Selectors within the hCaptcha challenge UI. Source: Asmodei513/hcaptcha-solver, +// NotHarshhaa/hc_audio_challenger, dev1siN/hc-audio-solver (cross-verified). +const AUDIO_BUTTON = "#audio-button" +const AUDIO_RESPONSE = "textarea#audio-response" +const AUDIO_SUBMIT = "#audio-submit" +const RELOAD_BUTTON = 'button[aria-label="Get a new challenge"]' + +const MAX_AUDIO_ATTEMPTS = 3 + +export async function solveHcaptcha(page: Page, timeoutMs = 30_000): Promise { try { const hasWidget = await page .waitForSelector(WIDGET_FRAME, { timeout: 8000, state: "attached" }) @@ -25,29 +38,142 @@ export async function solveHcaptcha(page: Page, timeoutMs = 15_000): Promise