captchakraken 2.6.0 → 2.8.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 +126 -16
- package/dist/cli-invocation.d.ts +2 -0
- package/dist/cli-invocation.js +5 -1
- package/dist/errors.d.ts +1 -1
- package/dist/humanize.d.ts +377 -0
- package/dist/humanize.js +716 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +44 -1
- package/dist/playwright-types.d.ts +18 -0
- package/dist/solver.d.ts +115 -5
- package/dist/solver.js +583 -190
- package/dist/timing.d.ts +48 -0
- package/dist/timing.js +103 -0
- package/dist/trajectory.d.ts +27 -0
- package/dist/trajectory.js +104 -0
- package/dist/types.d.ts +129 -0
- package/package.json +11 -3
- package/python/README.md +76 -5
- package/python/pyproject.toml +21 -4
- package/python/src/captchakraken/__init__.py +36 -1
- package/python/src/captchakraken/cli.py +62 -4
- package/python/src/captchakraken/config.py +28 -2
- package/python/src/captchakraken/errors.py +24 -0
- package/python/src/captchakraken/humanize.py +929 -0
- package/python/src/captchakraken/keyframes.py +99 -0
- package/python/src/captchakraken/models.json +149 -13
- package/python/src/captchakraken/page_solver.py +588 -291
- package/python/src/captchakraken/pinned_model.json +3 -2
- package/python/src/captchakraken/planner.py +118 -9
- package/python/src/captchakraken/prompts.py +182 -0
- package/python/src/captchakraken/server_manager.py +9 -4
- package/python/src/captchakraken/solver.py +97 -57
- package/python/src/captchakraken/timing.py +8 -3
- package/python/src/captchakraken/tool_calls/find_grid.py +357 -20
- package/python/src/captchakraken/trajectory.py +117 -0
- package/python/src/captchakraken/updater.py +62 -1
package/dist/index.d.ts
CHANGED
|
@@ -104,3 +104,36 @@ export type { Page, PlaywrightPage, PlaywrightFrame, PlaywrightElementHandle } f
|
|
|
104
104
|
*/
|
|
105
105
|
export { watchPage } from './watcher';
|
|
106
106
|
export type { CaptchaWatcher, WatchOptions, WatchableSolver } from './watcher';
|
|
107
|
+
/**
|
|
108
|
+
* Humanisation: how the driver moves. `humanization: 'mouse' | 'mobile' |
|
|
109
|
+
* 'none'` picks a built-in; `humanizer` takes one of your own.
|
|
110
|
+
*
|
|
111
|
+
* `MobileHumanizer` emits real touch events with finger kinematics, through a
|
|
112
|
+
* `TouchBackend` — CDP against a Chromium-family page, or W3C pointer actions
|
|
113
|
+
* against an Appium / WebdriverIO driver on a real handset.
|
|
114
|
+
*
|
|
115
|
+
* @example Mobile emulation in the browser
|
|
116
|
+
* ```typescript
|
|
117
|
+
* import { chromium, devices } from 'playwright';
|
|
118
|
+
* import { CaptchaKrakenSolver } from 'captchakraken';
|
|
119
|
+
*
|
|
120
|
+
* const browser = await chromium.launch();
|
|
121
|
+
* const context = await browser.newContext({ ...devices['Pixel 7'], hasTouch: true });
|
|
122
|
+
* const page = await context.newPage();
|
|
123
|
+
* await page.goto('https://example.com/signup');
|
|
124
|
+
*
|
|
125
|
+
* await new CaptchaKrakenSolver({ humanization: 'mobile' }).solve(page);
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @example A real device over Appium
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const solver = new CaptchaKrakenSolver({
|
|
131
|
+
* humanization: 'mobile',
|
|
132
|
+
* touchDriver: driver, // the WebdriverIO browser
|
|
133
|
+
* touchTransform: { scale: 3, origin: [0, 132] }, // CSS px -> screen px
|
|
134
|
+
* });
|
|
135
|
+
* await solver.solve(page);
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export { MouseHumanizer, MobileHumanizer, NullHumanizer, BaseHumanizer, resolveHumanizer, touchBackendFor, CdpTouchBackend, AppiumTouchBackend, TouchscreenTouchBackend, PAUSE_KINDS, MODES as HUMANIZATION_MODES, } from './humanize';
|
|
139
|
+
export type { Humanizer, HumanizationMode, HumanizerOptions, TouchBackend, TouchSample, TouchTransform, PauseKind, } from './humanize';
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.watchPage = exports.fromPuppeteer = exports.CaptchaKrakenAPIError = exports.resolveLoraName = exports.CaptchaKrakenSolver = void 0;
|
|
17
|
+
exports.HUMANIZATION_MODES = exports.PAUSE_KINDS = exports.TouchscreenTouchBackend = exports.AppiumTouchBackend = exports.CdpTouchBackend = exports.touchBackendFor = exports.resolveHumanizer = exports.BaseHumanizer = exports.NullHumanizer = exports.MobileHumanizer = exports.MouseHumanizer = exports.watchPage = exports.fromPuppeteer = exports.CaptchaKrakenAPIError = exports.resolveLoraName = exports.CaptchaKrakenSolver = void 0;
|
|
18
18
|
__exportStar(require("./types"), exports);
|
|
19
19
|
var solver_1 = require("./solver");
|
|
20
20
|
Object.defineProperty(exports, "CaptchaKrakenSolver", { enumerable: true, get: function () { return solver_1.CaptchaKrakenSolver; } });
|
|
@@ -92,3 +92,46 @@ Object.defineProperty(exports, "fromPuppeteer", { enumerable: true, get: functio
|
|
|
92
92
|
*/
|
|
93
93
|
var watcher_1 = require("./watcher");
|
|
94
94
|
Object.defineProperty(exports, "watchPage", { enumerable: true, get: function () { return watcher_1.watchPage; } });
|
|
95
|
+
/**
|
|
96
|
+
* Humanisation: how the driver moves. `humanization: 'mouse' | 'mobile' |
|
|
97
|
+
* 'none'` picks a built-in; `humanizer` takes one of your own.
|
|
98
|
+
*
|
|
99
|
+
* `MobileHumanizer` emits real touch events with finger kinematics, through a
|
|
100
|
+
* `TouchBackend` — CDP against a Chromium-family page, or W3C pointer actions
|
|
101
|
+
* against an Appium / WebdriverIO driver on a real handset.
|
|
102
|
+
*
|
|
103
|
+
* @example Mobile emulation in the browser
|
|
104
|
+
* ```typescript
|
|
105
|
+
* import { chromium, devices } from 'playwright';
|
|
106
|
+
* import { CaptchaKrakenSolver } from 'captchakraken';
|
|
107
|
+
*
|
|
108
|
+
* const browser = await chromium.launch();
|
|
109
|
+
* const context = await browser.newContext({ ...devices['Pixel 7'], hasTouch: true });
|
|
110
|
+
* const page = await context.newPage();
|
|
111
|
+
* await page.goto('https://example.com/signup');
|
|
112
|
+
*
|
|
113
|
+
* await new CaptchaKrakenSolver({ humanization: 'mobile' }).solve(page);
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @example A real device over Appium
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const solver = new CaptchaKrakenSolver({
|
|
119
|
+
* humanization: 'mobile',
|
|
120
|
+
* touchDriver: driver, // the WebdriverIO browser
|
|
121
|
+
* touchTransform: { scale: 3, origin: [0, 132] }, // CSS px -> screen px
|
|
122
|
+
* });
|
|
123
|
+
* await solver.solve(page);
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
var humanize_1 = require("./humanize");
|
|
127
|
+
Object.defineProperty(exports, "MouseHumanizer", { enumerable: true, get: function () { return humanize_1.MouseHumanizer; } });
|
|
128
|
+
Object.defineProperty(exports, "MobileHumanizer", { enumerable: true, get: function () { return humanize_1.MobileHumanizer; } });
|
|
129
|
+
Object.defineProperty(exports, "NullHumanizer", { enumerable: true, get: function () { return humanize_1.NullHumanizer; } });
|
|
130
|
+
Object.defineProperty(exports, "BaseHumanizer", { enumerable: true, get: function () { return humanize_1.BaseHumanizer; } });
|
|
131
|
+
Object.defineProperty(exports, "resolveHumanizer", { enumerable: true, get: function () { return humanize_1.resolveHumanizer; } });
|
|
132
|
+
Object.defineProperty(exports, "touchBackendFor", { enumerable: true, get: function () { return humanize_1.touchBackendFor; } });
|
|
133
|
+
Object.defineProperty(exports, "CdpTouchBackend", { enumerable: true, get: function () { return humanize_1.CdpTouchBackend; } });
|
|
134
|
+
Object.defineProperty(exports, "AppiumTouchBackend", { enumerable: true, get: function () { return humanize_1.AppiumTouchBackend; } });
|
|
135
|
+
Object.defineProperty(exports, "TouchscreenTouchBackend", { enumerable: true, get: function () { return humanize_1.TouchscreenTouchBackend; } });
|
|
136
|
+
Object.defineProperty(exports, "PAUSE_KINDS", { enumerable: true, get: function () { return humanize_1.PAUSE_KINDS; } });
|
|
137
|
+
Object.defineProperty(exports, "HUMANIZATION_MODES", { enumerable: true, get: function () { return humanize_1.MODES; } });
|
|
@@ -125,6 +125,24 @@ export interface PlaywrightPage {
|
|
|
125
125
|
}): Promise<PlaywrightElementHandle | null>;
|
|
126
126
|
/** Current viewport size, or null if not set. */
|
|
127
127
|
viewportSize(): ViewportSize | null;
|
|
128
|
+
/**
|
|
129
|
+
* The browser context this page belongs to. OPTIONAL because only
|
|
130
|
+
* `humanization: 'mobile'` needs it, and then only to open a CDP session for
|
|
131
|
+
* `Input.dispatchTouchEvent` — the mouse and none modes never touch it. A
|
|
132
|
+
* launcher that does not expose it simply cannot serve mobile mode, and says
|
|
133
|
+
* so at construction rather than mid-solve.
|
|
134
|
+
*/
|
|
135
|
+
context?(): {
|
|
136
|
+
newCDPSession(page: PlaywrightPage): Promise<any>;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Playwright's touchscreen, present when the context was created with
|
|
140
|
+
* `hasTouch: true`. OPTIONAL, and tap-only: it is the fallback for a browser
|
|
141
|
+
* with touch support but no CDP, and cannot express a drag.
|
|
142
|
+
*/
|
|
143
|
+
touchscreen?: {
|
|
144
|
+
tap(x: number, y: number): Promise<void>;
|
|
145
|
+
};
|
|
128
146
|
/** First matching element handle, or null. */
|
|
129
147
|
$(selector: string): Promise<PlaywrightElementHandle | null>;
|
|
130
148
|
/** All matching element handles. */
|
package/dist/solver.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PlaywrightPage as Page, PlaywrightElementHandle as ElementHandle } from './playwright-types';
|
|
2
2
|
import { CaptchaWatcher, WatchOptions } from './watcher';
|
|
3
|
+
import { PhaseBudget } from './timing';
|
|
3
4
|
import { CaptchaKrakenConfig, SolveResult } from './types';
|
|
4
5
|
/**
|
|
5
6
|
* The two numbers that bound a solve, together, because they only make sense
|
|
@@ -15,7 +16,15 @@ export declare const SOLVE_DEFAULTS: {
|
|
|
15
16
|
};
|
|
16
17
|
export declare class CaptchaKrakenSolver {
|
|
17
18
|
private config;
|
|
18
|
-
|
|
19
|
+
/** Extra ms this solve has been granted for a recording; see recordKeyframeBurst. */
|
|
20
|
+
private videoBudgetMs;
|
|
21
|
+
private videoBudgetGranted;
|
|
22
|
+
/**
|
|
23
|
+
* Every gesture goes through here, and it owns the pointer position. See
|
|
24
|
+
* humanize.ts — the solver names gestures, this decides what events they are
|
|
25
|
+
* and how long they take.
|
|
26
|
+
*/
|
|
27
|
+
private human;
|
|
19
28
|
private imageCounter;
|
|
20
29
|
private sessionDebugDir;
|
|
21
30
|
private stepIndex;
|
|
@@ -35,8 +44,47 @@ export declare class CaptchaKrakenSolver {
|
|
|
35
44
|
private state;
|
|
36
45
|
private lastSubmitFrameHash;
|
|
37
46
|
private solveSessionId;
|
|
47
|
+
/** Interpreter + CLI root, resolved once. See resolveCli. */
|
|
48
|
+
private cliCache;
|
|
49
|
+
/** The LoRA name models.json calls `latest`, read once. See askModel. */
|
|
50
|
+
private loraNameCache;
|
|
51
|
+
/**
|
|
52
|
+
* Phase accounting for the CURRENT or MOST RECENT solve; see timing.ts.
|
|
53
|
+
* Null before the first solve, and readable after a failed one.
|
|
54
|
+
*/
|
|
55
|
+
budget: PhaseBudget | null;
|
|
56
|
+
/**
|
|
57
|
+
* Attribute the enclosed wall-clock to `name` for this solve's budget.
|
|
58
|
+
*
|
|
59
|
+
* A no-op outside a solve, so every helper stays callable from tests and from
|
|
60
|
+
* the watcher without a budget having been opened.
|
|
61
|
+
*/
|
|
62
|
+
private ph;
|
|
38
63
|
constructor(config?: CaptchaKrakenConfig);
|
|
64
|
+
/**
|
|
65
|
+
* Where the pointer is. Owned by the humanizer, because a mode that
|
|
66
|
+
* dispatches no motion (mobile, between taps) still has to answer this.
|
|
67
|
+
*/
|
|
68
|
+
private get lastMousePosition();
|
|
69
|
+
private set lastMousePosition(value);
|
|
39
70
|
solve(page: Page): Promise<SolveResult | void>;
|
|
71
|
+
/**
|
|
72
|
+
* Tell the hosted API whether the widget accepted — the one fact the server
|
|
73
|
+
* cannot see for itself, since a wrong answer reaches it as a 200 with
|
|
74
|
+
* well-formed JSON in it.
|
|
75
|
+
*
|
|
76
|
+
* THROUGH THE PYTHON CLI, not a `fetch` here. The endpoint, the credential,
|
|
77
|
+
* the extra-header handling and the "one 404 and stop asking" rule all live
|
|
78
|
+
* in `planner.report_outcome`; a second copy in TypeScript would agree with
|
|
79
|
+
* it until one of them was edited. That is the drift `model-name.ts` exists
|
|
80
|
+
* to prevent, on the same seam, and this port already crosses that subprocess
|
|
81
|
+
* boundary for every model call.
|
|
82
|
+
*
|
|
83
|
+
* NOT AWAITED, and detached. The solve is over and its result is decided, so
|
|
84
|
+
* nothing here may add latency to what the caller gets back or keep the
|
|
85
|
+
* process alive past it. `unref()` is what makes the second true.
|
|
86
|
+
*/
|
|
87
|
+
private reportOutcome;
|
|
40
88
|
/**
|
|
41
89
|
* Install an auto-solver: probe `page` on a timer and solve any captcha that
|
|
42
90
|
* becomes visible, until `stop()`.
|
|
@@ -130,6 +178,25 @@ export declare class CaptchaKrakenSolver {
|
|
|
130
178
|
* `size=invisible` in the src, and never an `api2/bframe` challenge frame, so
|
|
131
179
|
* we exclude the invisible variant here.
|
|
132
180
|
*/
|
|
181
|
+
/**
|
|
182
|
+
* Which vendors' code this page actually loaded.
|
|
183
|
+
*
|
|
184
|
+
* Read out of the page's own resource timing plus the `src`/`href` of
|
|
185
|
+
* everything it linked, so it works whenever it is called — a request
|
|
186
|
+
* listener would have to have been attached before navigation, and by the
|
|
187
|
+
* time a solve fails that ship has sailed.
|
|
188
|
+
*
|
|
189
|
+
* Best-effort by construction: the resource-timing buffer is finite and a
|
|
190
|
+
* page may have cleared it. An empty answer is "nothing seen", never "no
|
|
191
|
+
* captcha here". Mirrors `vendors_on_the_wire` in page_solver.py.
|
|
192
|
+
*/
|
|
193
|
+
vendorsOnTheWire(page: Page): Promise<string[]>;
|
|
194
|
+
/**
|
|
195
|
+
* Why nothing was found — the two cases told apart. The old text guessed
|
|
196
|
+
* "reCAPTCHA v3 / invisible" every time, including the times a vendor had
|
|
197
|
+
* simply moved its DOM. See VENDOR_URL_MARKERS.
|
|
198
|
+
*/
|
|
199
|
+
private noWidgetMessage;
|
|
133
200
|
hasInteractiveWidgetInDom(page: Page): Promise<boolean>;
|
|
134
201
|
detectCaptcha(page: Page): Promise<ElementHandle | null>;
|
|
135
202
|
/**
|
|
@@ -155,6 +222,14 @@ export declare class CaptchaKrakenSolver {
|
|
|
155
222
|
* falling back to the configured/`python` command. Throws if the CLI folder
|
|
156
223
|
* is missing — callers that must not throw (e.g. runCliTool) wrap this.
|
|
157
224
|
*/
|
|
225
|
+
/**
|
|
226
|
+
* The adapter name models.json calls `latest`, resolved once per solver.
|
|
227
|
+
*
|
|
228
|
+
* `resolveLoraName` reads models.json (and pinned_model.json) off disk
|
|
229
|
+
* SYNCHRONOUSLY, and it was doing that on every inference for a file that
|
|
230
|
+
* cannot change inside one process.
|
|
231
|
+
*/
|
|
232
|
+
private loraName;
|
|
158
233
|
private resolveCli;
|
|
159
234
|
/**
|
|
160
235
|
* Run an OpenCV tool subcommand of the CLI (e.g. `grid-cell-states a.png
|
|
@@ -482,17 +557,22 @@ export declare class CaptchaKrakenSolver {
|
|
|
482
557
|
/**
|
|
483
558
|
* Run `fn` (typically the model query) while idly drifting the cursor over
|
|
484
559
|
* the captcha, so the mouse behaves like a human weighing the options instead
|
|
485
|
-
* of freezing during inference. Uses the same
|
|
486
|
-
*
|
|
487
|
-
* is swallowed and never fails the solve.
|
|
560
|
+
* of freezing during inference. Uses the same humanizer as real clicks, so a
|
|
561
|
+
* mode with no cursor skips it entirely; cancelled the instant `fn` resolves.
|
|
562
|
+
* Best-effort — any wander error is swallowed and never fails the solve.
|
|
563
|
+
* Disable via config.idleMouseWander.
|
|
488
564
|
*/
|
|
489
565
|
private withIdleWander;
|
|
490
566
|
move(page: Page, selectorOrElement: string | ElementHandle, options?: {
|
|
491
567
|
paddingPercentage?: number;
|
|
492
568
|
}): Promise<void>;
|
|
493
569
|
moveAndClick(page: Page, element: ElementHandle): Promise<void>;
|
|
570
|
+
/**
|
|
571
|
+
* Travel to a point. What that MEANS is the humanizer's business — on a
|
|
572
|
+
* touchscreen with no finger down it is a bookkeeping update and emits
|
|
573
|
+
* nothing at all.
|
|
574
|
+
*/
|
|
494
575
|
private performSmoothMove;
|
|
495
|
-
private tracePath;
|
|
496
576
|
private executeClick;
|
|
497
577
|
private executeDrag;
|
|
498
578
|
/**
|
|
@@ -505,6 +585,30 @@ export declare class CaptchaKrakenSolver {
|
|
|
505
585
|
* the document, and the answer would go there.
|
|
506
586
|
*/
|
|
507
587
|
private findControl;
|
|
588
|
+
/**
|
|
589
|
+
* Where a distorted-text captcha's answer goes.
|
|
590
|
+
*
|
|
591
|
+
* Inside the widget first. If the widget holds no text box at all, widen ONCE
|
|
592
|
+
* to its enclosing <fieldset>/<form> and look again for a VENDOR-NAMED box
|
|
593
|
+
* only.
|
|
594
|
+
*
|
|
595
|
+
* BotDetect is why, and nothing else needs it. BotDetect is a self-hosted
|
|
596
|
+
* LIBRARY, so the host application owns the layout: measured on captcha.com
|
|
597
|
+
* 2026-08-24, `.BDC_CaptchaDiv` — the element detectCaptcha returns — is
|
|
598
|
+
* 280x50 and holds the image alone, while `#captchaCode` sits in a sibling
|
|
599
|
+
* `<div class="validationDiv">` under the enclosing <fieldset>. Scoped to the
|
|
600
|
+
* widget the solver found no box, textMode stayed false, and it read the code
|
|
601
|
+
* correctly and never typed it. A clean solve thrown away, and
|
|
602
|
+
* indistinguishable in any report from a model that cannot read warped text.
|
|
603
|
+
*
|
|
604
|
+
* The widened pass is restricted to TEXT_INPUT_VENDOR_SELECTORS on purpose.
|
|
605
|
+
* The generic tail is what makes the in-widget lookup work for every other
|
|
606
|
+
* vendor, and OUTSIDE the widget it is exactly how a captcha's answer ends up
|
|
607
|
+
* in a login form's username box. One level, named selectors, never the page.
|
|
608
|
+
*
|
|
609
|
+
* Mirrors `_answer_box` in page_solver.py — CLAUDE.md 1c.
|
|
610
|
+
*/
|
|
611
|
+
private answerBox;
|
|
508
612
|
/** Put the model's reading of a distorted-text captcha into its box. */
|
|
509
613
|
private executeType;
|
|
510
614
|
/** `captchakraken track-piece` — box of what moved, handle masked out. */
|
|
@@ -529,5 +633,11 @@ export declare class CaptchaKrakenSolver {
|
|
|
529
633
|
* Returns false if there is nothing here to drag, leaving the caller's normal
|
|
530
634
|
* no-op handling to deal with it.
|
|
531
635
|
*/
|
|
636
|
+
/**
|
|
637
|
+
* Device pixels per CSS pixel, read off the shot we are about to measure.
|
|
638
|
+
* 1 when the image cannot be read — an unreadable shot is already the
|
|
639
|
+
* trackPiece-returns-null path, and guessing a ratio would steer by it.
|
|
640
|
+
*/
|
|
641
|
+
private shotScale;
|
|
532
642
|
private executeSlide;
|
|
533
643
|
}
|