captchakraken 3.2.1 → 4.0.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 +5 -9
- package/LICENSE +165 -145
- package/NOTICE +4 -4
- package/README.md +10 -11
- package/dist/cli-invocation.d.ts +6 -3
- package/dist/cli-invocation.js +26 -10
- package/dist/humanize.d.ts +37 -1
- package/dist/humanize.js +143 -9
- package/dist/index.d.ts +13 -12
- package/dist/index.js +25 -33
- package/dist/kinds.d.ts +15 -2
- package/dist/kinds.js +12 -4
- package/dist/playwright-types.d.ts +3 -0
- package/dist/selectors.d.ts +93 -11
- package/dist/selectors.js +226 -116
- package/dist/solver.d.ts +92 -12
- package/dist/solver.js +562 -203
- package/dist/types.d.ts +53 -8
- package/dist/verdicts.d.ts +3 -0
- package/dist/verdicts.js +6 -0
- package/package.json +4 -4
- package/python/AGENTS.md +7 -11
- package/python/LICENSE +165 -145
- package/python/NOTICE +4 -4
- package/python/README.md +10 -9
- package/python/pyproject.toml +27 -13
- package/python/src/captchakraken/__init__.py +43 -46
- package/python/src/captchakraken/action_types.py +32 -1
- package/python/src/captchakraken/captchakraken.config.json +416 -0
- package/python/src/captchakraken/cli.py +66 -30
- package/python/src/captchakraken/clipvideo.py +258 -0
- package/python/src/captchakraken/humanize.py +141 -10
- package/python/src/captchakraken/image_processor.py +3 -7
- package/python/src/captchakraken/keyframes.py +96 -14
- package/python/src/captchakraken/kinds.py +14 -2
- package/python/src/captchakraken/models.json +28 -3
- package/python/src/captchakraken/page_solver.py +595 -251
- package/python/src/captchakraken/planner.py +298 -47
- package/python/src/captchakraken/pointer/__init__.py +20 -0
- package/python/src/captchakraken/pointer/detector.py +333 -0
- package/python/src/captchakraken/pointer/elements.py +273 -0
- package/python/src/captchakraken/pointer/heads.py +163 -0
- package/python/src/captchakraken/pointer/layout.py +319 -0
- package/python/src/captchakraken/pointer/marks.py +184 -0
- package/python/src/captchakraken/pointer/server.py +146 -0
- package/python/src/captchakraken/pointer/service.py +553 -0
- package/python/src/captchakraken/pointer/vllm_plugin.py +109 -0
- package/python/src/captchakraken/prompts.py +111 -34
- package/python/src/captchakraken/selectors.py +221 -135
- package/python/src/captchakraken/server_manager.py +18 -0
- package/python/src/captchakraken/solver.py +169 -88
- package/python/src/captchakraken/tool_calls/board_painted.py +11 -6
- package/python/src/captchakraken/tool_calls/fill_tracker.py +152 -0
- package/python/src/captchakraken/tool_calls/find_checkbox.py +14 -59
- package/python/src/captchakraken/tool_calls/find_grid.py +273 -1191
- package/python/src/captchakraken/tool_calls/slide_tracker.py +1102 -0
- package/python/src/captchakraken/verdicts.py +16 -0
- package/dist/slide-geometry.d.ts +0 -6
- package/dist/slide-geometry.js +0 -38
- package/python/src/captchakraken/tool_calls/track_piece.py +0 -120
package/dist/humanize.js
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
* motionless tap is a synthetic one.
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.MODES = exports.NullHumanizer = exports.MobileHumanizer = exports.TouchscreenTouchBackend = exports.AppiumTouchBackend = exports.CdpTouchBackend = exports.MouseHumanizer = exports.BaseHumanizer = exports.PAUSE_KINDS = void 0;
|
|
15
|
+
exports.MODES = exports.NullHumanizer = exports.MobileHumanizer = exports.TouchscreenTouchBackend = exports.AppiumTouchBackend = exports.CdpTouchBackend = exports.MouseHumanizer = exports.BROWSER_PATH_MS = exports.BaseHumanizer = exports.HOLD_POLL_MS = exports.PAUSE_KINDS = exports.MIN_DEVIATION_PX = exports.MAX_DEVIATION = exports.MAX_EVENT_STEP_PX = exports.MAX_EVENT_GAP_MS = exports.gauss = void 0;
|
|
16
16
|
exports.trajectory = trajectory;
|
|
17
|
+
exports.hold = hold;
|
|
17
18
|
exports.touchBackendFor = touchBackendFor;
|
|
18
19
|
exports.resolveHumanizer = resolveHumanizer;
|
|
19
20
|
const cursory_js_1 = require("cursory-js");
|
|
@@ -29,19 +30,97 @@ const gauss = (sigma) => {
|
|
|
29
30
|
u = Math.random();
|
|
30
31
|
return sigma * Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * Math.random());
|
|
31
32
|
};
|
|
33
|
+
exports.gauss = gauss;
|
|
32
34
|
const closedError = (e) => /Target closed|Session closed/.test(e instanceof Error ? e.message : String(e));
|
|
33
|
-
|
|
35
|
+
// What one mouse event may cover. cursory's samples are up to 250 ms and 560 px apart, which the page sees as a
|
|
36
|
+
// cursor that vanishes and reappears: a teleport. A real mouse reports every 8 ms (125 Hz).
|
|
37
|
+
exports.MAX_EVENT_GAP_MS = 8;
|
|
38
|
+
exports.MAX_EVENT_STEP_PX = 16;
|
|
39
|
+
// How far a reach may swing off the straight line between its endpoints: a fraction of its length, at least a few
|
|
40
|
+
// pixels. cursory morphs a long recorded movement onto any pair of points, so a 5 px move swung 61 px wide and a
|
|
41
|
+
// 140 px one 300 px.
|
|
42
|
+
exports.MAX_DEVIATION = 0.15;
|
|
43
|
+
exports.MIN_DEVIATION_PX = 3;
|
|
44
|
+
function nearestOnSegment(p, a, b) {
|
|
45
|
+
const dx = b[0] - a[0], dy = b[1] - a[1];
|
|
46
|
+
const length2 = dx * dx + dy * dy;
|
|
47
|
+
if (length2 === 0)
|
|
48
|
+
return a;
|
|
49
|
+
const k = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / length2));
|
|
50
|
+
return [a[0] + k * dx, a[1] + k * dy];
|
|
51
|
+
}
|
|
52
|
+
/** Shrink the path's swing off the segment, shape kept, until it stays within MAX_DEVIATION of it. */
|
|
53
|
+
function tame(points, start, end) {
|
|
54
|
+
const limit = Math.max(exports.MIN_DEVIATION_PX, exports.MAX_DEVIATION * Math.hypot(end[0] - start[0], end[1] - start[1]));
|
|
55
|
+
const near = points.map((p) => nearestOnSegment(p, start, end));
|
|
56
|
+
const worst = points.reduce((m, p, i) => Math.max(m, Math.hypot(p[0] - near[i][0], p[1] - near[i][1])), 0);
|
|
57
|
+
if (worst <= limit)
|
|
58
|
+
return points;
|
|
59
|
+
const k = limit / worst;
|
|
60
|
+
return points.map((p, i) => [near[i][0] + (p[0] - near[i][0]) * k, near[i][1] + (p[1] - near[i][1]) * k]);
|
|
61
|
+
}
|
|
62
|
+
/** Fill every gap wider than one mouse report with evenly timed samples along it. */
|
|
63
|
+
function densify(points, timings) {
|
|
64
|
+
const outP = points.slice(0, 1), outT = timings.slice(0, 1);
|
|
65
|
+
for (let j = 1; j < points.length; j++) {
|
|
66
|
+
const a = points[j - 1], b = points[j], ta = timings[j - 1], tb = timings[j];
|
|
67
|
+
const n = Math.max(1, Math.ceil((tb - ta) / exports.MAX_EVENT_GAP_MS), Math.ceil(Math.hypot(b[0] - a[0], b[1] - a[1]) / exports.MAX_EVENT_STEP_PX));
|
|
68
|
+
for (let i = 1; i <= n; i++) {
|
|
69
|
+
const f = i / n;
|
|
70
|
+
outP.push([a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f]);
|
|
71
|
+
outT.push(ta + (tb - ta) * f);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return [outP, outT];
|
|
75
|
+
}
|
|
76
|
+
/** A recorded human movement morphed onto the endpoints, held near the line between them and sampled at least
|
|
77
|
+
* once per mouse report; timings are cumulative ms. */
|
|
34
78
|
function trajectory(start, end, frequency) {
|
|
35
79
|
const { points, timings } = (0, cursory_js_1.generateTrajectory)(start, end, { frequency });
|
|
36
|
-
|
|
80
|
+
const s = [Number(start[0]), Number(start[1])], e = [Number(end[0]), Number(end[1])];
|
|
81
|
+
return densify(tame(points.map((q) => [Number(q[0]), Number(q[1])]), s, e), timings.map(Number));
|
|
37
82
|
}
|
|
38
83
|
/** Every inter-gesture wait the driver takes, named, so each device supplies its own table. */
|
|
39
84
|
exports.PAUSE_KINDS = Object.values(kinds_js_1.PauseKind);
|
|
85
|
+
/** How often a held press looks at the widget, in ms: a frame hash per look, so not every frame. */
|
|
86
|
+
exports.HOLD_POLL_MS = [180, 320];
|
|
87
|
+
/**
|
|
88
|
+
* Press at `to` until `done()` says the widget completed, or `maxMs` passes; true when it completed.
|
|
89
|
+
* Built on move/press/release so any humanizer can hold. The hand drifts a pixel or so meanwhile, because a
|
|
90
|
+
* press that does not move for seconds is a synthetic one, and the button is released whatever `done` throws.
|
|
91
|
+
*/
|
|
92
|
+
async function hold(human, page, to, done, maxMs) {
|
|
93
|
+
await human.move(page, to);
|
|
94
|
+
const anchor = [human.at[0], human.at[1]];
|
|
95
|
+
await human.press(page);
|
|
96
|
+
const t0 = Date.now();
|
|
97
|
+
try {
|
|
98
|
+
for (;;) {
|
|
99
|
+
await delay(uniform(exports.HOLD_POLL_MS[0], exports.HOLD_POLL_MS[1]));
|
|
100
|
+
const to = [anchor[0] + (0, exports.gauss)(0.8), anchor[1] + (0, exports.gauss)(0.8)];
|
|
101
|
+
await (human.drift ? human.drift(page, to) : human.move(page, to)).catch(() => undefined);
|
|
102
|
+
if (await done())
|
|
103
|
+
return true;
|
|
104
|
+
if (Date.now() - t0 >= maxMs)
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
await human.release(page);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
40
112
|
class BaseHumanizer {
|
|
41
113
|
constructor(start = [0, 0]) {
|
|
42
114
|
this.at = [Number(start[0]), Number(start[1])];
|
|
43
115
|
}
|
|
44
116
|
async reset(_page) { }
|
|
117
|
+
/**
|
|
118
|
+
* A held hand's pixel of wander. `move` is a reach, and a reach overshoots: on a 1px move it wandered up to 34px,
|
|
119
|
+
* off a thin bar, which the widget takes for a release.
|
|
120
|
+
*/
|
|
121
|
+
async drift(page, to) {
|
|
122
|
+
await this.move(page, to);
|
|
123
|
+
}
|
|
45
124
|
/** An unknown kind waits nothing, so adding a pause site cannot break a custom humanizer written against an older release. */
|
|
46
125
|
pauseMs(kind) {
|
|
47
126
|
const range = this.pauses[kind];
|
|
@@ -66,6 +145,8 @@ class BaseHumanizer {
|
|
|
66
145
|
}
|
|
67
146
|
}
|
|
68
147
|
exports.BaseHumanizer = BaseHumanizer;
|
|
148
|
+
/** How much longer than the bare round trip a step takes when the browser re-draws it as a path of its own. */
|
|
149
|
+
exports.BROWSER_PATH_MS = 100;
|
|
69
150
|
class MouseHumanizer extends BaseHumanizer {
|
|
70
151
|
constructor(start = [0, 0], frequency = 60) {
|
|
71
152
|
super(start);
|
|
@@ -77,7 +158,10 @@ class MouseHumanizer extends BaseHumanizer {
|
|
|
77
158
|
};
|
|
78
159
|
this.frequency = frequency;
|
|
79
160
|
}
|
|
80
|
-
async reset(_page) {
|
|
161
|
+
async reset(_page) {
|
|
162
|
+
this.viewportCache = undefined;
|
|
163
|
+
this.browserDrawsPath = undefined;
|
|
164
|
+
}
|
|
81
165
|
/** The window to keep the cursor inside; null under camoufox, which reports no viewport and whose window we ask instead. */
|
|
82
166
|
async viewport(page) {
|
|
83
167
|
if (this.viewportCache !== undefined)
|
|
@@ -99,16 +183,60 @@ class MouseHumanizer extends BaseHumanizer {
|
|
|
99
183
|
}
|
|
100
184
|
return this.viewportCache;
|
|
101
185
|
}
|
|
186
|
+
async drift(page, to) {
|
|
187
|
+
try {
|
|
188
|
+
await page.mouse.move(to[0], to[1]);
|
|
189
|
+
this.at = [to[0], to[1]];
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
if (closedError(e))
|
|
193
|
+
log('could not move mouse; page or session closed');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** Clamp only when the viewport is known: a coordinate pinned to the edge of a guessed one deadlocks camoufox's juggler (upstream #225). */
|
|
197
|
+
async clamp(page, [x, y]) {
|
|
198
|
+
const viewport = await this.viewport(page);
|
|
199
|
+
return viewport ? [Math.max(1, Math.min(x, viewport.width - 1)), Math.max(1, Math.min(y, viewport.height - 1))] : [x, y];
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Whether the browser draws its own path for every mouse.move(), as camoufox's humanize does. Ours on top has every
|
|
203
|
+
* sample re-drawn: a 1px step took 190ms there against 1ms, and one GeeTest slider 82s against 13s. One short step
|
|
204
|
+
* towards `to`, timed against a zero-length move (the bare round trip, so a remote browser is not mistaken for
|
|
205
|
+
* one), tells them apart with no movement the page could single out.
|
|
206
|
+
*/
|
|
207
|
+
async measureBrowserPath(page, to) {
|
|
208
|
+
const from = await this.clamp(page, this.at);
|
|
209
|
+
const d = Math.hypot(to[0] - from[0], to[1] - from[1]);
|
|
210
|
+
const step = await this.clamp(page, d <= 3 ? to : [from[0] + 3 * (to[0] - from[0]) / d, from[1] + 3 * (to[1] - from[1]) / d]);
|
|
211
|
+
try {
|
|
212
|
+
await page.mouse.move(from[0], from[1]);
|
|
213
|
+
let t = Date.now();
|
|
214
|
+
await page.mouse.move(from[0], from[1]);
|
|
215
|
+
const roundTrip = Date.now() - t;
|
|
216
|
+
t = Date.now();
|
|
217
|
+
await page.mouse.move(step[0], step[1]);
|
|
218
|
+
this.at = step;
|
|
219
|
+
const drawn = Date.now() - t - roundTrip > exports.BROWSER_PATH_MS;
|
|
220
|
+
if (drawn)
|
|
221
|
+
log('the browser draws its own pointer paths (camoufox humanize); handing it one move per gesture');
|
|
222
|
+
return drawn;
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
if (closedError(e))
|
|
226
|
+
log('could not move mouse; page or session closed');
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
102
230
|
async move(page, to) {
|
|
103
231
|
if (samePoint(this.at, to))
|
|
104
232
|
return;
|
|
233
|
+
this.browserDrawsPath ?? (this.browserDrawsPath = await this.measureBrowserPath(page, to));
|
|
234
|
+
if (this.browserDrawsPath)
|
|
235
|
+
return this.drift(page, await this.clamp(page, to));
|
|
105
236
|
const [points, timings] = trajectory(this.at, to, this.frequency);
|
|
106
|
-
// Clamp only when the viewport is known: a coordinate pinned to the edge of a guessed one deadlocks camoufox's juggler (upstream #225).
|
|
107
|
-
const viewport = await this.viewport(page);
|
|
108
237
|
const startTime = Date.now();
|
|
109
238
|
for (let i = 0; i < points.length; i++) {
|
|
110
|
-
const cx =
|
|
111
|
-
const cy = viewport ? Math.max(1, Math.min(points[i][1], viewport.height - 1)) : points[i][1];
|
|
239
|
+
const [cx, cy] = await this.clamp(page, points[i]);
|
|
112
240
|
try {
|
|
113
241
|
await page.mouse.move(cx, cy);
|
|
114
242
|
}
|
|
@@ -340,6 +468,12 @@ class MobileHumanizer extends BaseHumanizer {
|
|
|
340
468
|
}
|
|
341
469
|
this.at = dest;
|
|
342
470
|
}
|
|
471
|
+
async drift(page, to) {
|
|
472
|
+
const dest = [Number(to[0]), Number(to[1])];
|
|
473
|
+
if (this.down)
|
|
474
|
+
await (await this.touch(page)).move([[dest[0], dest[1], 0]]);
|
|
475
|
+
this.at = dest;
|
|
476
|
+
}
|
|
343
477
|
async press(page) {
|
|
344
478
|
await (await this.touch(page)).down(this.at[0], this.at[1]);
|
|
345
479
|
this.down = true;
|
|
@@ -355,7 +489,7 @@ class MobileHumanizer extends BaseHumanizer {
|
|
|
355
489
|
const held = this.pauseMs(kinds_js_1.PauseKind.TAP);
|
|
356
490
|
await delay(held / 2);
|
|
357
491
|
try {
|
|
358
|
-
await (await this.touch(page)).move([[this.at[0] + gauss(0.9), this.at[1] + gauss(0.9), 0]]);
|
|
492
|
+
await (await this.touch(page)).move([[this.at[0] + (0, exports.gauss)(0.9), this.at[1] + (0, exports.gauss)(0.9), 0]]);
|
|
359
493
|
}
|
|
360
494
|
catch { /* a tap-only backend cannot wobble; the tap still lands */ }
|
|
361
495
|
await delay(held / 2);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
export * from './types';
|
|
2
|
-
export * from './kinds';
|
|
3
1
|
export { CaptchaKrakenSolver } from './solver';
|
|
4
|
-
export
|
|
5
|
-
export {
|
|
6
|
-
export { CaptchaKrakenAPIError } from './errors';
|
|
7
|
-
export type { CaptchaKrakenErrorCode } from './errors';
|
|
2
|
+
export { watchPage } from './watcher';
|
|
3
|
+
export type { CaptchaWatcher, WatchOptions, WatchableSolver } from './watcher';
|
|
8
4
|
export { fromPuppeteer } from './puppeteer-adapter';
|
|
9
5
|
export type { Page, PlaywrightPage, PlaywrightFrame, PlaywrightElementHandle, PlaywrightLocator, PlaywrightScope } from './playwright-types';
|
|
6
|
+
export type { CaptchaKrakenConfig } from './types';
|
|
7
|
+
export { DEFAULT_CONFIG_PATH, SelectorConfigError, loadConfig } from './selectors';
|
|
8
|
+
export type { CustomSelectors, DetectorModel, SelectorConfig, SelectorConfigFile, VendorSelectors } from './selectors';
|
|
9
|
+
export { HumanizationMode, PromptFamily, Vendor } from './kinds';
|
|
10
|
+
export type { SolveResult, SolveStepEvent, Vector } from './types';
|
|
10
11
|
export type { RoundVerdict } from './verdicts';
|
|
11
|
-
export {
|
|
12
|
-
export
|
|
13
|
-
export {
|
|
14
|
-
export
|
|
15
|
-
export {
|
|
16
|
-
export
|
|
12
|
+
export { ErrorCode, FrameRole, Outcome, SolveStage, Verdict } from './kinds';
|
|
13
|
+
export { CaptchaKrakenAPIError } from './errors';
|
|
14
|
+
export type { CaptchaKrakenErrorCode } from './errors';
|
|
15
|
+
export { MobileHumanizer, MouseHumanizer, NullHumanizer } from './humanize';
|
|
16
|
+
export type { Humanizer, Point, TouchBackend, TouchTransform } from './humanize';
|
|
17
|
+
export { PauseKind } from './kinds';
|
package/dist/index.js
CHANGED
|
@@ -1,43 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
2
|
+
// The published surface: what a caller constructs, configures, receives or implements. Everything else is internal
|
|
3
|
+
// and can change in any release; add a name here only when a caller needs to write it.
|
|
16
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
18
|
-
|
|
19
|
-
__exportStar(require("./kinds"), exports);
|
|
5
|
+
exports.PauseKind = exports.NullHumanizer = exports.MouseHumanizer = exports.MobileHumanizer = exports.CaptchaKrakenAPIError = exports.Verdict = exports.SolveStage = exports.Outcome = exports.FrameRole = exports.ErrorCode = exports.Vendor = exports.PromptFamily = exports.HumanizationMode = exports.loadConfig = exports.SelectorConfigError = exports.DEFAULT_CONFIG_PATH = exports.fromPuppeteer = exports.watchPage = exports.CaptchaKrakenSolver = void 0;
|
|
6
|
+
// Solving
|
|
20
7
|
var solver_1 = require("./solver");
|
|
21
8
|
Object.defineProperty(exports, "CaptchaKrakenSolver", { enumerable: true, get: function () { return solver_1.CaptchaKrakenSolver; } });
|
|
22
|
-
var
|
|
23
|
-
Object.defineProperty(exports, "
|
|
24
|
-
var errors_1 = require("./errors");
|
|
25
|
-
Object.defineProperty(exports, "CaptchaKrakenAPIError", { enumerable: true, get: function () { return errors_1.CaptchaKrakenAPIError; } });
|
|
9
|
+
var watcher_1 = require("./watcher");
|
|
10
|
+
Object.defineProperty(exports, "watchPage", { enumerable: true, get: function () { return watcher_1.watchPage; } });
|
|
26
11
|
var puppeteer_adapter_1 = require("./puppeteer-adapter");
|
|
27
12
|
Object.defineProperty(exports, "fromPuppeteer", { enumerable: true, get: function () { return puppeteer_adapter_1.fromPuppeteer; } });
|
|
28
13
|
var selectors_1 = require("./selectors");
|
|
29
|
-
Object.defineProperty(exports, "
|
|
30
|
-
|
|
31
|
-
Object.defineProperty(exports, "
|
|
14
|
+
Object.defineProperty(exports, "DEFAULT_CONFIG_PATH", { enumerable: true, get: function () { return selectors_1.DEFAULT_CONFIG_PATH; } });
|
|
15
|
+
Object.defineProperty(exports, "SelectorConfigError", { enumerable: true, get: function () { return selectors_1.SelectorConfigError; } });
|
|
16
|
+
Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return selectors_1.loadConfig; } });
|
|
17
|
+
var kinds_1 = require("./kinds");
|
|
18
|
+
Object.defineProperty(exports, "HumanizationMode", { enumerable: true, get: function () { return kinds_1.HumanizationMode; } });
|
|
19
|
+
Object.defineProperty(exports, "PromptFamily", { enumerable: true, get: function () { return kinds_1.PromptFamily; } });
|
|
20
|
+
Object.defineProperty(exports, "Vendor", { enumerable: true, get: function () { return kinds_1.Vendor; } });
|
|
21
|
+
var kinds_2 = require("./kinds");
|
|
22
|
+
Object.defineProperty(exports, "ErrorCode", { enumerable: true, get: function () { return kinds_2.ErrorCode; } });
|
|
23
|
+
Object.defineProperty(exports, "FrameRole", { enumerable: true, get: function () { return kinds_2.FrameRole; } });
|
|
24
|
+
Object.defineProperty(exports, "Outcome", { enumerable: true, get: function () { return kinds_2.Outcome; } });
|
|
25
|
+
Object.defineProperty(exports, "SolveStage", { enumerable: true, get: function () { return kinds_2.SolveStage; } });
|
|
26
|
+
Object.defineProperty(exports, "Verdict", { enumerable: true, get: function () { return kinds_2.Verdict; } });
|
|
27
|
+
var errors_1 = require("./errors");
|
|
28
|
+
Object.defineProperty(exports, "CaptchaKrakenAPIError", { enumerable: true, get: function () { return errors_1.CaptchaKrakenAPIError; } });
|
|
29
|
+
// Humanizers: the built-in three, and what one of your own implements
|
|
32
30
|
var humanize_1 = require("./humanize");
|
|
33
|
-
Object.defineProperty(exports, "MouseHumanizer", { enumerable: true, get: function () { return humanize_1.MouseHumanizer; } });
|
|
34
31
|
Object.defineProperty(exports, "MobileHumanizer", { enumerable: true, get: function () { return humanize_1.MobileHumanizer; } });
|
|
32
|
+
Object.defineProperty(exports, "MouseHumanizer", { enumerable: true, get: function () { return humanize_1.MouseHumanizer; } });
|
|
35
33
|
Object.defineProperty(exports, "NullHumanizer", { enumerable: true, get: function () { return humanize_1.NullHumanizer; } });
|
|
36
|
-
|
|
37
|
-
Object.defineProperty(exports, "
|
|
38
|
-
Object.defineProperty(exports, "touchBackendFor", { enumerable: true, get: function () { return humanize_1.touchBackendFor; } });
|
|
39
|
-
Object.defineProperty(exports, "CdpTouchBackend", { enumerable: true, get: function () { return humanize_1.CdpTouchBackend; } });
|
|
40
|
-
Object.defineProperty(exports, "AppiumTouchBackend", { enumerable: true, get: function () { return humanize_1.AppiumTouchBackend; } });
|
|
41
|
-
Object.defineProperty(exports, "TouchscreenTouchBackend", { enumerable: true, get: function () { return humanize_1.TouchscreenTouchBackend; } });
|
|
42
|
-
Object.defineProperty(exports, "PAUSE_KINDS", { enumerable: true, get: function () { return humanize_1.PAUSE_KINDS; } });
|
|
43
|
-
Object.defineProperty(exports, "HUMANIZATION_MODES", { enumerable: true, get: function () { return humanize_1.MODES; } });
|
|
34
|
+
var kinds_3 = require("./kinds");
|
|
35
|
+
Object.defineProperty(exports, "PauseKind", { enumerable: true, get: function () { return kinds_3.PauseKind; } });
|
package/dist/kinds.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Every closed set of names the two ports agree on. Mirrored in python/src/captchakraken/kinds.py; keep both in the same order.
|
|
3
3
|
* `as const` objects rather than `enum`: a literal off the wire (`'click'`) type-checks against them, an `enum` would not.
|
|
4
4
|
*/
|
|
5
|
-
/** `puzzleSource` everywhere. UNKNOWN must stay permissive:
|
|
5
|
+
/** `puzzleSource` everywhere. UNKNOWN must stay permissive: the CLI, scripts and the offline grader report it. */
|
|
6
6
|
export declare const Vendor: {
|
|
7
7
|
readonly HCAPTCHA: "hcaptcha";
|
|
8
8
|
readonly RECAPTCHA: "recaptcha";
|
|
@@ -15,6 +15,12 @@ export declare const Vendor: {
|
|
|
15
15
|
readonly PROSOPO: "prosopo";
|
|
16
16
|
readonly MTCAPTCHA: "mtcaptcha";
|
|
17
17
|
readonly BOTDETECT: "botdetect";
|
|
18
|
+
readonly HUMAN: "human";
|
|
19
|
+
readonly AWSWAF: "awswaf";
|
|
20
|
+
readonly CAPTCHAFOX: "captchafox";
|
|
21
|
+
readonly DINGXIANG: "dingxiang";
|
|
22
|
+
readonly FUNCAPTCHA: "funcaptcha";
|
|
23
|
+
readonly PROTON: "proton";
|
|
18
24
|
readonly UNKNOWN: "unknown";
|
|
19
25
|
};
|
|
20
26
|
export type Vendor = (typeof Vendor)[keyof typeof Vendor];
|
|
@@ -24,6 +30,9 @@ export declare const ActionKind: {
|
|
|
24
30
|
readonly TYPE: "type";
|
|
25
31
|
readonly WAIT: "wait";
|
|
26
32
|
readonly DONE: "done";
|
|
33
|
+
readonly HOLD: "hold";
|
|
34
|
+
readonly REFRESH: "refresh";
|
|
35
|
+
readonly SLIDE: "slide";
|
|
27
36
|
};
|
|
28
37
|
export type ActionKind = (typeof ActionKind)[keyof typeof ActionKind];
|
|
29
38
|
export declare const RetryMode: {
|
|
@@ -104,7 +113,9 @@ export declare const Phase: {
|
|
|
104
113
|
readonly GRID_LOAD: "grid-load";
|
|
105
114
|
readonly FADE_WAIT: "fade-wait";
|
|
106
115
|
readonly BOARD_PAINT: "board-paint";
|
|
107
|
-
readonly
|
|
116
|
+
readonly BOARD_IMAGES: "board-images";
|
|
117
|
+
/** @deprecated Use `BOARD_IMAGES`. */
|
|
118
|
+
readonly HCAPTCHA_IMAGES: "board-images";
|
|
108
119
|
readonly AWAIT_NEXT_ROUND: "await-next-round";
|
|
109
120
|
readonly AWAIT_VERDICT: "await-verdict";
|
|
110
121
|
readonly POST_SUBMIT_DELAY: "post-submit-delay";
|
|
@@ -118,6 +129,8 @@ export declare const SolveStage: {
|
|
|
118
129
|
readonly WAIT: "wait";
|
|
119
130
|
readonly SUBMIT: "submit";
|
|
120
131
|
readonly ROUND: "round";
|
|
132
|
+
readonly HOLD: "hold";
|
|
133
|
+
readonly REFRESH: "refresh";
|
|
121
134
|
};
|
|
122
135
|
export type SolveStage = (typeof SolveStage)[keyof typeof SolveStage];
|
|
123
136
|
export declare const FrameRole: {
|
package/dist/kinds.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.ErrorCode = exports.FrameRole = exports.SolveStage = exports.Phase = exports.Verdict = exports.Outcome = exports.PauseKind = exports.HumanizationMode = exports.PromptFamily = exports.RecaptchaBanner = exports.KeyframeMode = exports.PaintVerdict = exports.SettleVerdict = exports.RetryMode = exports.ActionKind = exports.Vendor = void 0;
|
|
8
8
|
exports.isOneOf = isOneOf;
|
|
9
|
-
/** `puzzleSource` everywhere. UNKNOWN must stay permissive:
|
|
9
|
+
/** `puzzleSource` everywhere. UNKNOWN must stay permissive: the CLI, scripts and the offline grader report it. */
|
|
10
10
|
exports.Vendor = {
|
|
11
11
|
HCAPTCHA: 'hcaptcha',
|
|
12
12
|
RECAPTCHA: 'recaptcha',
|
|
@@ -19,9 +19,15 @@ exports.Vendor = {
|
|
|
19
19
|
PROSOPO: 'prosopo',
|
|
20
20
|
MTCAPTCHA: 'mtcaptcha',
|
|
21
21
|
BOTDETECT: 'botdetect',
|
|
22
|
+
HUMAN: 'human',
|
|
23
|
+
AWSWAF: 'awswaf',
|
|
24
|
+
CAPTCHAFOX: 'captchafox',
|
|
25
|
+
DINGXIANG: 'dingxiang',
|
|
26
|
+
FUNCAPTCHA: 'funcaptcha',
|
|
27
|
+
PROTON: 'proton',
|
|
22
28
|
UNKNOWN: 'unknown',
|
|
23
29
|
};
|
|
24
|
-
exports.ActionKind = { CLICK: 'click', DRAG: 'drag', TYPE: 'type', WAIT: 'wait', DONE: 'done' };
|
|
30
|
+
exports.ActionKind = { CLICK: 'click', DRAG: 'drag', TYPE: 'type', WAIT: 'wait', DONE: 'done', HOLD: 'hold', REFRESH: 'refresh', SLIDE: 'slide' };
|
|
25
31
|
exports.RetryMode = { MISSED_TILES: 'missed-tiles' };
|
|
26
32
|
exports.SettleVerdict = { SETTLED: 'settled', ANIMATED: 'animated', TIMEOUT: 'timeout' };
|
|
27
33
|
exports.PaintVerdict = { PAINTED: 'painted', BLANK: 'blank', UNKNOWN: 'unknown' };
|
|
@@ -47,12 +53,14 @@ exports.Phase = {
|
|
|
47
53
|
GRID_LOAD: 'grid-load',
|
|
48
54
|
FADE_WAIT: 'fade-wait',
|
|
49
55
|
BOARD_PAINT: 'board-paint',
|
|
50
|
-
|
|
56
|
+
BOARD_IMAGES: 'board-images',
|
|
57
|
+
/** @deprecated Use `BOARD_IMAGES`. */
|
|
58
|
+
HCAPTCHA_IMAGES: 'board-images',
|
|
51
59
|
AWAIT_NEXT_ROUND: 'await-next-round',
|
|
52
60
|
AWAIT_VERDICT: 'await-verdict',
|
|
53
61
|
POST_SUBMIT_DELAY: 'post-submit-delay',
|
|
54
62
|
};
|
|
55
|
-
exports.SolveStage = { INITIAL: 'initial', CLICK: 'click', DRAG: 'drag', TYPE: 'type', WAIT: 'wait', SUBMIT: 'submit', ROUND: 'round' };
|
|
63
|
+
exports.SolveStage = { INITIAL: 'initial', CLICK: 'click', DRAG: 'drag', TYPE: 'type', WAIT: 'wait', SUBMIT: 'submit', ROUND: 'round', HOLD: 'hold', REFRESH: 'refresh' };
|
|
56
64
|
exports.FrameRole = { CHECKBOX: 'checkbox', CHALLENGE: 'challenge', UNKNOWN: 'unknown' };
|
|
57
65
|
/** The hosted API's machine-readable codes. Unknown codes still carry the server's message through; this is not a filter. */
|
|
58
66
|
exports.ErrorCode = {
|
|
@@ -58,6 +58,9 @@ export interface PlaywrightResponse {
|
|
|
58
58
|
url(): string;
|
|
59
59
|
status(): number;
|
|
60
60
|
text(): Promise<string>;
|
|
61
|
+
request?(): {
|
|
62
|
+
resourceType?(): string;
|
|
63
|
+
};
|
|
61
64
|
}
|
|
62
65
|
/**
|
|
63
66
|
* Structural on purpose: the package depends on no browser library, and the version skew between
|
package/dist/selectors.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Every selector the driver knows, keyed by vendor. Mirrored in python/src/captchakraken/selectors.py; keep both in the
|
|
3
|
-
* same order, because detection takes the first visible match in table order.
|
|
4
|
-
*/
|
|
5
1
|
import { FrameRole, RecaptchaBanner, Vendor } from './kinds';
|
|
6
2
|
export interface VendorSelectors {
|
|
7
3
|
/** URL substrings of the vendor's code on the wire. A tripwire, not a detector: the host stays on the wire when a vendor renames its markup (see TRIBAL_KNOWLEDGE.md). BotDetect is self-hosted, so it has none. */
|
|
@@ -18,7 +14,10 @@ export interface VendorSelectors {
|
|
|
18
14
|
readonly response?: string;
|
|
19
15
|
/** Inside the checkbox frame: the box shows accepted. Demo pages do not always fill `response`. */
|
|
20
16
|
readonly checked?: string;
|
|
21
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Inside the checkbox frame: the box itself, clicked from the DOM. A vendor without one is found on a photograph.
|
|
19
|
+
* An in-page checkbox that names one is a button with no tick box to find, and is clicked where it is.
|
|
20
|
+
*/
|
|
22
21
|
readonly box?: readonly string[];
|
|
23
22
|
/** On the host page: the vendor painted its success state. Visibility is part of the test (see TRIBAL_KNOWLEDGE.md). */
|
|
24
23
|
readonly accepted?: string;
|
|
@@ -40,24 +39,107 @@ export interface VendorSelectors {
|
|
|
40
39
|
readonly sliderHandle?: readonly string[];
|
|
41
40
|
/** Inside the widget: the piece, measured to steer the slide and dragged directly when there is no track. */
|
|
42
41
|
readonly piece?: readonly string[];
|
|
42
|
+
/** Inside the widget: the control that deals a new puzzle, pressed when the model answers `refresh`. */
|
|
43
|
+
readonly refresh?: readonly string[];
|
|
44
|
+
/**
|
|
45
|
+
* A press-and-hold that completes on its own once full, and resets when let go before it has: the fill reaching the
|
|
46
|
+
* answer's `to` is not a release, and the hold lasts until the widget says it is done.
|
|
47
|
+
*/
|
|
48
|
+
readonly holdCompletes?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** A selector config file's shape: the repository's `captchakraken.config.json`, or any part of it. */
|
|
51
|
+
export interface SelectorConfigFile {
|
|
52
|
+
readonly custom?: Record<string, unknown> | null;
|
|
53
|
+
readonly vendors?: Record<string, Record<string, unknown>>;
|
|
54
|
+
readonly generic?: Record<string, readonly string[]>;
|
|
55
|
+
readonly detector?: DetectorModel;
|
|
56
|
+
readonly [comment: `_${string}`]: unknown;
|
|
57
|
+
}
|
|
58
|
+
/** A selector config the solver cannot use; the message names the file and the key. */
|
|
59
|
+
export declare class SelectorConfigError extends Error {
|
|
60
|
+
constructor(message: string);
|
|
61
|
+
}
|
|
62
|
+
/** A loaded selector config: the caller's own entry, every vendor's in detection order, and the generic fallbacks. */
|
|
63
|
+
export interface SelectorConfig {
|
|
64
|
+
readonly custom: CustomSelectors | null;
|
|
65
|
+
readonly vendors: ReadonlyArray<readonly [Vendor, VendorSelectors]>;
|
|
66
|
+
readonly generic: Readonly<Record<GenericKey, readonly string[]>>;
|
|
67
|
+
/** Which element detector the engine runs: see `DETECTOR_MODELS`. */
|
|
68
|
+
readonly detector: DetectorModel;
|
|
43
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The element detector's weights: `lite` keeps a board near 100 ms on one vCPU, `standard` is the larger network, and
|
|
72
|
+
* `auto` takes `lite` on a machine of two CPUs or fewer, else times one board and takes `standard` when it fits.
|
|
73
|
+
* `CAPTCHA_KRAKEN_DETECTOR_MODEL` in the environment overrides the config.
|
|
74
|
+
*/
|
|
75
|
+
export declare const DETECTOR_MODELS: readonly ["auto", "standard", "lite"];
|
|
76
|
+
export type DetectorModel = (typeof DETECTOR_MODELS)[number];
|
|
77
|
+
declare const GENERIC: readonly ["submit_text", "text_input", "slider_handle", "piece"];
|
|
78
|
+
type GenericKey = (typeof GENERIC)[number];
|
|
79
|
+
/**
|
|
80
|
+
* The default config: the repository's own `captchakraken.config.json` when this package runs from a checkout, so an
|
|
81
|
+
* edit there is what the solver reads, else the copy the bundled Python engine ships (an installed package has no
|
|
82
|
+
* repository root). The Python port resolves the same file.
|
|
83
|
+
*/
|
|
84
|
+
export declare const DEFAULT_CONFIG_PATH: string;
|
|
85
|
+
/**
|
|
86
|
+
* The shipped default, or `source` (a path or a parsed object) laid over it: a key it gives replaces the default's
|
|
87
|
+
* `custom`, one vendor's whole entry, or one generic list. Throws SelectorConfigError naming the bad key.
|
|
88
|
+
*/
|
|
89
|
+
export declare function loadConfig(source?: string | SelectorConfigFile): SelectorConfig;
|
|
90
|
+
export declare const DEFAULT_CONFIG: SelectorConfig;
|
|
91
|
+
/** Every vendor's entry, in detection order, as the default config gives it. */
|
|
44
92
|
export declare const SELECTORS: Readonly<Record<Vendor, VendorSelectors>>;
|
|
45
|
-
export declare const VENDORS: ReadonlyArray<readonly [Vendor, VendorSelectors]>;
|
|
46
93
|
export interface WidgetProbe {
|
|
47
94
|
readonly vendor: Vendor;
|
|
48
95
|
readonly role: FrameRole;
|
|
49
96
|
readonly selector: string;
|
|
50
97
|
}
|
|
51
|
-
/**
|
|
52
|
-
export declare const
|
|
98
|
+
/** `.//` keeps the xpath relative so a host-page widget cannot reach the form's own submit. */
|
|
99
|
+
export declare const submitByText: (text: string) => string;
|
|
100
|
+
/** Your own vendor entry: the built-in schema, with `hosts` optional. */
|
|
101
|
+
export type CustomSelectors = Omit<VendorSelectors, 'hosts'> & {
|
|
102
|
+
readonly hosts?: readonly string[];
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* The table one solver detects and drives with: a selector config's vendors, plus its `custom` entry when it has one.
|
|
106
|
+
* `config` is a path, a parsed object or a loaded SelectorConfig, laid over the shipped default (see `loadConfig`);
|
|
107
|
+
* `custom` (the older `customSelectors`) replaces the config's own. A custom entry is reported as `Vendor.UNKNOWN` and
|
|
108
|
+
* searched before the built-in vendors, because the caller wrote it for the page in front of them.
|
|
109
|
+
*/
|
|
110
|
+
export declare class SelectorTable {
|
|
111
|
+
readonly config: SelectorConfig;
|
|
112
|
+
readonly selectors: Readonly<Record<Vendor, VendorSelectors>>;
|
|
113
|
+
readonly vendors: ReadonlyArray<readonly [Vendor, VendorSelectors]>;
|
|
114
|
+
/** Every host-page selector with what it names, in detection order: open challenges, then checkboxes, then inline widgets. */
|
|
115
|
+
readonly widgetProbes: ReadonlyArray<WidgetProbe>;
|
|
116
|
+
readonly enclave: readonly string[];
|
|
117
|
+
readonly response: readonly string[];
|
|
118
|
+
readonly accepted: readonly string[];
|
|
119
|
+
/** A real Verify/Next/Submit/Skip/Confirm first; the vendor-named controls are fallbacks. */
|
|
120
|
+
readonly submit: readonly string[];
|
|
121
|
+
readonly textInputVendor: readonly string[];
|
|
122
|
+
/**
|
|
123
|
+
* Vendor-named first, generic last: the driver takes the first visible match, and a generic selector reached before
|
|
124
|
+
* the vendor's own is how a captcha's answer ends up in a login form's username box.
|
|
125
|
+
*/
|
|
126
|
+
readonly textInput: readonly string[];
|
|
127
|
+
/** `[draggable=true]` is absent from the generic handles: HTML5 DnD fires dragstart, not pointermove. */
|
|
128
|
+
readonly sliderHandle: readonly string[];
|
|
129
|
+
readonly refresh: readonly string[];
|
|
130
|
+
readonly piece: readonly string[];
|
|
131
|
+
constructor(custom?: CustomSelectors, config?: string | SelectorConfigFile | SelectorConfig);
|
|
132
|
+
}
|
|
133
|
+
export declare const DEFAULT_TABLE: SelectorTable;
|
|
134
|
+
export declare const VENDORS: readonly (readonly [Vendor, VendorSelectors])[];
|
|
135
|
+
export declare const WIDGET_PROBES: readonly WidgetProbe[];
|
|
53
136
|
export declare const ENCLAVE_SELECTORS: readonly string[];
|
|
54
137
|
export declare const RESPONSE_SELECTORS: readonly string[];
|
|
55
138
|
export declare const ACCEPTED_SELECTORS: readonly string[];
|
|
56
|
-
/** `.//` keeps the xpath relative so a host-page widget cannot reach the form's own submit. */
|
|
57
|
-
export declare const submitByText: (text: string) => string;
|
|
58
|
-
/** A real Verify/Next/Submit/Skip first; the vendor-named controls are fallbacks. */
|
|
59
139
|
export declare const SUBMIT_SELECTORS: readonly string[];
|
|
60
140
|
export declare const TEXT_INPUT_VENDOR_SELECTORS: readonly string[];
|
|
61
141
|
export declare const TEXT_INPUT_SELECTORS: readonly string[];
|
|
62
142
|
export declare const SLIDER_HANDLE_SELECTORS: readonly string[];
|
|
143
|
+
export declare const REFRESH_SELECTORS: readonly string[];
|
|
63
144
|
export declare const PIECE_SELECTORS: readonly string[];
|
|
145
|
+
export {};
|