Commit Graph
3 Commits
Author SHA1 Message Date
Brandon LiandClaude Opus 5 dfa706c7b7 Target the captcha widget the open modal is actually showing
Second reset still failed: token solved, callback reported as fired, confirm
button never enabled. The button enabling is pure client-side state — the
site's callback storing the token — so if it stays dark the LIVE callback
never ran, whatever was reported. Picking the newest client was still a guess.

There is a precise link. reCAPTCHA numbers its response fields by widget id:
the first widget gets #g-recaptcha-response, the second
#g-recaptcha-response-1. The field the open modal is showing therefore names
the client holding its callback. Use that, fall back to newest-first when
there is no id to go on.

The run log now prints which field carried the token and which widgets were on
the page, so the next failure is diagnosable from the log instead of by
hypothesis: `filled 1 response field(s) ['g-recaptcha-response-1'], fired
grecaptcha callback (widget 1)` / `page had widgets ['0','1'], live one looked
like 1`.

Also surfaces the extension version on the AutoBuyer page. The extension has
always stamped it into every capture and nothing ever read it, so the README's
claim that a version mismatch shows there was no way to check. Chrome runs the
old service worker until an unpacked extension is reloaded by hand, which is
exactly the state that makes a deployed fix look like a broken one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 01:46:31 -05:00
Brandon LiandClaude Opus 5 2d53639c82 Hand the token to the live captcha widget, not a dead one
First live run reset one account, then failed on the second: the confirm
button never enabled and the step reported it covered by a bare `div`.

reCAPTCHA adds a client to ___grecaptcha_cfg.clients per rendered widget and
removes none, so the second modal leaves two — and the callback walk fired the
first it found, the one belonging to the modal that already closed. The token
goes to a widget nobody is listening to, the live modal never hears it was
solved, and its button stays dark. Iteration 1 worked only because there was
one client to find. Now the newest wins: hook widgets are walked in reverse,
clients sorted by descending key, and the count of candidates is reported so
`2 widgets on the page, used the newest` says plainly when a page is stacking
stale ones.

Also from that run:

- coveredBy printed `div` and nothing else, which diagnoses nothing. It only
  ever reported tag and id, and modal backdrops and captcha overlays carry
  neither. It now names classes too.
- A covered target gets 8s to clear before the step fails. Most things over a
  button are on their way out — a backdrop mid-fade, an overlay closing. This
  does not weaken the check: still covered at the deadline still fails, and
  nothing is clicked while anything is on top of it.
- The reset list's scroll container is confirmed as a class, so the
  class-or-id hedge is trimmed. The run reported it as
  `dashboard_body MuiBox-root mui-0`.

Extension to 0.11.1 so the dashboard can confirm the reload took. Runner
VERSION deliberately unchanged — the step vocabulary is the same, and a bump
would raise the mismatch banner for a change that does not need it. The
runner does still need restarting to pick up the covered-retry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:56:47 -05:00
Brandon LiandClaude Opus 5 159389be35 Solve captchas through the 2captcha API, not its extension
The two Tradeify automations clicked the 2captcha *extension's* solve button
and waited for it to go green. That needed a second extension installed and
signed in, put a real mouse click on a button whose markup is 2captcha's to
change, and gave a run no way to tell "still solving" from "not installed".

Replaced with one `solveCaptcha` step. No mouse: the extension reads the
widget's sitekey out of the page, the dashboard buys a token from 2captcha,
and the extension puts it in the response field and fires the page's own
callback.

captcha-hook.js is the part that earns its keep. A widget rendered
programmatically puts nothing in the DOM — no sitekey, and no callback. Most
pages only enable submit from that callback, so filling the hidden field alone
leaves the page looking unsolved. The hook wraps turnstile/grecaptcha/hcaptcha
`render` at document_start to hold on to what the page passed.

Solves go through 2captcha's residential proxy. Its API rejects host names in
proxyAddress, so na.proxy.2captcha.com is resolved to an IP first (cached);
the gateway was confirmed to work addressed that way.

Covers reCAPTCHA v2 (incl. invisible and Enterprise), hCaptcha and Turnstile.
NOT reCAPTCHA v3: a v3 page carries an anchor iframe with k=, so it is
detected as v2 and would be solved with the wrong task type. Everything else
unsupported fails loudly instead.

The API key and proxy credentials are hardcoded in the captcha route on
request. Unlike the sqlite database, that file is in the repo, so they travel
with it. TWOCAPTCHA_API_KEY / TWOCAPTCHA_PROXY and the settings rows still
override; neither has a field on the Settings page.

Runner VERSION and RUNNER_EXPECTED_VERSION both to 0.17.0 — they are a pair,
and a mismatch shows as a red "restart your runner" banner that blames the
wrong process.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 00:18:00 -05:00