capskip 1.5.0 → 1.6.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 +30 -0
- package/README.md +59 -7
- package/docs/API_REFERENCE.md +94 -0
- package/examples/hcaptcha.js +98 -0
- package/package.json +5 -2
- package/src/apiParams.js +87 -0
- package/src/index.js +1 -1
- package/src/solver.js +79 -1
- package/types/index.d.ts +63 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ 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.6.0] - 2026-11-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **hCaptcha support** via `hcaptcha(sitekey, url, options)`, sent as
|
|
13
|
+
`method=hcaptcha`. The arguments follow 2Captcha's `hcaptcha({ sitekey,
|
|
14
|
+
pageurl, ... })` in this SDK's usual shape; `pageurl` in `options` is accepted
|
|
15
|
+
in place of `url`. Checkbox, invisible and hCaptcha Enterprise widgets all use
|
|
16
|
+
this one method.
|
|
17
|
+
- Optional parameters: `invisible` (`1` or `true` for an invisible widget),
|
|
18
|
+
`data` (the Enterprise `rqdata`), `userAgent` / `useragent`, and `proxy`.
|
|
19
|
+
- The result carries the token as both `code` and `token`, `respKey` (always
|
|
20
|
+
present, empty when the widget handed out none) and `userAgent`, the
|
|
21
|
+
User-Agent the token was earned with. Polled results now carry a `respKey`
|
|
22
|
+
field whenever the server sends one.
|
|
23
|
+
- TypeScript definitions for `HCaptchaOptions` and the `hcaptcha()` method;
|
|
24
|
+
`SolveResult.respKey` now documents hCaptcha.
|
|
25
|
+
- `examples/hcaptcha.js`.
|
|
26
|
+
|
|
27
|
+
### Notes
|
|
28
|
+
|
|
29
|
+
- Refused locally, before a round-trip: a missing or blank sitekey, a page URL
|
|
30
|
+
that is missing or not an absolute http(s) address, an `invisible` value other
|
|
31
|
+
than 0/1/true/false, and any parameter the CapSkip hCaptcha API does not take
|
|
32
|
+
(2Captcha's `domain` among them).
|
|
33
|
+
- `invisible` is sent as `1`, or left out when off, so a boolean never reaches
|
|
34
|
+
the server as the string `true` or `false`. An optional passed as `''` is
|
|
35
|
+
treated as not sent.
|
|
36
|
+
- hCaptcha uses `recaptchaTimeout`: the widget is passed in a real browser.
|
|
37
|
+
|
|
8
38
|
## [1.5.0] - 2026-11-02
|
|
9
39
|
|
|
10
40
|
### Added
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://github.com/capskip/capskip-node/actions/workflows/ci.yml)
|
|
7
7
|
|
|
8
|
-
**Solve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, GeeTest, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF, DataDome and image captchas from Node.js.**
|
|
8
|
+
**Solve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, GeeTest, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF, DataDome, hCaptcha and image captchas from Node.js.**
|
|
9
9
|
|
|
10
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
11
|
|
|
@@ -19,7 +19,7 @@ npm install capskip
|
|
|
19
19
|
|
|
20
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
21
|
|
|
22
|
-
This SDK is a thin wrapper over that API, with the method names you would expect: `normal()`, `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()`, `amazonWaf()`, `dataDome()`. Nothing leaves your network, and there is no credit balance to keep an eye on.
|
|
22
|
+
This SDK is a thin wrapper over that API, with the method names you would expect: `normal()`, `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()`, `amazonWaf()`, `dataDome()`, `hcaptcha()`. Nothing leaves your network, and there is no credit balance to keep an eye on.
|
|
23
23
|
|
|
24
24
|
## Supported captcha types
|
|
25
25
|
|
|
@@ -41,8 +41,9 @@ This SDK is a thin wrapper over that API, with the method names you would expect
|
|
|
41
41
|
| **AWS WAF solver** (challenge page) | `solver.amazonWaf(sitekey, url, { iv, context })` |
|
|
42
42
|
| AWS WAF (widget) | `solver.amazonWaf(sitekey, url, { jsapiScript })` |
|
|
43
43
|
| **DataDome solver** (slider and device check) | `solver.dataDome(captchaUrl, url)` |
|
|
44
|
+
| **hCaptcha solver** (checkbox, invisible and Enterprise) | `solver.hcaptcha(sitekey, url)` |
|
|
44
45
|
|
|
45
|
-
**
|
|
46
|
+
**FunCaptcha/Arkose is not supported.** hCaptcha and reCAPTCHA both put a `data-sitekey` on the widget, so tell them apart before choosing a method: `class="h-captcha"` or a `js.hcaptcha.com` script means `hcaptcha()`, `class="g-recaptcha"` means `recaptcha()`.
|
|
46
47
|
|
|
47
48
|
Point the SDK at the [live captcha demo pages](https://capskip.com/captcha-demo/) to sanity-check your setup against real widgets.
|
|
48
49
|
|
|
@@ -138,7 +139,7 @@ const solver = new CapSkip({
|
|
|
138
139
|
port: 8080, // CapSkip port from app settings
|
|
139
140
|
defaultTimeout: 120, // seconds — image captcha polling timeout
|
|
140
141
|
recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest / CaptchaFox /
|
|
141
|
-
// Friendly Captcha / AWS WAF / DataDome polling timeout
|
|
142
|
+
// Friendly Captcha / AWS WAF / DataDome / hCaptcha polling timeout
|
|
142
143
|
// (ALTCHA and Capy use defaultTimeout — neither is a browser solve)
|
|
143
144
|
pollingInterval: 5, // max seconds between res.php polls (starts at 0.25s, backs off to this)
|
|
144
145
|
});
|
|
@@ -420,6 +421,56 @@ A block page whose `dd` object carries `'t':'bv'` means DataDome has banned the
|
|
|
420
421
|
address that fetched it; no solve from there can pass, so it is refused before
|
|
421
422
|
the request is sent. Change the address and request the page again.
|
|
422
423
|
|
|
424
|
+
### hCaptcha
|
|
425
|
+
|
|
426
|
+
hCaptcha is the checkbox widget behind many sign-up, login and checkout forms.
|
|
427
|
+
CapSkip passes the widget for the sitekey and page you send and returns the
|
|
428
|
+
token. The arguments follow 2Captcha's `hcaptcha({ sitekey, pageurl, ... })` in
|
|
429
|
+
this SDK's usual shape, `hcaptcha(sitekey, url, options)`, and invisible and
|
|
430
|
+
Enterprise widgets use the same method.
|
|
431
|
+
|
|
432
|
+
```js
|
|
433
|
+
const result = await solver.hcaptcha(
|
|
434
|
+
'a5f74b19-9e45-40e0-b45d-47ff91b7a6c2', // data-sitekey of the widget
|
|
435
|
+
'https://accounts.hcaptcha.com/demo', // the page the widget is on
|
|
436
|
+
{
|
|
437
|
+
// invisible: 1, // for a widget with data-size="invisible"
|
|
438
|
+
// data: '...', // hCaptcha Enterprise: the rqdata the page passes to hcaptcha.setData()
|
|
439
|
+
// userAgent: 'Mozilla/5.0 ...',// the User-Agent you will submit the form with
|
|
440
|
+
// proxy: { type: 'HTTP', uri: 'login:password@203.0.113.10:8080' },
|
|
441
|
+
},
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
result.token; // put it in h-captcha-response (and g-recaptcha-response, if present)
|
|
445
|
+
result.respKey; // the second value hCaptcha hands the widget; usually empty or unused
|
|
446
|
+
result.userAgent; // the User-Agent the token was earned with
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
The sitekey is on the widget container as `data-sitekey`, in the `sitekey`
|
|
450
|
+
option of `hcaptcha.render()`, or in the `sitekey=` parameter of the hCaptcha
|
|
451
|
+
iframe's address. An Enterprise `rqdata` value is issued for one challenge and
|
|
452
|
+
expires quickly, so read a fresh one for every task.
|
|
453
|
+
|
|
454
|
+
Put the token in the form's `h-captcha-response` field (and `g-recaptcha-response`,
|
|
455
|
+
if the form has one), or pass it to the widget's `data-callback`, and submit with
|
|
456
|
+
the User-Agent from the result. Tokens are single use and expire about two
|
|
457
|
+
minutes after they are issued, so submit each one soon.
|
|
458
|
+
|
|
459
|
+
```js
|
|
460
|
+
await page.evaluate((token) => {
|
|
461
|
+
document.querySelector('[name="h-captcha-response"]').value = token;
|
|
462
|
+
const g = document.querySelector('[name="g-recaptcha-response"]');
|
|
463
|
+
if (g) g.value = token;
|
|
464
|
+
}, result.token);
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
A `userAgent` you send is used only when it matches the browser CapSkip solves
|
|
468
|
+
with; the result always names the one the token was actually earned with. At
|
|
469
|
+
volume, hCaptcha scores the network a token comes from as well as the browser,
|
|
470
|
+
so send a proxy with each task or configure a pool in the CapSkip app (Settings,
|
|
471
|
+
Captcha types, hCaptcha, Configure Proxies). If a strict site still refuses the
|
|
472
|
+
tokens, turn on High score mode for hCaptcha in the same settings page.
|
|
473
|
+
|
|
423
474
|
### With a proxy (every method except image captcha)
|
|
424
475
|
|
|
425
476
|
```js
|
|
@@ -523,7 +574,8 @@ expanded into `captcha_voucher` and `existing_token`. ALTCHA adds `token` (the s
|
|
|
523
574
|
string as `code`) and `number`, the counter that solved it. DataDome adds
|
|
524
575
|
`cookie`, the `datadome=...` pair alone, while `code` keeps the cookie with its
|
|
525
576
|
attributes; it also carries `userAgent`, and `proxy` when CapSkip chose the
|
|
526
|
-
proxy.
|
|
577
|
+
proxy. hCaptcha adds `token`, the same string as `code`, `respKey` (always
|
|
578
|
+
present, empty when the widget handed out none) and `userAgent`.
|
|
527
579
|
|
|
528
580
|
---
|
|
529
581
|
|
|
@@ -568,7 +620,7 @@ const result: SolveResult = await solver.recaptcha('...', 'https://example.com')
|
|
|
568
620
|
|
|
569
621
|
### How do I solve a captcha in Node.js?
|
|
570
622
|
|
|
571
|
-
Install the CapSkip desktop app, `npm install capskip`, then call the method that matches the widget — `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()`, `amazonWaf()`, `dataDome()` or `normal()`. Each returns a Promise that resolves once CapSkip has an answer, giving you a token (a cookie for DataDome), or the recognized text in the case of an image captcha.
|
|
623
|
+
Install the CapSkip desktop app, `npm install capskip`, then call the method that matches the widget — `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()`, `amazonWaf()`, `dataDome()`, `hcaptcha()` or `normal()`. Each returns a Promise that resolves once CapSkip has an answer, giving you a token (a cookie for DataDome), or the recognized text in the case of an image captcha.
|
|
572
624
|
|
|
573
625
|
### Is this a free captcha solver?
|
|
574
626
|
|
|
@@ -576,7 +628,7 @@ The SDK itself is MIT-licensed and free. Solving needs the CapSkip app, which is
|
|
|
576
628
|
|
|
577
629
|
### Which captchas can it solve?
|
|
578
630
|
|
|
579
|
-
reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF, DataDome, and image/text captchas. Not
|
|
631
|
+
reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF, DataDome, hCaptcha (including invisible and Enterprise), and image/text captchas. Not FunCaptcha/Arkose.
|
|
580
632
|
|
|
581
633
|
### Does it work with Puppeteer and Playwright?
|
|
582
634
|
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -29,6 +29,7 @@ method returns a `Promise`.
|
|
|
29
29
|
| Friendly Captcha (proof-of-work) | `friendlyCaptcha()` | `friendly_captcha` |
|
|
30
30
|
| AWS WAF (challenge page or widget) | `amazonWaf()` | `amazon_waf` |
|
|
31
31
|
| DataDome (slider and device check) | `dataDome()` | `datadome` |
|
|
32
|
+
| hCaptcha (checkbox, invisible, Enterprise) | `hcaptcha()` | `hcaptcha` |
|
|
32
33
|
|
|
33
34
|
**Proxy** is supported for every method except image captcha. For ALTCHA the
|
|
34
35
|
proxy is used only for the `challengeUrl` fetch; for Capy, only for the
|
|
@@ -846,6 +847,85 @@ Uses `recaptchaTimeout`: the challenge is passed in a real browser.
|
|
|
846
847
|
|
|
847
848
|
---
|
|
848
849
|
|
|
850
|
+
## 12. hCaptcha — `hcaptcha(sitekey, url, { ... })`
|
|
851
|
+
|
|
852
|
+
hCaptcha is the checkbox widget behind many sign-up, login and checkout forms.
|
|
853
|
+
CapSkip passes the widget for the sitekey and page you send and returns the
|
|
854
|
+
token, ready to submit with the form. Invisible widgets and hCaptcha Enterprise
|
|
855
|
+
use the same method. Public docs: <https://capskip.com/api-docs/#hcaptcha>.
|
|
856
|
+
|
|
857
|
+
The arguments follow 2Captcha's `hcaptcha({ sitekey, pageurl, ... })` in this
|
|
858
|
+
SDK's usual shape, so code written against it ports directly. `pageurl` in the
|
|
859
|
+
options object is accepted in place of `url`.
|
|
860
|
+
|
|
861
|
+
### POST `/in.php`
|
|
862
|
+
|
|
863
|
+
| Parameter | Type | Required | Description |
|
|
864
|
+
|---|---|---|---|
|
|
865
|
+
| `key` | string | Yes | CapSkip API key |
|
|
866
|
+
| `method` | string | Yes | `hcaptcha` |
|
|
867
|
+
| `sitekey` | string | Yes | The widget's sitekey (`data-sitekey`) |
|
|
868
|
+
| `pageurl` | string | Yes | Full http or https URL of the page the widget is on |
|
|
869
|
+
| `invisible` | int | No | `1` for an invisible widget (`data-size="invisible"`). The SDK takes `1`/`0`, `true`/`false` or `'1'`/`'0'`/`'true'`/`'false'`, sends `1`, and leaves it out when off |
|
|
870
|
+
| `data` | string | No | hCaptcha Enterprise: the `rqdata` value the page passes to `hcaptcha.setData()`. Single use; fetch a fresh one per task |
|
|
871
|
+
| `userAgent` | string | No | The User-Agent you will submit the form with. Used only when it matches CapSkip's browser. Sent as `useragent` |
|
|
872
|
+
| `json` | int | No | `0` plain text, `1` JSON |
|
|
873
|
+
| `proxy` | string | No | Proxy address |
|
|
874
|
+
| `proxytype` | string | No | `HTTP`, `HTTPS`, `SOCKS5` or `SOCKS5H` |
|
|
875
|
+
|
|
876
|
+
An optional left `undefined`, `null` or `''` is not sent.
|
|
877
|
+
|
|
878
|
+
Refused locally with `ValidationException`, as CapSkip would refuse them: a
|
|
879
|
+
missing or blank sitekey (`ERROR_RECAPTCHA_INVALID_SITEKEY`), a `pageurl` that
|
|
880
|
+
is missing or not an absolute http(s) address (`ERROR_PAGEURL`), an `invisible`
|
|
881
|
+
value other than 0/1/true/false, and any parameter not in the table, 2Captcha's
|
|
882
|
+
`domain` among them.
|
|
883
|
+
|
|
884
|
+
### GET `/res.php`
|
|
885
|
+
|
|
886
|
+
The SDK polls with `json=1`, where CapSkip returns the token in `request` with
|
|
887
|
+
`respKey` and the User-Agent the token was earned with:
|
|
888
|
+
|
|
889
|
+
```json
|
|
890
|
+
{
|
|
891
|
+
"status": 1,
|
|
892
|
+
"request": "P1_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.hadwYXNza2V5xQZ2...f3Kq8nV2",
|
|
893
|
+
"respKey": "E0_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjA0...9wB1",
|
|
894
|
+
"useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36"
|
|
895
|
+
}
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
(In plain-text mode the User-Agent arrives in the `X-HCaptcha-User-Agent`
|
|
899
|
+
response header.) A task that hCaptcha refused the sitekey for fails with
|
|
900
|
+
`ERROR_RECAPTCHA_INVALID_SITEKEY`, one whose proxy could not be reached with
|
|
901
|
+
`ERROR_PROXY_CONNECT_REFUSED`; resubmitting the same task would fail again.
|
|
902
|
+
|
|
903
|
+
### SDK usage
|
|
904
|
+
|
|
905
|
+
```js
|
|
906
|
+
const result = await solver.hcaptcha(
|
|
907
|
+
'a5f74b19-9e45-40e0-b45d-47ff91b7a6c2',
|
|
908
|
+
'https://accounts.hcaptcha.com/demo',
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
// Invisible Enterprise widget, through a proxy
|
|
912
|
+
const result = await solver.hcaptcha(sitekey, pageUrl, {
|
|
913
|
+
invisible: 1,
|
|
914
|
+
data: rqdata,
|
|
915
|
+
proxy: { type: 'HTTP', uri: 'login:password@203.0.113.10:8080' },
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
form['h-captcha-response'] = result.token;
|
|
919
|
+
form['g-recaptcha-response'] = result.token; // if the form has one
|
|
920
|
+
headers['User-Agent'] = result.userAgent;
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
Tokens are single use and expire about two minutes after they are issued.
|
|
924
|
+
|
|
925
|
+
Uses `recaptchaTimeout`: the widget is passed in a real browser.
|
|
926
|
+
|
|
927
|
+
---
|
|
928
|
+
|
|
849
929
|
## Return value
|
|
850
930
|
|
|
851
931
|
Every solve method resolves to:
|
|
@@ -925,6 +1005,20 @@ only when the task sent no proxy and CapSkip used its own DataDome proxy list:
|
|
|
925
1005
|
}
|
|
926
1006
|
```
|
|
927
1007
|
|
|
1008
|
+
hCaptcha exposes `token`, the same string as `code`, plus `respKey` and the
|
|
1009
|
+
User-Agent the token was earned with. `respKey` is always present, and empty
|
|
1010
|
+
when the widget handed out none:
|
|
1011
|
+
|
|
1012
|
+
```js
|
|
1013
|
+
{
|
|
1014
|
+
captchaId: '12345',
|
|
1015
|
+
code: 'P1_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…',
|
|
1016
|
+
token: 'P1_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…',
|
|
1017
|
+
respKey: 'E0_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9…',
|
|
1018
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) …',
|
|
1019
|
+
}
|
|
1020
|
+
```
|
|
1021
|
+
|
|
928
1022
|
GeeTest additionally expands its answer into `challenge`, `validate`, and
|
|
929
1023
|
`seccode` (`code` keeps the raw JSON string):
|
|
930
1024
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Solve an hCaptcha widget.
|
|
5
|
+
*
|
|
6
|
+
* hCaptcha is the checkbox widget behind many sign-up, login and checkout
|
|
7
|
+
* forms. CapSkip passes the widget in a real browser for the sitekey and page
|
|
8
|
+
* you send and returns the token the widget would have put in the form.
|
|
9
|
+
* Invisible widgets and hCaptcha Enterprise use the same method.
|
|
10
|
+
*
|
|
11
|
+
* You need two values, both read off the target page:
|
|
12
|
+
*
|
|
13
|
+
* * sitekey - the public identifier of the widget, the same for every
|
|
14
|
+
* visitor. Most pages carry it on the container as `data-sitekey`:
|
|
15
|
+
* `<div class="h-captcha" data-sitekey="...">`. When a page builds the
|
|
16
|
+
* widget with a script, look for the `sitekey` option of
|
|
17
|
+
* `hcaptcha.render()`, or the `sitekey=` parameter in the address of the
|
|
18
|
+
* hCaptcha iframe.
|
|
19
|
+
* * url - the full URL of the page the widget is on.
|
|
20
|
+
*
|
|
21
|
+
* A widget with `data-size="invisible"` is invisible: pass `invisible: 1`.
|
|
22
|
+
* Some hCaptcha Enterprise sites pass an `rqdata` value to the widget through
|
|
23
|
+
* `hcaptcha.setData()` before it runs; send it as `data`. It is issued for one
|
|
24
|
+
* challenge and expires quickly, so read a fresh one for every task.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const assert = require('node:assert');
|
|
28
|
+
const { CapSkip } = require('capskip');
|
|
29
|
+
|
|
30
|
+
const solver = new CapSkip({
|
|
31
|
+
apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
|
|
32
|
+
host: process.env.CAPSKIP_HOST || '127.0.0.1',
|
|
33
|
+
port: Number(process.env.CAPSKIP_PORT || 8080),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// hCaptcha's own public demo page and its sitekey.
|
|
37
|
+
const SITEKEY = 'a5f74b19-9e45-40e0-b45d-47ff91b7a6c2';
|
|
38
|
+
const PAGE_URL = 'https://accounts.hcaptcha.com/demo';
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
const result = await solver.hcaptcha(SITEKEY, PAGE_URL);
|
|
42
|
+
|
|
43
|
+
console.log('Captcha ID:', result.captchaId);
|
|
44
|
+
console.log('Token: ', `${result.token.slice(0, 60)}...`);
|
|
45
|
+
console.log('respKey: ', result.respKey ? `${result.respKey.slice(0, 40)}...` : '(empty)');
|
|
46
|
+
console.log('User-Agent:', result.userAgent || '(none reported)');
|
|
47
|
+
|
|
48
|
+
// --- Invisible and Enterprise widgets ------------------------------------
|
|
49
|
+
//
|
|
50
|
+
// const result = await solver.hcaptcha(SITEKEY, PAGE_URL, {
|
|
51
|
+
// invisible: 1, // data-size="invisible"
|
|
52
|
+
// data: rqdata, // the rqdata the page passed to hcaptcha.setData()
|
|
53
|
+
// });
|
|
54
|
+
//
|
|
55
|
+
// --- With a per-request proxy --------------------------------------------
|
|
56
|
+
// CapSkip does not force a proxy. At volume, though, hCaptcha scores the
|
|
57
|
+
// network a token comes from as well as the browser, so send a proxy with
|
|
58
|
+
// each task (or configure a pool in the app: Settings, Captcha types,
|
|
59
|
+
// hCaptcha, Configure Proxies). A proxy sent with a task always takes
|
|
60
|
+
// priority over the pool.
|
|
61
|
+
//
|
|
62
|
+
// const result = await solver.hcaptcha(SITEKEY, PAGE_URL, {
|
|
63
|
+
// proxy: { type: 'HTTP', uri: 'login:password@1.2.3.4:8080' },
|
|
64
|
+
// });
|
|
65
|
+
|
|
66
|
+
// --- Submitting the token ------------------------------------------------
|
|
67
|
+
// The widget writes its token into a hidden textarea named
|
|
68
|
+
// h-captcha-response (and a copy named g-recaptcha-response). Put the token
|
|
69
|
+
// in both and submit the form, or call the widget's data-callback with it.
|
|
70
|
+
// Without a browser, post it as those form fields with the User-Agent from
|
|
71
|
+
// the result:
|
|
72
|
+
//
|
|
73
|
+
// await fetch(FORM_URL, {
|
|
74
|
+
// method: 'POST',
|
|
75
|
+
// headers: { 'User-Agent': result.userAgent },
|
|
76
|
+
// body: new URLSearchParams({
|
|
77
|
+
// email: 'someone@example.com',
|
|
78
|
+
// 'h-captcha-response': result.token,
|
|
79
|
+
// 'g-recaptcha-response': result.token,
|
|
80
|
+
// }),
|
|
81
|
+
// });
|
|
82
|
+
//
|
|
83
|
+
// Tokens are single use and expire about two minutes after they are issued,
|
|
84
|
+
// so submit each one soon and solve again for the next form submission.
|
|
85
|
+
console.log('Form fields:', {
|
|
86
|
+
'h-captcha-response': `${result.token.slice(0, 40)}...`,
|
|
87
|
+
'g-recaptcha-response': `${result.token.slice(0, 40)}...`,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// `code` holds the same string as `token`, which is what you forward if you
|
|
91
|
+
// are porting code written against another solver's API.
|
|
92
|
+
assert.strictEqual(result.code, result.token);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
main().catch((error) => {
|
|
96
|
+
console.error(`${error.name}: ${error.message}`);
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capskip",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Captcha solver for Node.js: solve reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest, AWS WAF, DataDome and image captchas with CapSkip, a local unlimited captcha solver with no per-solve fees.",
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "Captcha solver for Node.js: solve reCAPTCHA v2/v3, Cloudflare Turnstile, hCaptcha, GeeTest, AWS WAF, DataDome 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": {
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
"recaptcha-v2",
|
|
34
34
|
"recaptcha-v3",
|
|
35
35
|
"recaptcha-enterprise",
|
|
36
|
+
"hcaptcha",
|
|
37
|
+
"hcaptcha-solver",
|
|
38
|
+
"hcaptcha-enterprise",
|
|
36
39
|
"cloudflare",
|
|
37
40
|
"turnstile",
|
|
38
41
|
"turnstile-solver",
|
package/src/apiParams.js
CHANGED
|
@@ -59,6 +59,18 @@ const AMAZON_WAF_SCRIPTS = ['challenge_script', 'captcha_script', 'jsapiScript']
|
|
|
59
59
|
// the three AWS WAF script files.
|
|
60
60
|
const AMAZON_WAF_SCRIPT_PATH = /^(\/[A-Za-z0-9_-]+)+\/(challenge|captcha|jsapi)\.js$/;
|
|
61
61
|
|
|
62
|
+
const HCAPTCHA_SUBMIT = new Set([
|
|
63
|
+
'method', 'sitekey', 'pageurl', 'invisible', 'data', 'useragent', 'json',
|
|
64
|
+
'proxy', 'proxytype',
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
// What the server reads as "invisible widget" ("1" or "true") and as its
|
|
68
|
+
// default. Strings are matched case-insensitively. A boolean would otherwise
|
|
69
|
+
// reach the form body as 'true' or 'false', and the server reads 'false' as
|
|
70
|
+
// neither, so every accepted form is normalized to the 1 the server reads.
|
|
71
|
+
const HCAPTCHA_INVISIBLE_ON = [true, 1, '1', 'true'];
|
|
72
|
+
const HCAPTCHA_INVISIBLE_OFF = [false, 0, '0', 'false'];
|
|
73
|
+
|
|
62
74
|
const DATADOME_SUBMIT = new Set([
|
|
63
75
|
'method', 'captcha_url', 'pageurl', 'useragent', 'json', 'proxy', 'proxytype',
|
|
64
76
|
]);
|
|
@@ -648,6 +660,75 @@ function validateDataDomeSubmit(params) {
|
|
|
648
660
|
}
|
|
649
661
|
}
|
|
650
662
|
|
|
663
|
+
/**
|
|
664
|
+
* Drop unset and empty optionals, and send `invisible` as the 1 the server
|
|
665
|
+
* reads. `invisible: 0` is the server's default, so it is not sent at all.
|
|
666
|
+
*/
|
|
667
|
+
function normalizeHCaptchaSubmit(params) {
|
|
668
|
+
const out = {};
|
|
669
|
+
for (const [key, value] of Object.entries(dropUnset(params))) {
|
|
670
|
+
if (value !== '') {
|
|
671
|
+
out[key] = value;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (has(out, 'invisible')) {
|
|
675
|
+
const raw = out.invisible;
|
|
676
|
+
const key = typeof raw === 'string' ? raw.toLowerCase() : raw;
|
|
677
|
+
if (HCAPTCHA_INVISIBLE_ON.includes(key)) {
|
|
678
|
+
out.invisible = 1;
|
|
679
|
+
} else if (HCAPTCHA_INVISIBLE_OFF.includes(key)) {
|
|
680
|
+
delete out.invisible;
|
|
681
|
+
} else {
|
|
682
|
+
const shown = typeof raw === 'string' ? `'${raw}'` : String(raw);
|
|
683
|
+
throw new ValidationException(
|
|
684
|
+
`invisible must be 0 or 1 (or a boolean) for hCaptcha, not ${shown}.`,
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return out;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/** An absolute http(s) URL with a host. */
|
|
692
|
+
function isHttpUrl(url) {
|
|
693
|
+
let parsed;
|
|
694
|
+
try {
|
|
695
|
+
parsed = new URL(url);
|
|
696
|
+
} catch (error) {
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
return (parsed.protocol === 'http:' || parsed.protocol === 'https:')
|
|
700
|
+
&& parsed.hostname !== '';
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function validateHCaptchaSubmit(params) {
|
|
704
|
+
// The server answers ERROR_RECAPTCHA_INVALID_SITEKEY for a missing sitekey
|
|
705
|
+
// and ERROR_PAGEURL for a page URL that is missing or not http(s). Fail
|
|
706
|
+
// locally so neither costs a round-trip.
|
|
707
|
+
const sitekey = params.sitekey;
|
|
708
|
+
if (sitekey === undefined || sitekey === null || !String(sitekey).trim()) {
|
|
709
|
+
throw new ValidationException("'sitekey' is required for hCaptcha.");
|
|
710
|
+
}
|
|
711
|
+
const rawPageurl = params.pageurl;
|
|
712
|
+
const pageurl = rawPageurl === undefined || rawPageurl === null
|
|
713
|
+
? ''
|
|
714
|
+
: String(rawPageurl).trim();
|
|
715
|
+
if (!pageurl) {
|
|
716
|
+
throw new ValidationException("'pageurl' is required for hCaptcha.");
|
|
717
|
+
}
|
|
718
|
+
if (!isHttpUrl(pageurl)) {
|
|
719
|
+
throw new ValidationException(
|
|
720
|
+
`pageurl must be an absolute http or https URL for hCaptcha, not '${pageurl}'.`,
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const unknown = unknownKeys(params, HCAPTCHA_SUBMIT);
|
|
725
|
+
if (unknown.length > 0) {
|
|
726
|
+
throw new ValidationException(
|
|
727
|
+
`Unsupported parameters for hCaptcha: ${reprList(unknown)}.`,
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
651
732
|
function validateProxyType(params) {
|
|
652
733
|
const proxytype = params.proxytype;
|
|
653
734
|
if (proxytype === undefined || proxytype === null || proxytype === '') {
|
|
@@ -691,6 +772,9 @@ function prepareSubmitParams(params, captchaType, version = 'v2') {
|
|
|
691
772
|
} else if (captchaType === 'datadome') {
|
|
692
773
|
prepared = normalizeDataDomeSubmit(prepared);
|
|
693
774
|
validateDataDomeSubmit(prepared);
|
|
775
|
+
} else if (captchaType === 'hcaptcha') {
|
|
776
|
+
prepared = normalizeHCaptchaSubmit(prepared);
|
|
777
|
+
validateHCaptchaSubmit(prepared);
|
|
694
778
|
}
|
|
695
779
|
|
|
696
780
|
// Skipped for 'normal', which rejects proxy outright with a clearer message.
|
|
@@ -716,6 +800,7 @@ module.exports = {
|
|
|
716
800
|
DATADOME_SUBMIT,
|
|
717
801
|
DATADOME_CHALLENGE_DOMAIN,
|
|
718
802
|
DATADOME_CHALLENGE_PATHS,
|
|
803
|
+
HCAPTCHA_SUBMIT,
|
|
719
804
|
CAPY_VERSIONS,
|
|
720
805
|
FRIENDLY_CAPTCHA_VERSIONS,
|
|
721
806
|
PROXY_TYPES,
|
|
@@ -740,6 +825,8 @@ module.exports = {
|
|
|
740
825
|
isDataDomeUserAgent,
|
|
741
826
|
normalizeDataDomeSubmit,
|
|
742
827
|
validateDataDomeSubmit,
|
|
828
|
+
normalizeHCaptchaSubmit,
|
|
829
|
+
validateHCaptchaSubmit,
|
|
743
830
|
validateProxyType,
|
|
744
831
|
prepareSubmitParams,
|
|
745
832
|
};
|
package/src/index.js
CHANGED
package/src/solver.js
CHANGED
|
@@ -89,6 +89,11 @@ function applyPollResult(result, polled) {
|
|
|
89
89
|
if (polled.proxy) {
|
|
90
90
|
result.proxy = polled.proxy;
|
|
91
91
|
}
|
|
92
|
+
// hCaptcha hands the widget a second value next to the token. It is
|
|
93
|
+
// carried even when empty, which is how the server reports "none".
|
|
94
|
+
if (Object.prototype.hasOwnProperty.call(polled, 'respKey')) {
|
|
95
|
+
result.respKey = polled.respKey;
|
|
96
|
+
}
|
|
92
97
|
// ALTCHA's createTask-shaped `solution` object. Carried through so
|
|
93
98
|
// applyAltchaSolution can read the counter the server already worked out,
|
|
94
99
|
// which is the only reliable source for a proof-of-work v2 answer; that
|
|
@@ -325,6 +330,20 @@ function applyTokenSolution(result) {
|
|
|
325
330
|
return result;
|
|
326
331
|
}
|
|
327
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Expose the hCaptcha token as `token`, next to its `respKey`.
|
|
335
|
+
*
|
|
336
|
+
* `code` keeps the token as the server sent it and `token` is the same string,
|
|
337
|
+
* for the `h-captcha-response` field. `respKey` is always present, empty when
|
|
338
|
+
* the widget handed out none, so callers can read it without a key check.
|
|
339
|
+
*/
|
|
340
|
+
function applyHCaptchaSolution(result) {
|
|
341
|
+
applyTokenSolution(result);
|
|
342
|
+
const { respKey } = result;
|
|
343
|
+
result.respKey = typeof respKey === 'string' ? respKey : '';
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
|
|
328
347
|
// CapSkip's in.php returns OK|<id> by default, or {"status":1,"request":"<id>"}
|
|
329
348
|
// when the submit carried json=1. Accept both so submitting with json=1 works.
|
|
330
349
|
function parseSubmitResponse(response) {
|
|
@@ -351,7 +370,7 @@ function parseSubmitResponse(response) {
|
|
|
351
370
|
/**
|
|
352
371
|
* Client for the CapSkip local captcha solver (image, reCAPTCHA, Turnstile,
|
|
353
372
|
* GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF,
|
|
354
|
-
* DataDome).
|
|
373
|
+
* DataDome, hCaptcha).
|
|
355
374
|
*/
|
|
356
375
|
class CapSkip {
|
|
357
376
|
constructor({
|
|
@@ -700,6 +719,61 @@ class CapSkip {
|
|
|
700
719
|
return applyDataDomeSolution(result);
|
|
701
720
|
}
|
|
702
721
|
|
|
722
|
+
/**
|
|
723
|
+
* Solve an hCaptcha widget and return its token.
|
|
724
|
+
*
|
|
725
|
+
* `sitekey` and `url` are required; everything else goes in `options`:
|
|
726
|
+
*
|
|
727
|
+
* - `sitekey` is the widget's `data-sitekey` (or the `sitekey` option of
|
|
728
|
+
* `hcaptcha.render()`).
|
|
729
|
+
* - `url` is the full address of the page the widget is on. `pageurl` in
|
|
730
|
+
* `options` is accepted in its place.
|
|
731
|
+
* - `options.invisible` is `1` (or `true`) for a `data-size="invisible"`
|
|
732
|
+
* widget.
|
|
733
|
+
* - `options.data` is the `rqdata` value an hCaptcha Enterprise site passes
|
|
734
|
+
* to `hcaptcha.setData()`. It is single use and expires quickly, so fetch
|
|
735
|
+
* a fresh one for every task.
|
|
736
|
+
* - `options.userAgent` is the User-Agent you will submit the form with.
|
|
737
|
+
* CapSkip uses it only when it matches the browser it solves with.
|
|
738
|
+
* - `options.proxy` is a URI string or
|
|
739
|
+
* `{ type: 'HTTP', uri: 'login:password@IP:PORT' }`.
|
|
740
|
+
*
|
|
741
|
+
* hCaptcha Enterprise and invisible widgets use this same method.
|
|
742
|
+
*
|
|
743
|
+
* The result carries the token as both `code` and `token` (for the
|
|
744
|
+
* `h-captcha-response` and `g-recaptcha-response` fields), `respKey` (the
|
|
745
|
+
* second value hCaptcha hands the widget; most sites never read it, and it
|
|
746
|
+
* can be empty), and `userAgent`, the User-Agent the token was earned with.
|
|
747
|
+
* Submit the token with that User-Agent, soon: hCaptcha tokens are single
|
|
748
|
+
* use and expire about two minutes after they are issued.
|
|
749
|
+
*/
|
|
750
|
+
async hcaptcha(sitekey, url, options = {}) {
|
|
751
|
+
// An unset value is dropped rather than sent as undefined, so
|
|
752
|
+
// `hcaptcha(sitekey, undefined, { pageurl })` reaches the alias without
|
|
753
|
+
// tripping the conflict check; an empty one is dropped during validation.
|
|
754
|
+
const given = {};
|
|
755
|
+
const add = (key, value) => {
|
|
756
|
+
if (value !== undefined && value !== null) {
|
|
757
|
+
given[key] = value;
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
add('sitekey', sitekey);
|
|
761
|
+
add('url', url);
|
|
762
|
+
for (const [key, value] of Object.entries(options)) {
|
|
763
|
+
add(key, value);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// A real browser session, like reCAPTCHA, so it gets the longer of the
|
|
767
|
+
// two timeouts unless the caller asked for a specific one.
|
|
768
|
+
const result = await this.solve({
|
|
769
|
+
timeout: this.recaptchaTimeout,
|
|
770
|
+
...given,
|
|
771
|
+
method: 'hcaptcha',
|
|
772
|
+
poll_json: 1,
|
|
773
|
+
});
|
|
774
|
+
return applyHCaptchaSolution(result);
|
|
775
|
+
}
|
|
776
|
+
|
|
703
777
|
async solve(options = {}) {
|
|
704
778
|
const {
|
|
705
779
|
timeout = 0,
|
|
@@ -806,6 +880,9 @@ class CapSkip {
|
|
|
806
880
|
if (method === 'datadome') {
|
|
807
881
|
return prepareSubmitParams(params, 'datadome');
|
|
808
882
|
}
|
|
883
|
+
if (method === 'hcaptcha') {
|
|
884
|
+
return prepareSubmitParams(params, 'hcaptcha');
|
|
885
|
+
}
|
|
809
886
|
return applyProxy(applyParamAliases(params));
|
|
810
887
|
}
|
|
811
888
|
}
|
|
@@ -822,6 +899,7 @@ module.exports = {
|
|
|
822
899
|
applyCapySolution,
|
|
823
900
|
applyAmazonWafSolution,
|
|
824
901
|
applyDataDomeSolution,
|
|
902
|
+
applyHCaptchaSolution,
|
|
825
903
|
expandObjectAnswer,
|
|
826
904
|
applyTokenSolution,
|
|
827
905
|
};
|
package/types/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export interface CapSkipOptions {
|
|
|
20
20
|
defaultTimeout?: number;
|
|
21
21
|
/**
|
|
22
22
|
* Seconds to poll reCAPTCHA / Turnstile / GeeTest / CaptchaFox / Friendly
|
|
23
|
-
* Captcha / AWS WAF / DataDome before timing out. Default `300`. ALTCHA and Capy use
|
|
23
|
+
* Captcha / AWS WAF / DataDome / hCaptcha before timing out. Default `300`. ALTCHA and Capy use
|
|
24
24
|
* `defaultTimeout` instead — neither is a browser solve.
|
|
25
25
|
*/
|
|
26
26
|
recaptchaTimeout?: number;
|
|
@@ -42,9 +42,9 @@ export interface SolveResult {
|
|
|
42
42
|
*/
|
|
43
43
|
code: string | Record<string, unknown>;
|
|
44
44
|
/**
|
|
45
|
-
* Turnstile, CaptchaFox and DataDome — the User-Agent to use when
|
|
46
|
-
* the token. For CaptchaFox this is the browser's own,
|
|
47
|
-
* it is absent when the solve
|
|
45
|
+
* Turnstile, CaptchaFox, hCaptcha and DataDome — the User-Agent to use when
|
|
46
|
+
* submitting the token. For CaptchaFox and hCaptcha this is the browser's own,
|
|
47
|
+
* not one you sent, and it is absent when the solve reported none. For DataDome
|
|
48
48
|
* it is the User-Agent the cookie was earned with — the one you sent, or
|
|
49
49
|
* CapSkip's own browser's — and every request carrying the cookie must send it.
|
|
50
50
|
*/
|
|
@@ -72,7 +72,12 @@ export interface SolveResult {
|
|
|
72
72
|
*/
|
|
73
73
|
answer?: string;
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* hCaptcha — the second value hCaptcha hands the widget next to the token.
|
|
76
|
+
* Always present on an `hcaptcha()` result, `''` when the widget handed out
|
|
77
|
+
* none; most sites never read it. Other polled results carry it only when
|
|
78
|
+
* the server sent one.
|
|
79
|
+
*
|
|
80
|
+
* Capy — empty for a puzzle solve. Present for shape compatibility with
|
|
76
81
|
* 2Captcha's documented response; it carries nothing.
|
|
77
82
|
*/
|
|
78
83
|
respKey?: string;
|
|
@@ -324,6 +329,43 @@ export interface DataDomeOptions {
|
|
|
324
329
|
[key: string]: unknown;
|
|
325
330
|
}
|
|
326
331
|
|
|
332
|
+
/** Extra options for {@link CapSkip.hcaptcha}. */
|
|
333
|
+
export interface HCaptchaOptions {
|
|
334
|
+
/** The page URL (alias for the `url` argument). */
|
|
335
|
+
pageurl?: string;
|
|
336
|
+
/** The page URL (alias for the `url` argument). */
|
|
337
|
+
url?: string;
|
|
338
|
+
/**
|
|
339
|
+
* `1` / `true` for a `data-size="invisible"` widget; `0` / `false` (the
|
|
340
|
+
* default) is not sent. The strings `"1"`, `"0"`, `"true"` and `"false"`
|
|
341
|
+
* are accepted too; anything else is refused.
|
|
342
|
+
*/
|
|
343
|
+
invisible?: boolean | 0 | 1 | '0' | '1' | 'true' | 'false';
|
|
344
|
+
/**
|
|
345
|
+
* hCaptcha Enterprise `rqdata`, the value the site passes to
|
|
346
|
+
* `hcaptcha.setData()`. Single use and short-lived: fetch a fresh one per task.
|
|
347
|
+
*/
|
|
348
|
+
data?: string;
|
|
349
|
+
/**
|
|
350
|
+
* The User-Agent you will submit the form with. CapSkip uses it only when it
|
|
351
|
+
* matches the browser it solves with.
|
|
352
|
+
*/
|
|
353
|
+
userAgent?: string;
|
|
354
|
+
/** Alias for `userAgent`. */
|
|
355
|
+
useragent?: string;
|
|
356
|
+
/** Proxy used for solving. */
|
|
357
|
+
proxy?: Proxy | string;
|
|
358
|
+
/** Proxy type when `proxy` is a bare string. Default `HTTP`. */
|
|
359
|
+
proxytype?: string;
|
|
360
|
+
/** Poll timeout in seconds. Default: the client's `recaptchaTimeout`. */
|
|
361
|
+
timeout?: number;
|
|
362
|
+
/** Max seconds between polls for this solve. */
|
|
363
|
+
polling_interval?: number;
|
|
364
|
+
/** `1` to request the raw JSON response from CapSkip. */
|
|
365
|
+
json?: number;
|
|
366
|
+
[key: string]: unknown;
|
|
367
|
+
}
|
|
368
|
+
|
|
327
369
|
/** Options for the {@link CapSkip.solve} manual workflow. */
|
|
328
370
|
export interface SolveOptions {
|
|
329
371
|
/** Poll timeout in seconds (falls back to the configured default). */
|
|
@@ -436,6 +478,22 @@ export class CapSkip {
|
|
|
436
478
|
url: string,
|
|
437
479
|
options?: DataDomeOptions,
|
|
438
480
|
): Promise<SolveResult>;
|
|
481
|
+
/**
|
|
482
|
+
* Solve an hCaptcha widget (checkbox, invisible or Enterprise) and return
|
|
483
|
+
* its token.
|
|
484
|
+
*
|
|
485
|
+
* `sitekey` is the widget's `data-sitekey`, `url` the page it is on
|
|
486
|
+
* (`options.pageurl` is accepted in its place). The result carries the token
|
|
487
|
+
* as `code` and `token` (for `h-captcha-response` and
|
|
488
|
+
* `g-recaptcha-response`), `respKey` (always present, `''` when none) and
|
|
489
|
+
* `userAgent`, the User-Agent the token was earned with. Tokens are single
|
|
490
|
+
* use and expire about two minutes after they are issued.
|
|
491
|
+
*/
|
|
492
|
+
hcaptcha(
|
|
493
|
+
sitekey: string,
|
|
494
|
+
url: string | undefined,
|
|
495
|
+
options?: HCaptchaOptions,
|
|
496
|
+
): Promise<SolveResult>;
|
|
439
497
|
/** Submit then poll to completion. Used by the higher-level solve methods. */
|
|
440
498
|
solve(options?: SolveOptions): Promise<SolveResult>;
|
|
441
499
|
/** Submit a captcha without polling; resolves to the captcha id. */
|