capskip 1.0.2 → 1.2.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/CHANGELOG.md +45 -0
- package/README.md +180 -22
- package/docs/API_REFERENCE.md +201 -1
- package/docs/GETTING_STARTED.md +1 -0
- package/docs/TROUBLESHOOTING.md +42 -0
- package/docs/TUTORIAL.md +97 -21
- package/examples/altcha.js +103 -0
- package/examples/geetest.js +68 -0
- package/package.json +24 -2
- package/src/apiParams.js +118 -0
- package/src/index.js +1 -1
- package/src/solver.js +201 -1
- package/types/index.d.ts +80 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,51 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.0] - 2026-09-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **ALTCHA support** via `altcha(url, options)`. Pass `challengeUrl` for CapSkip
|
|
13
|
+
to fetch the challenge, or `challengeJson` with the document itself (a JSON
|
|
14
|
+
string, or an object which is serialized for you). Sending both is allowed —
|
|
15
|
+
the inline document wins. The result exposes `token` (the value the site's
|
|
16
|
+
`altcha` form field expects) and `number`, the counter that solved it; `code`
|
|
17
|
+
keeps the same raw string.
|
|
18
|
+
- Parameter aliases `challengeUrl`/`challengeURL` for `challenge_url` and
|
|
19
|
+
`challengeJson`/`challengeJSON` for `challenge_json`.
|
|
20
|
+
- `AltchaOptions` and the `token`/`number` result fields in the TypeScript
|
|
21
|
+
definitions.
|
|
22
|
+
- Both ALTCHA generations are handled: the legacy scheme (SHA-1/256/384/512) and
|
|
23
|
+
proof-of-work v2 (PBKDF2 or SHA). Their tokens are shaped differently — a v2
|
|
24
|
+
payload carries no top-level `number`, its counter sitting at
|
|
25
|
+
`solution.counter` — so the counter is taken from the server's own `solution`
|
|
26
|
+
object, the one field both report the same way, and dug out of the token only
|
|
27
|
+
when a poll did not carry it.
|
|
28
|
+
|
|
29
|
+
### Notes
|
|
30
|
+
|
|
31
|
+
- ALTCHA is CPU proof-of-work rather than a browser solve, so it uses
|
|
32
|
+
`defaultTimeout` instead of the longer `recaptchaTimeout` that reCAPTCHA,
|
|
33
|
+
Turnstile and GeeTest use.
|
|
34
|
+
- A proxy passed to `altcha()` applies only to the `challengeUrl` fetch; a task
|
|
35
|
+
carrying its challenge inline never touches the network.
|
|
36
|
+
|
|
37
|
+
## [1.1.0] - 2026-07-26
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- **GeeTest v3 (slide) support** via `geetest(gt, challenge, url, options)`.
|
|
42
|
+
Accepts the optional `api_server` domain override and the usual `proxy`
|
|
43
|
+
option. The result exposes the answer as the parsed `challenge`, `validate`,
|
|
44
|
+
and `seccode` fields, while `code` keeps the raw JSON string CapSkip returns.
|
|
45
|
+
- Parameter aliases `apiServer` and `api_subdomain` for `api_server`.
|
|
46
|
+
- TypeScript definitions for `geetest()`, `GeetestOptions`, and the new
|
|
47
|
+
`SolveResult` fields.
|
|
48
|
+
- `proxytype` is now validated against the values CapSkip accepts (`HTTP`,
|
|
49
|
+
`HTTPS`, `SOCKS5`, `SOCKS5H`, case-insensitive) for every proxy-capable captcha
|
|
50
|
+
type. `SOCKS4` and other values previously reached the server and came back as
|
|
51
|
+
`ERROR_BAD_PARAMETERS`; they now raise `ValidationException` locally.
|
|
52
|
+
|
|
8
53
|
## [1.0.2] - 2026-07-15
|
|
9
54
|
|
|
10
55
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,20 +1,52 @@
|
|
|
1
|
-
# CapSkip Node.js SDK
|
|
1
|
+
# CapSkip Node.js SDK — Captcha Solver for Node.js
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/capskip)
|
|
3
4
|
[](https://nodejs.org/)
|
|
4
5
|
[](LICENSE)
|
|
5
6
|
[](https://github.com/capskip/capskip-node/actions/workflows/ci.yml)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
**Solve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, GeeTest, ALTCHA and image captchas from Node.js.**
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
Official Node.js client for [CapSkip](https://capskip.com), a **local captcha solver** that runs on your own machine. Licensed once, not billed per solve. TypeScript definitions included.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install capskip
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## How it works
|
|
19
|
+
|
|
20
|
+
CapSkip is a desktop app. It does the solving on your machine and exposes the standard captcha-solver HTTP API — the same `in.php` / `res.php` endpoints every 2captcha-compatible client already speaks — on `127.0.0.1:8080`.
|
|
21
|
+
|
|
22
|
+
This SDK is a thin wrapper over that API, with the method names you would expect: `normal()`, `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`. Nothing leaves your network, and there is no credit balance to keep an eye on.
|
|
23
|
+
|
|
24
|
+
## Supported captcha types
|
|
25
|
+
|
|
26
|
+
| Captcha | Method |
|
|
27
|
+
|---|---|
|
|
28
|
+
| **Image captcha solver** (distorted text / OCR) | `solver.normal(file)` |
|
|
29
|
+
| **reCAPTCHA v2 solver** (checkbox) | `solver.recaptcha(sitekey, url)` |
|
|
30
|
+
| **reCAPTCHA v2 invisible solver** | `solver.recaptcha(sitekey, url, { invisible: 1 })` |
|
|
31
|
+
| **reCAPTCHA Enterprise solver** | `solver.recaptcha(sitekey, url, { enterprise: 1 })` |
|
|
32
|
+
| **reCAPTCHA v3 solver** | `solver.recaptcha(sitekey, url, { version: 'v3', action: 'submit' })` |
|
|
33
|
+
| reCAPTCHA v3 Enterprise | `solver.recaptcha(sitekey, url, { version: 'v3', enterprise: 1 })` |
|
|
34
|
+
| **Cloudflare Turnstile solver** (widget) | `solver.turnstile(sitekey, url)` |
|
|
35
|
+
| Cloudflare Turnstile (challenge page) | `solver.turnstile(sitekey, url, { data, pagedata })` |
|
|
36
|
+
| **GeeTest v3 solver** (slide puzzle) | `solver.geetest(gt, challenge, url)` |
|
|
37
|
+
| **ALTCHA solver** (proof-of-work) | `solver.altcha(url, { challengeUrl })` |
|
|
38
|
+
|
|
39
|
+
**hCaptcha and FunCaptcha/Arkose are not supported.** hCaptcha is the one people misidentify most often, since it also puts a `data-sitekey` on the widget — check for `class="h-captcha"` or a `js.hcaptcha.com` script before reaching for `recaptcha()`.
|
|
40
|
+
|
|
41
|
+
Point the SDK at the [live captcha demo pages](https://capskip.com/captcha-demo/) to sanity-check your setup against real widgets.
|
|
10
42
|
|
|
11
43
|
---
|
|
12
44
|
|
|
13
45
|
## Quick start (5 minutes)
|
|
14
46
|
|
|
15
|
-
### 1. Install CapSkip
|
|
47
|
+
### 1. Install the CapSkip captcha solver
|
|
16
48
|
|
|
17
|
-
Download and run the CapSkip desktop app
|
|
49
|
+
Download and run the CapSkip desktop app — [CapSkipInstaller.msi](https://capskip.com/download/CapSkipInstaller.msi). Leave it running in the background.
|
|
18
50
|
|
|
19
51
|
In CapSkip settings, note:
|
|
20
52
|
|
|
@@ -58,18 +90,20 @@ Every solve method returns a Promise — use `await` or `.then()`.
|
|
|
58
90
|
|
|
59
91
|
---
|
|
60
92
|
|
|
61
|
-
##
|
|
93
|
+
## Why solve captchas locally
|
|
62
94
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
95
|
+
Cloud captcha APIs charge per solve, which turns a retry loop into an expense and routes every page URL and sitekey you touch through someone else's queue.
|
|
96
|
+
|
|
97
|
+
CapSkip flips that around:
|
|
98
|
+
|
|
99
|
+
- **Unlimited solving** — one license, no per-captcha charge, no balance to top up
|
|
100
|
+
- **Runs on `127.0.0.1`** — the SDK never talks to a third-party server
|
|
101
|
+
- **No per-key rate limit** — throughput is whatever your machine can manage
|
|
102
|
+
- **Fast** — image captchas come back in well under a second; a typical reCAPTCHA v2 lands in 30–45 seconds
|
|
103
|
+
|
|
104
|
+
### Coming from 2captcha or Anti-Captcha
|
|
105
|
+
|
|
106
|
+
CapSkip answers on the same `in.php` / `res.php` endpoints, so it works as a **2captcha API alternative**: an existing integration usually needs nothing more than its host pointed at `127.0.0.1:8080`. The [migration notes](https://capskip.com/2captcha-api-alternative/) cover the details, if you would rather keep your current client library than switch to this one.
|
|
73
107
|
|
|
74
108
|
---
|
|
75
109
|
|
|
@@ -97,7 +131,8 @@ const solver = new CapSkip({
|
|
|
97
131
|
host: '127.0.0.1', // CapSkip host
|
|
98
132
|
port: 8080, // CapSkip port from app settings
|
|
99
133
|
defaultTimeout: 120, // seconds — image captcha polling timeout
|
|
100
|
-
recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile polling timeout
|
|
134
|
+
recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest polling timeout
|
|
135
|
+
// (ALTCHA uses defaultTimeout — CPU work, not a browser solve)
|
|
101
136
|
pollingInterval: 5, // max seconds between res.php polls (starts at 0.25s, backs off to this)
|
|
102
137
|
});
|
|
103
138
|
```
|
|
@@ -161,7 +196,41 @@ const v3 = await solver.recaptcha('...', 'https://example.com', {
|
|
|
161
196
|
const result = await solver.turnstile('0x4AAAAAAA...', 'https://example.com');
|
|
162
197
|
```
|
|
163
198
|
|
|
164
|
-
###
|
|
199
|
+
### GeeTest v3
|
|
200
|
+
|
|
201
|
+
`gt` is static per site, but `challenge` is single-use and expires in about a
|
|
202
|
+
minute — fetch a fresh pair right before solving.
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
const result = await solver.geetest(
|
|
206
|
+
'81388ea1fc187e0c335c0a8907ff2625',
|
|
207
|
+
'7cf6a8b1a2c34d5e6f7089abcdef0123',
|
|
208
|
+
'https://example.com/login',
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Post these back exactly as the site's own front-end would
|
|
212
|
+
result.challenge, result.validate, result.seccode;
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### ALTCHA
|
|
216
|
+
|
|
217
|
+
ALTCHA is proof-of-work, not recognition — there is nothing to read, so a solve
|
|
218
|
+
is deterministic and takes milliseconds. Give CapSkip the endpoint that serves
|
|
219
|
+
the challenge, or the challenge document itself.
|
|
220
|
+
|
|
221
|
+
```js
|
|
222
|
+
const result = await solver.altcha('https://example.com/signup', {
|
|
223
|
+
challengeUrl: 'https://example.com/captcha/api/altcha/challenge',
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Post this back in the form field the widget uses, named `altcha`
|
|
227
|
+
result.token;
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Challenges expire fast — some sites inside two minutes — so fetch one
|
|
231
|
+
immediately before solving and submit the token promptly.
|
|
232
|
+
|
|
233
|
+
### With a proxy (reCAPTCHA, Turnstile, GeeTest & ALTCHA only)
|
|
165
234
|
|
|
166
235
|
```js
|
|
167
236
|
// Proxy is not supported for image captcha
|
|
@@ -196,6 +265,53 @@ More examples: [`examples/`](examples/)
|
|
|
196
265
|
|
|
197
266
|
---
|
|
198
267
|
|
|
268
|
+
## Puppeteer, Playwright and Selenium
|
|
269
|
+
|
|
270
|
+
The SDK hands back a token; your existing browser tooling does the driving. The shape is the same whichever you use:
|
|
271
|
+
|
|
272
|
+
1. Read the sitekey off the page (`data-sitekey`, or the widget's config object).
|
|
273
|
+
2. Call the matching solve method with that sitekey and the page URL.
|
|
274
|
+
3. Write the token into the response field and submit.
|
|
275
|
+
|
|
276
|
+
### Puppeteer
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
const sitekey = await page.$eval('[data-sitekey]', el => el.dataset.sitekey);
|
|
280
|
+
const { code } = await solver.recaptcha(sitekey, page.url());
|
|
281
|
+
|
|
282
|
+
await page.evaluate(t => {
|
|
283
|
+
document.getElementById('g-recaptcha-response').value = t;
|
|
284
|
+
}, code);
|
|
285
|
+
await page.click('button[type=submit]');
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Playwright
|
|
289
|
+
|
|
290
|
+
```js
|
|
291
|
+
const sitekey = await page.getAttribute('[data-sitekey]', 'data-sitekey');
|
|
292
|
+
const { code } = await solver.recaptcha(sitekey, page.url());
|
|
293
|
+
|
|
294
|
+
await page.evaluate(t => {
|
|
295
|
+
document.getElementById('g-recaptcha-response').value = t;
|
|
296
|
+
}, code);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Selenium WebDriver
|
|
300
|
+
|
|
301
|
+
```js
|
|
302
|
+
const el = await driver.findElement(By.css('[data-sitekey]'));
|
|
303
|
+
const sitekey = await el.getAttribute('data-sitekey');
|
|
304
|
+
const { code } = await solver.recaptcha(sitekey, await driver.getCurrentUrl());
|
|
305
|
+
|
|
306
|
+
await driver.executeScript(
|
|
307
|
+
'document.getElementById("g-recaptcha-response").value = arguments[0];', code,
|
|
308
|
+
);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Longer walkthroughs: [Puppeteer](https://capskip.com/puppeteer-captcha-solver/), [Playwright](https://capskip.com/playwright-captcha-solver/) and [Selenium](https://capskip.com/selenium-captcha-solver/).
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
199
315
|
## Return value
|
|
200
316
|
|
|
201
317
|
Every solve method resolves to:
|
|
@@ -208,6 +324,10 @@ Every solve method resolves to:
|
|
|
208
324
|
}
|
|
209
325
|
```
|
|
210
326
|
|
|
327
|
+
GeeTest additionally expands its answer into `challenge`, `validate`, and
|
|
328
|
+
`seccode`, while `code` keeps the raw JSON string. ALTCHA adds `token` (the same
|
|
329
|
+
string as `code`) and `number`, the counter that solved it.
|
|
330
|
+
|
|
211
331
|
---
|
|
212
332
|
|
|
213
333
|
## Error handling
|
|
@@ -247,6 +367,42 @@ const result: SolveResult = await solver.recaptcha('...', 'https://example.com')
|
|
|
247
367
|
|
|
248
368
|
---
|
|
249
369
|
|
|
370
|
+
## FAQ
|
|
371
|
+
|
|
372
|
+
### How do I solve a captcha in Node.js?
|
|
373
|
+
|
|
374
|
+
Install the CapSkip desktop app, `npm install capskip`, then call the method that matches the widget — `recaptcha()`, `turnstile()`, `geetest()`, `altcha()` or `normal()`. Each returns a Promise that resolves once CapSkip has an answer, giving you a token, or the recognized text in the case of an image captcha.
|
|
375
|
+
|
|
376
|
+
### Is this a free captcha solver?
|
|
377
|
+
|
|
378
|
+
The SDK itself is MIT-licensed and free. Solving needs the CapSkip app, which is bought once rather than metered per captcha, so your cost stops scaling with volume.
|
|
379
|
+
|
|
380
|
+
### Which captchas can it solve?
|
|
381
|
+
|
|
382
|
+
reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, and image/text captchas. Not hCaptcha, and not FunCaptcha/Arkose.
|
|
383
|
+
|
|
384
|
+
### Does it work with Puppeteer and Playwright?
|
|
385
|
+
|
|
386
|
+
Yes — see [above](#puppeteer-playwright-and-selenium). The SDK never touches a browser itself, so it drops into whatever stack you already have, Selenium and plain `fetch` included.
|
|
387
|
+
|
|
388
|
+
### Why is my reCAPTCHA v3 score low?
|
|
389
|
+
|
|
390
|
+
Google derives v3 scores from IP reputation, cookies and browsing history. A solver returns a valid token, but it cannot change how Google grades that token — `score` is forwarded as the target you want, not a guarantee. If a site enforces a high threshold, solve through a cleaner IP using the `proxy` option.
|
|
391
|
+
|
|
392
|
+
### Can I use it as a 2captcha alternative?
|
|
393
|
+
|
|
394
|
+
Yes. CapSkip serves the same endpoints, so you can either move to this SDK or repoint an existing 2captcha client at `127.0.0.1:8080`.
|
|
395
|
+
|
|
396
|
+
### Does the captcha have to be on a public page?
|
|
397
|
+
|
|
398
|
+
For widget captchas, yes — CapSkip loads the URL you pass it. Image captchas only need the image, and that can be a local file.
|
|
399
|
+
|
|
400
|
+
### Does it work with TypeScript and ESM?
|
|
401
|
+
|
|
402
|
+
Types ship in the package. The runtime is CommonJS, so `import { CapSkip } from 'capskip'` works from ESM through Node's interop.
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
250
406
|
## Development
|
|
251
407
|
|
|
252
408
|
```bash
|
|
@@ -262,10 +418,12 @@ The test suite uses Node's built-in test runner — no extra dependencies. See [
|
|
|
262
418
|
|
|
263
419
|
## Links
|
|
264
420
|
|
|
265
|
-
- [CapSkip
|
|
266
|
-
- [
|
|
267
|
-
- [
|
|
268
|
-
- [
|
|
421
|
+
- [CapSkip — local captcha solver](https://capskip.com) · [download the app](https://capskip.com/download/CapSkipInstaller.msi)
|
|
422
|
+
- [Captcha demo pages](https://capskip.com/captcha-demo/) — live reCAPTCHA, Turnstile, GeeTest and image widgets
|
|
423
|
+
- [Node.js captcha solver guide](https://capskip.com/nodejs-captcha-solver/)
|
|
424
|
+
- [HTTP API docs](https://capskip.com/api-docs/)
|
|
425
|
+
- Other clients: [Python](https://github.com/capskip/capskip-python) · [PHP](https://github.com/capskip/capskip-php) · [.NET](https://github.com/capskip/capskip-dotnet) · [MCP server for AI agents](https://github.com/capskip/capskip-mcp)
|
|
426
|
+
- [npm package](https://www.npmjs.com/package/capskip) · [report an issue](https://github.com/capskip/capskip-node/issues)
|
|
269
427
|
|
|
270
428
|
---
|
|
271
429
|
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -22,8 +22,11 @@ method returns a `Promise`.
|
|
|
22
22
|
| reCAPTCHA v2 | `recaptcha(sitekey, url)` | `userrecaptcha` |
|
|
23
23
|
| reCAPTCHA v3 | `recaptcha(sitekey, url, { version: 'v3' })` | `userrecaptcha` + `version=v3` |
|
|
24
24
|
| Cloudflare Turnstile | `turnstile()` | `turnstile` |
|
|
25
|
+
| GeeTest v3 (slide) | `geetest()` | `geetest` |
|
|
26
|
+
| ALTCHA (proof-of-work) | `altcha()` | `altcha` |
|
|
25
27
|
|
|
26
|
-
**Proxy** is supported for reCAPTCHA and
|
|
28
|
+
**Proxy** is supported for reCAPTCHA, Turnstile, GeeTest, and ALTCHA — not for image
|
|
29
|
+
captcha. For ALTCHA the proxy is used only for the `challengeUrl` fetch.
|
|
27
30
|
|
|
28
31
|
---
|
|
29
32
|
|
|
@@ -206,6 +209,174 @@ const challenge = await solver.turnstile('0x4AAAAAAA...', 'https://example.com',
|
|
|
206
209
|
|
|
207
210
|
---
|
|
208
211
|
|
|
212
|
+
## 5. GeeTest v3 — `geetest(gt, challenge, url, { ... })`
|
|
213
|
+
|
|
214
|
+
### POST `/in.php`
|
|
215
|
+
|
|
216
|
+
| Parameter | Type | Required | Description |
|
|
217
|
+
|---|---|---|---|
|
|
218
|
+
| `key` | string | Yes | CapSkip API key |
|
|
219
|
+
| `method` | string | Yes | `geetest` |
|
|
220
|
+
| `gt` | string | Yes | Static per-site GeeTest id |
|
|
221
|
+
| `challenge` | string | Yes | Single-use challenge token |
|
|
222
|
+
| `pageurl` | string | Yes | Full page URL |
|
|
223
|
+
| `api_server` | string | No | GeeTest API server domain, e.g. `api-na.geetest.com` |
|
|
224
|
+
| `json` | int | No | `0` plain text, `1` JSON |
|
|
225
|
+
| `proxy` | string | No | Proxy address |
|
|
226
|
+
| `proxytype` | string | No | Proxy type |
|
|
227
|
+
|
|
228
|
+
### Getting `gt` and `challenge`
|
|
229
|
+
|
|
230
|
+
Both come from the target site, which fetches them from an endpoint returning
|
|
231
|
+
`{"gt": "...", "challenge": "..."}` (often `.../register.php` or a `gettype`/`get.php`
|
|
232
|
+
request). Find it in DevTools → Network, or read them out of the
|
|
233
|
+
`initGeetest({ gt, challenge })` call in the page scripts.
|
|
234
|
+
|
|
235
|
+
> **`challenge` is single-use and expires in about a minute.** Fetch a fresh pair
|
|
236
|
+
> immediately before each solve. If a solve comes back with a bad-challenge error,
|
|
237
|
+
> request a new pair and retry — reusing one never succeeds.
|
|
238
|
+
|
|
239
|
+
### SDK usage
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
const result = await solver.geetest(
|
|
243
|
+
'81388ea1fc187e0c335c0a8907ff2625',
|
|
244
|
+
'7cf6a8b1a2c34d5e6f7089abcdef0123',
|
|
245
|
+
'https://example.com/login',
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
result.challenge; // geetest_challenge
|
|
249
|
+
result.validate; // geetest_validate
|
|
250
|
+
result.seccode; // geetest_seccode
|
|
251
|
+
result.code; // the same answer as a raw JSON string
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Post the three fields back exactly as the site's own front-end would:
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
await fetch(LOGIN_URL, {
|
|
258
|
+
method: 'POST',
|
|
259
|
+
body: new URLSearchParams({
|
|
260
|
+
geetest_challenge: result.challenge,
|
|
261
|
+
geetest_validate: result.validate,
|
|
262
|
+
geetest_seccode: result.seccode,
|
|
263
|
+
}),
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
GeeTest is a real browser solve, so it uses the longer `recaptchaTimeout` budget
|
|
268
|
+
rather than `defaultTimeout`.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 6. ALTCHA — `altcha(url, { ... })`
|
|
273
|
+
|
|
274
|
+
ALTCHA is not a recognition captcha. There is no image, audio or text to read:
|
|
275
|
+
the site issues a proof-of-work challenge and the client must brute-force a
|
|
276
|
+
number that satisfies it. A solve is therefore deterministic and cheap —
|
|
277
|
+
typically milliseconds.
|
|
278
|
+
|
|
279
|
+
### POST `/in.php`
|
|
280
|
+
|
|
281
|
+
| Parameter | Type | Required | Description |
|
|
282
|
+
|---|---|---|---|
|
|
283
|
+
| `key` | string | Yes | CapSkip API key |
|
|
284
|
+
| `method` | string | Yes | `altcha` |
|
|
285
|
+
| `pageurl` | string | Yes | Full URL of the page the challenge came from |
|
|
286
|
+
| `challenge_url` | string | One of the two | Endpoint CapSkip fetches the challenge from |
|
|
287
|
+
| `challenge_json` | string | One of the two | The challenge document itself, as a JSON string |
|
|
288
|
+
| `json` | int | No | `0` plain text, `1` JSON |
|
|
289
|
+
| `proxy` | string | No | Proxy address — used **only** for the `challenge_url` fetch |
|
|
290
|
+
| `proxytype` | string | No | Proxy type |
|
|
291
|
+
|
|
292
|
+
Sending both challenge parameters is allowed: the inline `challengeJson` wins,
|
|
293
|
+
because fetching would only re-obtain what you already supplied.
|
|
294
|
+
|
|
295
|
+
### Getting the challenge
|
|
296
|
+
|
|
297
|
+
Open DevTools → Network on the target page and look for the request the
|
|
298
|
+
`<altcha-widget>` makes for its challenge (often something like
|
|
299
|
+
`/altcha/challenge`). The request URL is your `challengeUrl`; its JSON response
|
|
300
|
+
is your `challengeJson`.
|
|
301
|
+
|
|
302
|
+
The widget attribute that names that endpoint depends on the widget version, so
|
|
303
|
+
read the page source rather than assuming:
|
|
304
|
+
|
|
305
|
+
| Widget | Attribute |
|
|
306
|
+
|---|---|
|
|
307
|
+
| v1 / v2 | `challengeurl="…"`, with a separate `challengejson="…"` for an inline challenge |
|
|
308
|
+
| v3+ | `challenge="…"` — the same attribute takes either a URL or the challenge data |
|
|
309
|
+
|
|
310
|
+
> **Challenges expire, and the window is short** — some sites inside two minutes.
|
|
311
|
+
> Once expired, the site refuses the solution with a bare "verification failed"
|
|
312
|
+
> that looks exactly like a wrong answer. Fetch the challenge immediately before
|
|
313
|
+
> solving and submit the token promptly; do not fetch a batch in advance, and do
|
|
314
|
+
> not hold a token while a user fills in a form.
|
|
315
|
+
>
|
|
316
|
+
> CapSkip refuses an already-expired inline challenge immediately rather than
|
|
317
|
+
> burning CPU on a token that cannot work. If you passed `challengeUrl` and the
|
|
318
|
+
> challenge expired while the job queued, it fetches a fresh one automatically.
|
|
319
|
+
|
|
320
|
+
### SDK usage
|
|
321
|
+
|
|
322
|
+
```js
|
|
323
|
+
// CapSkip fetches the challenge for you
|
|
324
|
+
const result = await solver.altcha('https://example.com/signup', {
|
|
325
|
+
challengeUrl: 'https://example.com/captcha/api/altcha/challenge',
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// …or hand it the document you already have. No network request at all.
|
|
329
|
+
const inline = await solver.altcha('https://example.com/signup', {
|
|
330
|
+
challengeJson: {
|
|
331
|
+
algorithm: 'SHA-256', challenge: '…', salt: '…',
|
|
332
|
+
signature: '…', maxnumber: 1000000,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
result.token; // the base64 payload to post back
|
|
337
|
+
result.number; // the counter that solved it
|
|
338
|
+
result.code; // the same string as token
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
`challengeJson` accepts an object (serialized for you) or a JSON string.
|
|
342
|
+
|
|
343
|
+
`number` is reported for both ALTCHA generations. Their tokens differ — a legacy
|
|
344
|
+
payload carries it as a top-level `number`, while a proof-of-work v2 payload has
|
|
345
|
+
none, its counter sitting at `solution.counter` — so it is read from the server's
|
|
346
|
+
own `solution` object, which reports both the same way.
|
|
347
|
+
|
|
348
|
+
Post the token back in the form field the widget uses, named `altcha`:
|
|
349
|
+
|
|
350
|
+
```js
|
|
351
|
+
await fetch(SIGNUP_URL, {
|
|
352
|
+
method: 'POST',
|
|
353
|
+
body: new URLSearchParams({
|
|
354
|
+
email: 'someone@example.com',
|
|
355
|
+
altcha: result.token,
|
|
356
|
+
}),
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Do not re-encode, trim or re-order the token: it is base64 of a JSON document
|
|
361
|
+
whose fields are covered by the server's HMAC signature, so any modification
|
|
362
|
+
invalidates it. Some integrations read the payload from a JSON body field
|
|
363
|
+
instead — check what the page's own submit sends and mirror it.
|
|
364
|
+
|
|
365
|
+
Unlike GeeTest and reCAPTCHA this is CPU proof-of-work rather than a browser
|
|
366
|
+
solve, so it uses `defaultTimeout`, not `recaptchaTimeout`.
|
|
367
|
+
|
|
368
|
+
### Algorithms
|
|
369
|
+
|
|
370
|
+
CapSkip supports the legacy scheme (SHA-1/256/384/512) and PoW v2 with PBKDF2 or
|
|
371
|
+
SHA. **Argon2id and scrypt are refused**, not attempted: a task using one returns
|
|
372
|
+
`ERROR_CAPTCHA_UNSOLVABLE` and is never retried. ALTCHA itself recommends PBKDF2
|
|
373
|
+
as the default, so this affects a minority of sites.
|
|
374
|
+
|
|
375
|
+
All three widget types (`native`, `checkbox`, `switch`) work — the distinction is
|
|
376
|
+
purely visual and never reaches CapSkip.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
209
380
|
## Return value
|
|
210
381
|
|
|
211
382
|
Every solve method resolves to:
|
|
@@ -218,6 +389,31 @@ Every solve method resolves to:
|
|
|
218
389
|
}
|
|
219
390
|
```
|
|
220
391
|
|
|
392
|
+
ALTCHA additionally exposes `token` (the same string as `code`, named for the
|
|
393
|
+
form field it goes in) and `number`, the counter that solved it:
|
|
394
|
+
|
|
395
|
+
```js
|
|
396
|
+
{
|
|
397
|
+
captchaId: '12345',
|
|
398
|
+
code: 'eyJhbGdvcml0aG0iOiJTSEEtMjU2Iiwi…',
|
|
399
|
+
token: 'eyJhbGdvcml0aG0iOiJTSEEtMjU2Iiwi…',
|
|
400
|
+
number: 9661,
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
GeeTest additionally expands its answer into `challenge`, `validate`, and
|
|
405
|
+
`seccode` (`code` keeps the raw JSON string):
|
|
406
|
+
|
|
407
|
+
```js
|
|
408
|
+
{
|
|
409
|
+
captchaId: '12345',
|
|
410
|
+
code: '{"geetest_challenge":"...","geetest_validate":"...","geetest_seccode":"..."}',
|
|
411
|
+
challenge: '...',
|
|
412
|
+
validate: '...',
|
|
413
|
+
seccode: '...',
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
221
417
|
---
|
|
222
418
|
|
|
223
419
|
## SDK parameter aliases
|
|
@@ -231,6 +427,10 @@ Convenience aliases mapped before sending to CapSkip:
|
|
|
231
427
|
| `minScore` | `min_score` |
|
|
232
428
|
| `datas` | `data-s` |
|
|
233
429
|
| `data_s` | `data-s` |
|
|
430
|
+
| `apiServer` | `api_server` |
|
|
431
|
+
| `api_subdomain` | `api_server` |
|
|
432
|
+
| `challengeUrl` / `challengeURL` | `challenge_url` |
|
|
433
|
+
| `challengeJson` / `challengeJSON` | `challenge_json` |
|
|
234
434
|
| `proxy` object | `proxy` + `proxytype` strings |
|
|
235
435
|
|
|
236
436
|
```js
|
package/docs/GETTING_STARTED.md
CHANGED
|
@@ -136,6 +136,7 @@ node examples/recaptcha.js
|
|
|
136
136
|
| `image_captcha.js` | Image captcha from file, URL, or base64 |
|
|
137
137
|
| `recaptcha.js` | reCAPTCHA v2, v3, invisible, enterprise, proxy |
|
|
138
138
|
| `turnstile.js` | Cloudflare Turnstile widget and challenge page |
|
|
139
|
+
| `geetest.js` | GeeTest v3 slider, including fetching a fresh `gt`/`challenge` pair |
|
|
139
140
|
| `async_example.js` | Parallel solving |
|
|
140
141
|
| `verify_connection.js` | Check CapSkip is running |
|
|
141
142
|
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -152,6 +152,48 @@ const result = await solver.recaptcha('...', '...', { proxy });
|
|
|
152
152
|
|
|
153
153
|
---
|
|
154
154
|
|
|
155
|
+
## GeeTest keeps failing with a bad-challenge error
|
|
156
|
+
|
|
157
|
+
**Symptom:** `geetest()` rejects with `ApiException` (or times out) even though
|
|
158
|
+
`gt` and `challenge` were copied correctly from the page.
|
|
159
|
+
|
|
160
|
+
**Cause:** The `challenge` value is **single-use and expires in about a minute**.
|
|
161
|
+
A pair copied out of DevTools minutes earlier, cached in a config file, or reused
|
|
162
|
+
across two solves is already dead.
|
|
163
|
+
|
|
164
|
+
**Fix:** Fetch a fresh pair programmatically immediately before each solve, and on
|
|
165
|
+
failure request a *new* pair rather than retrying the old one:
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
const { gt, challenge } = await (await fetch(REGISTER_URL)).json();
|
|
169
|
+
const result = await solver.geetest(gt, challenge, PAGE_URL);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
If the site loads GeeTest from a non-default API server domain, pass it through as well:
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
await solver.geetest(gt, challenge, url, { api_server: 'api-na.geetest.com' });
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## GeeTest result — where are challenge/validate/seccode?
|
|
181
|
+
|
|
182
|
+
`result.code` holds the raw JSON string CapSkip returns. The SDK also parses it
|
|
183
|
+
for you, so prefer the individual fields:
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
result.challenge; // geetest_challenge
|
|
187
|
+
result.validate; // geetest_validate
|
|
188
|
+
result.seccode; // geetest_seccode
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Submit all three under their `geetest_`-prefixed names, exactly as the site's own
|
|
192
|
+
front-end does. Sending only `validate` is the most common reason a correct solve
|
|
193
|
+
is rejected.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
155
197
|
## NetworkException during manual polling
|
|
156
198
|
|
|
157
199
|
**Symptom:** `getResult()` keeps rejecting with `NetworkException`.
|