capskip 1.0.2 → 1.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/CHANGELOG.md +16 -0
- package/README.md +23 -3
- package/docs/API_REFERENCE.md +77 -1
- package/docs/GETTING_STARTED.md +1 -0
- package/docs/TROUBLESHOOTING.md +42 -0
- package/docs/TUTORIAL.md +97 -21
- package/examples/geetest.js +68 -0
- package/package.json +2 -1
- package/src/apiParams.js +55 -0
- package/src/index.js +1 -1
- package/src/solver.js +68 -1
- package/types/index.d.ts +42 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ 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.1.0] - 2026-07-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **GeeTest v3 (slide) support** via `geetest(gt, challenge, url, options)`.
|
|
13
|
+
Accepts the optional `api_server` domain override and the usual `proxy`
|
|
14
|
+
option. The result exposes the answer as the parsed `challenge`, `validate`,
|
|
15
|
+
and `seccode` fields, while `code` keeps the raw JSON string CapSkip returns.
|
|
16
|
+
- Parameter aliases `apiServer` and `api_subdomain` for `api_server`.
|
|
17
|
+
- TypeScript definitions for `geetest()`, `GeetestOptions`, and the new
|
|
18
|
+
`SolveResult` fields.
|
|
19
|
+
- `proxytype` is now validated against the values CapSkip accepts (`HTTP`,
|
|
20
|
+
`HTTPS`, `SOCKS5`, `SOCKS5H`, case-insensitive) for every proxy-capable captcha
|
|
21
|
+
type. `SOCKS4` and other values previously reached the server and came back as
|
|
22
|
+
`ERROR_BAD_PARAMETERS`; they now raise `ValidationException` locally.
|
|
23
|
+
|
|
8
24
|
## [1.0.2] - 2026-07-15
|
|
9
25
|
|
|
10
26
|
### Fixed
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Official Node.js client for the [CapSkip](https://capskip.com) **local** captcha solver.
|
|
8
8
|
|
|
9
|
-
CapSkip runs on your machine and exposes a standard captcha-solver HTTP API (the familiar `in.php` / `res.php` endpoints). This SDK wraps that API with clean, familiar method names, so you can solve captchas locally — no
|
|
9
|
+
CapSkip runs on your machine and exposes a standard captcha-solver HTTP API (the familiar `in.php` / `res.php` endpoints). This SDK wraps that API with clean, familiar method names, so you can solve captchas locally — no per-solve API fees beyond your CapSkip license.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -70,6 +70,7 @@ Every solve method returns a Promise — use `await` or `.then()`.
|
|
|
70
70
|
| reCAPTCHA v3 Enterprise | `solver.recaptcha(sitekey, url, { version: 'v3', enterprise: 1 })` |
|
|
71
71
|
| Cloudflare Turnstile (widget) | `solver.turnstile(sitekey, url)` |
|
|
72
72
|
| Cloudflare Turnstile (challenge page) | `solver.turnstile(sitekey, url, { data, pagedata })` |
|
|
73
|
+
| GeeTest v3 (slide) | `solver.geetest(gt, challenge, url)` |
|
|
73
74
|
|
|
74
75
|
---
|
|
75
76
|
|
|
@@ -97,7 +98,7 @@ const solver = new CapSkip({
|
|
|
97
98
|
host: '127.0.0.1', // CapSkip host
|
|
98
99
|
port: 8080, // CapSkip port from app settings
|
|
99
100
|
defaultTimeout: 120, // seconds — image captcha polling timeout
|
|
100
|
-
recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile polling timeout
|
|
101
|
+
recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest polling timeout
|
|
101
102
|
pollingInterval: 5, // max seconds between res.php polls (starts at 0.25s, backs off to this)
|
|
102
103
|
});
|
|
103
104
|
```
|
|
@@ -161,7 +162,23 @@ const v3 = await solver.recaptcha('...', 'https://example.com', {
|
|
|
161
162
|
const result = await solver.turnstile('0x4AAAAAAA...', 'https://example.com');
|
|
162
163
|
```
|
|
163
164
|
|
|
164
|
-
###
|
|
165
|
+
### GeeTest v3
|
|
166
|
+
|
|
167
|
+
`gt` is static per site, but `challenge` is single-use and expires in about a
|
|
168
|
+
minute — fetch a fresh pair right before solving.
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
const result = await solver.geetest(
|
|
172
|
+
'81388ea1fc187e0c335c0a8907ff2625',
|
|
173
|
+
'7cf6a8b1a2c34d5e6f7089abcdef0123',
|
|
174
|
+
'https://example.com/login',
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Post these back exactly as the site's own front-end would
|
|
178
|
+
result.challenge, result.validate, result.seccode;
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### With a proxy (reCAPTCHA, Turnstile & GeeTest only)
|
|
165
182
|
|
|
166
183
|
```js
|
|
167
184
|
// Proxy is not supported for image captcha
|
|
@@ -208,6 +225,9 @@ Every solve method resolves to:
|
|
|
208
225
|
}
|
|
209
226
|
```
|
|
210
227
|
|
|
228
|
+
GeeTest additionally expands its answer into `challenge`, `validate`, and
|
|
229
|
+
`seccode`, while `code` keeps the raw JSON string.
|
|
230
|
+
|
|
211
231
|
---
|
|
212
232
|
|
|
213
233
|
## Error handling
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -22,8 +22,9 @@ 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` |
|
|
25
26
|
|
|
26
|
-
**Proxy** is supported for reCAPTCHA and
|
|
27
|
+
**Proxy** is supported for reCAPTCHA, Turnstile, and GeeTest — not for image captcha.
|
|
27
28
|
|
|
28
29
|
---
|
|
29
30
|
|
|
@@ -206,6 +207,66 @@ const challenge = await solver.turnstile('0x4AAAAAAA...', 'https://example.com',
|
|
|
206
207
|
|
|
207
208
|
---
|
|
208
209
|
|
|
210
|
+
## 5. GeeTest v3 — `geetest(gt, challenge, url, { ... })`
|
|
211
|
+
|
|
212
|
+
### POST `/in.php`
|
|
213
|
+
|
|
214
|
+
| Parameter | Type | Required | Description |
|
|
215
|
+
|---|---|---|---|
|
|
216
|
+
| `key` | string | Yes | CapSkip API key |
|
|
217
|
+
| `method` | string | Yes | `geetest` |
|
|
218
|
+
| `gt` | string | Yes | Static per-site GeeTest id |
|
|
219
|
+
| `challenge` | string | Yes | Single-use challenge token |
|
|
220
|
+
| `pageurl` | string | Yes | Full page URL |
|
|
221
|
+
| `api_server` | string | No | GeeTest API server domain, e.g. `api-na.geetest.com` |
|
|
222
|
+
| `json` | int | No | `0` plain text, `1` JSON |
|
|
223
|
+
| `proxy` | string | No | Proxy address |
|
|
224
|
+
| `proxytype` | string | No | Proxy type |
|
|
225
|
+
|
|
226
|
+
### Getting `gt` and `challenge`
|
|
227
|
+
|
|
228
|
+
Both come from the target site, which fetches them from an endpoint returning
|
|
229
|
+
`{"gt": "...", "challenge": "..."}` (often `.../register.php` or a `gettype`/`get.php`
|
|
230
|
+
request). Find it in DevTools → Network, or read them out of the
|
|
231
|
+
`initGeetest({ gt, challenge })` call in the page scripts.
|
|
232
|
+
|
|
233
|
+
> **`challenge` is single-use and expires in about a minute.** Fetch a fresh pair
|
|
234
|
+
> immediately before each solve. If a solve comes back with a bad-challenge error,
|
|
235
|
+
> request a new pair and retry — reusing one never succeeds.
|
|
236
|
+
|
|
237
|
+
### SDK usage
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
const result = await solver.geetest(
|
|
241
|
+
'81388ea1fc187e0c335c0a8907ff2625',
|
|
242
|
+
'7cf6a8b1a2c34d5e6f7089abcdef0123',
|
|
243
|
+
'https://example.com/login',
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
result.challenge; // geetest_challenge
|
|
247
|
+
result.validate; // geetest_validate
|
|
248
|
+
result.seccode; // geetest_seccode
|
|
249
|
+
result.code; // the same answer as a raw JSON string
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Post the three fields back exactly as the site's own front-end would:
|
|
253
|
+
|
|
254
|
+
```js
|
|
255
|
+
await fetch(LOGIN_URL, {
|
|
256
|
+
method: 'POST',
|
|
257
|
+
body: new URLSearchParams({
|
|
258
|
+
geetest_challenge: result.challenge,
|
|
259
|
+
geetest_validate: result.validate,
|
|
260
|
+
geetest_seccode: result.seccode,
|
|
261
|
+
}),
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
GeeTest is a real browser solve, so it uses the longer `recaptchaTimeout` budget
|
|
266
|
+
rather than `defaultTimeout`.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
209
270
|
## Return value
|
|
210
271
|
|
|
211
272
|
Every solve method resolves to:
|
|
@@ -218,6 +279,19 @@ Every solve method resolves to:
|
|
|
218
279
|
}
|
|
219
280
|
```
|
|
220
281
|
|
|
282
|
+
GeeTest additionally expands its answer into `challenge`, `validate`, and
|
|
283
|
+
`seccode` (`code` keeps the raw JSON string):
|
|
284
|
+
|
|
285
|
+
```js
|
|
286
|
+
{
|
|
287
|
+
captchaId: '12345',
|
|
288
|
+
code: '{"geetest_challenge":"...","geetest_validate":"...","geetest_seccode":"..."}',
|
|
289
|
+
challenge: '...',
|
|
290
|
+
validate: '...',
|
|
291
|
+
seccode: '...',
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
221
295
|
---
|
|
222
296
|
|
|
223
297
|
## SDK parameter aliases
|
|
@@ -231,6 +305,8 @@ Convenience aliases mapped before sending to CapSkip:
|
|
|
231
305
|
| `minScore` | `min_score` |
|
|
232
306
|
| `datas` | `data-s` |
|
|
233
307
|
| `data_s` | `data-s` |
|
|
308
|
+
| `apiServer` | `api_server` |
|
|
309
|
+
| `api_subdomain` | `api_server` |
|
|
234
310
|
| `proxy` object | `proxy` + `proxytype` strings |
|
|
235
311
|
|
|
236
312
|
```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`.
|
package/docs/TUTORIAL.md
CHANGED
|
@@ -12,14 +12,15 @@ Work through it top to bottom, or jump to the section you need.
|
|
|
12
12
|
5. [reCAPTCHA v2](#5-recaptcha-v2)
|
|
13
13
|
6. [reCAPTCHA v3](#6-recaptcha-v3)
|
|
14
14
|
7. [Cloudflare Turnstile](#7-cloudflare-turnstile)
|
|
15
|
-
8. [
|
|
16
|
-
9. [
|
|
17
|
-
10. [
|
|
18
|
-
11. [
|
|
19
|
-
12. [
|
|
20
|
-
13. [
|
|
21
|
-
14. [
|
|
22
|
-
15. [
|
|
15
|
+
8. [GeeTest v3](#8-geetest-v3)
|
|
16
|
+
9. [Using a proxy](#9-using-a-proxy)
|
|
17
|
+
10. [Concurrency](#10-concurrency)
|
|
18
|
+
11. [The manual workflow](#11-the-manual-workflow)
|
|
19
|
+
12. [Return values](#12-return-values)
|
|
20
|
+
13. [Error handling](#13-error-handling)
|
|
21
|
+
14. [End-to-end: solve and submit](#14-end-to-end-solve-and-submit)
|
|
22
|
+
15. [Parameter reference](#15-parameter-reference)
|
|
23
|
+
16. [Best practices](#16-best-practices)
|
|
23
24
|
|
|
24
25
|
---
|
|
25
26
|
|
|
@@ -77,7 +78,7 @@ const solver = new CapSkip({
|
|
|
77
78
|
host: '127.0.0.1', // where CapSkip is listening
|
|
78
79
|
port: 8080, // API port from CapSkip settings
|
|
79
80
|
defaultTimeout: 120, // seconds to wait for an image captcha
|
|
80
|
-
recaptchaTimeout: 300, // seconds to wait for reCAPTCHA / Turnstile
|
|
81
|
+
recaptchaTimeout: 300, // seconds to wait for reCAPTCHA / Turnstile / GeeTest
|
|
81
82
|
pollingInterval: 5, // max seconds between result polls (starts at 0.25s, backs off to this)
|
|
82
83
|
});
|
|
83
84
|
```
|
|
@@ -149,7 +150,7 @@ await solver.normal('captcha.png', { json: 1 });
|
|
|
149
150
|
```
|
|
150
151
|
|
|
151
152
|
> **Note:** Proxies are **not** supported for image captcha — passing one throws
|
|
152
|
-
> `ValidationException`. Proxies apply only to reCAPTCHA and
|
|
153
|
+
> `ValidationException`. Proxies apply only to reCAPTCHA, Turnstile, and GeeTest.
|
|
153
154
|
|
|
154
155
|
---
|
|
155
156
|
|
|
@@ -231,24 +232,94 @@ await solver.turnstile('0x4AAAAAAA...', 'https://example.com', {
|
|
|
231
232
|
|
|
232
233
|
---
|
|
233
234
|
|
|
234
|
-
## 8.
|
|
235
|
+
## 8. GeeTest v3
|
|
236
|
+
|
|
237
|
+
`solver.geetest(gt, challenge, url)` solves the GeeTest v3 slide puzzle.
|
|
238
|
+
|
|
239
|
+
### Finding `gt` and `challenge`
|
|
240
|
+
|
|
241
|
+
Unlike a sitekey, GeeTest needs **two** values, and one of them is short-lived:
|
|
242
|
+
|
|
243
|
+
| Value | Lifetime | Where it comes from |
|
|
244
|
+
|---|---|---|
|
|
245
|
+
| `gt` | Static per site | The same place as `challenge` |
|
|
246
|
+
| `challenge` | **Single-use, expires in ~1 minute** | An endpoint the site calls that returns `{"gt": "...", "challenge": "..."}` |
|
|
247
|
+
|
|
248
|
+
Open DevTools → Network on the target page and look for a request to something like
|
|
249
|
+
`.../register.php`, `gettype`, or `get.php`. You can also read the values out of the
|
|
250
|
+
`initGeetest({ gt, challenge })` call in the page scripts.
|
|
251
|
+
|
|
252
|
+
```js
|
|
253
|
+
// Ask the target site for a fresh pair immediately before solving.
|
|
254
|
+
const resp = await fetch('https://example.com/captcha/register.php');
|
|
255
|
+
const { gt, challenge } = await resp.json();
|
|
256
|
+
|
|
257
|
+
const result = await solver.geetest(gt, challenge, 'https://example.com/login');
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Using the answer
|
|
261
|
+
|
|
262
|
+
The result carries the three values the site's own front-end would submit:
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
result.challenge; // geetest_challenge
|
|
266
|
+
result.validate; // geetest_validate
|
|
267
|
+
result.seccode; // geetest_seccode
|
|
268
|
+
|
|
269
|
+
result.code; // the same answer as a raw JSON string
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Post them back exactly as the site expects:
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
await fetch('https://example.com/login', {
|
|
276
|
+
method: 'POST',
|
|
277
|
+
body: new URLSearchParams({
|
|
278
|
+
username: '...',
|
|
279
|
+
password: '...',
|
|
280
|
+
geetest_challenge: result.challenge,
|
|
281
|
+
geetest_validate: result.validate,
|
|
282
|
+
geetest_seccode: result.seccode,
|
|
283
|
+
}),
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
> **`challenge` is one-shot.** Never cache or reuse a pair. If a solve fails with a
|
|
288
|
+
> bad-challenge error, request a *new* pair and retry — retrying with the same
|
|
289
|
+
> `challenge` can never succeed.
|
|
290
|
+
|
|
291
|
+
If the site uses a non-default GeeTest API server domain, pass it through:
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
await solver.geetest(gt, challenge, url, { api_server: 'api-na.geetest.com' });
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Because GeeTest is a real browser solve (load, slide, verify), it uses the longer
|
|
298
|
+
`recaptchaTimeout` budget rather than `defaultTimeout`.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## 9. Using a proxy
|
|
235
303
|
|
|
236
304
|
Solving through the same IP you will submit from greatly improves acceptance rates
|
|
237
|
-
for reCAPTCHA and
|
|
305
|
+
for reCAPTCHA, Turnstile, and GeeTest. Pass the proxy as an object with `type` and `uri`:
|
|
238
306
|
|
|
239
307
|
```js
|
|
240
308
|
const proxy = { type: 'HTTPS', uri: 'user:pass@1.2.3.4:3128' };
|
|
241
309
|
|
|
242
310
|
await solver.recaptcha('...', 'https://example.com', { proxy });
|
|
243
311
|
await solver.turnstile('...', 'https://example.com', { proxy });
|
|
312
|
+
await solver.geetest('gt', 'challenge', 'https://example.com', { proxy });
|
|
244
313
|
```
|
|
245
314
|
|
|
246
|
-
Supported proxy types: `HTTP`, `HTTPS`, `SOCKS5`, `SOCKS5H
|
|
315
|
+
Supported proxy types: `HTTP`, `HTTPS`, `SOCKS5`, `SOCKS5H` — matched
|
|
316
|
+
case-insensitively. Anything else (including `SOCKS4`) raises
|
|
317
|
+
`ValidationException` before the request is sent. The `uri` may include
|
|
247
318
|
credentials (`login:password@host:port`) or be a bare `host:port`.
|
|
248
319
|
|
|
249
320
|
---
|
|
250
321
|
|
|
251
|
-
##
|
|
322
|
+
## 10. Concurrency
|
|
252
323
|
|
|
253
324
|
Every solve method returns a Promise, so you can solve many captchas concurrently
|
|
254
325
|
with `Promise.all`:
|
|
@@ -279,7 +350,7 @@ const results = await Promise.allSettled([task1, task2]);
|
|
|
279
350
|
|
|
280
351
|
---
|
|
281
352
|
|
|
282
|
-
##
|
|
353
|
+
## 11. The manual workflow
|
|
283
354
|
|
|
284
355
|
If you want to submit now and collect the answer later, use the two low-level
|
|
285
356
|
steps directly.
|
|
@@ -316,7 +387,7 @@ Pass `1` as the second argument to `getResult` to get the full object (including
|
|
|
316
387
|
|
|
317
388
|
---
|
|
318
389
|
|
|
319
|
-
##
|
|
390
|
+
## 12. Return values
|
|
320
391
|
|
|
321
392
|
Every high-level solve method (`normal`, `recaptcha`, `turnstile`, `solve`)
|
|
322
393
|
resolves to an object:
|
|
@@ -334,7 +405,7 @@ the solution string (or an object when called with `json = 1`).
|
|
|
334
405
|
|
|
335
406
|
---
|
|
336
407
|
|
|
337
|
-
##
|
|
408
|
+
## 13. Error handling
|
|
338
409
|
|
|
339
410
|
The SDK throws four error types, all subclasses of `CapSkipError`:
|
|
340
411
|
|
|
@@ -388,7 +459,7 @@ try {
|
|
|
388
459
|
|
|
389
460
|
---
|
|
390
461
|
|
|
391
|
-
##
|
|
462
|
+
## 14. End-to-end: solve and submit
|
|
392
463
|
|
|
393
464
|
A realistic flow — solve a reCAPTCHA, then submit the token to the target site
|
|
394
465
|
through the **same** proxy:
|
|
@@ -453,7 +524,7 @@ await fetch(CHALLENGE_URL, {
|
|
|
453
524
|
|
|
454
525
|
---
|
|
455
526
|
|
|
456
|
-
##
|
|
527
|
+
## 15. Parameter reference
|
|
457
528
|
|
|
458
529
|
### Solve methods
|
|
459
530
|
|
|
@@ -462,6 +533,7 @@ await fetch(CHALLENGE_URL, {
|
|
|
462
533
|
| Image | `normal(file, { json })` |
|
|
463
534
|
| reCAPTCHA | `recaptcha(sitekey, url, { version, enterprise, ... })` |
|
|
464
535
|
| Turnstile | `turnstile(sitekey, url, { ... })` |
|
|
536
|
+
| GeeTest v3 | `geetest(gt, challenge, url, { ... })` |
|
|
465
537
|
| Manual submit | `send(params) -> Promise<id>` |
|
|
466
538
|
| Manual poll | `getResult(id, json)` |
|
|
467
539
|
|
|
@@ -474,6 +546,7 @@ The SDK accepts friendly names and converts them to the raw API parameters:
|
|
|
474
546
|
| `url` | `pageurl` |
|
|
475
547
|
| `score`, `minScore` | `min_score` |
|
|
476
548
|
| `datas`, `data_s` | `data-s` |
|
|
549
|
+
| `apiServer`, `api_subdomain` | `api_server` |
|
|
477
550
|
| `proxy` (object) | `proxy` + `proxytype` strings |
|
|
478
551
|
|
|
479
552
|
Anything CapSkip does not document for a given captcha type is rejected with
|
|
@@ -481,14 +554,17 @@ Anything CapSkip does not document for a given captcha type is rejected with
|
|
|
481
554
|
|
|
482
555
|
---
|
|
483
556
|
|
|
484
|
-
##
|
|
557
|
+
## 16. Best practices
|
|
485
558
|
|
|
486
559
|
- **Keep CapSkip running.** The SDK talks to a local app; if it is not running you
|
|
487
560
|
get `NetworkException`.
|
|
488
561
|
- **Use the token immediately.** reCAPTCHA and Turnstile tokens expire within a
|
|
489
562
|
couple of minutes.
|
|
490
563
|
- **Match sitekey and pageurl exactly** to the page the widget loads on.
|
|
491
|
-
- **
|
|
564
|
+
- **Fetch a fresh GeeTest `challenge` per solve.** It is single-use and expires in
|
|
565
|
+
about a minute; a cached pair always fails.
|
|
566
|
+
- **Solve and submit from the same IP** (same proxy) for reCAPTCHA, Turnstile, and
|
|
567
|
+
GeeTest.
|
|
492
568
|
- **Never commit secrets.** Read `CAPSKIP_API_KEY` and proxy credentials from the
|
|
493
569
|
environment, not source code.
|
|
494
570
|
- **Tune timeouts** for slow captcha types with `recaptchaTimeout` and
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Solve a GeeTest v3 slider.
|
|
5
|
+
*
|
|
6
|
+
* GeeTest v3 needs two values from the target site:
|
|
7
|
+
*
|
|
8
|
+
* - `gt` static per site
|
|
9
|
+
* - `challenge` single-use, expires in about a minute
|
|
10
|
+
*
|
|
11
|
+
* The site fetches them itself from an endpoint that returns
|
|
12
|
+
* `{"gt": "...", "challenge": "..."}` (often `.../register.php` or a
|
|
13
|
+
* `gettype`/`get.php` request). Open DevTools -> Network to find that request,
|
|
14
|
+
* then request a *fresh* pair right before solving, as this example does.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { CapSkip } = require('capskip');
|
|
18
|
+
|
|
19
|
+
const solver = new CapSkip({
|
|
20
|
+
apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
|
|
21
|
+
host: process.env.CAPSKIP_HOST || '127.0.0.1',
|
|
22
|
+
port: Number(process.env.CAPSKIP_PORT || 8080),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// A public GeeTest v3 demo page, and the endpoint that page calls to issue a
|
|
26
|
+
// fresh gt/challenge pair. Safe to run as-is.
|
|
27
|
+
const PAGE_URL = 'https://2captcha.com/demo/geetest';
|
|
28
|
+
const REGISTER_URL = 'https://2captcha.com/api/v1/captcha-demo/gee-test/init-params';
|
|
29
|
+
|
|
30
|
+
/** Get a fresh gt/challenge pair. Replace with the endpoint your target uses. */
|
|
31
|
+
async function fetchChallenge() {
|
|
32
|
+
const resp = await fetch(REGISTER_URL);
|
|
33
|
+
if (!resp.ok) {
|
|
34
|
+
throw new Error(`Could not fetch a gt/challenge pair: HTTP ${resp.status}`);
|
|
35
|
+
}
|
|
36
|
+
return resp.json();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
(async () => {
|
|
40
|
+
const { gt, challenge } = await fetchChallenge();
|
|
41
|
+
|
|
42
|
+
const result = await solver.geetest(gt, challenge, PAGE_URL);
|
|
43
|
+
|
|
44
|
+
console.log('Captcha ID:', result.captchaId);
|
|
45
|
+
console.log('Challenge: ', result.challenge);
|
|
46
|
+
console.log('Validate: ', result.validate);
|
|
47
|
+
console.log('Seccode: ', result.seccode);
|
|
48
|
+
|
|
49
|
+
// `code` holds the same answer as a raw JSON string, which is what you forward
|
|
50
|
+
// if you are porting code written against another solver's API.
|
|
51
|
+
console.log('Raw code: ', result.code);
|
|
52
|
+
|
|
53
|
+
// Post these back exactly as the site's own front-end would, e.g.:
|
|
54
|
+
//
|
|
55
|
+
// await fetch(LOGIN_URL, {
|
|
56
|
+
// method: 'POST',
|
|
57
|
+
// body: new URLSearchParams({
|
|
58
|
+
// geetest_challenge: result.challenge,
|
|
59
|
+
// geetest_validate: result.validate,
|
|
60
|
+
// geetest_seccode: result.seccode,
|
|
61
|
+
// }),
|
|
62
|
+
// });
|
|
63
|
+
console.log('Form fields:', {
|
|
64
|
+
geetest_challenge: result.challenge,
|
|
65
|
+
geetest_validate: result.validate,
|
|
66
|
+
geetest_seccode: result.seccode,
|
|
67
|
+
});
|
|
68
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capskip",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Node.js SDK for the CapSkip local captcha solver",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"recaptcha",
|
|
27
27
|
"cloudflare",
|
|
28
28
|
"turnstile",
|
|
29
|
+
"geetest",
|
|
29
30
|
"capskip",
|
|
30
31
|
"automation"
|
|
31
32
|
],
|
package/src/apiParams.js
CHANGED
|
@@ -21,12 +21,24 @@ const TURNSTILE_SUBMIT = new Set([
|
|
|
21
21
|
'proxy', 'proxytype',
|
|
22
22
|
]);
|
|
23
23
|
|
|
24
|
+
const GEETEST_SUBMIT = new Set([
|
|
25
|
+
'method', 'gt', 'challenge', 'pageurl', 'api_server', 'json',
|
|
26
|
+
'proxy', 'proxytype',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
// The only values CapSkip maps to a proxy scheme; it answers
|
|
30
|
+
// ERROR_BAD_PARAMETERS for anything else, SOCKS4 included. Matched
|
|
31
|
+
// case-insensitively, as the server does.
|
|
32
|
+
const PROXY_TYPES = ['HTTP', 'HTTPS', 'SOCKS5', 'SOCKS5H'];
|
|
33
|
+
|
|
24
34
|
const PARAM_ALIASES = {
|
|
25
35
|
url: 'pageurl',
|
|
26
36
|
score: 'min_score',
|
|
27
37
|
minScore: 'min_score',
|
|
28
38
|
datas: 'data-s',
|
|
29
39
|
data_s: 'data-s',
|
|
40
|
+
apiServer: 'api_server',
|
|
41
|
+
api_subdomain: 'api_server',
|
|
30
42
|
};
|
|
31
43
|
|
|
32
44
|
function has(obj, key) {
|
|
@@ -145,6 +157,38 @@ function validateTurnstileSubmit(params) {
|
|
|
145
157
|
}
|
|
146
158
|
}
|
|
147
159
|
|
|
160
|
+
function validateGeetestSubmit(params) {
|
|
161
|
+
// All three are documented as required. gt is static per site, challenge is
|
|
162
|
+
// single-use and expires in about a minute; without them CapSkip answers
|
|
163
|
+
// ERROR_BAD_PARAMETERS, and without pageurl ERROR_PAGEURL. Fail locally so a
|
|
164
|
+
// missing value does not cost a round-trip.
|
|
165
|
+
for (const key of ['gt', 'challenge', 'pageurl']) {
|
|
166
|
+
if (!params[key]) {
|
|
167
|
+
throw new ValidationException(`'${key}' is required for GeeTest v3.`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const unknown = unknownKeys(params, GEETEST_SUBMIT);
|
|
172
|
+
if (unknown.length > 0) {
|
|
173
|
+
throw new ValidationException(
|
|
174
|
+
`Unsupported parameters for GeeTest: ${reprList(unknown)}.`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function validateProxyType(params) {
|
|
180
|
+
const proxytype = params.proxytype;
|
|
181
|
+
if (proxytype === undefined || proxytype === null || proxytype === '') {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!PROXY_TYPES.includes(String(proxytype).toUpperCase())) {
|
|
185
|
+
throw new ValidationException(
|
|
186
|
+
`Unsupported proxytype '${proxytype}'. `
|
|
187
|
+
+ `CapSkip accepts: ${PROXY_TYPES.join(', ')}.`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
148
192
|
function prepareSubmitParams(params, captchaType, version = 'v2') {
|
|
149
193
|
let prepared = applyParamAliases(params);
|
|
150
194
|
prepared = applyProxy(prepared);
|
|
@@ -155,6 +199,13 @@ function prepareSubmitParams(params, captchaType, version = 'v2') {
|
|
|
155
199
|
validateRecaptchaSubmit(prepared, version);
|
|
156
200
|
} else if (captchaType === 'turnstile') {
|
|
157
201
|
validateTurnstileSubmit(prepared);
|
|
202
|
+
} else if (captchaType === 'geetest') {
|
|
203
|
+
validateGeetestSubmit(prepared);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Skipped for 'normal', which rejects proxy outright with a clearer message.
|
|
207
|
+
if (captchaType !== 'normal') {
|
|
208
|
+
validateProxyType(prepared);
|
|
158
209
|
}
|
|
159
210
|
|
|
160
211
|
return prepared;
|
|
@@ -165,11 +216,15 @@ module.exports = {
|
|
|
165
216
|
RECAPTCHA_V2_SUBMIT,
|
|
166
217
|
RECAPTCHA_V3_SUBMIT,
|
|
167
218
|
TURNSTILE_SUBMIT,
|
|
219
|
+
GEETEST_SUBMIT,
|
|
220
|
+
PROXY_TYPES,
|
|
168
221
|
PARAM_ALIASES,
|
|
169
222
|
applyParamAliases,
|
|
170
223
|
applyProxy,
|
|
171
224
|
validateNormalSubmit,
|
|
172
225
|
validateRecaptchaSubmit,
|
|
173
226
|
validateTurnstileSubmit,
|
|
227
|
+
validateGeetestSubmit,
|
|
228
|
+
validateProxyType,
|
|
174
229
|
prepareSubmitParams,
|
|
175
230
|
};
|
package/src/index.js
CHANGED
package/src/solver.js
CHANGED
|
@@ -89,6 +89,43 @@ function applyPollResult(result, polled) {
|
|
|
89
89
|
return result;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// GeeTest answers come back as a JSON string in `request`, keyed with the
|
|
93
|
+
// geetest_ prefix that the target site's own form fields use.
|
|
94
|
+
const GEETEST_FIELDS = [
|
|
95
|
+
['challenge', 'geetest_challenge'],
|
|
96
|
+
['validate', 'geetest_validate'],
|
|
97
|
+
['seccode', 'geetest_seccode'],
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Expand the GeeTest answer into `challenge` / `validate` / `seccode`.
|
|
102
|
+
*
|
|
103
|
+
* `code` keeps the raw JSON string so callers that forward it verbatim (or that
|
|
104
|
+
* were written against another solver's API) keep working. If it does not parse,
|
|
105
|
+
* the result is returned untouched rather than masking the server's reply.
|
|
106
|
+
*/
|
|
107
|
+
function applyGeetestSolution(result) {
|
|
108
|
+
let payload;
|
|
109
|
+
try {
|
|
110
|
+
payload = JSON.parse(result.code || '');
|
|
111
|
+
} catch (err) {
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const [short, prefixed] of GEETEST_FIELDS) {
|
|
120
|
+
const value = payload[prefixed] !== undefined ? payload[prefixed] : payload[short];
|
|
121
|
+
if (value !== undefined && value !== null) {
|
|
122
|
+
result[short] = value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
92
129
|
// CapSkip's in.php returns OK|<id> by default, or {"status":1,"request":"<id>"}
|
|
93
130
|
// when the submit carried json=1. Accept both so submitting with json=1 works.
|
|
94
131
|
function parseSubmitResponse(response) {
|
|
@@ -112,7 +149,7 @@ function parseSubmitResponse(response) {
|
|
|
112
149
|
throw new ApiException(`cannot recognize response ${response}`);
|
|
113
150
|
}
|
|
114
151
|
|
|
115
|
-
/** Client for the CapSkip local captcha solver (image, reCAPTCHA, Turnstile). */
|
|
152
|
+
/** Client for the CapSkip local captcha solver (image, reCAPTCHA, Turnstile, GeeTest v3). */
|
|
116
153
|
class CapSkip {
|
|
117
154
|
constructor({
|
|
118
155
|
apiKey = 'capskip',
|
|
@@ -167,6 +204,32 @@ class CapSkip {
|
|
|
167
204
|
});
|
|
168
205
|
}
|
|
169
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Solve a GeeTest v3 slider.
|
|
209
|
+
*
|
|
210
|
+
* `gt` is static per site; `challenge` is single-use and expires in about a
|
|
211
|
+
* minute, so fetch a fresh pair immediately before calling this. Pass
|
|
212
|
+
* `api_server` when the site uses a non-default GeeTest API server domain.
|
|
213
|
+
*
|
|
214
|
+
* The result carries the raw answer as `code` (a JSON string) plus the parsed
|
|
215
|
+
* `challenge`, `validate`, and `seccode` fields to post back to the target site.
|
|
216
|
+
*/
|
|
217
|
+
async geetest(gt, challenge, url, options = {}) {
|
|
218
|
+
// Like reCAPTCHA, this is a real browser solve (load, slide, verify) and can
|
|
219
|
+
// retry internally, so it gets the longer of the two timeouts unless the
|
|
220
|
+
// caller asked for a specific one.
|
|
221
|
+
const result = await this.solve({
|
|
222
|
+
timeout: this.recaptchaTimeout,
|
|
223
|
+
gt,
|
|
224
|
+
challenge,
|
|
225
|
+
url,
|
|
226
|
+
...options,
|
|
227
|
+
method: 'geetest',
|
|
228
|
+
poll_json: 1,
|
|
229
|
+
});
|
|
230
|
+
return applyGeetestSolution(result);
|
|
231
|
+
}
|
|
232
|
+
|
|
170
233
|
async solve(options = {}) {
|
|
171
234
|
const {
|
|
172
235
|
timeout = 0,
|
|
@@ -252,6 +315,9 @@ class CapSkip {
|
|
|
252
315
|
if (method === 'turnstile') {
|
|
253
316
|
return prepareSubmitParams(params, 'turnstile');
|
|
254
317
|
}
|
|
318
|
+
if (method === 'geetest') {
|
|
319
|
+
return prepareSubmitParams(params, 'geetest');
|
|
320
|
+
}
|
|
255
321
|
return applyProxy(applyParamAliases(params));
|
|
256
322
|
}
|
|
257
323
|
}
|
|
@@ -263,4 +329,5 @@ module.exports = {
|
|
|
263
329
|
parsePollResponse,
|
|
264
330
|
parseSubmitResponse,
|
|
265
331
|
applyPollResult,
|
|
332
|
+
applyGeetestSolution,
|
|
266
333
|
};
|
package/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Type definitions for the CapSkip Node.js SDK.
|
|
2
2
|
|
|
3
|
-
/** Proxy passed to reCAPTCHA / Turnstile solves. */
|
|
3
|
+
/** Proxy passed to reCAPTCHA / Turnstile / GeeTest solves. */
|
|
4
4
|
export interface Proxy {
|
|
5
5
|
/** Proxy type: `HTTP`, `HTTPS`, `SOCKS5`, or `SOCKS5H`. */
|
|
6
6
|
type: string;
|
|
@@ -18,7 +18,7 @@ export interface CapSkipOptions {
|
|
|
18
18
|
port?: number;
|
|
19
19
|
/** Seconds to poll an image captcha before timing out. Default `120`. */
|
|
20
20
|
defaultTimeout?: number;
|
|
21
|
-
/** Seconds to poll reCAPTCHA / Turnstile before timing out. Default `300`. */
|
|
21
|
+
/** Seconds to poll reCAPTCHA / Turnstile / GeeTest before timing out. Default `300`. */
|
|
22
22
|
recaptchaTimeout?: number;
|
|
23
23
|
/** Max seconds between polls; starts at `0.25` and backs off to this. Default `5`. */
|
|
24
24
|
pollingInterval?: number;
|
|
@@ -28,10 +28,20 @@ export interface CapSkipOptions {
|
|
|
28
28
|
export interface SolveResult {
|
|
29
29
|
/** CapSkip's internal id for this solve. */
|
|
30
30
|
captchaId: string;
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The solution: recognized text for images, a token otherwise. For GeeTest
|
|
33
|
+
* this is the raw JSON string CapSkip returns — prefer the parsed
|
|
34
|
+
* `challenge` / `validate` / `seccode` fields below.
|
|
35
|
+
*/
|
|
32
36
|
code: string;
|
|
33
37
|
/** Turnstile only — the User-Agent to use when submitting the token. */
|
|
34
38
|
userAgent?: string;
|
|
39
|
+
/** GeeTest only — the `geetest_challenge` value to post back. */
|
|
40
|
+
challenge?: string;
|
|
41
|
+
/** GeeTest only — the `geetest_validate` value to post back. */
|
|
42
|
+
validate?: string;
|
|
43
|
+
/** GeeTest only — the `geetest_seccode` value to post back. */
|
|
44
|
+
seccode?: string;
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
/** Extra options for {@link CapSkip.normal}. */
|
|
@@ -84,6 +94,23 @@ export interface TurnstileOptions {
|
|
|
84
94
|
[key: string]: unknown;
|
|
85
95
|
}
|
|
86
96
|
|
|
97
|
+
/** Extra options for {@link CapSkip.geetest}. */
|
|
98
|
+
export interface GeetestOptions {
|
|
99
|
+
/** GeeTest API server domain (alias for `api_server`). */
|
|
100
|
+
apiServer?: string;
|
|
101
|
+
/** GeeTest API server domain (alias for `api_server`). */
|
|
102
|
+
api_subdomain?: string;
|
|
103
|
+
/** GeeTest API server domain, e.g. `"api-na.geetest.com"`. */
|
|
104
|
+
api_server?: string;
|
|
105
|
+
/** `1` to request the raw JSON response from CapSkip. */
|
|
106
|
+
json?: number;
|
|
107
|
+
/** Proxy used for solving. */
|
|
108
|
+
proxy?: Proxy | string;
|
|
109
|
+
/** Proxy type when `proxy` is a bare string. */
|
|
110
|
+
proxytype?: string;
|
|
111
|
+
[key: string]: unknown;
|
|
112
|
+
}
|
|
113
|
+
|
|
87
114
|
/** Options for the {@link CapSkip.solve} manual workflow. */
|
|
88
115
|
export interface SolveOptions {
|
|
89
116
|
/** Poll timeout in seconds (falls back to the configured default). */
|
|
@@ -111,6 +138,18 @@ export class CapSkip {
|
|
|
111
138
|
recaptcha(sitekey: string, url: string, options?: RecaptchaOptions): Promise<SolveResult>;
|
|
112
139
|
/** Solve Cloudflare Turnstile (widget or challenge page). */
|
|
113
140
|
turnstile(sitekey: string, url: string, options?: TurnstileOptions): Promise<SolveResult>;
|
|
141
|
+
/**
|
|
142
|
+
* Solve a GeeTest v3 slider.
|
|
143
|
+
*
|
|
144
|
+
* `gt` is static per site; `challenge` is single-use and expires in about a
|
|
145
|
+
* minute, so fetch a fresh pair immediately before calling this.
|
|
146
|
+
*/
|
|
147
|
+
geetest(
|
|
148
|
+
gt: string,
|
|
149
|
+
challenge: string,
|
|
150
|
+
url: string,
|
|
151
|
+
options?: GeetestOptions,
|
|
152
|
+
): Promise<SolveResult>;
|
|
114
153
|
/** Submit then poll to completion. Used by the higher-level solve methods. */
|
|
115
154
|
solve(options?: SolveOptions): Promise<SolveResult>;
|
|
116
155
|
/** Submit a captcha without polling; resolves to the captcha id. */
|