captchakraken 3.0.0 → 3.2.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/AGENTS.md +4 -4
- package/LICENSE +1 -1
- package/README.md +10 -13
- package/dist/index.d.ts +1 -0
- package/dist/kinds.d.ts +8 -0
- package/dist/kinds.js +3 -1
- package/dist/model-name.d.ts +6 -0
- package/dist/model-name.js +28 -0
- package/dist/playwright-types.d.ts +18 -0
- package/dist/png.d.ts +16 -0
- package/dist/png.js +119 -0
- package/dist/puppeteer-adapter.d.ts +7 -1
- package/dist/puppeteer-adapter.js +6 -0
- package/dist/selectors.d.ts +6 -0
- package/dist/selectors.js +4 -0
- package/dist/solver.d.ts +118 -5
- package/dist/solver.js +840 -143
- package/dist/types.d.ts +12 -5
- package/dist/verdicts.d.ts +45 -0
- package/dist/verdicts.js +125 -0
- package/package.json +7 -7
- package/python/AGENTS.md +13 -8
- package/python/LICENSE +1 -1
- package/python/README.md +10 -13
- package/python/pyproject.toml +6 -6
- package/python/src/captchakraken/__init__.py +5 -1
- package/python/src/captchakraken/cli.py +7 -1
- package/python/src/captchakraken/kinds.py +9 -0
- package/python/src/captchakraken/page_solver.py +651 -137
- package/python/src/captchakraken/planner.py +68 -12
- package/python/src/captchakraken/prompts.py +24 -0
- package/python/src/captchakraken/selectors.py +10 -0
- package/python/src/captchakraken/tool_calls/find_checkbox.py +4 -0
- package/python/src/captchakraken/tool_calls/find_grid.py +22 -0
- package/python/src/captchakraken/verdicts.py +154 -0
- package/python/Dockerfile +0 -41
- package/python/examples/README.md +0 -114
- package/python/examples/_harness.py +0 -177
- package/python/examples/demoHcaptcha.py +0 -8
- package/python/examples/demoRecaptcha.py +0 -8
- package/python/examples/watch_playwright.py +0 -31
- package/python/examples/with_local_gguf.py +0 -28
- package/python/examples/with_playwright.py +0 -24
- package/scripts/copy-python.mjs +0 -29
package/dist/solver.d.ts
CHANGED
|
@@ -20,9 +20,13 @@ export declare function answerNeedsElementBox(actions: ReadonlyArray<{
|
|
|
20
20
|
}>): boolean;
|
|
21
21
|
/** The widget moved on under us (hCaptcha swapped rounds, GeeTest closed on accept): re-detect, do not fail. */
|
|
22
22
|
export declare function isStaleHandleError(message: string): boolean;
|
|
23
|
+
/** A closed target is not a stale handle: retried as one, a dead page was re-detected until the loops ran out. */
|
|
24
|
+
export declare function isClosedTargetError(message: string): boolean;
|
|
25
|
+
export declare const INPUT_SETTLE_MS = 400;
|
|
23
26
|
export declare const SOLVE_DEFAULTS: {
|
|
24
27
|
readonly maxSolveLoops: 6;
|
|
25
28
|
readonly overallSolveTimeoutMs: 45000;
|
|
29
|
+
readonly detectionTimeoutMs: 15000;
|
|
26
30
|
readonly keyframeWaitTimeoutMs: 9000;
|
|
27
31
|
};
|
|
28
32
|
/** A hang detector for a burst whose screenshot never returns, sized off the ceiling. */
|
|
@@ -44,7 +48,9 @@ export declare class CaptchaKrakenSolver {
|
|
|
44
48
|
/** Answers keyed by screenshot hash; a hit means the answer already ran and changed nothing. */
|
|
45
49
|
private solutionCache;
|
|
46
50
|
private repeatedAnswerSeen;
|
|
47
|
-
|
|
51
|
+
private knownAnimated;
|
|
52
|
+
private animatedProbeDone;
|
|
53
|
+
/** The one recording for the animated board on screen, and its answer until the widget refuses it. */
|
|
48
54
|
private animatedPlan;
|
|
49
55
|
private keyframeMode;
|
|
50
56
|
private keyframeSteadyScreens;
|
|
@@ -54,28 +60,103 @@ export declare class CaptchaKrakenSolver {
|
|
|
54
60
|
private resampleLevel;
|
|
55
61
|
/** The recording `classifyByRecording` started and left running for the branch below to finish or drop. */
|
|
56
62
|
private pendingBurst;
|
|
63
|
+
/**
|
|
64
|
+
* The camera on the animated board in front of us, running across rounds until that board is gone.
|
|
65
|
+
*
|
|
66
|
+
* Distinct from `pendingBurst`, which is the speculative film one round starts and the same round spends.
|
|
67
|
+
* This one outlives a rejected answer on purpose: it is the only thing that can give the next ask a
|
|
68
|
+
* different question to answer, since the answer itself is deterministic at temperature 0.
|
|
69
|
+
*/
|
|
70
|
+
private animatedFilm;
|
|
57
71
|
private lastBurstFps;
|
|
58
72
|
private actedOnBoard;
|
|
59
73
|
private lastSubmitFrameHash;
|
|
60
74
|
private solveSessionId;
|
|
75
|
+
/** Which vendor and site this solve is for, sent to the hosted API; the site is a hostname only. */
|
|
76
|
+
private solveVendor;
|
|
77
|
+
private solveSite;
|
|
78
|
+
/** The host the widget itself was served from (its iframe's); null for an inline widget. */
|
|
79
|
+
private solveWidgetHost;
|
|
80
|
+
private legacyModelWarned;
|
|
81
|
+
private verdicts;
|
|
82
|
+
/** False once a self-hosted endpoint has answered 404: it has no outcome route to report to. */
|
|
83
|
+
private outcomeSupported;
|
|
61
84
|
private cliCache;
|
|
62
85
|
private loraNameCache;
|
|
63
86
|
budget: PhaseBudget | null;
|
|
87
|
+
/** The page being solved: every capture is taken of its viewport. Set by `solve`. */
|
|
88
|
+
private page;
|
|
89
|
+
/** When our last gesture ended; the board's reaction to it is never read as the board's own motion. */
|
|
90
|
+
private lastInputAt;
|
|
64
91
|
private ph;
|
|
92
|
+
/** A pointer gesture, timed as MOUSE and remembered: see `INPUT_SETTLE_MS`. */
|
|
93
|
+
private gesture;
|
|
65
94
|
constructor(config?: CaptchaKrakenConfig);
|
|
66
95
|
private get lastMousePosition();
|
|
67
96
|
private set lastMousePosition(value);
|
|
68
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* Every capture the driver takes: the whole viewport, cropped here to the element.
|
|
99
|
+
*
|
|
100
|
+
* Never a clipped capture. An element screenshot, or any `clip`, makes a headed Chromium repaint the page at the
|
|
101
|
+
* clip's size for that frame, which the user watches as the page flashing and jumping; a viewport capture
|
|
102
|
+
* repaints nothing, and measured twice as fast. `disabled` freezes CSS animation, which once sliced GeeTest svg to
|
|
103
|
+
* a static clip; callers filming motion pass `allow`.
|
|
104
|
+
*/
|
|
69
105
|
private shot;
|
|
106
|
+
private rectOf;
|
|
107
|
+
/** Where the element sits in the viewport, scrolled in when it is merely off screen; null when it cannot fit. */
|
|
108
|
+
private rectInView;
|
|
109
|
+
/**
|
|
110
|
+
* Take the pointer off the board and let our own feedback fade before the board is judged still or animated.
|
|
111
|
+
*
|
|
112
|
+
* A hovered or pressed control repaints, and a board wearing our highlight read as motion: a still board was
|
|
113
|
+
* filmed as animated because of the Verify button under the cursor.
|
|
114
|
+
*/
|
|
115
|
+
private stepOffTheBoard;
|
|
70
116
|
solve(page: Page): Promise<SolveResult | void>;
|
|
71
|
-
/** Tell the hosted API whether the widget accepted, through the CLI
|
|
117
|
+
/** Tell the hosted API whether the widget accepted, through the CLI. The solve never awaits it; the promise is for callers that must know it finished. */
|
|
72
118
|
private reportOutcome;
|
|
73
119
|
/** Solve captchas on `page` as they appear, until `stop()`. Injects nothing into the page. */
|
|
74
120
|
watch(page: Page, options?: WatchOptions): CaptchaWatcher;
|
|
121
|
+
/**
|
|
122
|
+
* Give up only when the vendor refuses to serve us, or when every one of `maxSolveLoops` has been spent. Any other
|
|
123
|
+
* failed round — a stale handle, an unusable or repeated answer, a board that would not film — counts one loop,
|
|
124
|
+
* pauses, and goes again.
|
|
125
|
+
*/
|
|
75
126
|
private solveImpl;
|
|
127
|
+
/** What the vendor's server said since the last look: BLOCKED outranks everything, else the latest answer. */
|
|
128
|
+
private vendorVerdict;
|
|
129
|
+
/**
|
|
130
|
+
* A page that has not drawn its widget yet is looked at again until `detectionTimeoutMs`. Solving straight after
|
|
131
|
+
* `domcontentloaded` failed 3/3 on a demo page whose frames arrived a moment later.
|
|
132
|
+
*/
|
|
133
|
+
private awaitWidget;
|
|
134
|
+
/** The vendor showing its refusal screen: rate-limited or flagged, with no board to answer. */
|
|
135
|
+
private isBlocked;
|
|
136
|
+
/** The env the engine reads its routing headers from: session, and the vendor and site once they are known. */
|
|
137
|
+
private routingEnv;
|
|
76
138
|
/** Fire the optional onStep observer with a fresh screenshot; best-effort, never fails the solve. */
|
|
77
139
|
private emitStep;
|
|
78
140
|
private solveSingle;
|
|
141
|
+
/**
|
|
142
|
+
* Tick the box and wait for what it opens. A checkbox is not a board: it is never polled, filmed or asked about.
|
|
143
|
+
*
|
|
144
|
+
* Treated as a board it cost 70 screenshots and three model calls in one measured solve, two of them answering the
|
|
145
|
+
* checkbox picture with a drag.
|
|
146
|
+
*/
|
|
147
|
+
private clickCheckbox;
|
|
148
|
+
/**
|
|
149
|
+
* The box in a checkbox frame once the frame has drawn it; null when the DOM cannot reach it. Turnstile's box is in
|
|
150
|
+
* a closed shadow root, and a frame can refuse access; both are found on screen instead.
|
|
151
|
+
*/
|
|
152
|
+
private checkboxBox;
|
|
153
|
+
/** One capture and OpenCV: a point inside the tick box, in page coordinates. No model is asked. */
|
|
154
|
+
private findCheckboxOnScreen;
|
|
155
|
+
/**
|
|
156
|
+
* A clicked checkbox passes or opens a challenge, and both show in the DOM.
|
|
157
|
+
* Without this wait the next round found the same checkbox still up and clicked it again.
|
|
158
|
+
*/
|
|
159
|
+
private awaitWhatTheCheckboxOpened;
|
|
79
160
|
private getVerifyButton;
|
|
80
161
|
/** Any element at `selector` carrying a non-blank value; response fields are hidden, so no visibility filter. */
|
|
81
162
|
private hasValue;
|
|
@@ -96,6 +177,8 @@ export declare class CaptchaKrakenSolver {
|
|
|
96
177
|
private waitForBoardImages;
|
|
97
178
|
/** Which vendors' code the page loaded, from resource timing and linked URLs. A tripwire, not a detector. */
|
|
98
179
|
vendorsOnTheWire(page: Page): Promise<Vendor[]>;
|
|
180
|
+
/** Vendors showing a frame no selector names. Only asked once detection has found nothing, and a passive frame (an invisible badge) is not a widget, so what is left is markup the table no longer matches. */
|
|
181
|
+
private unmatchedVendorFrames;
|
|
99
182
|
private noWidgetMessage;
|
|
100
183
|
/**
|
|
101
184
|
* Is a widget in the DOM at all, rendered or not? Invisible reCAPTCHA is excluded by its selector. The inline vendors
|
|
@@ -110,6 +193,13 @@ export declare class CaptchaKrakenSolver {
|
|
|
110
193
|
/** The `--model` argv, or nothing: without VLLM_BASE_URL the CLI can see a credentials file this process cannot. */
|
|
111
194
|
private modelName;
|
|
112
195
|
private resolveCli;
|
|
196
|
+
/** What is LEFT of the solve, as a bound on one ask.
|
|
197
|
+
*
|
|
198
|
+
* The CLI is a child process and the solve deadline is only read between steps, so nothing stops an
|
|
199
|
+
* ask already in flight — measured against the hosted endpoint, one hung for ~124s inside a 45s
|
|
200
|
+
* budget and the attempt ran 143s. Never below the floor: a keyframe ask carries six images, and a
|
|
201
|
+
* timeout shorter than the work turns a busy endpoint into a guaranteed failure. */
|
|
202
|
+
private askTimeoutMs;
|
|
113
203
|
/** One-shot CLI tool call; `{}` on any failure so polling callers keep going. */
|
|
114
204
|
private runCliTool;
|
|
115
205
|
/** Start the persistent CV worker once; false when it cannot start, so callers fall back to one-shot spawns. */
|
|
@@ -142,17 +232,27 @@ export declare class CaptchaKrakenSolver {
|
|
|
142
232
|
private waitForAnyClickedTileLoaded;
|
|
143
233
|
/** Click, watch the clicked tiles, re-solve while they swap, submit on `done` or a chipped board. */
|
|
144
234
|
private solveRecaptchaGrid;
|
|
145
|
-
private recordKeyframeBurst;
|
|
146
235
|
/**
|
|
147
236
|
* Start filming now and decide later what it was for: the classifier, the speculative film, or the
|
|
148
237
|
* recording an animated answer is sliced from. Every window is wall-clock. A cycle is a screen that
|
|
149
238
|
* comes back; a board that shows nothing new for a floor window has settled. There is deliberately no
|
|
150
239
|
* "enough screens, stop" exit: measured on number_with_highest_value_video it failed every seed either way.
|
|
240
|
+
*
|
|
241
|
+
* `continuous` keeps the camera rolling past the first answer, for the whole solve. A one-shot burst can
|
|
242
|
+
* only ever show the model the screens that happened to fall inside its window — measured, a 4s burst
|
|
243
|
+
* against a 5.3s cycle showed two screens of three, and when the answer lived on the third there was no
|
|
244
|
+
* second chance, because the reused answer is identical by construction. Filming on means every later
|
|
245
|
+
* round slices a STRICTLY LONGER film: more repetitions for `_detect_cycle` to confirm against, and, on a
|
|
246
|
+
* board that never repeats, six evenly-spread picks across the whole solve instead of across four seconds.
|
|
247
|
+
*
|
|
248
|
+
* Longer, but never of TWO boards. A vendor that deals a fresh puzzle on a miss has replaced everything
|
|
249
|
+
* filmed so far, and keyframes cut across both states describe neither. `resume` cuts the film where our
|
|
250
|
+
* answer landed; a screen from before the cut coming back is what un-cuts it. See `snapshot`.
|
|
151
251
|
*/
|
|
152
252
|
private startKeyframeBurst;
|
|
153
253
|
/** Slice a burst into keyframes and solve them in one model request. The frame number is the guard. */
|
|
154
254
|
private getAnimatedSolution;
|
|
155
|
-
/** The
|
|
255
|
+
/** The routing values and the credential travel in the environment, never in argv. */
|
|
156
256
|
private solveEnvironment;
|
|
157
257
|
/** Exit 2 is an unsupported puzzle, exit 3 the hosted API's own refusal; both are relayed, not reworded. */
|
|
158
258
|
private cliError;
|
|
@@ -163,15 +263,28 @@ export declare class CaptchaKrakenSolver {
|
|
|
163
263
|
private resetSolveState;
|
|
164
264
|
/** Keyed on the retry mode too: the missed-tiles answer legitimately overlaps the previous one. */
|
|
165
265
|
private static answerSignature;
|
|
266
|
+
/** The recording still stands; dropping only the refused answer makes the retry an inference, not a burst. */
|
|
267
|
+
private invalidateAnimatedAnswer;
|
|
166
268
|
/** True when this answer already ran and changed nothing: resample, and let the recording path have a go. */
|
|
167
269
|
private noteAnswer;
|
|
168
270
|
/** A cache hit costs no inference, and means the answer already ran: the board cycles. */
|
|
169
271
|
private answerFor;
|
|
272
|
+
/**
|
|
273
|
+
* Drop the ANSWER, keep the camera.
|
|
274
|
+
*
|
|
275
|
+
* The two are deliberately separable. An answer the widget refused is spent — reusing it just presses the
|
|
276
|
+
* same wrong thing until the no-progress fence trips, three rounds in, with half the budget unspent. The
|
|
277
|
+
* film is the opposite: every second it keeps running makes the next slice a better question. So a
|
|
278
|
+
* refusal discards this and leaves `animatedFilm` alone; only a board that is GONE stops the camera.
|
|
279
|
+
*/
|
|
170
280
|
private discardAnimatedPlan;
|
|
281
|
+
/** The board is gone: its film can never describe the next one, so end it and free the frames. */
|
|
282
|
+
private stopAnimatedFilm;
|
|
171
283
|
/** Static or animated, decided by filming once. The film stays running for the branch that uses it. */
|
|
172
284
|
private classifyByRecording;
|
|
173
285
|
private releasePendingBurst;
|
|
174
286
|
private shouldSpeculate;
|
|
287
|
+
/** One second look per solve: a repeated answer arms it, and the recording it takes spends it. */
|
|
175
288
|
private shouldRetryAsAnimated;
|
|
176
289
|
private getSolution;
|
|
177
290
|
/** One inference through the CLI. */
|