capskip 1.4.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 +66 -0
- package/README.md +125 -7
- package/docs/API_REFERENCE.md +243 -0
- package/examples/datadome.js +142 -0
- package/examples/hcaptcha.js +98 -0
- package/package.json +7 -2
- package/src/apiParams.js +290 -0
- package/src/index.js +1 -1
- package/src/solver.js +163 -1
- package/types/index.d.ts +124 -6
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,72 @@ 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
|
+
|
|
38
|
+
## [1.5.0] - 2026-11-02
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **DataDome support** via `dataDome(captchaUrl, url, options)`, sent as
|
|
43
|
+
`method=datadome`, covering both DataDome challenges: the slider
|
|
44
|
+
(`/captcha/`) and the device check (`/interstitial/`). The arguments follow
|
|
45
|
+
2Captcha's `datadome(captcha_url, pageurl, userAgent, proxy)`, with
|
|
46
|
+
`userAgent` and `proxy` as optional options, as on every other method.
|
|
47
|
+
- The result carries the cookie twice: `code` exactly as DataDome set it,
|
|
48
|
+
attributes included, and `cookie`, the `datadome=...` pair alone for a Cookie
|
|
49
|
+
header. `userAgent` is the User-Agent the cookie was earned with, the one sent
|
|
50
|
+
or CapSkip's own browser's.
|
|
51
|
+
- `proxy` in the result: when a task is sent without a proxy and the CapSkip app
|
|
52
|
+
solves it through its own DataDome proxy list, the result names that proxy as
|
|
53
|
+
`host:port`. The cookie only works from that address, so it is reported
|
|
54
|
+
rather than dropped. Polled results now carry a `proxy` field whenever the
|
|
55
|
+
server sends one.
|
|
56
|
+
- Parameter aliases `captchaUrl` / `captchaURL` for `captcha_url`.
|
|
57
|
+
- TypeScript definitions for `DataDomeOptions`, plus the new `SolveResult`
|
|
58
|
+
fields `cookie` and `proxy`.
|
|
59
|
+
- `examples/datadome.js`.
|
|
60
|
+
|
|
61
|
+
### Notes
|
|
62
|
+
|
|
63
|
+
- Refused locally, before a round-trip: a `captcha_url` that is not an https
|
|
64
|
+
challenge address on captcha-delivery.com carrying `initialCid`, `hash` and
|
|
65
|
+
`cid`; a block page that bans the requesting address (`t=bv`); a `pageurl`
|
|
66
|
+
that is not http(s), or is on captcha-delivery.com; and a `userAgent` that is
|
|
67
|
+
not stock Chrome for Windows. CapSkip itself checks that the Chrome major
|
|
68
|
+
version matches its browser and answers `ERROR_WRONG_USERAGENT` when it does
|
|
69
|
+
not. An optional passed as `''` is treated as not sent.
|
|
70
|
+
- An iframe `src` copied HTML-escaped (`&`) is accepted, as the server
|
|
71
|
+
decodes it, and is sent unchanged.
|
|
72
|
+
- DataDome uses `recaptchaTimeout`: the challenge is passed in a real browser.
|
|
73
|
+
|
|
8
74
|
## [1.4.0] - 2026-11-01
|
|
9
75
|
|
|
10
76
|
### 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 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()`. 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
|
|
|
@@ -40,8 +40,10 @@ This SDK is a thin wrapper over that API, with the method names you would expect
|
|
|
40
40
|
| **Friendly Captcha solver** (proof-of-work) | `solver.friendlyCaptcha(sitekey, url, { version })` |
|
|
41
41
|
| **AWS WAF solver** (challenge page) | `solver.amazonWaf(sitekey, url, { iv, context })` |
|
|
42
42
|
| AWS WAF (widget) | `solver.amazonWaf(sitekey, url, { jsapiScript })` |
|
|
43
|
+
| **DataDome solver** (slider and device check) | `solver.dataDome(captchaUrl, url)` |
|
|
44
|
+
| **hCaptcha solver** (checkbox, invisible and Enterprise) | `solver.hcaptcha(sitekey, url)` |
|
|
43
45
|
|
|
44
|
-
**
|
|
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()`.
|
|
45
47
|
|
|
46
48
|
Point the SDK at the [live captcha demo pages](https://capskip.com/captcha-demo/) to sanity-check your setup against real widgets.
|
|
47
49
|
|
|
@@ -137,7 +139,7 @@ const solver = new CapSkip({
|
|
|
137
139
|
port: 8080, // CapSkip port from app settings
|
|
138
140
|
defaultTimeout: 120, // seconds — image captcha polling timeout
|
|
139
141
|
recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest / CaptchaFox /
|
|
140
|
-
// Friendly Captcha / AWS WAF polling timeout
|
|
142
|
+
// Friendly Captcha / AWS WAF / DataDome / hCaptcha polling timeout
|
|
141
143
|
// (ALTCHA and Capy use defaultTimeout — neither is a browser solve)
|
|
142
144
|
pollingInterval: 5, // max seconds between res.php polls (starts at 0.25s, backs off to this)
|
|
143
145
|
});
|
|
@@ -357,6 +359,118 @@ https URL on an `awswaf.com` host. CapSkip posts the solution to the host the
|
|
|
357
359
|
script names, so anything else is refused before the request is made. See the
|
|
358
360
|
[AWS WAF API docs](https://capskip.com/api-docs/#aws-waf).
|
|
359
361
|
|
|
362
|
+
### DataDome
|
|
363
|
+
|
|
364
|
+
DataDome answers a request it doubts with a block page (status 403, sometimes
|
|
365
|
+
401) instead of the site. CapSkip passes the challenge on that page, a slider
|
|
366
|
+
or a device check, and returns the `datadome` cookie that lets your requests
|
|
367
|
+
through. The arguments follow 2Captcha's `datadome(captcha_url, pageurl,
|
|
368
|
+
userAgent, proxy)`, with `userAgent` and `proxy` as optional options.
|
|
369
|
+
|
|
370
|
+
```js
|
|
371
|
+
const result = await solver.dataDome(
|
|
372
|
+
'https://geo.captcha-delivery.com/captcha/?initialCid=...&hash=...&cid=...&t=fe&referer=...&s=45225&e=...&dm=cd',
|
|
373
|
+
'https://www.example.com/', // the page that returned the block page
|
|
374
|
+
{
|
|
375
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
|
|
376
|
+
+ '(KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36',
|
|
377
|
+
proxy: { type: 'HTTP', uri: 'login:password@203.0.113.10:8080' },
|
|
378
|
+
},
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
result.cookie; // "datadome=..." -- send it as the Cookie header
|
|
382
|
+
result.code; // the cookie exactly as DataDome set it: Max-Age, Domain, Path...
|
|
383
|
+
result.userAgent; // the User-Agent the cookie was earned with
|
|
384
|
+
result.proxy; // set only when CapSkip picked the proxy, see below
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
`captchaUrl` is the address of the challenge iframe on
|
|
388
|
+
`geo.captcha-delivery.com`: `/captcha/` for a slider, `/interstitial/` for a
|
|
389
|
+
device check. Take the iframe's `src` when the block page has one; most build it
|
|
390
|
+
with a script instead, so assemble it from the page's `var dd={...}` object as
|
|
391
|
+
the [API docs](https://capskip.com/api-docs/#datadome) show. Each block page is
|
|
392
|
+
a fresh challenge, so submit it soon and fetch the page again for the next task.
|
|
393
|
+
|
|
394
|
+
**The cookie belongs to one IP address and one browser.** DataDome refuses it
|
|
395
|
+
from any other address, or with any other User-Agent, so:
|
|
396
|
+
|
|
397
|
+
- **Send the User-Agent you will use**, or none. It must be Chrome for Windows,
|
|
398
|
+
with the same major version as the browser CapSkip solves with; anything else
|
|
399
|
+
(Firefox, Safari, Edge, mobile, macOS) would earn a cookie the site rejects,
|
|
400
|
+
so it is refused before the request is sent, and a different Chrome major is
|
|
401
|
+
refused by CapSkip with `ERROR_WRONG_USERAGENT`. Send none and CapSkip uses
|
|
402
|
+
its own browser's: copy `result.userAgent` into your requests as it is.
|
|
403
|
+
- **Solve from the address you will use the cookie from.** If your requests go
|
|
404
|
+
out through a proxy, pass that proxy with the task. If they come from the
|
|
405
|
+
machine running CapSkip, pass none.
|
|
406
|
+
- **When CapSkip picks the proxy, it tells you which.** The CapSkip app has a
|
|
407
|
+
DataDome proxy list of its own (Settings, Captcha types, DataDome, Configure
|
|
408
|
+
Proxies). A task sent without a proxy is solved through that list, and the
|
|
409
|
+
result then carries `proxy` as `host:port`: send the cookie through that
|
|
410
|
+
proxy, or it will be refused. A proxy you send always takes priority over
|
|
411
|
+
the list, and then no `proxy` comes back.
|
|
412
|
+
|
|
413
|
+
```js
|
|
414
|
+
const headers = { 'User-Agent': result.userAgent, Cookie: result.cookie };
|
|
415
|
+
// When result.proxy is set, route this request through it (it is named without
|
|
416
|
+
// its login: add your credentials if it has one), e.g. with undici's ProxyAgent.
|
|
417
|
+
const page = await fetch('https://www.example.com/', { headers });
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
A block page whose `dd` object carries `'t':'bv'` means DataDome has banned the
|
|
421
|
+
address that fetched it; no solve from there can pass, so it is refused before
|
|
422
|
+
the request is sent. Change the address and request the page again.
|
|
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
|
+
|
|
360
474
|
### With a proxy (every method except image captcha)
|
|
361
475
|
|
|
362
476
|
```js
|
|
@@ -457,7 +571,11 @@ Captcha add `token`, the same string as `code`. Capy expands its answer into
|
|
|
457
571
|
`captchakey`, `challengekey` and `answer` — it returns an object rather than a
|
|
458
572
|
token, and `code` keeps the raw answer. AWS WAF likewise returns an object,
|
|
459
573
|
expanded into `captcha_voucher` and `existing_token`. ALTCHA adds `token` (the same
|
|
460
|
-
string as `code`) and `number`, the counter that solved it.
|
|
574
|
+
string as `code`) and `number`, the counter that solved it. DataDome adds
|
|
575
|
+
`cookie`, the `datadome=...` pair alone, while `code` keeps the cookie with its
|
|
576
|
+
attributes; it also carries `userAgent`, and `proxy` when CapSkip chose the
|
|
577
|
+
proxy. hCaptcha adds `token`, the same string as `code`, `respKey` (always
|
|
578
|
+
present, empty when the widget handed out none) and `userAgent`.
|
|
461
579
|
|
|
462
580
|
---
|
|
463
581
|
|
|
@@ -502,7 +620,7 @@ const result: SolveResult = await solver.recaptcha('...', 'https://example.com')
|
|
|
502
620
|
|
|
503
621
|
### How do I solve a captcha in Node.js?
|
|
504
622
|
|
|
505
|
-
Install the CapSkip desktop app, `npm install capskip`, then call the method that matches the widget — `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()`, `amazonWaf()` or `normal()`. Each returns a Promise that resolves once CapSkip has an answer, giving you a token, or the recognized text in the case of an image captcha.
|
|
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.
|
|
506
624
|
|
|
507
625
|
### Is this a free captcha solver?
|
|
508
626
|
|
|
@@ -510,7 +628,7 @@ The SDK itself is MIT-licensed and free. Solving needs the CapSkip app, which is
|
|
|
510
628
|
|
|
511
629
|
### Which captchas can it solve?
|
|
512
630
|
|
|
513
|
-
reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF, 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.
|
|
514
632
|
|
|
515
633
|
### Does it work with Puppeteer and Playwright?
|
|
516
634
|
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -28,6 +28,8 @@ method returns a `Promise`.
|
|
|
28
28
|
| CaptchaFox (widget) | `captchafox()` | `captchafox` |
|
|
29
29
|
| Friendly Captcha (proof-of-work) | `friendlyCaptcha()` | `friendly_captcha` |
|
|
30
30
|
| AWS WAF (challenge page or widget) | `amazonWaf()` | `amazon_waf` |
|
|
31
|
+
| DataDome (slider and device check) | `dataDome()` | `datadome` |
|
|
32
|
+
| hCaptcha (checkbox, invisible, Enterprise) | `hcaptcha()` | `hcaptcha` |
|
|
31
33
|
|
|
32
34
|
**Proxy** is supported for every method except image captcha. For ALTCHA the
|
|
33
35
|
proxy is used only for the `challengeUrl` fetch; for Capy, only for the
|
|
@@ -713,6 +715,217 @@ for, and the puzzle rounds go through AWS on top of that.
|
|
|
713
715
|
|
|
714
716
|
---
|
|
715
717
|
|
|
718
|
+
## 11. DataDome — `dataDome(captchaUrl, url, { ... })`
|
|
719
|
+
|
|
720
|
+
DataDome is the bot protection behind many large shops, news sites and
|
|
721
|
+
marketplaces. When it doubts a visitor, the site answers with a block page
|
|
722
|
+
(status 403, sometimes 401) holding a slider or a short device check. CapSkip
|
|
723
|
+
passes that challenge and returns the `datadome` cookie; requests that carry it
|
|
724
|
+
are let through. Public docs: <https://capskip.com/api-docs/#datadome>.
|
|
725
|
+
|
|
726
|
+
The arguments follow 2Captcha's `datadome(captcha_url, pageurl, userAgent,
|
|
727
|
+
proxy)`, so code written against it ports directly; `userAgent` and `proxy` go
|
|
728
|
+
in the options object and are optional here.
|
|
729
|
+
|
|
730
|
+
### POST `/in.php`
|
|
731
|
+
|
|
732
|
+
| Parameter | Type | Required | Description |
|
|
733
|
+
|---|---|---|---|
|
|
734
|
+
| `key` | string | Yes | CapSkip API key |
|
|
735
|
+
| `method` | string | Yes | `datadome` |
|
|
736
|
+
| `captcha_url` | string | Yes | Address of the challenge iframe on `geo.captcha-delivery.com` |
|
|
737
|
+
| `pageurl` | string | Yes | Full http or https URL of the page that returned the block page |
|
|
738
|
+
| `userAgent` | string | No | The User-Agent you will send with the cookie: Chrome for Windows, same major version as CapSkip's browser. Sent as `useragent` |
|
|
739
|
+
| `json` | int | No | `0` plain text, `1` JSON |
|
|
740
|
+
| `proxy` | string | No | The proxy your later requests go through |
|
|
741
|
+
| `proxytype` | string | No | `HTTP`, `HTTPS`, `SOCKS5` or `SOCKS5H` |
|
|
742
|
+
|
|
743
|
+
The SDK also accepts `captchaUrl` / `captchaURL` for `captcha_url`. An optional
|
|
744
|
+
left `undefined`, `null` or `''` is not sent — the server reads an empty value
|
|
745
|
+
as absent. The `captcha_url` is sent exactly as given.
|
|
746
|
+
|
|
747
|
+
Refused by the SDK before the request is made, as the server would refuse them
|
|
748
|
+
(`ValidationException`):
|
|
749
|
+
|
|
750
|
+
- a missing `captcha_url` or `pageurl`;
|
|
751
|
+
- a `captcha_url` that is not https on captcha-delivery.com (or a subdomain),
|
|
752
|
+
under `/captcha/` or `/interstitial/`, carrying `initialCid`, `hash` and
|
|
753
|
+
`cid` (server: `ERROR_BAD_PARAMETERS`);
|
|
754
|
+
- a `captcha_url` with `t=bv`: the block page bans the address that fetched it,
|
|
755
|
+
so nothing sent from there can pass (server: `ERROR_PROXY_BANNED`);
|
|
756
|
+
- a `pageurl` that is not http(s), or that points at captcha-delivery.com
|
|
757
|
+
(server: `ERROR_PAGEURL`);
|
|
758
|
+
- a `userAgent` that is not stock Chrome for Windows: Firefox, Safari, Edge,
|
|
759
|
+
Opera, macOS, Linux, Android and iOS all fail (server:
|
|
760
|
+
`ERROR_WRONG_USERAGENT`).
|
|
761
|
+
|
|
762
|
+
The Chrome **major version** is checked by CapSkip itself, against the browser
|
|
763
|
+
it solves with, and a mismatch comes back as `ApiException('ERROR_WRONG_USERAGENT')`.
|
|
764
|
+
|
|
765
|
+
### Building the `captchaUrl`
|
|
766
|
+
|
|
767
|
+
If the block page already contains the challenge iframe, take its `src` as it
|
|
768
|
+
is (an HTML-escaped `&` is fine). Most block pages build it with a script
|
|
769
|
+
from a `var dd={...}` object instead:
|
|
770
|
+
|
|
771
|
+
```js
|
|
772
|
+
const dd = JSON.parse(html.match(/var dd=(\{[^}]*\})/)[1].replace(/'/g, '"'));
|
|
773
|
+
const path = dd.rt === 'i' ? 'interstitial' : 'captcha';
|
|
774
|
+
const q = new URLSearchParams({ initialCid: dd.cid, hash: dd.hsh, cid: dd.cookie });
|
|
775
|
+
if (dd.t) q.set('t', dd.t);
|
|
776
|
+
q.set('referer', pageUrl);
|
|
777
|
+
q.set('s', String(dd.s));
|
|
778
|
+
for (const k of ['e', 'b']) {
|
|
779
|
+
if (dd[k]) q.set(k, String(dd[k]));
|
|
780
|
+
}
|
|
781
|
+
q.set('dm', 'cd');
|
|
782
|
+
const captchaUrl = `https://${dd.host || 'geo.captcha-delivery.com'}/${path}/?${q}`;
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
Each block page is a fresh challenge: submit it soon, and request the page again
|
|
786
|
+
for the next task rather than reusing an old `captchaUrl`.
|
|
787
|
+
|
|
788
|
+
### The cookie belongs to one IP and one User-Agent
|
|
789
|
+
|
|
790
|
+
DataDome refuses the cookie from any other address, or with any other
|
|
791
|
+
User-Agent:
|
|
792
|
+
|
|
793
|
+
- **Solve from the address you will use the cookie from.** Pass the proxy your
|
|
794
|
+
requests go through; pass none if they come from the machine running CapSkip.
|
|
795
|
+
- **Send the User-Agent you will use, or none.** With none, CapSkip uses its
|
|
796
|
+
browser's own, and the result names it.
|
|
797
|
+
- **CapSkip's own DataDome proxy list.** The app can hold a list of proxies for
|
|
798
|
+
DataDome (Settings, Captcha types, DataDome, Configure Proxies). A task sent
|
|
799
|
+
without a proxy is solved through that list, and the result then carries
|
|
800
|
+
`proxy`: that proxy as `host:port`, without its login. The cookie only works
|
|
801
|
+
through it. A proxy sent with the task always takes priority, and then no
|
|
802
|
+
`proxy` comes back.
|
|
803
|
+
|
|
804
|
+
### GET `/res.php`
|
|
805
|
+
|
|
806
|
+
The SDK polls with `json=1`, where CapSkip returns the cookie in `request` with
|
|
807
|
+
`userAgent` and, when its list supplied the proxy, `proxy` next to it:
|
|
808
|
+
|
|
809
|
+
```json
|
|
810
|
+
{
|
|
811
|
+
"status": 1,
|
|
812
|
+
"request": "datadome=FAPPpli4...; Max-Age=31536000; Domain=.example.com; Path=/; Secure; SameSite=Lax",
|
|
813
|
+
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36",
|
|
814
|
+
"proxy": "203.0.113.10:8080"
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
(In plain-text mode the same two values arrive in the `X-DataDome-User-Agent`
|
|
819
|
+
and `X-DataDome-Proxy` response headers.) See [Return value](#return-value).
|
|
820
|
+
|
|
821
|
+
### SDK usage
|
|
822
|
+
|
|
823
|
+
```js
|
|
824
|
+
// 2Captcha's argument order, with userAgent and proxy as options
|
|
825
|
+
const result = await solver.dataDome(captchaUrl, 'https://www.example.com/', {
|
|
826
|
+
userAgent,
|
|
827
|
+
proxy: { type: 'HTTP', uri: 'login:password@203.0.113.10:8080' },
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
// No proxy, no User-Agent: CapSkip's browser, and its proxy list if it has one
|
|
831
|
+
const result = await solver.dataDome(captchaUrl, 'https://www.example.com/');
|
|
832
|
+
|
|
833
|
+
const headers = {
|
|
834
|
+
'User-Agent': result.userAgent,
|
|
835
|
+
Cookie: result.cookie, // "datadome=..."
|
|
836
|
+
};
|
|
837
|
+
if (result.proxy) {
|
|
838
|
+
// send this request through result.proxy
|
|
839
|
+
}
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
DataDome may send a replacement `datadome` cookie on later responses; keep the
|
|
843
|
+
newest one, as a browser would. When the block page comes back anyway, build a
|
|
844
|
+
new `captchaUrl` from it and submit a new task.
|
|
845
|
+
|
|
846
|
+
Uses `recaptchaTimeout`: the challenge is passed in a real browser.
|
|
847
|
+
|
|
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
|
+
|
|
716
929
|
## Return value
|
|
717
930
|
|
|
718
931
|
Every solve method resolves to:
|
|
@@ -777,6 +990,35 @@ answer):
|
|
|
777
990
|
}
|
|
778
991
|
```
|
|
779
992
|
|
|
993
|
+
DataDome returns a cookie. `code` keeps it exactly as DataDome set it, for a
|
|
994
|
+
cookie jar; `cookie` is the `datadome=...` pair alone, for a Cookie header.
|
|
995
|
+
`userAgent` is the User-Agent the cookie was earned with, and `proxy` appears
|
|
996
|
+
only when the task sent no proxy and CapSkip used its own DataDome proxy list:
|
|
997
|
+
|
|
998
|
+
```js
|
|
999
|
+
{
|
|
1000
|
+
captchaId: '12345',
|
|
1001
|
+
code: 'datadome=FAPPpli4...; Max-Age=31536000; Domain=.example.com; Path=/; Secure; SameSite=Lax',
|
|
1002
|
+
cookie: 'datadome=FAPPpli4...',
|
|
1003
|
+
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36',
|
|
1004
|
+
proxy: '203.0.113.10:8080',
|
|
1005
|
+
}
|
|
1006
|
+
```
|
|
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
|
+
|
|
780
1022
|
GeeTest additionally expands its answer into `challenge`, `validate`, and
|
|
781
1023
|
`seccode` (`code` keeps the raw JSON string):
|
|
782
1024
|
|
|
@@ -814,6 +1056,7 @@ Convenience aliases mapped before sending to CapSkip:
|
|
|
814
1056
|
| `challengeScript` | `challenge_script` |
|
|
815
1057
|
| `captchaScript` | `captcha_script` |
|
|
816
1058
|
| `jsapi_script` | `jsapiScript` |
|
|
1059
|
+
| `captchaUrl` / `captchaURL` | `captcha_url` |
|
|
817
1060
|
| `proxy` object | `proxy` + `proxytype` strings |
|
|
818
1061
|
|
|
819
1062
|
```js
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pass a DataDome challenge and fetch the page with the cookie.
|
|
5
|
+
*
|
|
6
|
+
* DataDome answers a request it doubts with a block page (status 403, sometimes
|
|
7
|
+
* 401) instead of the site. That page carries a `var dd={...}` object and loads
|
|
8
|
+
* the challenge in an iframe from geo.captcha-delivery.com: a slider under
|
|
9
|
+
* /captcha/, or a few-second device check under /interstitial/. CapSkip passes
|
|
10
|
+
* either and returns the `datadome` cookie.
|
|
11
|
+
*
|
|
12
|
+
* The cookie belongs to one IP address and one User-Agent, so:
|
|
13
|
+
*
|
|
14
|
+
* * solve through the proxy your requests use (or none, if they leave from
|
|
15
|
+
* the machine running CapSkip), and
|
|
16
|
+
* * send the User-Agent CapSkip reports back with every request.
|
|
17
|
+
*
|
|
18
|
+
* When you send no proxy and the CapSkip app has its own DataDome proxy list,
|
|
19
|
+
* it solves through one of those and the result names it as `proxy`
|
|
20
|
+
* (host:port): the cookie then only works through that proxy.
|
|
21
|
+
*
|
|
22
|
+
* Requests go out with Node's built-in fetch. Sending them through a proxy
|
|
23
|
+
* needs undici's ProxyAgent (`npm install undici`); without it, the example
|
|
24
|
+
* prints what to do instead of re-fetching through the proxy.
|
|
25
|
+
*
|
|
26
|
+
* Environment:
|
|
27
|
+
* TARGET_URL a page behind DataDome (default: https://www.example.com/)
|
|
28
|
+
* DATADOME_PROXY optional, e.g. http://login:password@203.0.113.10:8080
|
|
29
|
+
* CAPSKIP_API_KEY / CAPSKIP_HOST / CAPSKIP_PORT
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const { CapSkip } = require('capskip');
|
|
33
|
+
|
|
34
|
+
const solver = new CapSkip({
|
|
35
|
+
apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
|
|
36
|
+
host: process.env.CAPSKIP_HOST || '127.0.0.1',
|
|
37
|
+
port: Number(process.env.CAPSKIP_PORT || 8080),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const PAGE_URL = process.env.TARGET_URL || 'https://www.example.com/';
|
|
41
|
+
const PROXY = process.env.DATADOME_PROXY; // scheme://[login:password@]host:port, or unset
|
|
42
|
+
|
|
43
|
+
let ProxyAgent;
|
|
44
|
+
try {
|
|
45
|
+
({ ProxyAgent } = require('undici'));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
ProxyAgent = undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The challenge iframe address, as the block page's own script builds it. */
|
|
51
|
+
function captchaUrlFromBlockPage(html, pageUrl) {
|
|
52
|
+
const iframe = html.match(/<iframe[^>]+src="(https:\/\/[^"]*captcha-delivery\.com\/[^"]+)"/);
|
|
53
|
+
if (iframe) {
|
|
54
|
+
return iframe[1]; // HTML-escaped & is fine, CapSkip decodes it
|
|
55
|
+
}
|
|
56
|
+
const match = html.match(/var dd=(\{[^}]*\})/);
|
|
57
|
+
if (!match) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const dd = JSON.parse(match[1].replace(/'/g, '"'));
|
|
61
|
+
const path = dd.rt === 'i' ? 'interstitial' : 'captcha';
|
|
62
|
+
const query = new URLSearchParams({ initialCid: dd.cid, hash: dd.hsh, cid: dd.cookie });
|
|
63
|
+
if (dd.t) {
|
|
64
|
+
query.set('t', dd.t);
|
|
65
|
+
}
|
|
66
|
+
query.set('referer', pageUrl);
|
|
67
|
+
query.set('s', String(dd.s));
|
|
68
|
+
for (const key of ['e', 'b']) {
|
|
69
|
+
if (dd[key]) {
|
|
70
|
+
query.set(key, String(dd[key]));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
query.set('dm', 'cd');
|
|
74
|
+
return `https://${dd.host || 'geo.captcha-delivery.com'}/${path}/?${query}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** fetch, through `proxyUrl` when one is given and undici is installed. */
|
|
78
|
+
function get(url, { proxyUrl, headers } = {}) {
|
|
79
|
+
const options = { headers, redirect: 'follow' };
|
|
80
|
+
if (proxyUrl) {
|
|
81
|
+
options.dispatcher = new ProxyAgent(proxyUrl);
|
|
82
|
+
}
|
|
83
|
+
return fetch(url, options);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function main() {
|
|
87
|
+
if (PROXY && !ProxyAgent) {
|
|
88
|
+
console.log('DATADOME_PROXY is set: run `npm install undici` so the requests use it.');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 1. Request the page. A 403/401 with a dd object is DataDome's block page.
|
|
93
|
+
const response = await get(PAGE_URL, { proxyUrl: PROXY });
|
|
94
|
+
const html = await response.text();
|
|
95
|
+
const captchaUrl = captchaUrlFromBlockPage(html, PAGE_URL);
|
|
96
|
+
if (![401, 403].includes(response.status) || !captchaUrl) {
|
|
97
|
+
console.log(`No DataDome block page at ${PAGE_URL} (HTTP ${response.status}).`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.log('Block page:', `${captchaUrl.slice(0, 90)}...`);
|
|
101
|
+
|
|
102
|
+
// 2. Solve it. 2Captcha's argument order: datadome(captcha_url, pageurl,
|
|
103
|
+
// userAgent, proxy), with userAgent and proxy as options. No userAgent:
|
|
104
|
+
// CapSkip uses its own browser's and reports it. With one, it must be
|
|
105
|
+
// Chrome for Windows with the same major version as that browser.
|
|
106
|
+
const options = {};
|
|
107
|
+
if (PROXY) {
|
|
108
|
+
const scheme = PROXY.includes('://') ? PROXY.split('://', 1)[0] : 'http';
|
|
109
|
+
options.proxy = { type: scheme.toUpperCase(), uri: PROXY.split('://').pop() };
|
|
110
|
+
}
|
|
111
|
+
const result = await solver.dataDome(captchaUrl, PAGE_URL, options);
|
|
112
|
+
|
|
113
|
+
console.log('Captcha ID:', result.captchaId);
|
|
114
|
+
console.log('Cookie: ', `${result.cookie.slice(0, 60)}...`);
|
|
115
|
+
console.log('User-Agent:', result.userAgent);
|
|
116
|
+
if (result.proxy) {
|
|
117
|
+
console.log('Proxy: ', result.proxy, '(from the CapSkip app DataDome list; the cookie only works through it)');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 3. Repeat the request with the cookie, the same User-Agent and the same IP.
|
|
121
|
+
let proxyUrl = PROXY;
|
|
122
|
+
if (result.proxy && !PROXY) {
|
|
123
|
+
// Named without its login: add yours if that list entry has one.
|
|
124
|
+
if (!ProxyAgent) {
|
|
125
|
+
console.log(`Send the cookie through http://${result.proxy}: run \`npm install undici\` to do it here.`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
proxyUrl = `http://${result.proxy}`;
|
|
129
|
+
}
|
|
130
|
+
// `cookie` is the datadome=... pair, ready for the Cookie header; `code`
|
|
131
|
+
// keeps the attributes (Domain, Max-Age...) for a cookie jar.
|
|
132
|
+
const again = await get(PAGE_URL, {
|
|
133
|
+
proxyUrl,
|
|
134
|
+
headers: { 'User-Agent': result.userAgent, Cookie: result.cookie },
|
|
135
|
+
});
|
|
136
|
+
console.log('With the cookie: HTTP', again.status);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
main().catch((error) => {
|
|
140
|
+
console.error(`${error.name}: ${error.message}`);
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
});
|