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.
- package/README.md +54 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +43 -0
- package/dist/playwright-types.d.ts +119 -0
- package/dist/playwright-types.js +25 -0
- package/dist/puppeteer-adapter.d.ts +70 -0
- package/dist/puppeteer-adapter.js +96 -0
- package/dist/solver.d.ts +264 -0
- package/dist/solver.js +1922 -0
- package/dist/token-usage.d.ts +15 -0
- package/dist/token-usage.js +102 -0
- package/dist/types.d.ts +248 -0
- package/dist/types.js +2 -0
- package/package.json +49 -0
- package/python/Dockerfile +41 -0
- package/python/README.md +71 -0
- package/python/examples/README.md +68 -0
- package/python/examples/_harness.py +158 -0
- package/python/examples/demoHcaptcha.py +20 -0
- package/python/examples/demoRecaptcha.py +17 -0
- package/python/pyproject.toml +79 -0
- package/python/src/captchakraken/__init__.py +61 -0
- package/python/src/captchakraken/action_types.py +56 -0
- package/python/src/captchakraken/cli.py +656 -0
- package/python/src/captchakraken/config.py +78 -0
- package/python/src/captchakraken/image_processor.py +244 -0
- package/python/src/captchakraken/overlay.py +520 -0
- package/python/src/captchakraken/planner.py +408 -0
- package/python/src/captchakraken/planner_types.py +74 -0
- package/python/src/captchakraken/server_manager.py +290 -0
- package/python/src/captchakraken/solver.py +434 -0
- package/python/src/captchakraken/timing.py +42 -0
- package/python/src/captchakraken/tool_calls/find_checkbox.py +72 -0
- package/python/src/captchakraken/tool_calls/find_grid.py +1762 -0
- package/python/src/captchakraken/tool_calls/move_indicator.py +431 -0
- package/scripts/copy-python.mjs +29 -0
- package/scripts/setup-python.js +104 -0
package/dist/solver.d.ts
ADDED
|
@@ -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
|
+
}
|