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 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: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
6
6
  [![Tests](https://github.com/capskip/capskip-node/actions/workflows/ci.yml/badge.svg)](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
- **hCaptcha and FunCaptcha/Arkose are not supported.** hCaptcha is the one people misidentify most often, since it also puts a `data-sitekey` on the widget — check for `class="h-captcha"` or a `js.hcaptcha.com` script before reaching for `recaptcha()`.
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 hCaptcha, and not FunCaptcha/Arkose.
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
 
@@ -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 `&amp;` 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 &amp; 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
+ });