capskip 1.4.0 → 1.5.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,42 @@ 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.5.0] - 2026-11-02
9
+
10
+ ### Added
11
+
12
+ - **DataDome support** via `dataDome(captchaUrl, url, options)`, sent as
13
+ `method=datadome`, covering both DataDome challenges: the slider
14
+ (`/captcha/`) and the device check (`/interstitial/`). The arguments follow
15
+ 2Captcha's `datadome(captcha_url, pageurl, userAgent, proxy)`, with
16
+ `userAgent` and `proxy` as optional options, as on every other method.
17
+ - The result carries the cookie twice: `code` exactly as DataDome set it,
18
+ attributes included, and `cookie`, the `datadome=...` pair alone for a Cookie
19
+ header. `userAgent` is the User-Agent the cookie was earned with, the one sent
20
+ or CapSkip's own browser's.
21
+ - `proxy` in the result: when a task is sent without a proxy and the CapSkip app
22
+ solves it through its own DataDome proxy list, the result names that proxy as
23
+ `host:port`. The cookie only works from that address, so it is reported
24
+ rather than dropped. Polled results now carry a `proxy` field whenever the
25
+ server sends one.
26
+ - Parameter aliases `captchaUrl` / `captchaURL` for `captcha_url`.
27
+ - TypeScript definitions for `DataDomeOptions`, plus the new `SolveResult`
28
+ fields `cookie` and `proxy`.
29
+ - `examples/datadome.js`.
30
+
31
+ ### Notes
32
+
33
+ - Refused locally, before a round-trip: a `captcha_url` that is not an https
34
+ challenge address on captcha-delivery.com carrying `initialCid`, `hash` and
35
+ `cid`; a block page that bans the requesting address (`t=bv`); a `pageurl`
36
+ that is not http(s), or is on captcha-delivery.com; and a `userAgent` that is
37
+ not stock Chrome for Windows. CapSkip itself checks that the Chrome major
38
+ version matches its browser and answers `ERROR_WRONG_USERAGENT` when it does
39
+ not. An optional passed as `''` is treated as not sent.
40
+ - An iframe `src` copied HTML-escaped (`&`) is accepted, as the server
41
+ decodes it, and is sent unchanged.
42
+ - DataDome uses `recaptchaTimeout`: the challenge is passed in a real browser.
43
+
8
44
  ## [1.4.0] - 2026-11-01
9
45
 
10
46
  ### 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 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()`. 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,6 +40,7 @@ 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)` |
43
44
 
44
45
  **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()`.
45
46
 
@@ -137,7 +138,7 @@ const solver = new CapSkip({
137
138
  port: 8080, // CapSkip port from app settings
138
139
  defaultTimeout: 120, // seconds — image captcha polling timeout
139
140
  recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest / CaptchaFox /
140
- // Friendly Captcha / AWS WAF polling timeout
141
+ // Friendly Captcha / AWS WAF / DataDome polling timeout
141
142
  // (ALTCHA and Capy use defaultTimeout — neither is a browser solve)
142
143
  pollingInterval: 5, // max seconds between res.php polls (starts at 0.25s, backs off to this)
143
144
  });
@@ -357,6 +358,68 @@ https URL on an `awswaf.com` host. CapSkip posts the solution to the host the
357
358
  script names, so anything else is refused before the request is made. See the
358
359
  [AWS WAF API docs](https://capskip.com/api-docs/#aws-waf).
359
360
 
361
+ ### DataDome
362
+
363
+ DataDome answers a request it doubts with a block page (status 403, sometimes
364
+ 401) instead of the site. CapSkip passes the challenge on that page, a slider
365
+ or a device check, and returns the `datadome` cookie that lets your requests
366
+ through. The arguments follow 2Captcha's `datadome(captcha_url, pageurl,
367
+ userAgent, proxy)`, with `userAgent` and `proxy` as optional options.
368
+
369
+ ```js
370
+ const result = await solver.dataDome(
371
+ 'https://geo.captcha-delivery.com/captcha/?initialCid=...&hash=...&cid=...&t=fe&referer=...&s=45225&e=...&dm=cd',
372
+ 'https://www.example.com/', // the page that returned the block page
373
+ {
374
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
375
+ + '(KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36',
376
+ proxy: { type: 'HTTP', uri: 'login:password@203.0.113.10:8080' },
377
+ },
378
+ );
379
+
380
+ result.cookie; // "datadome=..." -- send it as the Cookie header
381
+ result.code; // the cookie exactly as DataDome set it: Max-Age, Domain, Path...
382
+ result.userAgent; // the User-Agent the cookie was earned with
383
+ result.proxy; // set only when CapSkip picked the proxy, see below
384
+ ```
385
+
386
+ `captchaUrl` is the address of the challenge iframe on
387
+ `geo.captcha-delivery.com`: `/captcha/` for a slider, `/interstitial/` for a
388
+ device check. Take the iframe's `src` when the block page has one; most build it
389
+ with a script instead, so assemble it from the page's `var dd={...}` object as
390
+ the [API docs](https://capskip.com/api-docs/#datadome) show. Each block page is
391
+ a fresh challenge, so submit it soon and fetch the page again for the next task.
392
+
393
+ **The cookie belongs to one IP address and one browser.** DataDome refuses it
394
+ from any other address, or with any other User-Agent, so:
395
+
396
+ - **Send the User-Agent you will use**, or none. It must be Chrome for Windows,
397
+ with the same major version as the browser CapSkip solves with; anything else
398
+ (Firefox, Safari, Edge, mobile, macOS) would earn a cookie the site rejects,
399
+ so it is refused before the request is sent, and a different Chrome major is
400
+ refused by CapSkip with `ERROR_WRONG_USERAGENT`. Send none and CapSkip uses
401
+ its own browser's: copy `result.userAgent` into your requests as it is.
402
+ - **Solve from the address you will use the cookie from.** If your requests go
403
+ out through a proxy, pass that proxy with the task. If they come from the
404
+ machine running CapSkip, pass none.
405
+ - **When CapSkip picks the proxy, it tells you which.** The CapSkip app has a
406
+ DataDome proxy list of its own (Settings, Captcha types, DataDome, Configure
407
+ Proxies). A task sent without a proxy is solved through that list, and the
408
+ result then carries `proxy` as `host:port`: send the cookie through that
409
+ proxy, or it will be refused. A proxy you send always takes priority over
410
+ the list, and then no `proxy` comes back.
411
+
412
+ ```js
413
+ const headers = { 'User-Agent': result.userAgent, Cookie: result.cookie };
414
+ // When result.proxy is set, route this request through it (it is named without
415
+ // its login: add your credentials if it has one), e.g. with undici's ProxyAgent.
416
+ const page = await fetch('https://www.example.com/', { headers });
417
+ ```
418
+
419
+ A block page whose `dd` object carries `'t':'bv'` means DataDome has banned the
420
+ address that fetched it; no solve from there can pass, so it is refused before
421
+ the request is sent. Change the address and request the page again.
422
+
360
423
  ### With a proxy (every method except image captcha)
361
424
 
362
425
  ```js
@@ -457,7 +520,10 @@ Captcha add `token`, the same string as `code`. Capy expands its answer into
457
520
  `captchakey`, `challengekey` and `answer` — it returns an object rather than a
458
521
  token, and `code` keeps the raw answer. AWS WAF likewise returns an object,
459
522
  expanded into `captcha_voucher` and `existing_token`. ALTCHA adds `token` (the same
460
- string as `code`) and `number`, the counter that solved it.
523
+ string as `code`) and `number`, the counter that solved it. DataDome adds
524
+ `cookie`, the `datadome=...` pair alone, while `code` keeps the cookie with its
525
+ attributes; it also carries `userAgent`, and `proxy` when CapSkip chose the
526
+ proxy.
461
527
 
462
528
  ---
463
529
 
@@ -502,7 +568,7 @@ const result: SolveResult = await solver.recaptcha('...', 'https://example.com')
502
568
 
503
569
  ### How do I solve a captcha in Node.js?
504
570
 
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.
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.
506
572
 
507
573
  ### Is this a free captcha solver?
508
574
 
@@ -510,7 +576,7 @@ The SDK itself is MIT-licensed and free. Solving needs the CapSkip app, which is
510
576
 
511
577
  ### Which captchas can it solve?
512
578
 
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.
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 hCaptcha, and not FunCaptcha/Arkose.
514
580
 
515
581
  ### Does it work with Puppeteer and Playwright?
516
582
 
@@ -28,6 +28,7 @@ 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` |
31
32
 
32
33
  **Proxy** is supported for every method except image captcha. For ALTCHA the
33
34
  proxy is used only for the `challengeUrl` fetch; for Capy, only for the
@@ -713,6 +714,138 @@ for, and the puzzle rounds go through AWS on top of that.
713
714
 
714
715
  ---
715
716
 
717
+ ## 11. DataDome — `dataDome(captchaUrl, url, { ... })`
718
+
719
+ DataDome is the bot protection behind many large shops, news sites and
720
+ marketplaces. When it doubts a visitor, the site answers with a block page
721
+ (status 403, sometimes 401) holding a slider or a short device check. CapSkip
722
+ passes that challenge and returns the `datadome` cookie; requests that carry it
723
+ are let through. Public docs: <https://capskip.com/api-docs/#datadome>.
724
+
725
+ The arguments follow 2Captcha's `datadome(captcha_url, pageurl, userAgent,
726
+ proxy)`, so code written against it ports directly; `userAgent` and `proxy` go
727
+ in the options object and are optional here.
728
+
729
+ ### POST `/in.php`
730
+
731
+ | Parameter | Type | Required | Description |
732
+ |---|---|---|---|
733
+ | `key` | string | Yes | CapSkip API key |
734
+ | `method` | string | Yes | `datadome` |
735
+ | `captcha_url` | string | Yes | Address of the challenge iframe on `geo.captcha-delivery.com` |
736
+ | `pageurl` | string | Yes | Full http or https URL of the page that returned the block page |
737
+ | `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` |
738
+ | `json` | int | No | `0` plain text, `1` JSON |
739
+ | `proxy` | string | No | The proxy your later requests go through |
740
+ | `proxytype` | string | No | `HTTP`, `HTTPS`, `SOCKS5` or `SOCKS5H` |
741
+
742
+ The SDK also accepts `captchaUrl` / `captchaURL` for `captcha_url`. An optional
743
+ left `undefined`, `null` or `''` is not sent — the server reads an empty value
744
+ as absent. The `captcha_url` is sent exactly as given.
745
+
746
+ Refused by the SDK before the request is made, as the server would refuse them
747
+ (`ValidationException`):
748
+
749
+ - a missing `captcha_url` or `pageurl`;
750
+ - a `captcha_url` that is not https on captcha-delivery.com (or a subdomain),
751
+ under `/captcha/` or `/interstitial/`, carrying `initialCid`, `hash` and
752
+ `cid` (server: `ERROR_BAD_PARAMETERS`);
753
+ - a `captcha_url` with `t=bv`: the block page bans the address that fetched it,
754
+ so nothing sent from there can pass (server: `ERROR_PROXY_BANNED`);
755
+ - a `pageurl` that is not http(s), or that points at captcha-delivery.com
756
+ (server: `ERROR_PAGEURL`);
757
+ - a `userAgent` that is not stock Chrome for Windows: Firefox, Safari, Edge,
758
+ Opera, macOS, Linux, Android and iOS all fail (server:
759
+ `ERROR_WRONG_USERAGENT`).
760
+
761
+ The Chrome **major version** is checked by CapSkip itself, against the browser
762
+ it solves with, and a mismatch comes back as `ApiException('ERROR_WRONG_USERAGENT')`.
763
+
764
+ ### Building the `captchaUrl`
765
+
766
+ If the block page already contains the challenge iframe, take its `src` as it
767
+ is (an HTML-escaped `&amp;` is fine). Most block pages build it with a script
768
+ from a `var dd={...}` object instead:
769
+
770
+ ```js
771
+ const dd = JSON.parse(html.match(/var dd=(\{[^}]*\})/)[1].replace(/'/g, '"'));
772
+ const path = dd.rt === 'i' ? 'interstitial' : 'captcha';
773
+ const q = new URLSearchParams({ initialCid: dd.cid, hash: dd.hsh, cid: dd.cookie });
774
+ if (dd.t) q.set('t', dd.t);
775
+ q.set('referer', pageUrl);
776
+ q.set('s', String(dd.s));
777
+ for (const k of ['e', 'b']) {
778
+ if (dd[k]) q.set(k, String(dd[k]));
779
+ }
780
+ q.set('dm', 'cd');
781
+ const captchaUrl = `https://${dd.host || 'geo.captcha-delivery.com'}/${path}/?${q}`;
782
+ ```
783
+
784
+ Each block page is a fresh challenge: submit it soon, and request the page again
785
+ for the next task rather than reusing an old `captchaUrl`.
786
+
787
+ ### The cookie belongs to one IP and one User-Agent
788
+
789
+ DataDome refuses the cookie from any other address, or with any other
790
+ User-Agent:
791
+
792
+ - **Solve from the address you will use the cookie from.** Pass the proxy your
793
+ requests go through; pass none if they come from the machine running CapSkip.
794
+ - **Send the User-Agent you will use, or none.** With none, CapSkip uses its
795
+ browser's own, and the result names it.
796
+ - **CapSkip's own DataDome proxy list.** The app can hold a list of proxies for
797
+ DataDome (Settings, Captcha types, DataDome, Configure Proxies). A task sent
798
+ without a proxy is solved through that list, and the result then carries
799
+ `proxy`: that proxy as `host:port`, without its login. The cookie only works
800
+ through it. A proxy sent with the task always takes priority, and then no
801
+ `proxy` comes back.
802
+
803
+ ### GET `/res.php`
804
+
805
+ The SDK polls with `json=1`, where CapSkip returns the cookie in `request` with
806
+ `userAgent` and, when its list supplied the proxy, `proxy` next to it:
807
+
808
+ ```json
809
+ {
810
+ "status": 1,
811
+ "request": "datadome=FAPPpli4...; Max-Age=31536000; Domain=.example.com; Path=/; Secure; SameSite=Lax",
812
+ "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36",
813
+ "proxy": "203.0.113.10:8080"
814
+ }
815
+ ```
816
+
817
+ (In plain-text mode the same two values arrive in the `X-DataDome-User-Agent`
818
+ and `X-DataDome-Proxy` response headers.) See [Return value](#return-value).
819
+
820
+ ### SDK usage
821
+
822
+ ```js
823
+ // 2Captcha's argument order, with userAgent and proxy as options
824
+ const result = await solver.dataDome(captchaUrl, 'https://www.example.com/', {
825
+ userAgent,
826
+ proxy: { type: 'HTTP', uri: 'login:password@203.0.113.10:8080' },
827
+ });
828
+
829
+ // No proxy, no User-Agent: CapSkip's browser, and its proxy list if it has one
830
+ const result = await solver.dataDome(captchaUrl, 'https://www.example.com/');
831
+
832
+ const headers = {
833
+ 'User-Agent': result.userAgent,
834
+ Cookie: result.cookie, // "datadome=..."
835
+ };
836
+ if (result.proxy) {
837
+ // send this request through result.proxy
838
+ }
839
+ ```
840
+
841
+ DataDome may send a replacement `datadome` cookie on later responses; keep the
842
+ newest one, as a browser would. When the block page comes back anyway, build a
843
+ new `captchaUrl` from it and submit a new task.
844
+
845
+ Uses `recaptchaTimeout`: the challenge is passed in a real browser.
846
+
847
+ ---
848
+
716
849
  ## Return value
717
850
 
718
851
  Every solve method resolves to:
@@ -777,6 +910,21 @@ answer):
777
910
  }
778
911
  ```
779
912
 
913
+ DataDome returns a cookie. `code` keeps it exactly as DataDome set it, for a
914
+ cookie jar; `cookie` is the `datadome=...` pair alone, for a Cookie header.
915
+ `userAgent` is the User-Agent the cookie was earned with, and `proxy` appears
916
+ only when the task sent no proxy and CapSkip used its own DataDome proxy list:
917
+
918
+ ```js
919
+ {
920
+ captchaId: '12345',
921
+ code: 'datadome=FAPPpli4...; Max-Age=31536000; Domain=.example.com; Path=/; Secure; SameSite=Lax',
922
+ cookie: 'datadome=FAPPpli4...',
923
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36',
924
+ proxy: '203.0.113.10:8080',
925
+ }
926
+ ```
927
+
780
928
  GeeTest additionally expands its answer into `challenge`, `validate`, and
781
929
  `seccode` (`code` keeps the raw JSON string):
782
930
 
@@ -814,6 +962,7 @@ Convenience aliases mapped before sending to CapSkip:
814
962
  | `challengeScript` | `challenge_script` |
815
963
  | `captchaScript` | `captcha_script` |
816
964
  | `jsapi_script` | `jsapiScript` |
965
+ | `captchaUrl` / `captchaURL` | `captcha_url` |
817
966
  | `proxy` object | `proxy` + `proxytype` strings |
818
967
 
819
968
  ```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
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "capskip",
3
- "version": "1.4.0",
4
- "description": "Captcha solver for Node.js: solve reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest, AWS WAF and image captchas with CapSkip, a local unlimited captcha solver with no per-solve fees.",
3
+ "version": "1.5.0",
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.",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
7
7
  "exports": {
@@ -41,6 +41,8 @@
41
41
  "geetest-solver",
42
42
  "aws-waf",
43
43
  "aws-waf-captcha",
44
+ "datadome",
45
+ "datadome-solver",
44
46
  "image-captcha",
45
47
  "ocr",
46
48
  "2captcha-alternative",
package/src/apiParams.js CHANGED
@@ -59,6 +59,29 @@ 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 DATADOME_SUBMIT = new Set([
63
+ 'method', 'captcha_url', 'pageurl', 'useragent', 'json', 'proxy', 'proxytype',
64
+ ]);
65
+
66
+ // Every DataDome challenge is served from this domain or a subdomain of it
67
+ // (geo.captcha-delivery.com in practice). CapSkip refuses any other host.
68
+ const DATADOME_CHALLENGE_DOMAIN = 'captcha-delivery.com';
69
+
70
+ // The two challenge pages: /captcha/ draws the slider, /interstitial/ runs the
71
+ // device check. Any other path renders nothing to solve.
72
+ const DATADOME_CHALLENGE_PATHS = ['/captcha', '/interstitial'];
73
+
74
+ // A Chrome version as Chrome writes it in its User-Agent: four dotted numbers.
75
+ const CHROME_VERSION = /\bChrome\/(\d+)\.\d+\.\d+\.\d+\b/;
76
+
77
+ // Chrome-derived browsers and non-desktop builds. A User-Agent carrying any of
78
+ // these is not stock Windows Chrome, so a cookie earned under it would be
79
+ // refused on replay; the server answers ERROR_WRONG_USERAGENT for each.
80
+ const NOT_STOCK_WINDOWS_CHROME = [
81
+ 'Edg/', 'EdgA/', 'OPR/', 'YaBrowser/', 'SamsungBrowser/', 'Vivaldi/',
82
+ 'HeadlessChrome/', 'CriOS/', 'Mobile', 'Android',
83
+ ];
84
+
62
85
  // CapSkip solves the puzzle family only. `avatar` is a different challenge
63
86
  // behind a different endpoint; the server refuses it at submit time rather than
64
87
  // answering it with a puzzle answer, which would bill for a solve the target
@@ -100,6 +123,10 @@ const PARAM_ALIASES = {
100
123
  challengeScript: 'challenge_script',
101
124
  captchaScript: 'captcha_script',
102
125
  jsapi_script: 'jsapiScript',
126
+ // DataDome: 2Captcha and the API document captcha_url; createTask-style
127
+ // callers arrive with captchaUrl.
128
+ captchaUrl: 'captcha_url',
129
+ captchaURL: 'captcha_url',
103
130
  };
104
131
 
105
132
  function has(obj, key) {
@@ -456,6 +483,171 @@ function validateAmazonWafSubmit(params) {
456
483
  }
457
484
  }
458
485
 
486
+ const HTML_NAMED_ENTITIES = {
487
+ amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ',
488
+ };
489
+
490
+ /**
491
+ * Decode HTML character references, as the server does before it parses a
492
+ * DataDome captcha_url: an iframe src copied out of the page source carries
493
+ * &amp; between its query parameters.
494
+ */
495
+ function htmlUnescape(text) {
496
+ return text.replace(/&(#[0-9]+|#[xX][0-9a-fA-F]+|[A-Za-z]+);/g, (match, ref) => {
497
+ if (ref[0] === '#') {
498
+ const code = ref[1] === 'x' || ref[1] === 'X'
499
+ ? parseInt(ref.slice(2), 16)
500
+ : parseInt(ref.slice(1), 10);
501
+ try {
502
+ return String.fromCodePoint(code);
503
+ } catch (error) {
504
+ return match;
505
+ }
506
+ }
507
+ const named = HTML_NAMED_ENTITIES[ref.toLowerCase()];
508
+ return named === undefined ? match : named;
509
+ });
510
+ }
511
+
512
+ function isDataDomeHost(host) {
513
+ const lowered = String(host || '').toLowerCase();
514
+ return lowered === DATADOME_CHALLENGE_DOMAIN
515
+ || lowered.endsWith(`.${DATADOME_CHALLENGE_DOMAIN}`);
516
+ }
517
+
518
+ /**
519
+ * The query of a DataDome challenge URL as a URLSearchParams, or null if it is
520
+ * not one.
521
+ *
522
+ * Mirrors the server: an https URL on captcha-delivery.com (or a subdomain),
523
+ * path /captcha/ or /interstitial/, carrying initialCid, hash and cid. An
524
+ * iframe src copied HTML-escaped (&amp;) and a scheme-relative //geo... URL
525
+ * are both read the way the server reads them.
526
+ */
527
+ function parseDataDomeCaptchaUrl(url) {
528
+ let text = htmlUnescape(String(url === undefined || url === null ? '' : url).trim());
529
+ if (text.startsWith('//')) {
530
+ text = `https:${text}`;
531
+ }
532
+ let parsed;
533
+ try {
534
+ parsed = new URL(text);
535
+ } catch (error) {
536
+ return null;
537
+ }
538
+ if (parsed.protocol !== 'https:' || !isDataDomeHost(parsed.hostname)) {
539
+ return null;
540
+ }
541
+ const pathname = parsed.pathname.replace(/\/+$/, '').toLowerCase();
542
+ if (!DATADOME_CHALLENGE_PATHS.includes(pathname)) {
543
+ return null;
544
+ }
545
+ const query = parsed.searchParams;
546
+ if (!['initialCid', 'hash', 'cid'].every((key) => query.get(key))) {
547
+ return null;
548
+ }
549
+ return query;
550
+ }
551
+
552
+ /**
553
+ * True for a stock Chrome-for-Windows User-Agent, the only kind CapSkip takes.
554
+ *
555
+ * DataDome ties the cookie to the browser that earned it, and CapSkip solves
556
+ * in Chrome on Windows, so a cookie for any other User-Agent would be refused
557
+ * on replay. The server also requires the same Chrome major version as its
558
+ * own browser; that part can only be checked there.
559
+ */
560
+ function isDataDomeUserAgent(userAgent) {
561
+ const ua = String(userAgent === undefined || userAgent === null ? '' : userAgent);
562
+ return ua.includes('Windows NT ')
563
+ && CHROME_VERSION.test(ua)
564
+ && !NOT_STOCK_WINDOWS_CHROME.some((token) => ua.includes(token));
565
+ }
566
+
567
+ /**
568
+ * Drop unset optionals, and the empty ones too: a DataDome optional left as ''
569
+ * means "not sent" to the server, exactly like an absent one.
570
+ */
571
+ function normalizeDataDomeSubmit(params) {
572
+ const out = {};
573
+ for (const [key, value] of Object.entries(dropUnset(params))) {
574
+ if (value !== '') {
575
+ out[key] = value;
576
+ }
577
+ }
578
+ return out;
579
+ }
580
+
581
+ /** An absolute http(s) URL with a host that is not DataDome's own. */
582
+ function isDataDomePageUrl(pageurl) {
583
+ let parsed;
584
+ try {
585
+ parsed = new URL(pageurl);
586
+ } catch (error) {
587
+ return false;
588
+ }
589
+ return (parsed.protocol === 'http:' || parsed.protocol === 'https:')
590
+ && parsed.hostname !== ''
591
+ && !isDataDomeHost(parsed.hostname);
592
+ }
593
+
594
+ function validateDataDomeSubmit(params) {
595
+ // Without captcha_url the server answers ERROR_BAD_PARAMETERS and without
596
+ // pageurl ERROR_PAGEURL. Fail locally so a missing value costs no round-trip.
597
+ for (const key of ['captcha_url', 'pageurl']) {
598
+ if (!params[key]) {
599
+ throw new ValidationException(`'${key}' is required for DataDome.`);
600
+ }
601
+ }
602
+
603
+ const query = parseDataDomeCaptchaUrl(params.captcha_url);
604
+ if (query === null) {
605
+ throw new ValidationException(
606
+ "'captcha_url' must be the https address of a DataDome challenge on "
607
+ + 'captcha-delivery.com, under /captcha/ or /interstitial/ and carrying '
608
+ + `initialCid, hash and cid, got '${params.captcha_url}'. Take it from `
609
+ + "the block page's iframe src, or build it from the page's `var dd` object.",
610
+ );
611
+ }
612
+ // t=bv is DataDome saying the address that fetched the block page is
613
+ // banned. Nothing sent from it can pass, and the server refuses the task
614
+ // with ERROR_PROXY_BANNED, so say so before the round-trip.
615
+ if (query.get('t') === 'bv') {
616
+ throw new ValidationException(
617
+ 'This DataDome block page bans the IP address that requested it '
618
+ + '(t=bv), so no solve from that address can pass. Change the address '
619
+ + '(proxy), request the page again and submit the new captcha_url.',
620
+ );
621
+ }
622
+
623
+ const pageurl = String(params.pageurl).trim();
624
+ if (!isDataDomePageUrl(pageurl)) {
625
+ throw new ValidationException(
626
+ "'pageurl' must be the full http or https URL of the page that "
627
+ + `returned the DataDome block page, got '${pageurl}'.`,
628
+ );
629
+ }
630
+
631
+ const userAgent = params.useragent;
632
+ if (userAgent !== undefined && userAgent !== null && userAgent !== ''
633
+ && !isDataDomeUserAgent(userAgent)) {
634
+ throw new ValidationException(
635
+ "DataDome 'userAgent' must be a stock Chrome for Windows User-Agent "
636
+ + 'with the same major version as the browser CapSkip solves with, got '
637
+ + `'${userAgent}'. The cookie only works with the User-Agent that `
638
+ + "earned it. Leave it out and CapSkip uses its own browser's, which "
639
+ + 'the result reports as `userAgent`.',
640
+ );
641
+ }
642
+
643
+ const unknown = unknownKeys(params, DATADOME_SUBMIT);
644
+ if (unknown.length > 0) {
645
+ throw new ValidationException(
646
+ `Unsupported parameters for DataDome: ${reprList(unknown)}.`,
647
+ );
648
+ }
649
+ }
650
+
459
651
  function validateProxyType(params) {
460
652
  const proxytype = params.proxytype;
461
653
  if (proxytype === undefined || proxytype === null || proxytype === '') {
@@ -496,6 +688,9 @@ function prepareSubmitParams(params, captchaType, version = 'v2') {
496
688
  } else if (captchaType === 'amazon_waf') {
497
689
  prepared = normalizeAmazonWafSubmit(prepared);
498
690
  validateAmazonWafSubmit(prepared);
691
+ } else if (captchaType === 'datadome') {
692
+ prepared = normalizeDataDomeSubmit(prepared);
693
+ validateDataDomeSubmit(prepared);
499
694
  }
500
695
 
501
696
  // Skipped for 'normal', which rejects proxy outright with a clearer message.
@@ -518,6 +713,9 @@ module.exports = {
518
713
  FRIENDLY_CAPTCHA_SUBMIT,
519
714
  AMAZON_WAF_SUBMIT,
520
715
  AMAZON_WAF_SCRIPTS,
716
+ DATADOME_SUBMIT,
717
+ DATADOME_CHALLENGE_DOMAIN,
718
+ DATADOME_CHALLENGE_PATHS,
521
719
  CAPY_VERSIONS,
522
720
  FRIENDLY_CAPTCHA_VERSIONS,
523
721
  PROXY_TYPES,
@@ -537,6 +735,11 @@ module.exports = {
537
735
  isAmazonWafScriptUrl,
538
736
  normalizeAmazonWafSubmit,
539
737
  validateAmazonWafSubmit,
738
+ htmlUnescape,
739
+ parseDataDomeCaptchaUrl,
740
+ isDataDomeUserAgent,
741
+ normalizeDataDomeSubmit,
742
+ validateDataDomeSubmit,
540
743
  validateProxyType,
541
744
  prepareSubmitParams,
542
745
  };
package/src/index.js CHANGED
@@ -27,5 +27,5 @@ module.exports = {
27
27
  NetworkException,
28
28
  ApiException,
29
29
  TimeoutException,
30
- version: '1.4.0',
30
+ version: '1.5.0',
31
31
  };
package/src/solver.js CHANGED
@@ -83,6 +83,12 @@ function applyPollResult(result, polled) {
83
83
  if (userAgent) {
84
84
  result.userAgent = userAgent;
85
85
  }
86
+ // DataDome names the proxy a task was solved through when the task sent
87
+ // none and CapSkip took one from its own DataDome proxy list. The cookie
88
+ // only works from that address, so it is reported rather than dropped.
89
+ if (polled.proxy) {
90
+ result.proxy = polled.proxy;
91
+ }
86
92
  // ALTCHA's createTask-shaped `solution` object. Carried through so
87
93
  // applyAltchaSolution can read the counter the server already worked out,
88
94
  // which is the only reliable source for a proof-of-work v2 answer; that
@@ -277,6 +283,24 @@ function applyAmazonWafSolution(result) {
277
283
  return expandObjectAnswer(result, AMAZON_WAF_FIELDS);
278
284
  }
279
285
 
286
+ /**
287
+ * Expose the DataDome cookie as `cookie`, ready for a Cookie header.
288
+ *
289
+ * `code` keeps the cookie exactly as DataDome set it, attributes included
290
+ * (`datadome=...; Max-Age=...; Domain=...; Path=/; Secure; SameSite=Lax`),
291
+ * for a cookie jar. `cookie` is the part before the first semicolon, the
292
+ * `datadome=...` pair a request sends back. `userAgent` and, when CapSkip
293
+ * picked the proxy, `proxy` are carried through from the poll as they are.
294
+ * An answer that is not a string is left untouched.
295
+ */
296
+ function applyDataDomeSolution(result) {
297
+ const { code } = result;
298
+ if (typeof code === 'string' && code) {
299
+ result.cookie = code.split(';', 1)[0].trim();
300
+ }
301
+ return result;
302
+ }
303
+
280
304
  /**
281
305
  * Expose a single-token answer as `token`, named for the form field it fills.
282
306
  *
@@ -326,7 +350,8 @@ function parseSubmitResponse(response) {
326
350
 
327
351
  /**
328
352
  * Client for the CapSkip local captcha solver (image, reCAPTCHA, Turnstile,
329
- * GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF).
353
+ * GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF,
354
+ * DataDome).
330
355
  */
331
356
  class CapSkip {
332
357
  constructor({
@@ -620,6 +645,61 @@ class CapSkip {
620
645
  return applyAmazonWafSolution(result);
621
646
  }
622
647
 
648
+ /**
649
+ * Solve a DataDome challenge and return the `datadome` cookie.
650
+ *
651
+ * `captchaUrl` and `url` are required; everything else goes in `options`:
652
+ *
653
+ * - `captchaUrl` is the address of the challenge iframe on
654
+ * geo.captcha-delivery.com, under /captcha/ (slider) or /interstitial/
655
+ * (device check). Take the iframe's src from the block page, or build it
656
+ * from the page's `var dd` object as the API docs show.
657
+ * - `url` is the page that returned the block page.
658
+ * - `options.userAgent` is the one you will send with the cookie. It must be
659
+ * Chrome for Windows with the same major version as the browser CapSkip
660
+ * solves with; leave it out and CapSkip uses that browser's own.
661
+ * - `options.proxy` is the proxy your later requests go through, as a URI
662
+ * string or `{ type: 'HTTP', uri: 'login:password@IP:PORT' }`.
663
+ *
664
+ * DataDome ties the cookie to the IP address and User-Agent that earned it.
665
+ * The result carries:
666
+ *
667
+ * - `code` -- the cookie exactly as DataDome set it, attributes included;
668
+ * - `cookie` -- just the `datadome=...` pair, for a Cookie header;
669
+ * - `userAgent` -- the User-Agent the cookie was earned with. Send it with
670
+ * every request that carries the cookie;
671
+ * - `proxy` -- present only when you sent no proxy and CapSkip solved
672
+ * through its own DataDome proxy list: that proxy as `host:port`. The
673
+ * cookie works only through it.
674
+ */
675
+ async dataDome(captchaUrl, url, options = {}) {
676
+ // An unset value is dropped rather than sent as undefined, so
677
+ // `dataDome(undefined, url, { captchaUrl })` reaches the alias without
678
+ // tripping the conflict check; an empty one is dropped during validation,
679
+ // since the server reads '' as "not sent".
680
+ const given = {};
681
+ const add = (key, value) => {
682
+ if (value !== undefined && value !== null) {
683
+ given[key] = value;
684
+ }
685
+ };
686
+ add('captcha_url', captchaUrl);
687
+ add('url', url);
688
+ for (const [key, value] of Object.entries(options)) {
689
+ add(key, value);
690
+ }
691
+
692
+ // A real browser session, slider or device check, so it gets the longer
693
+ // of the two timeouts unless the caller asked for a specific one.
694
+ const result = await this.solve({
695
+ timeout: this.recaptchaTimeout,
696
+ ...given,
697
+ method: 'datadome',
698
+ poll_json: 1,
699
+ });
700
+ return applyDataDomeSolution(result);
701
+ }
702
+
623
703
  async solve(options = {}) {
624
704
  const {
625
705
  timeout = 0,
@@ -723,6 +803,9 @@ class CapSkip {
723
803
  if (method === 'amazon_waf') {
724
804
  return prepareSubmitParams(params, 'amazon_waf');
725
805
  }
806
+ if (method === 'datadome') {
807
+ return prepareSubmitParams(params, 'datadome');
808
+ }
726
809
  return applyProxy(applyParamAliases(params));
727
810
  }
728
811
  }
@@ -738,6 +821,7 @@ module.exports = {
738
821
  applyAltchaSolution,
739
822
  applyCapySolution,
740
823
  applyAmazonWafSolution,
824
+ applyDataDomeSolution,
741
825
  expandObjectAnswer,
742
826
  applyTokenSolution,
743
827
  };
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 before timing out. Default `300`. ALTCHA and Capy use
23
+ * Captcha / AWS WAF / DataDome before timing out. Default `300`. ALTCHA and Capy use
24
24
  * `defaultTimeout` instead — neither is a browser solve.
25
25
  */
26
26
  recaptchaTimeout?: number;
@@ -37,13 +37,16 @@ export interface SolveResult {
37
37
  * this is the raw JSON string CapSkip returns — prefer the parsed
38
38
  * `challenge` / `validate` / `seccode` fields below. For ALTCHA it is the
39
39
  * base64 token, also exposed as `token`. For Capy and AWS WAF it is the raw
40
- * answer object, expanded into the fields below.
40
+ * answer object, expanded into the fields below. For DataDome it is the
41
+ * `datadome` cookie exactly as DataDome set it, attributes included.
41
42
  */
42
43
  code: string | Record<string, unknown>;
43
44
  /**
44
- * Turnstile and CaptchaFox — the User-Agent to use when submitting the token.
45
- * For CaptchaFox this is the browser's own, not one you sent, and it is absent
46
- * when the solve captured none rather than guessed.
45
+ * Turnstile, CaptchaFox and DataDome — the User-Agent to use when submitting
46
+ * the token. For CaptchaFox this is the browser's own, not one you sent, and
47
+ * it is absent when the solve captured none rather than guessed. For DataDome
48
+ * it is the User-Agent the cookie was earned with — the one you sent, or
49
+ * CapSkip's own browser's — and every request carrying the cookie must send it.
47
50
  */
48
51
  userAgent?: string;
49
52
  /** GeeTest only — the `geetest_challenge` value to post back. */
@@ -82,6 +85,17 @@ export interface SolveResult {
82
85
  captcha_voucher?: string;
83
86
  /** AWS WAF only — the token the voucher was issued for. Pair with `captcha_voucher`. */
84
87
  existing_token?: string;
88
+ /**
89
+ * DataDome only — the `datadome=...` pair alone (`code` up to its first
90
+ * `;`), ready for a `Cookie` header.
91
+ */
92
+ cookie?: string;
93
+ /**
94
+ * DataDome only — present only when the task sent no proxy and CapSkip solved
95
+ * through its own DataDome proxy list: that proxy as `host:port`. The cookie
96
+ * works only through it.
97
+ */
98
+ proxy?: string;
85
99
  }
86
100
 
87
101
  /** Extra options for {@link CapSkip.normal}. */
@@ -280,6 +294,36 @@ export interface AmazonWafOptions {
280
294
  [key: string]: unknown;
281
295
  }
282
296
 
297
+ /** Extra options for {@link CapSkip.dataDome}. */
298
+ export interface DataDomeOptions {
299
+ /**
300
+ * The User-Agent you will send with the cookie. Must be stock Chrome for
301
+ * Windows with the same major version as the browser CapSkip solves with.
302
+ * Leave it out and CapSkip uses its own browser's, reported as `userAgent`.
303
+ */
304
+ userAgent?: string;
305
+ /** Alias for `userAgent`. */
306
+ useragent?: string;
307
+ /** The challenge iframe URL (alias for the `captchaUrl` argument). */
308
+ captcha_url?: string;
309
+ /** The challenge iframe URL (alias for the `captchaUrl` argument). */
310
+ captchaUrl?: string;
311
+ /**
312
+ * The proxy your later requests go through. Left out, CapSkip solves through
313
+ * its own DataDome proxy list and the result names that proxy as `proxy`.
314
+ */
315
+ proxy?: Proxy | string;
316
+ /** Proxy type when `proxy` is a bare string. Default `HTTP`. */
317
+ proxytype?: string;
318
+ /** Poll timeout in seconds. Default: the client's `recaptchaTimeout`. */
319
+ timeout?: number;
320
+ /** Max seconds between polls for this solve. */
321
+ polling_interval?: number;
322
+ /** `1` to request the raw JSON response from CapSkip. */
323
+ json?: number;
324
+ [key: string]: unknown;
325
+ }
326
+
283
327
  /** Options for the {@link CapSkip.solve} manual workflow. */
284
328
  export interface SolveOptions {
285
329
  /** Poll timeout in seconds (falls back to the configured default). */
@@ -376,6 +420,22 @@ export class CapSkip {
376
420
  url: string,
377
421
  options?: AmazonWafOptions,
378
422
  ): Promise<SolveResult>;
423
+ /**
424
+ * Solve a DataDome challenge (slider or device check) and return the
425
+ * `datadome` cookie.
426
+ *
427
+ * `captchaUrl` is the challenge iframe's address on captcha-delivery.com
428
+ * (`/captcha/` or `/interstitial/`), `url` the page that returned the block
429
+ * page. The result carries `code` (the cookie as set), `cookie` (the
430
+ * `datadome=...` pair), `userAgent` (send it with the cookie) and, only when
431
+ * no proxy was passed and CapSkip used its own DataDome proxy list, `proxy`
432
+ * — the cookie works only through that address.
433
+ */
434
+ dataDome(
435
+ captchaUrl: string | undefined,
436
+ url: string,
437
+ options?: DataDomeOptions,
438
+ ): Promise<SolveResult>;
379
439
  /** Submit then poll to completion. Used by the higher-level solve methods. */
380
440
  solve(options?: SolveOptions): Promise<SolveResult>;
381
441
  /** Submit a captcha without polling; resolves to the captcha id. */