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/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. [Using a proxy](#8-using-a-proxy)
16
- 9. [Concurrency](#9-concurrency)
17
- 10. [The manual workflow](#10-the-manual-workflow)
18
- 11. [Return values](#11-return-values)
19
- 12. [Error handling](#12-error-handling)
20
- 13. [End-to-end: solve and submit](#13-end-to-end-solve-and-submit)
21
- 14. [Parameter reference](#14-parameter-reference)
22
- 15. [Best practices](#15-best-practices)
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 Turnstile.
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. Using a proxy
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 Turnstile. Pass the proxy as an object with `type` and `uri`:
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`. The `uri` may include
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
- ## 9. Concurrency
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
- ## 10. The manual workflow
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
- ## 11. Return values
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
- ## 12. Error handling
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
- ## 13. End-to-end: solve and submit
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
- ## 14. Parameter reference
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
- ## 15. Best practices
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
- - **Solve and submit from the same IP** (same proxy) for reCAPTCHA and Turnstile.
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,103 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Solve an ALTCHA proof-of-work challenge.
5
+ *
6
+ * ALTCHA is not a recognition captcha -- there is nothing to read. The site
7
+ * issues a challenge and the browser must brute-force a number that satisfies
8
+ * it. CapSkip does that work for you, in milliseconds.
9
+ *
10
+ * You need the challenge, in one of two forms:
11
+ *
12
+ * * `challengeUrl` - the endpoint that serves it; CapSkip fetches it for you
13
+ * * `challengeJson` - the challenge document itself, if you already have it
14
+ *
15
+ * To find them, open DevTools -> Network on the target page and look for the
16
+ * request the `<altcha-widget>` makes for its challenge (often something like
17
+ * `/altcha/challenge`). The request URL is your `challengeUrl`; its JSON
18
+ * response is your `challengeJson`.
19
+ *
20
+ * Note the widget attribute that names the endpoint changed between versions:
21
+ * v1/v2 use `challengeurl="..."`, while v3+ uses `challenge="..."` for both a
22
+ * URL and inline data. Read the page source rather than assuming.
23
+ *
24
+ * Challenges expire fast -- some sites inside two minutes -- so fetch one
25
+ * immediately before solving and post the token promptly. An expired challenge
26
+ * is rejected with a bare "verification failed" that looks exactly like a wrong
27
+ * answer.
28
+ *
29
+ * This example issues its own challenge the way a site's server would, so it
30
+ * runs as-is with no third-party dependency. Swap in your target's endpoint to
31
+ * use it for real.
32
+ */
33
+
34
+ const crypto = require('crypto');
35
+
36
+ const { CapSkip } = require('../src');
37
+
38
+ const solver = new CapSkip({
39
+ apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
40
+ host: process.env.CAPSKIP_HOST || '127.0.0.1',
41
+ port: Number(process.env.CAPSKIP_PORT || 8080),
42
+ });
43
+
44
+ const PAGE_URL = 'https://example.com/signup';
45
+
46
+ /**
47
+ * Mint an ALTCHA challenge, exactly as a site's own server would.
48
+ *
49
+ * Replace this with a fetch of your target's challenge endpoint -- or skip it
50
+ * entirely and pass `challengeUrl` so CapSkip does the fetching.
51
+ */
52
+ function issueChallenge(number = 54321) {
53
+ const salt = `${crypto.randomBytes(12).toString('hex')}?expires=${
54
+ Math.floor(Date.now() / 1000) + 600}`;
55
+ return {
56
+ algorithm: 'SHA-256',
57
+ challenge: crypto.createHash('sha256').update(`${salt}${number}`).digest('hex'),
58
+ salt,
59
+ signature: '0'.repeat(64),
60
+ maxnumber: 100000,
61
+ };
62
+ }
63
+
64
+ (async () => {
65
+ // --- Option A: you already have the challenge document ---------------------
66
+ // No network request at all: CapSkip solves it locally.
67
+ const result = await solver.altcha(PAGE_URL, { challengeJson: issueChallenge() });
68
+
69
+ console.log('Captcha ID:', result.captchaId);
70
+ console.log('Number: ', result.number);
71
+ console.log('Token: ', `${result.token.slice(0, 60)}...`);
72
+
73
+ // --- Option B: let CapSkip fetch the challenge ------------------------------
74
+ // Point it at the endpoint the widget calls. Add `proxy` if the endpoint
75
+ // should be fetched from a particular IP -- the proxy is used only for that
76
+ // fetch, never for the solve itself.
77
+ //
78
+ // const result = await solver.altcha(PAGE_URL, {
79
+ // challengeUrl: 'https://example.com/captcha/api/altcha/challenge',
80
+ // proxy: { type: 'HTTP', uri: 'login:password@1.2.3.4:8080' },
81
+ // });
82
+
83
+ // Post the token back in the form field the widget uses, named `altcha`:
84
+ //
85
+ // await fetch(SIGNUP_URL, {
86
+ // method: 'POST',
87
+ // body: new URLSearchParams({
88
+ // email: 'someone@example.com',
89
+ // altcha: result.token,
90
+ // }),
91
+ // });
92
+ //
93
+ // Do not re-encode, trim or re-order it. The token is base64 of a JSON
94
+ // document whose fields are covered by the server's HMAC signature, so any
95
+ // modification invalidates it.
96
+
97
+ // `code` holds the same string as `token`, which is what you forward if you
98
+ // are porting code written against another solver's API.
99
+ console.assert(result.code === result.token);
100
+ })().catch((err) => {
101
+ console.error(err);
102
+ process.exit(1);
103
+ });
@@ -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,7 +1,7 @@
1
1
  {
2
2
  "name": "capskip",
3
- "version": "1.0.2",
4
- "description": "Node.js SDK for the CapSkip local captcha solver",
3
+ "version": "1.2.0",
4
+ "description": "Captcha solver for Node.js: solve reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest and image captchas with CapSkip, a local unlimited captcha solver with no per-solve fees.",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
7
7
  "exports": {
@@ -23,9 +23,31 @@
23
23
  "keywords": [
24
24
  "captcha",
25
25
  "captcha-solver",
26
+ "captcha-solving",
27
+ "captcha-bypass",
28
+ "unlimited-captcha-solver",
29
+ "local-captcha-solver",
30
+ "free-captcha-solver",
26
31
  "recaptcha",
32
+ "recaptcha-solver",
33
+ "recaptcha-v2",
34
+ "recaptcha-v3",
35
+ "recaptcha-enterprise",
27
36
  "cloudflare",
28
37
  "turnstile",
38
+ "turnstile-solver",
39
+ "cloudflare-turnstile",
40
+ "geetest",
41
+ "geetest-solver",
42
+ "image-captcha",
43
+ "ocr",
44
+ "2captcha-alternative",
45
+ "anti-captcha-alternative",
46
+ "puppeteer",
47
+ "playwright",
48
+ "selenium",
49
+ "browser-automation",
50
+ "web-scraping",
29
51
  "capskip",
30
52
  "automation"
31
53
  ],
package/src/apiParams.js CHANGED
@@ -21,12 +21,33 @@ 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
+ const ALTCHA_SUBMIT = new Set([
30
+ 'method', 'pageurl', 'challenge_url', 'challenge_json', 'json',
31
+ 'proxy', 'proxytype',
32
+ ]);
33
+
34
+ // The only values CapSkip maps to a proxy scheme; it answers
35
+ // ERROR_BAD_PARAMETERS for anything else, SOCKS4 included. Matched
36
+ // case-insensitively, as the server does.
37
+ const PROXY_TYPES = ['HTTP', 'HTTPS', 'SOCKS5', 'SOCKS5H'];
38
+
24
39
  const PARAM_ALIASES = {
25
40
  url: 'pageurl',
26
41
  score: 'min_score',
27
42
  minScore: 'min_score',
28
43
  datas: 'data-s',
29
44
  data_s: 'data-s',
45
+ apiServer: 'api_server',
46
+ api_subdomain: 'api_server',
47
+ challengeUrl: 'challenge_url',
48
+ challengeURL: 'challenge_url',
49
+ challengeJson: 'challenge_json',
50
+ challengeJSON: 'challenge_json',
30
51
  };
31
52
 
32
53
  function has(obj, key) {
@@ -145,6 +166,86 @@ function validateTurnstileSubmit(params) {
145
166
  }
146
167
  }
147
168
 
169
+ function validateGeetestSubmit(params) {
170
+ // All three are documented as required. gt is static per site, challenge is
171
+ // single-use and expires in about a minute; without them CapSkip answers
172
+ // ERROR_BAD_PARAMETERS, and without pageurl ERROR_PAGEURL. Fail locally so a
173
+ // missing value does not cost a round-trip.
174
+ for (const key of ['gt', 'challenge', 'pageurl']) {
175
+ if (!params[key]) {
176
+ throw new ValidationException(`'${key}' is required for GeeTest v3.`);
177
+ }
178
+ }
179
+
180
+ const unknown = unknownKeys(params, GEETEST_SUBMIT);
181
+ if (unknown.length > 0) {
182
+ throw new ValidationException(
183
+ `Unsupported parameters for GeeTest: ${reprList(unknown)}.`,
184
+ );
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Drop unset challenge params and serialize an inline challenge document.
190
+ *
191
+ * `altcha(url, { challengeUrl, challengeJson })` is normally called with one of
192
+ * the two left out, and the form body can only carry a string -- so a document
193
+ * passed as an object is serialized rather than stringified into
194
+ * "[object Object]". Mirrors the server, which reads a JSON-body `null` as
195
+ * "not sent".
196
+ */
197
+ function normalizeAltchaSubmit(params) {
198
+ const out = {};
199
+ for (const [key, value] of Object.entries(params)) {
200
+ if (value !== undefined && value !== null) {
201
+ out[key] = value;
202
+ }
203
+ }
204
+
205
+ const challenge = out.challenge_json;
206
+ if (typeof challenge === 'object') {
207
+ out.challenge_json = JSON.stringify(challenge);
208
+ }
209
+
210
+ return out;
211
+ }
212
+
213
+ function validateAltchaSubmit(params) {
214
+ if (!params.pageurl) {
215
+ throw new ValidationException("'pageurl' is required for ALTCHA.");
216
+ }
217
+
218
+ // CapSkip answers ERROR_BAD_PARAMETERS when neither is sent. Sending both is
219
+ // deliberately allowed -- the inline document simply wins, because fetching
220
+ // would only re-obtain what the caller already supplied.
221
+ if (!params.challenge_url && !params.challenge_json) {
222
+ throw new ValidationException(
223
+ "ALTCHA needs a challenge: pass 'challenge_url' for CapSkip to fetch it, "
224
+ + "or 'challenge_json' with the challenge document itself.",
225
+ );
226
+ }
227
+
228
+ const unknown = unknownKeys(params, ALTCHA_SUBMIT);
229
+ if (unknown.length > 0) {
230
+ throw new ValidationException(
231
+ `Unsupported parameters for ALTCHA: ${reprList(unknown)}.`,
232
+ );
233
+ }
234
+ }
235
+
236
+ function validateProxyType(params) {
237
+ const proxytype = params.proxytype;
238
+ if (proxytype === undefined || proxytype === null || proxytype === '') {
239
+ return;
240
+ }
241
+ if (!PROXY_TYPES.includes(String(proxytype).toUpperCase())) {
242
+ throw new ValidationException(
243
+ `Unsupported proxytype '${proxytype}'. `
244
+ + `CapSkip accepts: ${PROXY_TYPES.join(', ')}.`,
245
+ );
246
+ }
247
+ }
248
+
148
249
  function prepareSubmitParams(params, captchaType, version = 'v2') {
149
250
  let prepared = applyParamAliases(params);
150
251
  prepared = applyProxy(prepared);
@@ -155,6 +256,16 @@ function prepareSubmitParams(params, captchaType, version = 'v2') {
155
256
  validateRecaptchaSubmit(prepared, version);
156
257
  } else if (captchaType === 'turnstile') {
157
258
  validateTurnstileSubmit(prepared);
259
+ } else if (captchaType === 'geetest') {
260
+ validateGeetestSubmit(prepared);
261
+ } else if (captchaType === 'altcha') {
262
+ prepared = normalizeAltchaSubmit(prepared);
263
+ validateAltchaSubmit(prepared);
264
+ }
265
+
266
+ // Skipped for 'normal', which rejects proxy outright with a clearer message.
267
+ if (captchaType !== 'normal') {
268
+ validateProxyType(prepared);
158
269
  }
159
270
 
160
271
  return prepared;
@@ -165,11 +276,18 @@ module.exports = {
165
276
  RECAPTCHA_V2_SUBMIT,
166
277
  RECAPTCHA_V3_SUBMIT,
167
278
  TURNSTILE_SUBMIT,
279
+ GEETEST_SUBMIT,
280
+ ALTCHA_SUBMIT,
281
+ PROXY_TYPES,
168
282
  PARAM_ALIASES,
169
283
  applyParamAliases,
170
284
  applyProxy,
171
285
  validateNormalSubmit,
172
286
  validateRecaptchaSubmit,
173
287
  validateTurnstileSubmit,
288
+ validateGeetestSubmit,
289
+ normalizeAltchaSubmit,
290
+ validateAltchaSubmit,
291
+ validateProxyType,
174
292
  prepareSubmitParams,
175
293
  };
package/src/index.js CHANGED
@@ -27,5 +27,5 @@ module.exports = {
27
27
  NetworkException,
28
28
  ApiException,
29
29
  TimeoutException,
30
- version: '1.0.2',
30
+ version: '1.1.0',
31
31
  };