captchakraken 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +54 -0
  2. package/dist/index.d.ts +56 -0
  3. package/dist/index.js +43 -0
  4. package/dist/playwright-types.d.ts +119 -0
  5. package/dist/playwright-types.js +25 -0
  6. package/dist/puppeteer-adapter.d.ts +70 -0
  7. package/dist/puppeteer-adapter.js +96 -0
  8. package/dist/solver.d.ts +264 -0
  9. package/dist/solver.js +1922 -0
  10. package/dist/token-usage.d.ts +15 -0
  11. package/dist/token-usage.js +102 -0
  12. package/dist/types.d.ts +248 -0
  13. package/dist/types.js +2 -0
  14. package/package.json +49 -0
  15. package/python/Dockerfile +41 -0
  16. package/python/README.md +71 -0
  17. package/python/examples/README.md +68 -0
  18. package/python/examples/_harness.py +158 -0
  19. package/python/examples/demoHcaptcha.py +20 -0
  20. package/python/examples/demoRecaptcha.py +17 -0
  21. package/python/pyproject.toml +79 -0
  22. package/python/src/captchakraken/__init__.py +61 -0
  23. package/python/src/captchakraken/action_types.py +56 -0
  24. package/python/src/captchakraken/cli.py +656 -0
  25. package/python/src/captchakraken/config.py +78 -0
  26. package/python/src/captchakraken/image_processor.py +244 -0
  27. package/python/src/captchakraken/overlay.py +520 -0
  28. package/python/src/captchakraken/planner.py +408 -0
  29. package/python/src/captchakraken/planner_types.py +74 -0
  30. package/python/src/captchakraken/server_manager.py +290 -0
  31. package/python/src/captchakraken/solver.py +434 -0
  32. package/python/src/captchakraken/timing.py +42 -0
  33. package/python/src/captchakraken/tool_calls/find_checkbox.py +72 -0
  34. package/python/src/captchakraken/tool_calls/find_grid.py +1762 -0
  35. package/python/src/captchakraken/tool_calls/move_indicator.py +431 -0
  36. package/scripts/copy-python.mjs +29 -0
  37. package/scripts/setup-python.js +104 -0
@@ -0,0 +1,264 @@
1
+ import { PlaywrightPage as Page, PlaywrightElementHandle as ElementHandle } from './playwright-types';
2
+ import { CaptchaKrakenConfig, SolveResult } from './types';
3
+ export declare class CaptchaKrakenSolver {
4
+ private config;
5
+ private lastMousePosition;
6
+ private imageCounter;
7
+ private sessionDebugDir;
8
+ private stepIndex;
9
+ private solveStartMs;
10
+ private gridDebugDir;
11
+ private gridDebugSeq;
12
+ private cvWorker;
13
+ private cvWorkerReady;
14
+ private cvWorkerSeq;
15
+ private cvWorkerPending;
16
+ private cvWorkerBuf;
17
+ private solutionCache;
18
+ constructor(config?: CaptchaKrakenConfig);
19
+ solve(page: Page): Promise<SolveResult | void>;
20
+ private solveImpl;
21
+ /**
22
+ * Fire the optional onStep observer with a fresh screenshot of the captcha
23
+ * element. No-op (beyond a cheap early return) when no callback is set, so it
24
+ * stays off the critical path in normal runs. The emitted PNG is owned by the
25
+ * callback — we never delete it. Best-effort: a screenshot or callback error
26
+ * never fails the solve.
27
+ */
28
+ private emitStep;
29
+ private solveSingle;
30
+ private getVerifyButton;
31
+ private hasNonEmptyFieldValue;
32
+ /**
33
+ * Detect reCAPTCHA's "Please select all matching images" error banner
34
+ * (and the related "Please try again" / "Please also check the new images"
35
+ * variants). These appear in the bframe AFTER clicking Verify with an
36
+ * incomplete selection. The tiles do NOT refresh on this error — without
37
+ * special handling the LoRA sees the same image, returns "done" (because
38
+ * to it everything matching IS selected), we click Verify again, and we
39
+ * loop until the session times out. We use this signal to switch the next
40
+ * grid call into "missed-tiles" retry mode.
41
+ */
42
+ private hasRecaptchaUnderselectError;
43
+ private isRecaptchaAnchorChecked;
44
+ private isHcaptchaAnchorChecked;
45
+ /**
46
+ * True the moment the vendor reports the whole captcha solved — the anchor
47
+ * checkbox flipped to checked, or the response token got populated. This is a
48
+ * definitive "done" signal that a lingering, animating-closed challenge frame
49
+ * is not: after the final submit, hCaptcha keeps the challenge iframe VISIBLE
50
+ * for a couple of seconds while it verifies, so treating that frame as a fresh
51
+ * puzzle (the old behavior) burned ~18s re-running the pipeline on it.
52
+ */
53
+ private isCaptchaSolved;
54
+ /**
55
+ * True when an image challenge is open AND has actually rendered its prompt —
56
+ * i.e. a fresh round we should solve, as opposed to a frame animating closed
57
+ * (whose prompt has already gone). Used to tell "next round" from "solved,
58
+ * closing" after a submit without waiting out a fixed timeout.
59
+ */
60
+ private isChallengeFreshlyRendered;
61
+ /**
62
+ * Block until the hCaptcha challenge frame's task images have actually
63
+ * painted, so we don't screenshot a blank/half-loaded grid.
64
+ *
65
+ * hCaptcha renders each grid tile as a `.task-image .image` div whose
66
+ * `background-image` is set once the asset loads; the prompt sits in
67
+ * `.prompt-text`. Image-select (click/drag) challenges use a single
68
+ * `.challenge-example` / `canvas` surface instead. We wait for either family
69
+ * to be present AND for the background-image URLs to be populated (not the
70
+ * empty `url("")` placeholder hCaptcha ships before the asset arrives).
71
+ *
72
+ * Best-effort: a timeout or a missing content frame just falls through to the
73
+ * screenshot rather than throwing — the existing fail-fast path still covers a
74
+ * genuinely unsupported puzzle.
75
+ */
76
+ private waitForHcaptchaChallengeImages;
77
+ /**
78
+ * Stage-1 detection: is an *interactive* captcha widget present in the DOM at
79
+ * all — even if its iframe hasn't finished rendering yet?
80
+ *
81
+ * This is deliberately broader than detectCaptcha (which only returns a
82
+ * VISIBLE, not-yet-solved element). We use it to distinguish two cases that
83
+ * detectCaptcha() === null cannot tell apart:
84
+ *
85
+ * - A reCAPTCHA-v2 / hCaptcha widget IS in the DOM but is still loading
86
+ * (iframe present, glyph not painted) → we should WAIT for it.
87
+ * - There is no interactive widget — reCAPTCHA v3 (score-based, invisible)
88
+ * or an hCaptcha that only triggers on a user action → we must FAIL FAST.
89
+ *
90
+ * reCAPTCHA v3 injects only `iframe[src*="recaptcha/api2/anchor"]` with
91
+ * `size=invisible` in the src, and never an `api2/bframe` challenge frame, so
92
+ * we exclude the invisible variant here.
93
+ */
94
+ hasInteractiveWidgetInDom(page: Page): Promise<boolean>;
95
+ detectCaptcha(page: Page): Promise<ElementHandle | null>;
96
+ /**
97
+ * Initialize a fresh dump directory for one reCAPTCHA 3x3 dynamic-driver
98
+ * session. Frames + a state.jsonl log land here so the click/fade/wait timing
99
+ * can be replayed offline. Gated on CAPTCHA_DEBUG=1 — the per-frame dumps and
100
+ * extra state queries add latency, so they stay off in normal runs. Set
101
+ * CAPTCHA_DEBUG=1 to capture them when diagnosing timing. Best-effort.
102
+ */
103
+ private initGridDebug;
104
+ /**
105
+ * Log a structured event for the grid driver: prints a one-line summary to the
106
+ * console and appends a JSON record to state.jsonl. If `framePath` is given,
107
+ * copies that frame into the dump dir under a sequenced, labeled name so the
108
+ * record can be matched to the exact pixels the detector saw. Best-effort.
109
+ */
110
+ private gridDebug;
111
+ private saveImageForDebug;
112
+ private archiveLatestDebugRun;
113
+ /**
114
+ * Resolve the bundled CaptchaKraken CLI root and the python interpreter to
115
+ * run it with. Prefers the packaged venv python (postinstall bootstrap),
116
+ * falling back to the configured/`python` command. Throws if the CLI folder
117
+ * is missing — callers that must not throw (e.g. runCliTool) wrap this.
118
+ */
119
+ private resolveCli;
120
+ /**
121
+ * Run an OpenCV tool subcommand of the CLI (e.g. `grid-cell-states a.png
122
+ * b.png`) and return its parsed single-line JSON. These subcommands print
123
+ * exactly one JSON object on stdout (timing records go to stderr), so we
124
+ * parse the whole trimmed stdout. Best-effort: returns `{}` on any failure so
125
+ * polling callers can treat it as "inconclusive, keep going" without throwing.
126
+ */
127
+ private runCliTool;
128
+ /**
129
+ * Lazily start the persistent CV worker (`python -m captchakraken.cli serve`) and resolve
130
+ * once it has imported cv2/numpy and emitted its `{"ready":true}` handshake.
131
+ * Returns false if it can't be started (caller then falls back to one-shot
132
+ * subprocesses). Idempotent: subsequent calls await the same readiness promise.
133
+ */
134
+ private ensureCvWorker;
135
+ /** Send one request to the CV worker and await its JSON result. Throws on any
136
+ * worker failure so callers can fall back to the one-shot path. */
137
+ private cvWorkerRequest;
138
+ /** Kill the worker and clear state. Safe to call repeatedly. */
139
+ private teardownCvWorker;
140
+ /**
141
+ * Run a CV tool through the persistent worker when available, falling back to a
142
+ * one-shot `runCliTool` subprocess otherwise. `cmd`/`payload` map to the
143
+ * worker's protocol; `fallbackArgs` is the equivalent one-shot argv. Worker
144
+ * results are wrapped to match the one-shot JSON shape:
145
+ * - grid-cell-states[-fixed]: worker returns the states object directly, or
146
+ * {grid:null}; the one-shot returns the same shape, so just pass through.
147
+ * - find-grid: worker returns the array (or null) as `result`.
148
+ * Best-effort: never throws.
149
+ */
150
+ private runCvTool;
151
+ /**
152
+ * Block until a reCAPTCHA grid's cells have settled — none blank, none
153
+ * mid-fade — before we screenshot it for the model. reCAPTCHA fades new tiles
154
+ * in over ~1s; capturing mid-fade feeds the LoRA a blank/partial grid.
155
+ *
156
+ * We poll: screenshot the challenge element, keep the last two frames, and ask
157
+ * the CLI's batched `grid-cell-states` (one subprocess per poll) which cells
158
+ * are empty/changing/loaded. We return as soon as every cell is loaded, or on
159
+ * timeout. Best-effort, mirroring `waitForHcaptchaChallengeImages`: never
160
+ * throws, and falls through on timeout so a stuck/odd grid still proceeds to
161
+ * the normal screenshot path. Temp frames are always cleaned up.
162
+ */
163
+ private waitForGridCellsLoaded;
164
+ /**
165
+ * Read a PNG's pixel dimensions from its IHDR chunk (bytes 16-23, big-endian).
166
+ * Avoids pulling in an image-size dependency. Returns null if the file isn't a
167
+ * readable PNG.
168
+ */
169
+ private readPngDimensions;
170
+ /**
171
+ * Detect the reCAPTCHA grid once for a puzzle session: screenshot the element,
172
+ * run `find-grid`, and read the screenshot's pixel dimensions. Grid boxes are
173
+ * pixel coords in SCREENSHOT space (not page CSS space). Returns null if no
174
+ * grid is detected. The geometry is stable across the in-place dynamic refresh
175
+ * (only tile images change), so callers cache the result for the session.
176
+ */
177
+ private getGridBoxes;
178
+ /**
179
+ * Map a model-returned normalized bbox (fractions of the element/screenshot)
180
+ * to a 1-indexed grid cell. Uses the bbox center, converts to screenshot
181
+ * pixels, and returns the cell whose pixel box contains it. Cell numbering is
182
+ * row-major (matches the CLI's find_grid output). Returns null if the center
183
+ * falls outside every cell (e.g. in a gutter) — callers click the raw bbox
184
+ * anyway and skip per-tile tracking.
185
+ */
186
+ private bboxToCell;
187
+ /**
188
+ * Center of a 1-indexed grid cell in PAGE pixel space, for mouse moves.
189
+ * Converts the cached screenshot-pixel box to page coords via the session's
190
+ * scaleX/scaleY (screenshot px -> page px) and element origin.
191
+ */
192
+ private cellCenterPage;
193
+ /** Smooth-move the mouse over one cell's center with intra-cell jitter. */
194
+ private hoverCell;
195
+ /**
196
+ * Query per-cell grid state using the SESSION'S CACHED grid boxes via the
197
+ * `grid-cell-states-fixed` CLI command. This is critical: the dynamic refresh
198
+ * blanks tiles to near-white, which makes find_grid fail on that frame, so the
199
+ * self-detecting `grid-cell-states` would return {grid:null} mid-fade and a
200
+ * naive caller would misread that as "nothing loading / solved". Passing the
201
+ * cached boxes keeps empty/changing/selected correct even while tiles are
202
+ * blank. Returns null only on a genuine CLI failure. Best-effort.
203
+ */
204
+ private gridCellStates;
205
+ /** Order a loading set so `priority` cells (just-clicked) come first. */
206
+ private orderByPriority;
207
+ /**
208
+ * Detect whether any tiles are blank or fading, watching for the ONSET of the
209
+ * reCAPTCHA refresh over a short grace window. The blank/fade transition lags
210
+ * the click by a beat, so a single snapshot right after clicking misses it
211
+ * (the tile still shows its old image — not yet white, not yet changing). We
212
+ * poll consecutive frames and mark a cell loading if it is `empty` (≥97%
213
+ * near-white) OR `changing` (>2% pixels differ). HOVERS a clicked tile each
214
+ * poll so the mouse keeps moving (no unnatural pauses). Returns the loading
215
+ * cells (priority/clicked first) as soon as any appears, or [] if the whole
216
+ * window passes with nothing loading (→ solved). Logs every poll + frame.
217
+ */
218
+ private currentLoadingCells;
219
+ /**
220
+ * After loading is detected, wait until at least one of the given blank/fading
221
+ * cells reaches the `loaded` state, HOVERING those cells (in order) the whole
222
+ * time so the mouse never sits still. Returns true once a tile loads, false on
223
+ * timeout (caller proceeds anyway). Uses the session's cached grid boxes so it
224
+ * works while tiles are blank. Logs every poll + frame.
225
+ */
226
+ private waitForAnyClickedTileLoaded;
227
+ /**
228
+ * Multi-round driver for reCAPTCHA 3x3 dynamic puzzles ("click all X" where
229
+ * tiles refresh in place). One invocation = one puzzle session.
230
+ *
231
+ * The CLI is authoritative about WHAT to do — it runs the blue-badge detector,
232
+ * filters out already-selected and still-loading tiles, and returns one of:
233
+ * - `click`: click these tiles (already filtered to fresh, ready tiles)
234
+ * - `wait` : nothing to click yet, tiles are still loading — do NOT submit
235
+ * - `done` : nothing matching remains — submit (click Verify)
236
+ *
237
+ * This driver owns the HUMAN-LIKE WAITING the CLI can't: after a click round,
238
+ * and on a `wait`, it hovers the just-clicked / currently blank+fading tiles
239
+ * (in click order) and waits for at least one to finish reloading before
240
+ * re-screenshotting and re-solving — so we don't burn a solver call on a grid
241
+ * that's still mid-fade. It submits only on `done`.
242
+ *
243
+ * Returns the same shape as solveSingle so the outer solve loop — including the
244
+ * under-selection retry and post-solve detectCaptcha — wraps it unchanged.
245
+ */
246
+ private solveRecaptchaGrid;
247
+ private getSolution;
248
+ /**
249
+ * Run `fn` (typically the model query) while idly drifting the cursor over
250
+ * the captcha, so the mouse behaves like a human weighing the options instead
251
+ * of freezing during inference. Uses the same cursory-ts trajectories as real
252
+ * clicks; cancelled the instant `fn` resolves. Best-effort — any wander error
253
+ * is swallowed and never fails the solve. Disable via config.idleMouseWander.
254
+ */
255
+ private withIdleWander;
256
+ move(page: Page, selectorOrElement: string | ElementHandle, options?: {
257
+ paddingPercentage?: number;
258
+ }): Promise<void>;
259
+ moveAndClick(page: Page, element: ElementHandle): Promise<void>;
260
+ private performSmoothMove;
261
+ private tracePath;
262
+ private executeClick;
263
+ private executeDrag;
264
+ }