fiptcha 0.1.0 → 0.1.2
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 +197 -151
- package/package.json +1 -1
- package/src/captcha-grid-solver.js +26 -7
- package/src/captcha-local-solver.js +58 -38
package/README.md
CHANGED
|
@@ -1,139 +1,203 @@
|
|
|
1
1
|
# Fiptcha
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/fiptcha)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Local CAPTCHA solving for browser automation.
|
|
6
6
|
|
|
7
|
-
Fiptcha
|
|
7
|
+
Fiptcha is the lightweight CAPTCHA runtime used by Figranium, packaged as a standalone Apache-2.0 Node.js library so it can be embedded in other browser automation projects.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Install
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
```bash
|
|
12
|
+
npm install fiptcha
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
CommonJS:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const { solveLocalCaptcha } = require('fiptcha');
|
|
19
|
+
```
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
Fiptcha does not launch a browser for you. Your application owns the browser lifecycle and passes an active Playwright-compatible `page` into the solver.
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- support optional companion runtimes without making them mandatory for every installation.
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
const { chromium } = require('playwright');
|
|
27
|
+
const { solveLocalCaptcha } = require('fiptcha');
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
const browser = await chromium.launch({ headless: false });
|
|
30
|
+
const page = await browser.newPage();
|
|
23
31
|
|
|
24
|
-
|
|
32
|
+
await page.goto('https://example.com');
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
const logs = [];
|
|
35
|
+
const result = await solveLocalCaptcha(page, {
|
|
36
|
+
captchaType: 'recaptcha_v2',
|
|
37
|
+
timeout: 60_000,
|
|
38
|
+
logs
|
|
39
|
+
});
|
|
29
40
|
|
|
30
|
-
|
|
41
|
+
console.log(result.token);
|
|
42
|
+
console.log(result.provider); // "local"
|
|
43
|
+
console.log(result.model); // present when a vision model was used
|
|
44
|
+
console.log(result.device); // "browser", "cpu", or the active inference device
|
|
31
45
|
|
|
32
|
-
|
|
46
|
+
await browser.close();
|
|
47
|
+
```
|
|
33
48
|
|
|
34
|
-
|
|
35
|
-
- Resource-aware model selection.
|
|
36
|
-
- Verified, on-demand model downloads and model lifecycle management.
|
|
37
|
-
- Image-grid classification and challenge handling.
|
|
38
|
-
- Browser challenge interception helpers.
|
|
39
|
-
- Companion-process client and runtime support.
|
|
40
|
-
- Optional MLX acceleration for compatible Apple Silicon environments.
|
|
41
|
-
- Container/resource probing utilities.
|
|
42
|
-
- CAPTCHA benchmarking helpers.
|
|
43
|
-
- Environment-variable configuration through the `CAPTCHA_*` settings used by the runtime.
|
|
44
|
-
- Apache-2.0 licensing for reuse in other applications.
|
|
49
|
+
## Supported challenge types
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
Pass one of these values as `captchaType`:
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
| Value | Behavior |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `recaptcha_v2` | Interacts with the checkbox and solves supported image-grid challenges locally when required. |
|
|
56
|
+
| `hcaptcha` | Interacts with the widget and solves supported image-grid challenges locally when required. |
|
|
57
|
+
| `turnstile` | Performs active-browser interaction and waits for a token. It does not use the image-grid vision path. |
|
|
49
58
|
|
|
50
|
-
|
|
59
|
+
Fiptcha cannot guarantee that every challenge will be solvable. CAPTCHA providers can change their UI, challenge format, and risk checks independently of the visible widget.
|
|
51
60
|
|
|
52
|
-
|
|
61
|
+
## `solveLocalCaptcha(page, options)`
|
|
53
62
|
|
|
54
|
-
|
|
63
|
+
This is the main integration API.
|
|
55
64
|
|
|
56
|
-
```
|
|
57
|
-
|
|
65
|
+
```js
|
|
66
|
+
const result = await solveLocalCaptcha(page, {
|
|
67
|
+
captchaType: 'hcaptcha',
|
|
68
|
+
timeout: 60_000,
|
|
69
|
+
logs: []
|
|
70
|
+
});
|
|
58
71
|
```
|
|
59
72
|
|
|
60
|
-
|
|
73
|
+
### Arguments
|
|
74
|
+
|
|
75
|
+
#### `page`
|
|
76
|
+
|
|
77
|
+
An active Playwright-compatible page. Fiptcha uses page/frame APIs, locators, mouse interaction, evaluation, and timeouts against this object.
|
|
78
|
+
|
|
79
|
+
#### `options.captchaType`
|
|
80
|
+
|
|
81
|
+
Required. One of:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
recaptcha_v2
|
|
85
|
+
hcaptcha
|
|
86
|
+
turnstile
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### `options.timeout`
|
|
90
|
+
|
|
91
|
+
Optional. Maximum solve time in milliseconds.
|
|
92
|
+
|
|
93
|
+
Default:
|
|
61
94
|
|
|
62
95
|
```js
|
|
63
|
-
|
|
96
|
+
60_000
|
|
64
97
|
```
|
|
65
98
|
|
|
66
|
-
|
|
99
|
+
#### `options.logs`
|
|
67
100
|
|
|
68
|
-
|
|
101
|
+
Optional array. Fiptcha appends diagnostic messages to it while solving.
|
|
69
102
|
|
|
70
103
|
```js
|
|
71
|
-
const
|
|
104
|
+
const logs = [];
|
|
72
105
|
|
|
73
|
-
|
|
106
|
+
await solveLocalCaptcha(page, {
|
|
74
107
|
captchaType: 'recaptcha_v2',
|
|
75
|
-
|
|
108
|
+
logs
|
|
76
109
|
});
|
|
77
110
|
|
|
78
|
-
console.log(
|
|
111
|
+
console.log(logs);
|
|
79
112
|
```
|
|
80
113
|
|
|
81
|
-
|
|
114
|
+
### Return value
|
|
82
115
|
|
|
83
|
-
|
|
116
|
+
A successful solve returns an object similar to:
|
|
84
117
|
|
|
85
|
-
|
|
118
|
+
```js
|
|
119
|
+
{
|
|
120
|
+
token: '...',
|
|
121
|
+
provider: 'local',
|
|
122
|
+
model: 'owlvit', // only when a vision backend was used
|
|
123
|
+
device: 'cpu'
|
|
124
|
+
}
|
|
125
|
+
```
|
|
86
126
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
| +-- optional companion / MLX runtime
|
|
101
|
-
|
|
|
102
|
-
+-- application-specific result handling
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
This separation is deliberate. Fiptcha does not know about Figranium Tasks, blocks, Cabinets, executions, authentication, scheduling, or its UI.
|
|
106
|
-
|
|
107
|
-
## Public API
|
|
108
|
-
|
|
109
|
-
The root package currently re-exports the public functions from the following modules:
|
|
110
|
-
|
|
111
|
-
- `captcha-local-solver` — high-level local solving logic;
|
|
112
|
-
- `captcha-grid-solver` — image-grid solving primitives;
|
|
113
|
-
- `captcha-interceptor` — browser/challenge interception helpers;
|
|
114
|
-
- `captcha-resources` — host/container resource detection;
|
|
115
|
-
- `captcha-model-manager` — model selection and lifecycle management;
|
|
116
|
-
- `captcha-model-downloader` — model acquisition and verification;
|
|
117
|
-
- `captcha-model-manifest` — model metadata;
|
|
118
|
-
- `captcha-companion-client` — communication with the optional companion process;
|
|
119
|
-
- `captcha-mlx-runtime` — MLX runtime integration;
|
|
120
|
-
- `captcha-benchmark` — benchmark helpers.
|
|
121
|
-
|
|
122
|
-
For example:
|
|
127
|
+
`model` is omitted when no vision model was needed.
|
|
128
|
+
|
|
129
|
+
### Errors
|
|
130
|
+
|
|
131
|
+
`solveLocalCaptcha` throws when:
|
|
132
|
+
|
|
133
|
+
- the requested CAPTCHA type is unsupported;
|
|
134
|
+
- a challenge never exposes a usable token or image challenge;
|
|
135
|
+
- a required vision backend cannot be prepared;
|
|
136
|
+
- the challenge ends without producing a token;
|
|
137
|
+
- Turnstile does not issue a token after browser interaction.
|
|
138
|
+
|
|
139
|
+
Wrap solves in your application's normal error handling:
|
|
123
140
|
|
|
124
141
|
```js
|
|
125
|
-
|
|
126
|
-
solveLocalCaptcha,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
142
|
+
try {
|
|
143
|
+
const result = await solveLocalCaptcha(page, {
|
|
144
|
+
captchaType: 'recaptcha_v2',
|
|
145
|
+
timeout: 60_000
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
console.log(result.token);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('CAPTCHA solve failed:', error.message);
|
|
151
|
+
}
|
|
130
152
|
```
|
|
131
153
|
|
|
132
|
-
|
|
154
|
+
## Browser integration pattern
|
|
155
|
+
|
|
156
|
+
A typical integration should keep orchestration outside Fiptcha:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
const { solveLocalCaptcha } = require('fiptcha');
|
|
160
|
+
|
|
161
|
+
async function handleCaptcha(page, captchaType) {
|
|
162
|
+
const logs = [];
|
|
163
|
+
|
|
164
|
+
const result = await solveLocalCaptcha(page, {
|
|
165
|
+
captchaType,
|
|
166
|
+
timeout: 60_000,
|
|
167
|
+
logs
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
token: result.token,
|
|
172
|
+
diagnostics: logs
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Your application should remain responsible for:
|
|
178
|
+
|
|
179
|
+
- launching and closing browsers;
|
|
180
|
+
- choosing browser contexts and sessions;
|
|
181
|
+
- navigation;
|
|
182
|
+
- deciding when CAPTCHA handling should run;
|
|
183
|
+
- retries at the workflow level;
|
|
184
|
+
- proxies and network policy;
|
|
185
|
+
- persistence;
|
|
186
|
+
- translating Fiptcha errors/results into your own execution model.
|
|
187
|
+
|
|
188
|
+
Fiptcha is responsible for the CAPTCHA-specific runtime: token detection, widget interaction, grid solving, model selection, model downloads, resource checks, and optional companion runtimes.
|
|
189
|
+
|
|
190
|
+
## Models and memory requirements
|
|
191
|
+
|
|
192
|
+
Fiptcha does not bundle large model weights into the npm package. Required assets are acquired when needed.
|
|
193
|
+
|
|
194
|
+
The runtime chooses an appropriate backend based on available resources. A provider-advertised **2 GB** machine is interpreted using decimal sizing (2,000,000,000 bytes, about 1907 MiB) rather than requiring 2048 MiB.
|
|
195
|
+
|
|
196
|
+
If your application runs in an ephemeral container, persist the model/cache directory so models do not need to be downloaded again on every container recreation.
|
|
133
197
|
|
|
134
198
|
## Companion runtime
|
|
135
199
|
|
|
136
|
-
Fiptcha includes
|
|
200
|
+
Fiptcha includes an optional companion process for environments that need it.
|
|
137
201
|
|
|
138
202
|
Install companion requirements:
|
|
139
203
|
|
|
@@ -141,103 +205,85 @@ Install companion requirements:
|
|
|
141
205
|
npm run companion:install
|
|
142
206
|
```
|
|
143
207
|
|
|
144
|
-
Start
|
|
208
|
+
Start it:
|
|
145
209
|
|
|
146
210
|
```bash
|
|
147
211
|
npm run companion:start
|
|
148
212
|
```
|
|
149
213
|
|
|
150
|
-
|
|
214
|
+
Start the Docker-oriented path:
|
|
151
215
|
|
|
152
216
|
```bash
|
|
153
217
|
npm run companion:start:docker
|
|
154
218
|
```
|
|
155
219
|
|
|
156
|
-
The companion is optional.
|
|
220
|
+
The companion is optional. Ordinary Node.js integrations do not need it unless they use that runtime path.
|
|
157
221
|
|
|
158
|
-
##
|
|
222
|
+
## Apple Silicon / MLX
|
|
159
223
|
|
|
160
|
-
|
|
224
|
+
Fiptcha supports an optional MLX runtime for compatible Apple Silicon environments. Consumers that do not use MLX can ignore it.
|
|
225
|
+
|
|
226
|
+
## Inspect the runtime environment
|
|
227
|
+
|
|
228
|
+
Run the included resource probe:
|
|
161
229
|
|
|
162
230
|
```bash
|
|
163
231
|
npm run probe
|
|
164
232
|
```
|
|
165
233
|
|
|
166
|
-
This is useful
|
|
167
|
-
|
|
168
|
-
Resource detection is kept inside Fiptcha so applications embedding it do not need to reproduce Figranium-specific hardware checks.
|
|
169
|
-
|
|
170
|
-
## Model management
|
|
171
|
-
|
|
172
|
-
Fiptcha separates model metadata, downloading, resource selection, and inference/runtime behavior. This allows the package to choose a suitable backend without forcing every application to ship every supported model.
|
|
234
|
+
This is useful in containers and VMs where cgroup/container limits may differ from the physical host's reported memory.
|
|
173
235
|
|
|
174
|
-
|
|
236
|
+
## Using Fiptcha from another project
|
|
175
237
|
|
|
176
|
-
|
|
238
|
+
Install it normally:
|
|
177
239
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
240
|
+
```bash
|
|
241
|
+
npm install fiptcha
|
|
242
|
+
```
|
|
181
243
|
|
|
182
|
-
|
|
244
|
+
Then keep the integration at your browser/runtime boundary:
|
|
183
245
|
|
|
184
|
-
|
|
246
|
+
```js
|
|
247
|
+
const { solveLocalCaptcha } = require('fiptcha');
|
|
185
248
|
|
|
186
|
-
|
|
249
|
+
async function solve(page) {
|
|
250
|
+
return solveLocalCaptcha(page, {
|
|
251
|
+
captchaType: 'recaptcha_v2',
|
|
252
|
+
timeout: 45_000
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
```
|
|
187
256
|
|
|
188
|
-
|
|
189
|
-
2. detect or decide when a CAPTCHA needs to be handled;
|
|
190
|
-
3. invoke Fiptcha with the active page/challenge context;
|
|
191
|
-
4. allow Fiptcha to manage its local model/runtime concerns;
|
|
192
|
-
5. translate the returned result into the host application's own success/error model.
|
|
257
|
+
No Figranium task schema, server, UI, database, scheduler, authentication system, or storage layer is required.
|
|
193
258
|
|
|
194
|
-
|
|
259
|
+
## Troubleshooting
|
|
195
260
|
|
|
196
|
-
|
|
261
|
+
### `Local solver does not support ...`
|
|
197
262
|
|
|
198
|
-
|
|
263
|
+
Check that `captchaType` is exactly one of:
|
|
199
264
|
|
|
200
|
-
|
|
265
|
+
```text
|
|
266
|
+
recaptcha_v2
|
|
267
|
+
hcaptcha
|
|
268
|
+
turnstile
|
|
269
|
+
```
|
|
201
270
|
|
|
202
|
-
|
|
271
|
+
### `vision backend unavailable`
|
|
203
272
|
|
|
204
|
-
|
|
273
|
+
The challenge requires the image-solving path, but Fiptcha could not prepare a supported vision backend. Check available memory, model availability/downloads, and the output of:
|
|
205
274
|
|
|
206
275
|
```bash
|
|
207
|
-
npm
|
|
276
|
+
npm run probe
|
|
208
277
|
```
|
|
209
278
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
## Current maturity
|
|
213
|
-
|
|
214
|
-
Fiptcha began as an extraction of a production subsystem rather than a greenfield SDK. The underlying solver has already been exercised through Figranium, but the standalone package API is new.
|
|
215
|
-
|
|
216
|
-
The high-level integration boundary is usable today. The main area that should be considered pre-stable is the exact shape and naming of lower-level exported helpers. Until a stable standalone API is declared, pinning an exact Fiptcha version is recommended for third-party applications.
|
|
217
|
-
|
|
218
|
-
## Security and responsible use
|
|
219
|
-
|
|
220
|
-
CAPTCHA systems are one part of a site's abuse-prevention and access-control strategy. Integrators are responsible for ensuring their automation is authorized and complies with applicable terms, policies, and law.
|
|
221
|
-
|
|
222
|
-
Do not treat successful CAPTCHA handling as permission to access data or perform actions that the application is otherwise not authorized to perform.
|
|
223
|
-
|
|
224
|
-
## Relationship to Figranium
|
|
225
|
-
|
|
226
|
-
Fiptcha is part of the Figranium ecosystem but is independently reusable. Figranium provides the visual browser-automation product and orchestration layer; Fiptcha focuses on the CAPTCHA-solving runtime.
|
|
227
|
-
|
|
228
|
-
## Contributing
|
|
279
|
+
### Challenge appears but no token is returned
|
|
229
280
|
|
|
230
|
-
|
|
281
|
+
Increase the solve timeout and collect the `logs` array. Also verify that the challenge family still matches the provider structure expected by the current Fiptcha version.
|
|
231
282
|
|
|
232
|
-
|
|
233
|
-
- Node.js version;
|
|
234
|
-
- container/runtime details if applicable;
|
|
235
|
-
- available memory;
|
|
236
|
-
- CAPTCHA provider/challenge family;
|
|
237
|
-
- whether the ordinary runtime or companion/MLX path was used.
|
|
283
|
+
### Models download repeatedly in containers
|
|
238
284
|
|
|
239
|
-
|
|
285
|
+
Persist the model/cache storage used by the runtime instead of recreating it with every ephemeral container.
|
|
240
286
|
|
|
241
287
|
## License
|
|
242
288
|
|
|
243
|
-
|
|
289
|
+
Apache-2.0.
|
package/package.json
CHANGED
|
@@ -42,6 +42,17 @@ function normalizePrompt(adapter, text) {
|
|
|
42
42
|
return adapter.cleanPrompt(String(text || '')).replace(/\s+/g, ' ').replace(/[.!]+$/, '').trim();
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function normalizeModelLabel(label) {
|
|
46
|
+
return String(label || '').replace(/\b[a-z]+\b/gi, (word) => {
|
|
47
|
+
const lower = word.toLowerCase();
|
|
48
|
+
if (lower === 'buses') return 'bus';
|
|
49
|
+
if (lower.endsWith('ies')) return `${lower.slice(0, -3)}y`;
|
|
50
|
+
if (/(?:ches|shes|xes|zes)$/.test(lower)) return lower.slice(0, -2);
|
|
51
|
+
if (lower.endsWith('s') && !lower.endsWith('ss') && !lower.endsWith('us')) return lower.slice(0, -1);
|
|
52
|
+
return lower;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
function normalizeBox(detection) {
|
|
46
57
|
const box = detection?.box || detection?.bbox;
|
|
47
58
|
if (Array.isArray(box) && box.length >= 4) {
|
|
@@ -81,16 +92,16 @@ function mapDetectionsToCells(detections, gridBox, cellBoxes, imageSize = null)
|
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
async function classifyGrid(frame, adapter, cells, label, seenTiles) {
|
|
95
|
+
const modelLabel = normalizeModelLabel(label);
|
|
84
96
|
const grid = frame.locator(adapter.grid).first();
|
|
85
97
|
const gridBox = await grid.boundingBox().catch(() => null);
|
|
86
98
|
if (gridBox) {
|
|
87
99
|
const image = await grid.screenshot({ type: 'png' });
|
|
88
|
-
const detections = await captchaModelManager.detect(image,
|
|
89
|
-
if (detections.length === 0) return [];
|
|
100
|
+
const detections = await captchaModelManager.detect(image, modelLabel);
|
|
90
101
|
const cellBoxes = [];
|
|
91
102
|
for (let index = 0; index < await cells.count(); index += 1) cellBoxes.push(await cells.nth(index).boundingBox().catch(() => null));
|
|
92
103
|
const mapped = mapDetectionsToCells(detections, gridBox, cellBoxes, pngDimensions(image));
|
|
93
|
-
if (mapped.length
|
|
104
|
+
if (mapped.length) {
|
|
94
105
|
const changed = [];
|
|
95
106
|
for (const index of mapped) {
|
|
96
107
|
const image = await cells.nth(index).screenshot({ type: 'png' });
|
|
@@ -110,13 +121,19 @@ async function classifyGrid(frame, adapter, cells, label, seenTiles) {
|
|
|
110
121
|
const fingerprint = checksumBuffer(image);
|
|
111
122
|
if (seenTiles.get(index) === fingerprint) continue;
|
|
112
123
|
seenTiles.set(index, fingerprint);
|
|
113
|
-
if ((await captchaModelManager.detect(image,
|
|
124
|
+
if ((await captchaModelManager.detect(image, modelLabel)).length) selected.push(index);
|
|
114
125
|
}
|
|
115
126
|
return selected;
|
|
116
127
|
}
|
|
117
128
|
|
|
118
129
|
async function visibleText(frame, selector) {
|
|
119
|
-
|
|
130
|
+
const matches = frame.locator(selector);
|
|
131
|
+
for (let index = 0; index < await matches.count().catch(() => 0); index += 1) {
|
|
132
|
+
const locator = matches.nth(index);
|
|
133
|
+
if (!await locator.isVisible({ timeout: 250 }).catch(() => false)) continue;
|
|
134
|
+
return locator.innerText({ timeout: 1500 }).catch(() => '');
|
|
135
|
+
}
|
|
136
|
+
return '';
|
|
120
137
|
}
|
|
121
138
|
|
|
122
139
|
async function solveImageGrid(page, { captchaType, deadline, waitForToken, logs = [] }) {
|
|
@@ -128,7 +145,9 @@ async function solveImageGrid(page, { captchaType, deadline, waitForToken, logs
|
|
|
128
145
|
const frame = await findChallengeFrame(page, adapter);
|
|
129
146
|
if (!frame) throw new Error(`${captchaType} image challenge frame disappeared`);
|
|
130
147
|
const rejection = await visibleText(frame, adapter.error);
|
|
131
|
-
if (rejection
|
|
148
|
+
if (rejection && round > 0) {
|
|
149
|
+
throw new Error(`${captchaType} rejected the previous selection: ${rejection.replace(/\s+/g, ' ').trim()}`);
|
|
150
|
+
}
|
|
132
151
|
const prompt = normalizePrompt(adapter, await visibleText(frame, adapter.instruction));
|
|
133
152
|
if (!prompt) throw new Error(`${captchaType} solver could not read the image challenge instruction`);
|
|
134
153
|
if (prompt !== previousPrompt) {
|
|
@@ -155,4 +174,4 @@ async function solveImageGrid(page, { captchaType, deadline, waitForToken, logs
|
|
|
155
174
|
return null;
|
|
156
175
|
}
|
|
157
176
|
|
|
158
|
-
module.exports = { PROVIDERS, normalizePrompt, normalizeBox, pngDimensions, mapDetectionsToCells, solveImageGrid };
|
|
177
|
+
module.exports = { PROVIDERS, normalizePrompt, normalizeModelLabel, normalizeBox, pngDimensions, mapDetectionsToCells, classifyGrid, visibleText, solveImageGrid };
|
|
@@ -7,6 +7,21 @@ const TOKEN_SELECTORS = Object.freeze({
|
|
|
7
7
|
turnstile: ['input[name="cf-turnstile-response"]', 'textarea[name="cf-turnstile-response"]']
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
+
const WIDGET_INTERACTIONS = Object.freeze({
|
|
11
|
+
recaptcha_v2: Object.freeze({
|
|
12
|
+
framePatterns: ['/recaptcha/api2/anchor', '/recaptcha/enterprise/anchor'],
|
|
13
|
+
selectors: ['#recaptcha-anchor', '[role="checkbox"]']
|
|
14
|
+
}),
|
|
15
|
+
hcaptcha: Object.freeze({
|
|
16
|
+
framePatterns: ['hcaptcha.com/captcha', 'newassets.hcaptcha.com/captcha'],
|
|
17
|
+
selectors: ['#checkbox', '[role="checkbox"]', 'input[type="checkbox"]', 'label']
|
|
18
|
+
}),
|
|
19
|
+
turnstile: Object.freeze({
|
|
20
|
+
framePatterns: ['challenges.cloudflare.com'],
|
|
21
|
+
selectors: ['[role="checkbox"]', 'input[type="checkbox"]', 'label', 'button']
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
|
|
10
25
|
async function readToken(page, captchaType) {
|
|
11
26
|
return page.evaluate(({ type, selectors }) => {
|
|
12
27
|
for (const selector of selectors[type] || []) {
|
|
@@ -57,43 +72,40 @@ async function waitForTokenOrChallenge(page, captchaType, timeout) {
|
|
|
57
72
|
return null;
|
|
58
73
|
}
|
|
59
74
|
|
|
60
|
-
async function clickCheckbox(page, captchaType) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
await page.mouse.move(x, y, { steps: 8 });
|
|
89
|
-
await page.mouse.down();
|
|
90
|
-
await page.waitForTimeout(45);
|
|
91
|
-
await page.mouse.up();
|
|
92
|
-
} else {
|
|
93
|
-
await checkbox.click({ timeout: 2000 });
|
|
75
|
+
async function clickCheckbox(page, captchaType, timeout = 10_000) {
|
|
76
|
+
const interaction = WIDGET_INTERACTIONS[captchaType];
|
|
77
|
+
if (!interaction) return false;
|
|
78
|
+
const deadline = Date.now() + Math.max(0, timeout);
|
|
79
|
+
|
|
80
|
+
do {
|
|
81
|
+
for (const frame of page.frames?.() || []) {
|
|
82
|
+
const frameUrl = String(frame.url?.() || '');
|
|
83
|
+
if (!interaction.framePatterns.some((pattern) => frameUrl.includes(pattern))) continue;
|
|
84
|
+
|
|
85
|
+
for (const selector of interaction.selectors) {
|
|
86
|
+
const matches = frame.locator(selector);
|
|
87
|
+
const count = await matches.count().catch(() => 0);
|
|
88
|
+
for (let index = 0; index < count; index += 1) {
|
|
89
|
+
const checkbox = matches.nth(index);
|
|
90
|
+
if (!await checkbox.isVisible({ timeout: 100 }).catch(() => false)) continue;
|
|
91
|
+
if (!await checkbox.isEnabled({ timeout: 100 }).catch(() => true)) continue;
|
|
92
|
+
const pointerReady = await checkbox.evaluate((element) => {
|
|
93
|
+
const style = getComputedStyle(element);
|
|
94
|
+
return style.pointerEvents !== 'none' && element.getAttribute('aria-disabled') !== 'true';
|
|
95
|
+
}).catch(() => true);
|
|
96
|
+
if (!pointerReady) continue;
|
|
97
|
+
|
|
98
|
+
const clickTimeout = Math.max(1, Math.min(2000, deadline - Date.now()));
|
|
99
|
+
const clicked = await checkbox.click({ timeout: clickTimeout }).then(() => true, () => false);
|
|
100
|
+
if (clicked) return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
94
103
|
}
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
|
|
105
|
+
if (Date.now() >= deadline) break;
|
|
106
|
+
await page.waitForTimeout(Math.min(100, Math.max(1, deadline - Date.now())));
|
|
107
|
+
} while (Date.now() <= deadline);
|
|
108
|
+
|
|
97
109
|
return false;
|
|
98
110
|
}
|
|
99
111
|
|
|
@@ -121,7 +133,15 @@ async function solveLocalCaptcha(page, { captchaType, timeout = 60_000, logs = [
|
|
|
121
133
|
: null;
|
|
122
134
|
let token = await readToken(page, captchaType);
|
|
123
135
|
if (!token) {
|
|
124
|
-
|
|
136
|
+
const clickTimeout = Math.min(10_000, Math.max(0, deadline - Date.now()));
|
|
137
|
+
const clicked = await clickCheckbox(page, captchaType, clickTimeout)
|
|
138
|
+
.catch((error) => {
|
|
139
|
+
logs.push(`Local ${captchaType} checkbox click failed: ${error.message}`);
|
|
140
|
+
return false;
|
|
141
|
+
});
|
|
142
|
+
logs.push(clicked
|
|
143
|
+
? `Local ${captchaType} checkbox clicked`
|
|
144
|
+
: `Local ${captchaType} checkbox was not found before the interaction timeout`);
|
|
125
145
|
const initialWait = captchaType === 'hcaptcha' ? 1500 : 7000;
|
|
126
146
|
token = await waitForTokenOrChallenge(page, captchaType, Math.min(initialWait, Math.max(0, deadline - Date.now())));
|
|
127
147
|
if (!token && captchaType === 'hcaptcha' && Date.now() < deadline) {
|
|
@@ -165,4 +185,4 @@ async function solveLocalCaptcha(page, { captchaType, timeout = 60_000, logs = [
|
|
|
165
185
|
};
|
|
166
186
|
}
|
|
167
187
|
|
|
168
|
-
module.exports = { TOKEN_SELECTORS, readToken, waitForToken, waitForTokenOrChallenge, clickCheckbox, executeHcaptchaWidget, solveLocalCaptcha };
|
|
188
|
+
module.exports = { TOKEN_SELECTORS, WIDGET_INTERACTIONS, readToken, waitForToken, waitForTokenOrChallenge, clickCheckbox, executeHcaptchaWidget, solveLocalCaptcha };
|