captchakraken 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +43 -0
- package/dist/playwright-types.d.ts +119 -0
- package/dist/playwright-types.js +25 -0
- package/dist/puppeteer-adapter.d.ts +70 -0
- package/dist/puppeteer-adapter.js +96 -0
- package/dist/solver.d.ts +264 -0
- package/dist/solver.js +1922 -0
- package/dist/token-usage.d.ts +15 -0
- package/dist/token-usage.js +102 -0
- package/dist/types.d.ts +248 -0
- package/dist/types.js +2 -0
- package/package.json +49 -0
- package/python/Dockerfile +41 -0
- package/python/README.md +71 -0
- package/python/examples/README.md +68 -0
- package/python/examples/_harness.py +158 -0
- package/python/examples/demoHcaptcha.py +20 -0
- package/python/examples/demoRecaptcha.py +17 -0
- package/python/pyproject.toml +79 -0
- package/python/src/captchakraken/__init__.py +61 -0
- package/python/src/captchakraken/action_types.py +56 -0
- package/python/src/captchakraken/cli.py +656 -0
- package/python/src/captchakraken/config.py +78 -0
- package/python/src/captchakraken/image_processor.py +244 -0
- package/python/src/captchakraken/overlay.py +520 -0
- package/python/src/captchakraken/planner.py +408 -0
- package/python/src/captchakraken/planner_types.py +74 -0
- package/python/src/captchakraken/server_manager.py +290 -0
- package/python/src/captchakraken/solver.py +434 -0
- package/python/src/captchakraken/timing.py +42 -0
- package/python/src/captchakraken/tool_calls/find_checkbox.py +72 -0
- package/python/src/captchakraken/tool_calls/find_grid.py +1762 -0
- package/python/src/captchakraken/tool_calls/move_indicator.py +431 -0
- package/scripts/copy-python.mjs +29 -0
- package/scripts/setup-python.js +104 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# captchakraken
|
|
2
|
+
|
|
3
|
+
The TypeScript browser driver for [CaptchaKraken](https://github.com/JWriter20/CaptchaKraken).
|
|
4
|
+
Hand it a Playwright/Puppeteer `Page`; it finds the captcha, reads the grid with
|
|
5
|
+
a fine-tuned **Qwen3.5-9B** vision model on **vLLM**, clicks human-like, and
|
|
6
|
+
verifies through to a token.
|
|
7
|
+
|
|
8
|
+
> Full docs — demo videos, accuracy, self-hosting — live in the main repo
|
|
9
|
+
> **[CaptchaKraken](https://github.com/JWriter20/CaptchaKraken)**.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install captchakraken
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The package bundles the Python engine (`captchakraken`) and, on `postinstall`,
|
|
18
|
+
creates a local venv with its lightweight core deps so grid detection + the vLLM
|
|
19
|
+
planner work out of the box. It ships **no browser** — bring your own
|
|
20
|
+
Playwright-compatible launcher.
|
|
21
|
+
|
|
22
|
+
- Skip the Python bootstrap: `CAPTCHA_KRAKEN_SKIP_PYTHON_SETUP=1`
|
|
23
|
+
- To **self-host** the model, run the repo's `setup.sh` (installs vLLM + weights).
|
|
24
|
+
Otherwise point `VLLM_BASE_URL` at a server you already run.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { chromium } from 'playwright'; // or patchright / camoufox-js
|
|
30
|
+
import { CaptchaKrakenSolver } from 'captchakraken';
|
|
31
|
+
|
|
32
|
+
const browser = await chromium.launch({ headless: false });
|
|
33
|
+
const page = await (await browser.newContext()).newPage();
|
|
34
|
+
await page.goto('https://www.google.com/recaptcha/api2/demo');
|
|
35
|
+
|
|
36
|
+
// Reads VLLM_BASE_URL + CAPTCHA_KRAKEN_API_KEY from the environment.
|
|
37
|
+
const solver = new CaptchaKrakenSolver();
|
|
38
|
+
await solver.solve(page); // detect → solve grid → click → verify
|
|
39
|
+
|
|
40
|
+
await browser.close();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Puppeteer users wrap the page once with `fromPuppeteer(page)`.
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
| Variable | Meaning |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `VLLM_BASE_URL` | Inference endpoint (local vLLM, or a server you run) |
|
|
50
|
+
| `CAPTCHA_KRAKEN_API_KEY` | Bearer token (`VLLM_API_KEY` also accepted) |
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
GPL-3.0-or-later.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export { CaptchaKrakenSolver } from './solver';
|
|
3
|
+
/**
|
|
4
|
+
* Adapter for driving the solver with **Puppeteer** instead of a Playwright
|
|
5
|
+
* launcher. Puppeteer's `Page` is API-compatible with Playwright's except for a
|
|
6
|
+
* few method names/options; `fromPuppeteer(page)` wraps it so `solve()` accepts
|
|
7
|
+
* it. Playwright-family launchers (vanilla `playwright`, `patchright`,
|
|
8
|
+
* `camoufox-js`) need no adapter — pass their `Page` directly.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import puppeteer from 'puppeteer';
|
|
13
|
+
* import { CaptchaKrakenSolver, fromPuppeteer } from 'captchakraken';
|
|
14
|
+
*
|
|
15
|
+
* const browser = await puppeteer.launch({ headless: false });
|
|
16
|
+
* const page = await browser.newPage();
|
|
17
|
+
* await page.goto('https://www.google.com/recaptcha/api2/demo');
|
|
18
|
+
*
|
|
19
|
+
* const solver = new CaptchaKrakenSolver();
|
|
20
|
+
* await solver.solve(fromPuppeteer(page));
|
|
21
|
+
* await browser.close();
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export { fromPuppeteer } from './puppeteer-adapter';
|
|
25
|
+
/**
|
|
26
|
+
* The Playwright `Page` type the solver operates on.
|
|
27
|
+
*
|
|
28
|
+
* This is an implementation-neutral, structurally-typed Playwright `Page` — a
|
|
29
|
+
* duck-typed surface defined by this package, NOT imported from any browser
|
|
30
|
+
* library. The package depends on no concrete browser implementation, so you
|
|
31
|
+
* can drive the solver with ANY Playwright-compatible launcher: vanilla
|
|
32
|
+
* `playwright`, `patchright`, `camoufox-js`, or anything else that hands back a
|
|
33
|
+
* standard Playwright `Page`. They all structurally satisfy this type — pass
|
|
34
|
+
* yours directly, no cast needed.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* // Pick whichever Playwright-compatible launcher you want — install it
|
|
39
|
+
* // yourself; this package does not bundle a browser:
|
|
40
|
+
* import { chromium } from 'playwright'; // vanilla
|
|
41
|
+
* // import { chromium } from 'patchright'; // stealth-patched
|
|
42
|
+
* // import { Camoufox } from 'camoufox-js'; // Firefox stealth
|
|
43
|
+
* import { CaptchaKrakenSolver } from 'captchakraken';
|
|
44
|
+
*
|
|
45
|
+
* const browser = await chromium.launch({ headless: false });
|
|
46
|
+
* const page = await (await browser.newContext()).newPage();
|
|
47
|
+
* await page.goto('https://www.google.com/recaptcha/api2/demo');
|
|
48
|
+
*
|
|
49
|
+
* // Reads VLLM_BASE_URL + CAPTCHA_KRAKEN_API_KEY from the environment.
|
|
50
|
+
* const solver = new CaptchaKrakenSolver();
|
|
51
|
+
* await solver.solve(page); // detect → solve grid → click → verify
|
|
52
|
+
*
|
|
53
|
+
* await browser.close();
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export type { Page, PlaywrightPage, PlaywrightFrame, PlaywrightElementHandle } from './playwright-types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
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
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.fromPuppeteer = exports.CaptchaKrakenSolver = void 0;
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
19
|
+
var solver_1 = require("./solver");
|
|
20
|
+
Object.defineProperty(exports, "CaptchaKrakenSolver", { enumerable: true, get: function () { return solver_1.CaptchaKrakenSolver; } });
|
|
21
|
+
/**
|
|
22
|
+
* Adapter for driving the solver with **Puppeteer** instead of a Playwright
|
|
23
|
+
* launcher. Puppeteer's `Page` is API-compatible with Playwright's except for a
|
|
24
|
+
* few method names/options; `fromPuppeteer(page)` wraps it so `solve()` accepts
|
|
25
|
+
* it. Playwright-family launchers (vanilla `playwright`, `patchright`,
|
|
26
|
+
* `camoufox-js`) need no adapter — pass their `Page` directly.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import puppeteer from 'puppeteer';
|
|
31
|
+
* import { CaptchaKrakenSolver, fromPuppeteer } from 'captchakraken';
|
|
32
|
+
*
|
|
33
|
+
* const browser = await puppeteer.launch({ headless: false });
|
|
34
|
+
* const page = await browser.newPage();
|
|
35
|
+
* await page.goto('https://www.google.com/recaptcha/api2/demo');
|
|
36
|
+
*
|
|
37
|
+
* const solver = new CaptchaKrakenSolver();
|
|
38
|
+
* await solver.solve(fromPuppeteer(page));
|
|
39
|
+
* await browser.close();
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
var puppeteer_adapter_1 = require("./puppeteer-adapter");
|
|
43
|
+
Object.defineProperty(exports, "fromPuppeteer", { enumerable: true, get: function () { return puppeteer_adapter_1.fromPuppeteer; } });
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, implementation-neutral structural types for the slice of the
|
|
3
|
+
* Playwright API the solver actually uses.
|
|
4
|
+
*
|
|
5
|
+
* **Why these exist.** CaptchaKraken never launches a browser — you bring your
|
|
6
|
+
* own Playwright-compatible launcher and hand `solve()` a `Page`. To stay truly
|
|
7
|
+
* browser-agnostic the package depends on NO concrete browser implementation
|
|
8
|
+
* (not `playwright`, not `patchright`, not `camoufox-js`). Typing the public API
|
|
9
|
+
* against any one of those packages' `Page` would (a) force that package into
|
|
10
|
+
* consumers' trees and (b) break across version skew — e.g. `patchright` pins
|
|
11
|
+
* its own forked, older core whose `Page` is missing the newest `playwright-core`
|
|
12
|
+
* methods, so a `patchright` page won't structurally match a `playwright-core`
|
|
13
|
+
* page and vice-versa.
|
|
14
|
+
*
|
|
15
|
+
* Instead we declare a duck-typed surface covering only what the solver calls.
|
|
16
|
+
* Every real Playwright `Page` / `Frame` / `ElementHandle` — from vanilla
|
|
17
|
+
* `playwright`, `patchright`, `camoufox-js`, or anything else — structurally
|
|
18
|
+
* satisfies these, regardless of which Playwright version it was built against.
|
|
19
|
+
*
|
|
20
|
+
* Keep these in sync with `solver.ts`: if the solver starts calling a new
|
|
21
|
+
* Playwright method, add it here. The set is intentionally small and stable —
|
|
22
|
+
* these are long-standing Playwright primitives, not bleeding-edge additions.
|
|
23
|
+
*/
|
|
24
|
+
/** `{ x, y, width, height }` in CSS pixels — the shape `boundingBox()` returns. */
|
|
25
|
+
export interface BoundingBoxRect {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
}
|
|
31
|
+
/** `{ width, height }` — the shape `viewportSize()` returns. */
|
|
32
|
+
export interface ViewportSize {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Structural subset of Playwright's `ElementHandle`. Returned by `Page.$` /
|
|
38
|
+
* `Frame.$` / `Page.waitForSelector` and accepted by the solver as the captcha
|
|
39
|
+
* element / verify button / iframe handle.
|
|
40
|
+
*/
|
|
41
|
+
export interface PlaywrightElementHandle {
|
|
42
|
+
/** Screenshot just this element to a PNG file (the only option the solver passes). */
|
|
43
|
+
screenshot(options?: {
|
|
44
|
+
path?: string;
|
|
45
|
+
}): Promise<Buffer>;
|
|
46
|
+
/** The content document of an `<iframe>` element handle, or null if not a frame. */
|
|
47
|
+
contentFrame(): Promise<PlaywrightFrame | null>;
|
|
48
|
+
/** Element box in page CSS pixels, or null if not rendered. */
|
|
49
|
+
boundingBox(): Promise<BoundingBoxRect | null>;
|
|
50
|
+
/** Scroll the element into view if it isn't already. */
|
|
51
|
+
scrollIntoViewIfNeeded(options?: {
|
|
52
|
+
timeout?: number;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
/** Read an attribute, or null if absent. */
|
|
55
|
+
getAttribute(name: string): Promise<string | null>;
|
|
56
|
+
/** Whether the element is visible. */
|
|
57
|
+
isVisible(): Promise<boolean>;
|
|
58
|
+
/** The element's text content, or null. */
|
|
59
|
+
textContent(): Promise<string | null>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Structural subset of Playwright's `Frame` (an iframe's content document).
|
|
63
|
+
*/
|
|
64
|
+
export interface PlaywrightFrame {
|
|
65
|
+
/** First matching element handle, or null. */
|
|
66
|
+
$(selector: string): Promise<PlaywrightElementHandle | null>;
|
|
67
|
+
/** Wait for a selector to reach the given state; resolves to the handle (or null). */
|
|
68
|
+
waitForSelector(selector: string, options?: {
|
|
69
|
+
state?: 'attached' | 'detached' | 'visible' | 'hidden';
|
|
70
|
+
timeout?: number;
|
|
71
|
+
}): Promise<PlaywrightElementHandle | null>;
|
|
72
|
+
/** Poll a predicate evaluated in the page until it returns truthy. */
|
|
73
|
+
waitForFunction(pageFunction: Function | string, arg?: any, options?: {
|
|
74
|
+
timeout?: number;
|
|
75
|
+
polling?: number | 'raf';
|
|
76
|
+
}): Promise<unknown>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Structural subset of Playwright's `Page` — exactly the members `solve()` and
|
|
80
|
+
* its helpers call. Any real Playwright `Page` satisfies this.
|
|
81
|
+
*/
|
|
82
|
+
export interface PlaywrightPage {
|
|
83
|
+
/** Low-level mouse control used to drive human-like trajectories and clicks. */
|
|
84
|
+
mouse: {
|
|
85
|
+
move(x: number, y: number, options?: {
|
|
86
|
+
steps?: number;
|
|
87
|
+
}): Promise<void>;
|
|
88
|
+
down(options?: {
|
|
89
|
+
button?: 'left' | 'right' | 'middle';
|
|
90
|
+
clickCount?: number;
|
|
91
|
+
}): Promise<void>;
|
|
92
|
+
up(options?: {
|
|
93
|
+
button?: 'left' | 'right' | 'middle';
|
|
94
|
+
clickCount?: number;
|
|
95
|
+
}): Promise<void>;
|
|
96
|
+
};
|
|
97
|
+
/** Sleep `timeout` ms (Playwright's own timer). */
|
|
98
|
+
waitForTimeout(timeout: number): Promise<void>;
|
|
99
|
+
/** Wait for a selector to reach the given state; resolves to the handle (or null). */
|
|
100
|
+
waitForSelector(selector: string, options?: {
|
|
101
|
+
state?: 'attached' | 'detached' | 'visible' | 'hidden';
|
|
102
|
+
timeout?: number;
|
|
103
|
+
}): Promise<PlaywrightElementHandle | null>;
|
|
104
|
+
/** Current viewport size, or null if not set. */
|
|
105
|
+
viewportSize(): ViewportSize | null;
|
|
106
|
+
/** First matching element handle, or null. */
|
|
107
|
+
$(selector: string): Promise<PlaywrightElementHandle | null>;
|
|
108
|
+
/** All matching element handles. */
|
|
109
|
+
$$(selector: string): Promise<PlaywrightElementHandle[]>;
|
|
110
|
+
/** Run `pageFunction` against the first matching element, in the page context. */
|
|
111
|
+
$eval<R>(selector: string, pageFunction: (element: Element) => R, arg?: any): Promise<R>;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Public alias. This is the type the solver's `solve(page)` accepts — kept under
|
|
115
|
+
* a friendly name so consumers can annotate against it. It is the
|
|
116
|
+
* implementation-neutral Playwright `Page`; pass a page from whichever
|
|
117
|
+
* Playwright-compatible launcher you prefer.
|
|
118
|
+
*/
|
|
119
|
+
export type Page = PlaywrightPage;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal, implementation-neutral structural types for the slice of the
|
|
4
|
+
* Playwright API the solver actually uses.
|
|
5
|
+
*
|
|
6
|
+
* **Why these exist.** CaptchaKraken never launches a browser — you bring your
|
|
7
|
+
* own Playwright-compatible launcher and hand `solve()` a `Page`. To stay truly
|
|
8
|
+
* browser-agnostic the package depends on NO concrete browser implementation
|
|
9
|
+
* (not `playwright`, not `patchright`, not `camoufox-js`). Typing the public API
|
|
10
|
+
* against any one of those packages' `Page` would (a) force that package into
|
|
11
|
+
* consumers' trees and (b) break across version skew — e.g. `patchright` pins
|
|
12
|
+
* its own forked, older core whose `Page` is missing the newest `playwright-core`
|
|
13
|
+
* methods, so a `patchright` page won't structurally match a `playwright-core`
|
|
14
|
+
* page and vice-versa.
|
|
15
|
+
*
|
|
16
|
+
* Instead we declare a duck-typed surface covering only what the solver calls.
|
|
17
|
+
* Every real Playwright `Page` / `Frame` / `ElementHandle` — from vanilla
|
|
18
|
+
* `playwright`, `patchright`, `camoufox-js`, or anything else — structurally
|
|
19
|
+
* satisfies these, regardless of which Playwright version it was built against.
|
|
20
|
+
*
|
|
21
|
+
* Keep these in sync with `solver.ts`: if the solver starts calling a new
|
|
22
|
+
* Playwright method, add it here. The set is intentionally small and stable —
|
|
23
|
+
* these are long-standing Playwright primitives, not bleeding-edge additions.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Puppeteer → CaptchaKraken adapter.
|
|
3
|
+
*
|
|
4
|
+
* The solver speaks the Playwright API surface (see playwright-types.ts). Puppeteer
|
|
5
|
+
* is ~95% the same shape but differs in a handful of method names/options. Rather
|
|
6
|
+
* than couple the solver to either library, this thin adapter wraps a Puppeteer
|
|
7
|
+
* `Page` (and, lazily, the `Frame`/`ElementHandle` objects it hands back) so it
|
|
8
|
+
* satisfies the structural `PlaywrightPage` interface the solver consumes.
|
|
9
|
+
*
|
|
10
|
+
* The deltas it bridges (verified against Puppeteer 24.x):
|
|
11
|
+
* - `page.viewportSize()` → `page.viewport()` (same `{width,height}` shape)
|
|
12
|
+
* - `page.waitForTimeout(ms)` → removed in modern Puppeteer; use a timer
|
|
13
|
+
* - `*.waitForSelector(sel, {state}) → Puppeteer uses `{visible}/{hidden}`
|
|
14
|
+
* - `handle.getAttribute(name)` → `handle.evaluate((el,n)=>el.getAttribute(n), name)`
|
|
15
|
+
* - `handle.textContent()` → `handle.evaluate(el=>el.textContent)`
|
|
16
|
+
* - `handle.scrollIntoViewIfNeeded()`→ `handle.scrollIntoView()`
|
|
17
|
+
* Everything else (`$`, `$$`, `$eval`, `waitForFunction`, `mouse.*`,
|
|
18
|
+
* `screenshot({path})`, `boundingBox`, `contentFrame`, `isVisible`) is
|
|
19
|
+
* call-compatible and passed straight through.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import puppeteer from 'puppeteer';
|
|
24
|
+
* import { CaptchaKrakenSolver, fromPuppeteer } from 'captchakraken';
|
|
25
|
+
*
|
|
26
|
+
* const browser = await puppeteer.launch({ headless: false });
|
|
27
|
+
* const page = await browser.newPage();
|
|
28
|
+
* await page.goto('https://www.google.com/recaptcha/api2/demo');
|
|
29
|
+
*
|
|
30
|
+
* const solver = new CaptchaKrakenSolver();
|
|
31
|
+
* await solver.solve(fromPuppeteer(page));
|
|
32
|
+
* await browser.close();
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
import { PlaywrightPage, BoundingBoxRect, ViewportSize } from './playwright-types';
|
|
36
|
+
interface PuppeteerElementHandle {
|
|
37
|
+
screenshot(options?: {
|
|
38
|
+
path?: string;
|
|
39
|
+
}): Promise<unknown>;
|
|
40
|
+
contentFrame(): Promise<PuppeteerFrame | null>;
|
|
41
|
+
boundingBox(): Promise<BoundingBoxRect | null>;
|
|
42
|
+
scrollIntoView(): Promise<void>;
|
|
43
|
+
isVisible(): Promise<boolean>;
|
|
44
|
+
evaluate(pageFunction: (el: any, ...args: any[]) => any, ...args: any[]): Promise<any>;
|
|
45
|
+
}
|
|
46
|
+
interface PuppeteerFrame {
|
|
47
|
+
$(selector: string): Promise<PuppeteerElementHandle | null>;
|
|
48
|
+
waitForSelector(selector: string, options?: any): Promise<PuppeteerElementHandle | null>;
|
|
49
|
+
waitForFunction(pageFunction: Function | string, options?: any, ...args: any[]): Promise<unknown>;
|
|
50
|
+
}
|
|
51
|
+
interface PuppeteerPage {
|
|
52
|
+
mouse: {
|
|
53
|
+
move(x: number, y: number, options?: {
|
|
54
|
+
steps?: number;
|
|
55
|
+
}): Promise<void>;
|
|
56
|
+
down(options?: any): Promise<void>;
|
|
57
|
+
up(options?: any): Promise<void>;
|
|
58
|
+
};
|
|
59
|
+
waitForSelector(selector: string, options?: any): Promise<PuppeteerElementHandle | null>;
|
|
60
|
+
viewport(): ViewportSize | null;
|
|
61
|
+
$(selector: string): Promise<PuppeteerElementHandle | null>;
|
|
62
|
+
$$(selector: string): Promise<PuppeteerElementHandle[]>;
|
|
63
|
+
$eval(selector: string, pageFunction: (element: Element) => any, ...args: any[]): Promise<any>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Wrap a Puppeteer `Page` so it satisfies the solver's structural Playwright
|
|
67
|
+
* `Page`. Pass the result to `solver.solve(...)`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function fromPuppeteer(page: PuppeteerPage): PlaywrightPage;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Puppeteer → CaptchaKraken adapter.
|
|
4
|
+
*
|
|
5
|
+
* The solver speaks the Playwright API surface (see playwright-types.ts). Puppeteer
|
|
6
|
+
* is ~95% the same shape but differs in a handful of method names/options. Rather
|
|
7
|
+
* than couple the solver to either library, this thin adapter wraps a Puppeteer
|
|
8
|
+
* `Page` (and, lazily, the `Frame`/`ElementHandle` objects it hands back) so it
|
|
9
|
+
* satisfies the structural `PlaywrightPage` interface the solver consumes.
|
|
10
|
+
*
|
|
11
|
+
* The deltas it bridges (verified against Puppeteer 24.x):
|
|
12
|
+
* - `page.viewportSize()` → `page.viewport()` (same `{width,height}` shape)
|
|
13
|
+
* - `page.waitForTimeout(ms)` → removed in modern Puppeteer; use a timer
|
|
14
|
+
* - `*.waitForSelector(sel, {state}) → Puppeteer uses `{visible}/{hidden}`
|
|
15
|
+
* - `handle.getAttribute(name)` → `handle.evaluate((el,n)=>el.getAttribute(n), name)`
|
|
16
|
+
* - `handle.textContent()` → `handle.evaluate(el=>el.textContent)`
|
|
17
|
+
* - `handle.scrollIntoViewIfNeeded()`→ `handle.scrollIntoView()`
|
|
18
|
+
* Everything else (`$`, `$$`, `$eval`, `waitForFunction`, `mouse.*`,
|
|
19
|
+
* `screenshot({path})`, `boundingBox`, `contentFrame`, `isVisible`) is
|
|
20
|
+
* call-compatible and passed straight through.
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* ```typescript
|
|
24
|
+
* import puppeteer from 'puppeteer';
|
|
25
|
+
* import { CaptchaKrakenSolver, fromPuppeteer } from 'captchakraken';
|
|
26
|
+
*
|
|
27
|
+
* const browser = await puppeteer.launch({ headless: false });
|
|
28
|
+
* const page = await browser.newPage();
|
|
29
|
+
* await page.goto('https://www.google.com/recaptcha/api2/demo');
|
|
30
|
+
*
|
|
31
|
+
* const solver = new CaptchaKrakenSolver();
|
|
32
|
+
* await solver.solve(fromPuppeteer(page));
|
|
33
|
+
* await browser.close();
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.fromPuppeteer = fromPuppeteer;
|
|
38
|
+
/** Translate Playwright `{state}` selector options to Puppeteer `{visible}/{hidden}`. */
|
|
39
|
+
function toPuppeteerSelectorOptions(options) {
|
|
40
|
+
if (!options)
|
|
41
|
+
return undefined;
|
|
42
|
+
const { state, timeout } = options;
|
|
43
|
+
const out = {};
|
|
44
|
+
if (timeout !== undefined)
|
|
45
|
+
out.timeout = timeout;
|
|
46
|
+
if (state === 'visible')
|
|
47
|
+
out.visible = true;
|
|
48
|
+
else if (state === 'hidden')
|
|
49
|
+
out.hidden = true;
|
|
50
|
+
// 'attached'/'detached' have no direct Puppeteer flag — default wait (attached)
|
|
51
|
+
// is the closest match, so we pass no visibility flag.
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function wrapHandle(h) {
|
|
55
|
+
if (!h)
|
|
56
|
+
return null;
|
|
57
|
+
return {
|
|
58
|
+
screenshot: (options) => h.screenshot(options),
|
|
59
|
+
contentFrame: async () => wrapFrame(await h.contentFrame()),
|
|
60
|
+
boundingBox: () => h.boundingBox(),
|
|
61
|
+
scrollIntoViewIfNeeded: () => h.scrollIntoView(),
|
|
62
|
+
getAttribute: (name) => h.evaluate((el, n) => el.getAttribute(n), name),
|
|
63
|
+
isVisible: () => h.isVisible(),
|
|
64
|
+
textContent: () => h.evaluate((el) => el.textContent),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function wrapFrame(f) {
|
|
68
|
+
if (!f)
|
|
69
|
+
return null;
|
|
70
|
+
return {
|
|
71
|
+
$: async (selector) => wrapHandle(await f.$(selector)),
|
|
72
|
+
waitForSelector: async (selector, options) => wrapHandle(await f.waitForSelector(selector, toPuppeteerSelectorOptions(options))),
|
|
73
|
+
waitForFunction: (pageFunction, arg, options) =>
|
|
74
|
+
// Playwright: (fn, arg, {timeout,polling}); Puppeteer: (fn, {timeout,polling}, ...args).
|
|
75
|
+
f.waitForFunction(pageFunction, options, arg),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Wrap a Puppeteer `Page` so it satisfies the solver's structural Playwright
|
|
80
|
+
* `Page`. Pass the result to `solver.solve(...)`.
|
|
81
|
+
*/
|
|
82
|
+
function fromPuppeteer(page) {
|
|
83
|
+
return {
|
|
84
|
+
mouse: {
|
|
85
|
+
move: (x, y, options) => page.mouse.move(x, y, options),
|
|
86
|
+
down: (options) => page.mouse.down(options),
|
|
87
|
+
up: (options) => page.mouse.up(options),
|
|
88
|
+
},
|
|
89
|
+
waitForTimeout: (timeout) => new Promise((resolve) => setTimeout(resolve, timeout)),
|
|
90
|
+
waitForSelector: async (selector, options) => wrapHandle(await page.waitForSelector(selector, toPuppeteerSelectorOptions(options))),
|
|
91
|
+
viewportSize: () => page.viewport(),
|
|
92
|
+
$: async (selector) => wrapHandle(await page.$(selector)),
|
|
93
|
+
$$: async (selector) => (await page.$$(selector)).map((h) => wrapHandle(h)).filter(Boolean),
|
|
94
|
+
$eval: (selector, pageFunction, arg) => page.$eval(selector, pageFunction, arg),
|
|
95
|
+
};
|
|
96
|
+
}
|