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
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TokenUsage } from './types';
|
|
2
|
+
export interface ModelPricing {
|
|
3
|
+
inputPricePerM: number;
|
|
4
|
+
outputPricePerM: number;
|
|
5
|
+
cachedInputPricePerM: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const PRICING: Record<string, ModelPricing>;
|
|
8
|
+
export declare function estimateCost(usage: TokenUsage): number;
|
|
9
|
+
export declare function aggregateTokenUsage(usages: TokenUsage[]): {
|
|
10
|
+
modelName: string;
|
|
11
|
+
inputTokens: number;
|
|
12
|
+
outputTokens: number;
|
|
13
|
+
cachedInputTokens: number;
|
|
14
|
+
estimatedCost: number;
|
|
15
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PRICING = void 0;
|
|
4
|
+
exports.estimateCost = estimateCost;
|
|
5
|
+
exports.aggregateTokenUsage = aggregateTokenUsage;
|
|
6
|
+
exports.PRICING = {
|
|
7
|
+
'gemini-3-pro-preview': {
|
|
8
|
+
inputPricePerM: 2.00,
|
|
9
|
+
outputPricePerM: 12.00,
|
|
10
|
+
cachedInputPricePerM: 0.20,
|
|
11
|
+
},
|
|
12
|
+
'gemini-3-flash-preview': {
|
|
13
|
+
inputPricePerM: 0.50,
|
|
14
|
+
outputPricePerM: 3.00,
|
|
15
|
+
cachedInputPricePerM: 0.05,
|
|
16
|
+
},
|
|
17
|
+
'gemini-2.5-pro': {
|
|
18
|
+
inputPricePerM: 1.25,
|
|
19
|
+
outputPricePerM: 10.00,
|
|
20
|
+
cachedInputPricePerM: 0.125,
|
|
21
|
+
},
|
|
22
|
+
'gemini-2.5-flash': {
|
|
23
|
+
inputPricePerM: 0.30,
|
|
24
|
+
outputPricePerM: 2.50,
|
|
25
|
+
cachedInputPricePerM: 0.03,
|
|
26
|
+
},
|
|
27
|
+
'gemini-2.5-flash-preview': {
|
|
28
|
+
inputPricePerM: 0.30,
|
|
29
|
+
outputPricePerM: 2.50,
|
|
30
|
+
cachedInputPricePerM: 0.03,
|
|
31
|
+
},
|
|
32
|
+
'gemini-2.5-flash-lite': {
|
|
33
|
+
inputPricePerM: 0.10,
|
|
34
|
+
outputPricePerM: 0.40,
|
|
35
|
+
cachedInputPricePerM: 0.01,
|
|
36
|
+
},
|
|
37
|
+
'gemini-2.5-flash-lite-preview': {
|
|
38
|
+
inputPricePerM: 0.10,
|
|
39
|
+
outputPricePerM: 0.40,
|
|
40
|
+
cachedInputPricePerM: 0.01,
|
|
41
|
+
},
|
|
42
|
+
'gemini-2.0-flash': {
|
|
43
|
+
inputPricePerM: 0.10,
|
|
44
|
+
outputPricePerM: 0.40,
|
|
45
|
+
cachedInputPricePerM: 0.025,
|
|
46
|
+
},
|
|
47
|
+
'gemini-2.5-computer-use-preview-10-2025': {
|
|
48
|
+
inputPricePerM: 1.25,
|
|
49
|
+
outputPricePerM: 10.00,
|
|
50
|
+
cachedInputPricePerM: 0.025,
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
// The CaptchaKraken CLI (vLLM/OpenAI shape) emits `prompt_tokens` /
|
|
54
|
+
// `completion_tokens`, while older Gemini-path usages used `input_tokens` /
|
|
55
|
+
// `output_tokens`. Normalize both so aggregation never produces NaN.
|
|
56
|
+
function inTok(u) {
|
|
57
|
+
return (u.input_tokens ?? u.prompt_tokens ?? 0);
|
|
58
|
+
}
|
|
59
|
+
function outTok(u) {
|
|
60
|
+
return (u.output_tokens ?? u.completion_tokens ?? 0);
|
|
61
|
+
}
|
|
62
|
+
function cachedTok(u) {
|
|
63
|
+
return (u.cached_input_tokens ?? u.prompt_tokens_details?.cached_tokens ?? 0);
|
|
64
|
+
}
|
|
65
|
+
function estimateCost(usage) {
|
|
66
|
+
const model = usage.model;
|
|
67
|
+
// Default to gemini-2.5-flash pricing if unknown
|
|
68
|
+
const pricing = exports.PRICING[model] || exports.PRICING['gemini-2.5-flash'];
|
|
69
|
+
const inputCost = (inTok(usage) / 1000000) * pricing.inputPricePerM;
|
|
70
|
+
const outputCost = (outTok(usage) / 1000000) * pricing.outputPricePerM;
|
|
71
|
+
const cachedCost = (cachedTok(usage) / 1000000) * pricing.cachedInputPricePerM;
|
|
72
|
+
return inputCost + outputCost + cachedCost;
|
|
73
|
+
}
|
|
74
|
+
function aggregateTokenUsage(usages) {
|
|
75
|
+
if (usages.length === 0) {
|
|
76
|
+
return {
|
|
77
|
+
modelName: 'none',
|
|
78
|
+
inputTokens: 0,
|
|
79
|
+
outputTokens: 0,
|
|
80
|
+
cachedInputTokens: 0,
|
|
81
|
+
estimatedCost: 0,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const modelName = usages[0].model;
|
|
85
|
+
let inputTokens = 0;
|
|
86
|
+
let outputTokens = 0;
|
|
87
|
+
let cachedInputTokens = 0;
|
|
88
|
+
let estimatedCost = 0;
|
|
89
|
+
for (const usage of usages) {
|
|
90
|
+
inputTokens += inTok(usage);
|
|
91
|
+
outputTokens += outTok(usage);
|
|
92
|
+
cachedInputTokens += cachedTok(usage);
|
|
93
|
+
estimatedCost += estimateCost(usage);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
modelName,
|
|
97
|
+
inputTokens,
|
|
98
|
+
outputTokens,
|
|
99
|
+
cachedInputTokens,
|
|
100
|
+
estimatedCost,
|
|
101
|
+
};
|
|
102
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle event emitted by {@link CaptchaKrakenConfig.onStep}. One is fired
|
|
3
|
+
* before any interaction (`stage: 'initial'`) and one after every executed
|
|
4
|
+
* action (click batch, drag, wait, submit) plus per dynamic-grid round, so a
|
|
5
|
+
* caller can record the exact sequence of intermediate stages, time them, and
|
|
6
|
+
* count steps without scraping CAPTCHA_DEBUG dumps.
|
|
7
|
+
*/
|
|
8
|
+
export interface SolveStepEvent {
|
|
9
|
+
/** 1-based monotonically increasing step index across the whole solve. */
|
|
10
|
+
index: number;
|
|
11
|
+
/**
|
|
12
|
+
* Coarse stage kind:
|
|
13
|
+
* - `initial` : screenshot taken before any action (baseline)
|
|
14
|
+
* - `click` : after a click (or batch of clicks) was executed
|
|
15
|
+
* - `drag` : after a drag was executed
|
|
16
|
+
* - `wait` : after a CLI-requested wait elapsed
|
|
17
|
+
* - `submit` : after the Verify/Next/Submit button was clicked
|
|
18
|
+
* - `round` : a dynamic reCAPTCHA 3x3 round boundary (pre-solve snapshot)
|
|
19
|
+
*/
|
|
20
|
+
stage: 'initial' | 'click' | 'drag' | 'wait' | 'submit' | 'round';
|
|
21
|
+
/** Short human label, e.g. "round-2:clicked 3 tile(s)". */
|
|
22
|
+
label: string;
|
|
23
|
+
/**
|
|
24
|
+
* Absolute path to a PNG screenshot of the captcha element at this step.
|
|
25
|
+
* The file is owned by the callback once emitted — the solver does NOT
|
|
26
|
+
* delete it (copy/move it where you need it). Null if the screenshot failed.
|
|
27
|
+
*/
|
|
28
|
+
screenshotPath: string | null;
|
|
29
|
+
/** Detected puzzle vendor, if known. */
|
|
30
|
+
puzzleSource?: 'hcaptcha' | 'recaptcha' | 'unknown';
|
|
31
|
+
/**
|
|
32
|
+
* Which captcha frame this screenshot is of:
|
|
33
|
+
* - `checkbox` : the anchor "I'm not a robot" widget (no puzzle yet)
|
|
34
|
+
* - `challenge` : the open image/grid challenge frame (the real puzzle)
|
|
35
|
+
* - `unknown` : could not be determined from the frame src
|
|
36
|
+
* Recorders that only want the actual solve (not the pre-challenge checkbox
|
|
37
|
+
* clicks) filter to `challenge`.
|
|
38
|
+
*/
|
|
39
|
+
frameRole?: 'checkbox' | 'challenge' | 'unknown';
|
|
40
|
+
/** Outer solve-loop attempt this step belongs to. */
|
|
41
|
+
attempt: number;
|
|
42
|
+
/** ms since solve() started. */
|
|
43
|
+
elapsedMs: number;
|
|
44
|
+
/** Free-form per-stage detail (action payload, clicked cells, etc.). */
|
|
45
|
+
meta?: Record<string, any>;
|
|
46
|
+
}
|
|
47
|
+
export interface CaptchaKrakenConfig {
|
|
48
|
+
/**
|
|
49
|
+
* Optional observer fired at each intermediate solve stage. Receives a
|
|
50
|
+
* baseline screenshot before any action and one after every executed action.
|
|
51
|
+
* Use it to capture intermediate-stage screenshots, count steps, and time
|
|
52
|
+
* each phase. The callback may be async; the solver awaits it. Errors thrown
|
|
53
|
+
* by the callback are swallowed (never fail a solve because logging failed).
|
|
54
|
+
*
|
|
55
|
+
* The PNG at `event.screenshotPath` is owned by the callback once emitted —
|
|
56
|
+
* the solver will not delete it.
|
|
57
|
+
*/
|
|
58
|
+
onStep?: (event: SolveStepEvent) => void | Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Path to the bundled CaptchaKraken CLI root.
|
|
61
|
+
*
|
|
62
|
+
* Usually you do NOT need to set this. If omitted, the solver will auto-resolve the
|
|
63
|
+
* `python/` directory (the captchakraken package) shipped inside this npm package.
|
|
64
|
+
*/
|
|
65
|
+
repoPath?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Command to run python (default: 'python' or 'python3').
|
|
68
|
+
*/
|
|
69
|
+
pythonCommand?: string;
|
|
70
|
+
/**
|
|
71
|
+
* vLLM LoRA name to invoke (default: 'captcha' — our bbox-aware LoRA).
|
|
72
|
+
* Override if you've registered a different module with the vLLM server.
|
|
73
|
+
*/
|
|
74
|
+
model?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Bearer token for the vLLM server (also picked up from VLLM_API_KEY env).
|
|
77
|
+
*/
|
|
78
|
+
apiKey?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Starting mouse position (default: { x: 100, y: 100 }).
|
|
81
|
+
* HIGHLY RECOMMENDED to set this, prevents jumping around of the cursor when solving.
|
|
82
|
+
*/
|
|
83
|
+
startingMousePosition?: {
|
|
84
|
+
x: number;
|
|
85
|
+
y: number;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Automatically re-check for newly opened / next-step captchas after each solve
|
|
89
|
+
* attempt (e.g., clicking a checkbox opens an image challenge).
|
|
90
|
+
*
|
|
91
|
+
* Default: 10
|
|
92
|
+
*/
|
|
93
|
+
maxSolveLoops?: number;
|
|
94
|
+
/**
|
|
95
|
+
* Delay (ms) after executing actions before re-detecting captchas.
|
|
96
|
+
* Useful to allow challenge frames / new images to appear.
|
|
97
|
+
*
|
|
98
|
+
* Default: 1200
|
|
99
|
+
*/
|
|
100
|
+
postSolveDelayMs?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Overall time limit (ms) for the entire solve loop.
|
|
103
|
+
*
|
|
104
|
+
* Default: 120000 (2 minutes)
|
|
105
|
+
*/
|
|
106
|
+
overallSolveTimeoutMs?: number;
|
|
107
|
+
/**
|
|
108
|
+
* Poll interval (ms) for the reCAPTCHA grid-cell-load wait — how often the
|
|
109
|
+
* solver re-screenshots the grid while waiting for tiles to stop fading in.
|
|
110
|
+
* Doubles as the inter-frame gap for the settle change-detector, so keep it
|
|
111
|
+
* comfortably above zero.
|
|
112
|
+
*
|
|
113
|
+
* Default: 250
|
|
114
|
+
*/
|
|
115
|
+
gridLoadPollIntervalMs?: number;
|
|
116
|
+
/**
|
|
117
|
+
* Overall timeout (ms) for the reCAPTCHA grid-cell-load wait. On timeout the
|
|
118
|
+
* solver proceeds to screenshot anyway (best-effort).
|
|
119
|
+
*
|
|
120
|
+
* Default: 8000
|
|
121
|
+
*/
|
|
122
|
+
gridLoadTimeoutMs?: number;
|
|
123
|
+
/**
|
|
124
|
+
* reCAPTCHA 3x3 dynamic puzzles only. After clicking a round of tiles, the
|
|
125
|
+
* max time (ms) to wait for at least one clicked blank/fading tile to finish
|
|
126
|
+
* loading before re-screenshotting and re-solving. On timeout the solver
|
|
127
|
+
* proceeds anyway (best-effort, backstopped by overallSolveTimeoutMs).
|
|
128
|
+
*
|
|
129
|
+
* Default: 6000
|
|
130
|
+
*/
|
|
131
|
+
recaptchaDynamicFadeWaitMs?: number;
|
|
132
|
+
/**
|
|
133
|
+
* reCAPTCHA 3x3 dynamic puzzles only. Minimum gap (ms) between the two frames
|
|
134
|
+
* the fade detectors diff, and the poll cadence for waitForAnyClickedTileLoaded
|
|
135
|
+
* / currentLoadingCells. Kept comfortably above zero so two consecutive frames
|
|
136
|
+
* during a slow fade differ enough for the change detector to fire (frames
|
|
137
|
+
* captured back-to-back can look identical mid-fade and read as "loaded").
|
|
138
|
+
*
|
|
139
|
+
* Default: 250
|
|
140
|
+
*/
|
|
141
|
+
recaptchaDynamicFadePollMs?: number;
|
|
142
|
+
/**
|
|
143
|
+
* reCAPTCHA 3x3 dynamic puzzles only. Grace window (ms) after clicking during
|
|
144
|
+
* which the solver watches the clicked tiles for the ONSET of a blank/fade
|
|
145
|
+
* before deciding the puzzle is solved. reCAPTCHA keeps a clicked tile
|
|
146
|
+
* SELECTED (showing its old image + a blue badge) for a couple of seconds and
|
|
147
|
+
* only THEN blanks it to swap in a replacement, so the window must comfortably
|
|
148
|
+
* exceed that delay or we submit while the refresh is still pending. If no
|
|
149
|
+
* clicked tile goes blank/changing within this window, the puzzle is treated
|
|
150
|
+
* as solved.
|
|
151
|
+
*
|
|
152
|
+
* Default: 4000
|
|
153
|
+
*/
|
|
154
|
+
recaptchaFadeOnsetGraceMs?: number;
|
|
155
|
+
/**
|
|
156
|
+
* reCAPTCHA 3x3 dynamic puzzles only. Cap on the number of
|
|
157
|
+
* click → refresh → re-solve rounds within a single puzzle, independent of
|
|
158
|
+
* maxSolveLoops.
|
|
159
|
+
*
|
|
160
|
+
* Default: 8
|
|
161
|
+
*/
|
|
162
|
+
recaptchaMaxDynamicRounds?: number;
|
|
163
|
+
/**
|
|
164
|
+
* reCAPTCHA 3x3 dynamic puzzles only. When true, the solver hovers the mouse
|
|
165
|
+
* over the just-clicked blank/fading tiles (in click order) while waiting for
|
|
166
|
+
* them to reload, mimicking a human. Disable to skip the hover behavior.
|
|
167
|
+
*
|
|
168
|
+
* Default: true
|
|
169
|
+
*/
|
|
170
|
+
recaptchaTileHoverEnabled?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* While the model is generating a solution (the main idle window), drift the
|
|
173
|
+
* cursor over the challenge area with human-like trajectories instead of
|
|
174
|
+
* leaving it frozen. Cancelled the instant the model responds. Set false to
|
|
175
|
+
* keep the cursor still during inference.
|
|
176
|
+
*
|
|
177
|
+
* Default: true
|
|
178
|
+
*/
|
|
179
|
+
idleMouseWander?: boolean;
|
|
180
|
+
/**
|
|
181
|
+
* After an action, the max time (ms) to watch for the solve outcome — the
|
|
182
|
+
* vendor's solved signal (checkbox checked / response token) or a freshly
|
|
183
|
+
* rendered next round — before falling back to a full re-detect. Returning as
|
|
184
|
+
* soon as the solved signal appears avoids re-entering the solve pipeline on a
|
|
185
|
+
* challenge frame that is merely animating closed.
|
|
186
|
+
*
|
|
187
|
+
* Default: 4000
|
|
188
|
+
*/
|
|
189
|
+
postSolveOutcomeTimeoutMs?: number;
|
|
190
|
+
}
|
|
191
|
+
export interface BoundingBox {
|
|
192
|
+
0: number;
|
|
193
|
+
1: number;
|
|
194
|
+
2: number;
|
|
195
|
+
3: number;
|
|
196
|
+
}
|
|
197
|
+
export interface ClickAction {
|
|
198
|
+
action: 'click';
|
|
199
|
+
/**
|
|
200
|
+
* One or more normalized [x1, y1, x2, y2] bboxes (0–1 fractions of the
|
|
201
|
+
* screenshot). Each entry produces one click. Emitted by v2 CLI for both
|
|
202
|
+
* grid selections and click-puzzle points.
|
|
203
|
+
*/
|
|
204
|
+
target_bounding_boxes?: Array<[number, number, number, number]>;
|
|
205
|
+
/** Legacy v1 fields kept for backwards-compat with older CLI builds. */
|
|
206
|
+
target_number?: number | null;
|
|
207
|
+
target_bounding_box?: [number, number, number, number] | null;
|
|
208
|
+
target_coordinates?: [number, number] | null;
|
|
209
|
+
}
|
|
210
|
+
export interface DragAction {
|
|
211
|
+
action: 'drag';
|
|
212
|
+
source_bounding_box: [number, number, number, number];
|
|
213
|
+
target_bounding_box: [number, number, number, number];
|
|
214
|
+
}
|
|
215
|
+
export interface DoneAction {
|
|
216
|
+
action: 'done';
|
|
217
|
+
}
|
|
218
|
+
export interface WaitAction {
|
|
219
|
+
action: 'wait';
|
|
220
|
+
duration_ms: number;
|
|
221
|
+
}
|
|
222
|
+
export type CaptchaAction = ClickAction | WaitAction | DragAction | DoneAction;
|
|
223
|
+
export type SolverResult = CaptchaAction | CaptchaAction[];
|
|
224
|
+
export interface TokenUsage {
|
|
225
|
+
input_tokens: number;
|
|
226
|
+
output_tokens: number;
|
|
227
|
+
cached_input_tokens?: number;
|
|
228
|
+
model: string;
|
|
229
|
+
}
|
|
230
|
+
export interface CliResponse {
|
|
231
|
+
actions: SolverResult;
|
|
232
|
+
token_usage: TokenUsage[];
|
|
233
|
+
}
|
|
234
|
+
export interface Vector {
|
|
235
|
+
x: number;
|
|
236
|
+
y: number;
|
|
237
|
+
}
|
|
238
|
+
export interface SolveResult {
|
|
239
|
+
isSolved: boolean;
|
|
240
|
+
finalMousePosition: Vector;
|
|
241
|
+
tokenUsage: {
|
|
242
|
+
modelName: string;
|
|
243
|
+
inputTokens: number;
|
|
244
|
+
outputTokens: number;
|
|
245
|
+
cachedInputTokens: number;
|
|
246
|
+
estimatedCost: number;
|
|
247
|
+
};
|
|
248
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "captchakraken",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Browser-agnostic Playwright/Puppeteer captcha solver. Detects the captcha, reads the grid with a fine-tuned Qwen3.5-9B vision model on vLLM, and clicks through to a token.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"python",
|
|
10
|
+
"scripts"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"prebuild": "node scripts/copy-python.mjs",
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
16
|
+
"test": "tsc --noEmit -p tsconfig.json",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"postinstall": "node scripts/setup-python.js"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/JWriter20/CaptchaKraken.git",
|
|
23
|
+
"directory": "js"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"captcha",
|
|
27
|
+
"recaptcha",
|
|
28
|
+
"hcaptcha",
|
|
29
|
+
"playwright",
|
|
30
|
+
"puppeteer",
|
|
31
|
+
"automation",
|
|
32
|
+
"vllm",
|
|
33
|
+
"qwen"
|
|
34
|
+
],
|
|
35
|
+
"author": "Jake Writer",
|
|
36
|
+
"license": "GPL-3.0-or-later",
|
|
37
|
+
"type": "commonjs",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/JWriter20/CaptchaKraken/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/JWriter20/CaptchaKraken#readme",
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^24.10.2",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"cursory-ts": "^1.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# CaptchaKraken vLLM server image.
|
|
2
|
+
#
|
|
3
|
+
# Serves the base model + captcha LoRA over an OpenAI-compatible endpoint on
|
|
4
|
+
# :8000. The image bakes in the `captchakraken[serve]` stack and (optionally)
|
|
5
|
+
# the weights, then hands off to `captchakraken server run`, which execs
|
|
6
|
+
# `vllm serve` assembled entirely from the env-overridable config — so you can
|
|
7
|
+
# repoint it at any base model / adapter without editing this file.
|
|
8
|
+
#
|
|
9
|
+
# docker build -t captchakraken-vllm .
|
|
10
|
+
# docker run --gpus all -p 8000:8000 --ipc=host \
|
|
11
|
+
# -e VLLM_API_KEY=your_key captchakraken-vllm
|
|
12
|
+
FROM pytorch/pytorch:2.7.0-cuda12.8-cudnn9-devel
|
|
13
|
+
|
|
14
|
+
WORKDIR /app
|
|
15
|
+
|
|
16
|
+
RUN apt-get update && apt-get install -y \
|
|
17
|
+
git ninja-build libgl1 libglib2.0-0 wget ffmpeg \
|
|
18
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
19
|
+
|
|
20
|
+
# Install the package + serving extra (vllm/torch/transformers/accelerate/hf).
|
|
21
|
+
COPY . /app/captchakraken
|
|
22
|
+
RUN pip install --upgrade pip && pip install "/app/captchakraken[serve]"
|
|
23
|
+
|
|
24
|
+
# Model identity — all overridable at build/run time (model-agnostic image).
|
|
25
|
+
ARG HF_TOKEN
|
|
26
|
+
ENV HF_TOKEN=${HF_TOKEN}
|
|
27
|
+
ENV CAPTCHA_BASE_MODEL="Qwen/Qwen3.5-9B"
|
|
28
|
+
ENV CAPTCHA_LORA_ADAPTER="CaptchaKraken/CaptchaKraken_v1"
|
|
29
|
+
ENV CAPTCHA_LORA_NAME="captcha"
|
|
30
|
+
ENV VLLM_GPU_MEMORY_UTILIZATION="0.80"
|
|
31
|
+
ENV VLLM_PORT=8000
|
|
32
|
+
|
|
33
|
+
# Bake weights into the image (best-effort; falls back to runtime download).
|
|
34
|
+
RUN python3 -c "import os; from huggingface_hub import snapshot_download; \
|
|
35
|
+
token = os.getenv('HF_TOKEN'); \
|
|
36
|
+
snapshot_download(os.getenv('CAPTCHA_BASE_MODEL'), token=token); \
|
|
37
|
+
snapshot_download(os.getenv('CAPTCHA_LORA_ADAPTER'), token=token)" \
|
|
38
|
+
|| echo "Warning: model prefetch failed; weights download at first run."
|
|
39
|
+
|
|
40
|
+
EXPOSE 8000
|
|
41
|
+
CMD ["captchakraken", "server", "run"]
|
package/python/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# captchakraken
|
|
2
|
+
|
|
3
|
+
The Python engine + CLI behind [CaptchaKraken](https://github.com/JWriter20/CaptchaKraken):
|
|
4
|
+
OpenCV grid detection + a fine-tuned **Qwen3.5-9B** vision LoRA served on
|
|
5
|
+
**vLLM**. Given a screenshot of a captcha grid, it locates the tiles and returns
|
|
6
|
+
the click plan. Ships the `captchakraken` command.
|
|
7
|
+
|
|
8
|
+
> For demo videos, accuracy numbers, the browser driver, and the full
|
|
9
|
+
> self-hosting guide, see the main repo
|
|
10
|
+
> **[CaptchaKraken](https://github.com/JWriter20/CaptchaKraken)**.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install captchakraken # client: OpenCV detection + vLLM HTTP planner
|
|
16
|
+
pip install "captchakraken[serve]" # + the serving stack (vLLM/torch) to self-host
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The base install is lightweight — everything you need to solve captchas against
|
|
20
|
+
a vLLM server (local or remote). The `[serve]` extra pulls the heavy stack only
|
|
21
|
+
if you want to run the model yourself. The one-command
|
|
22
|
+
[`setup.sh`](https://github.com/JWriter20/CaptchaKraken) installs `[serve]`,
|
|
23
|
+
downloads the weights, and writes an env file for you.
|
|
24
|
+
|
|
25
|
+
## Hands-off server
|
|
26
|
+
|
|
27
|
+
The vLLM server is managed for you. On your first solve, if the configured
|
|
28
|
+
endpoint is **local** and nothing is listening, a server is started
|
|
29
|
+
automatically and reused. Point `VLLM_BASE_URL` at a server you already run to
|
|
30
|
+
skip local management entirely.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
captchakraken server start | stop | status | run
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Solve an image/video: classify → find_grid → plan. Prints the click actions.
|
|
40
|
+
captchakraken path/to/captcha.png
|
|
41
|
+
captchakraken path/to/captcha.png --puzzle-source hcaptcha
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from captchakraken import CaptchaSolver
|
|
46
|
+
|
|
47
|
+
solver = CaptchaSolver() # connects to / auto-starts a local vLLM
|
|
48
|
+
actions = solver.solve("captcha.png")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Pure-OpenCV tool subcommands (no model): `find-grid`, `find-checkbox`,
|
|
52
|
+
`detect-selected`, `grid-cell-states`, `find-move`, `find-movable`, and a
|
|
53
|
+
persistent `serve` worker the browser driver polls.
|
|
54
|
+
|
|
55
|
+
## Configuration (model-agnostic)
|
|
56
|
+
|
|
57
|
+
Everything model-specific lives in `captchakraken.config` and is env-overridable
|
|
58
|
+
— the solver never hard-codes a model.
|
|
59
|
+
|
|
60
|
+
| Variable | Meaning | Default |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `VLLM_BASE_URL` | Inference endpoint | `http://localhost:8000/v1` |
|
|
63
|
+
| `CAPTCHA_KRAKEN_API_KEY` | Bearer token (`VLLM_API_KEY` also accepted) | `EMPTY` |
|
|
64
|
+
| `CAPTCHA_BASE_MODEL` | Base weights vLLM loads | `Qwen/Qwen3.5-9B` |
|
|
65
|
+
| `CAPTCHA_LORA_ADAPTER` | Captcha adapter (HF id or path) | `CaptchaKraken/CaptchaKraken_v1` |
|
|
66
|
+
| `CAPTCHA_LORA_NAME` | Served adapter name the client requests | `captcha` |
|
|
67
|
+
| `CAPTCHA_KRAKEN_AUTOSTART` | `0` disables local auto-start | `1` |
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
GPL-3.0-or-later.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Examples (Python)
|
|
2
|
+
|
|
3
|
+
Two runnable demos that drive a real stealth browser
|
|
4
|
+
([camoufox](https://github.com/JWriter20/camoufox)) to a live captcha demo site,
|
|
5
|
+
screenshot the challenge, run the **engine** on it, and print token speed /
|
|
6
|
+
total time / outcome:
|
|
7
|
+
|
|
8
|
+
| File | Site |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `demoRecaptcha.py` | Google reCAPTCHA v2 demo |
|
|
11
|
+
| `demoHcaptcha.py` | hCaptcha demo |
|
|
12
|
+
|
|
13
|
+
> The Python port is the engine (detection + planner). These demos validate the
|
|
14
|
+
> engine + model + server on a real challenge frame. Full click-replay and
|
|
15
|
+
> multi-round verification in a live page are what the TypeScript port
|
|
16
|
+
> (`captchakraken`) does end-to-end.
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd python
|
|
22
|
+
pip install -e ".[serve]" # engine + serving stack (use ".[]" for a remote server)
|
|
23
|
+
pip install camoufox # example-only dep
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### The camoufox binary (from your fork)
|
|
27
|
+
|
|
28
|
+
Uses the **camoufox binary from the fork's releases**:
|
|
29
|
+
[JWriter20/camoufox → Releases](https://github.com/JWriter20/camoufox/releases).
|
|
30
|
+
|
|
31
|
+
1. Download the latest release asset for your OS/arch and extract it.
|
|
32
|
+
2. Point the demo at the extracted `camoufox` executable:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
export CAMOUFOX_BINARY=/path/to/camoufox/camoufox # your fork binary
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If `CAMOUFOX_BINARY` is unset, camoufox falls back to its default binary
|
|
39
|
+
(`python -m camoufox fetch`).
|
|
40
|
+
|
|
41
|
+
### Point at a model
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
source ../captchakraken.env # VLLM_BASE_URL + CAPTCHA_KRAKEN_API_KEY
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Run
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python examples/demoRecaptcha.py
|
|
51
|
+
python examples/demoHcaptcha.py
|
|
52
|
+
HEADLESS=0 python examples/demoRecaptcha.py # watch the browser
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Reading the report
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
result : ✓ engine produced a solution
|
|
59
|
+
click plan: 4 tile(s)/target(s)
|
|
60
|
+
total time : 6.1s (solve: 3.4s)
|
|
61
|
+
tokens : 812 in / 34 out
|
|
62
|
+
gen speed : ~10.0 tok/s
|
|
63
|
+
reason : <only on failure — unsupported puzzle, unreachable server, …>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`gen speed` = model output tokens ÷ solve seconds. Failure reasons the harness
|
|
67
|
+
reports: unreachable vLLM server, an unsupported hCaptcha puzzle (drag/video),
|
|
68
|
+
the challenge iframe never appearing, or the model returning no matching tiles.
|