capskip 1.3.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/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
@@ -222,7 +228,7 @@ function applyAltchaSolution(result) {
222
228
  const CAPY_FIELDS = ['captchakey', 'challengekey', 'answer', 'respKey'];
223
229
 
224
230
  /**
225
- * Expand the Capy answer into `captchakey` / `challengekey` / `answer`.
231
+ * Copy `fields` out of an answer that is an object rather than a token.
226
232
  *
227
233
  * `code` keeps the raw answer -- an object when polled with json=1, where the
228
234
  * server puts it straight into `request`, or the JSON string it sends after
@@ -232,7 +238,7 @@ const CAPY_FIELDS = ['captchakey', 'challengekey', 'answer', 'respKey'];
232
238
  * If the answer does not parse, the result is returned untouched rather than
233
239
  * masking the server's reply.
234
240
  */
235
- function applyCapySolution(result) {
241
+ function expandObjectAnswer(result, fields) {
236
242
  let payload = result.solution;
237
243
  delete result.solution;
238
244
 
@@ -253,7 +259,7 @@ function applyCapySolution(result) {
253
259
  return result;
254
260
  }
255
261
 
256
- for (const field of CAPY_FIELDS) {
262
+ for (const field of fields) {
257
263
  if (Object.prototype.hasOwnProperty.call(payload, field)) {
258
264
  result[field] = payload[field];
259
265
  }
@@ -262,6 +268,39 @@ function applyCapySolution(result) {
262
268
  return result;
263
269
  }
264
270
 
271
+ /** Expand the Capy answer into `captchakey` / `challengekey` / `answer`. */
272
+ function applyCapySolution(result) {
273
+ return expandObjectAnswer(result, CAPY_FIELDS);
274
+ }
275
+
276
+ // An AWS WAF solution is two values that only work as a pair: the voucher AWS
277
+ // issued for the answered puzzle, and the token it was issued for. The site's
278
+ // own /voucher endpoint trades them for the aws-waf-token cookie.
279
+ const AMAZON_WAF_FIELDS = ['captcha_voucher', 'existing_token'];
280
+
281
+ /** Expand the AWS WAF answer into `captcha_voucher` / `existing_token`. */
282
+ function applyAmazonWafSolution(result) {
283
+ return expandObjectAnswer(result, AMAZON_WAF_FIELDS);
284
+ }
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
+
265
304
  /**
266
305
  * Expose a single-token answer as `token`, named for the form field it fills.
267
306
  *
@@ -309,7 +348,11 @@ function parseSubmitResponse(response) {
309
348
  throw new ApiException(`cannot recognize response ${response}`);
310
349
  }
311
350
 
312
- /** Client for the CapSkip local captcha solver (image, reCAPTCHA, Turnstile, GeeTest v3, ALTCHA). */
351
+ /**
352
+ * Client for the CapSkip local captcha solver (image, reCAPTCHA, Turnstile,
353
+ * GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF,
354
+ * DataDome).
355
+ */
313
356
  class CapSkip {
314
357
  constructor({
315
358
  apiKey = 'capskip',
@@ -553,6 +596,110 @@ class CapSkip {
553
596
  return applyTokenSolution(result);
554
597
  }
555
598
 
599
+ /**
600
+ * Solve an AWS WAF (Amazon WAF) CAPTCHA.
601
+ *
602
+ * Only `sitekey` and `url` are required:
603
+ *
604
+ * - On a challenge page (the HTTP 405 page with `window.gokuProps`), `sitekey`
605
+ * is `gokuProps.key`, and `iv` and `context` are `gokuProps.iv` and
606
+ * `gokuProps.context`. Send both or neither -- left out, CapSkip reads them
607
+ * from `url` at solve time, which is the safer choice when your copy of the
608
+ * page is more than a few minutes old. `challenge_script` and
609
+ * `captcha_script` are the full URLs of the page's challenge.js and
610
+ * captcha.js.
611
+ * - On a widget page (`AwsWafCaptcha.renderCaptcha`), `sitekey` is the
612
+ * widget's `apiKey`; pass `jsapiScript` with the full URL of jsapi.js and no
613
+ * `iv` / `context`.
614
+ *
615
+ * Every script URL must be an https URL on an awswaf.com host; anything else
616
+ * is refused before the request is sent.
617
+ *
618
+ * The result carries `captcha_voucher` and `existing_token`, plus the raw
619
+ * answer as `code`. The two only work as a pair: POST them as JSON to the
620
+ * `voucher` endpoint next to the page's challenge.js and the response's
621
+ * `token` is the aws-waf-token cookie. Exchange them as soon as they arrive
622
+ * rather than storing them for later.
623
+ */
624
+ async amazonWaf(sitekey, url, options = {}) {
625
+ // An unset optional is dropped rather than sent as undefined; an empty one
626
+ // is dropped during validation, since the server reads '' as "not sent".
627
+ const given = {};
628
+ for (const [key, value] of Object.entries(options)) {
629
+ if (value !== undefined && value !== null) {
630
+ given[key] = value;
631
+ }
632
+ }
633
+
634
+ // A real browser mints the token the voucher is issued for, and the puzzle
635
+ // rounds go through AWS on top of that, so it gets the longer of the two
636
+ // timeouts unless the caller asked for a specific one.
637
+ const result = await this.solve({
638
+ timeout: this.recaptchaTimeout,
639
+ sitekey,
640
+ url,
641
+ ...given,
642
+ method: 'amazon_waf',
643
+ poll_json: 1,
644
+ });
645
+ return applyAmazonWafSolution(result);
646
+ }
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
+
556
703
  async solve(options = {}) {
557
704
  const {
558
705
  timeout = 0,
@@ -653,6 +800,12 @@ class CapSkip {
653
800
  if (method === 'friendly_captcha') {
654
801
  return prepareSubmitParams(params, 'friendly_captcha');
655
802
  }
803
+ if (method === 'amazon_waf') {
804
+ return prepareSubmitParams(params, 'amazon_waf');
805
+ }
806
+ if (method === 'datadome') {
807
+ return prepareSubmitParams(params, 'datadome');
808
+ }
656
809
  return applyProxy(applyParamAliases(params));
657
810
  }
658
811
  }
@@ -667,5 +820,8 @@ module.exports = {
667
820
  applyGeetestSolution,
668
821
  applyAltchaSolution,
669
822
  applyCapySolution,
823
+ applyAmazonWafSolution,
824
+ applyDataDomeSolution,
825
+ expandObjectAnswer,
670
826
  applyTokenSolution,
671
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 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;
@@ -36,14 +36,17 @@ export interface SolveResult {
36
36
  * The solution: recognized text for images, a token otherwise. For GeeTest
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
- * base64 token, also exposed as `token`. For Capy it is the raw answer
40
- * object, expanded into the three fields below.
39
+ * base64 token, also exposed as `token`. For Capy and AWS WAF it is the raw
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. */
@@ -73,6 +76,26 @@ export interface SolveResult {
73
76
  * 2Captcha's documented response; it carries nothing.
74
77
  */
75
78
  respKey?: string;
79
+ /**
80
+ * AWS WAF only — the voucher AWS issued for the answered puzzle. Only valid
81
+ * together with `existing_token`: POST both as JSON to the `voucher` endpoint
82
+ * beside the page's challenge.js, whose response `token` is the
83
+ * `aws-waf-token` cookie. Exchange immediately.
84
+ */
85
+ captcha_voucher?: string;
86
+ /** AWS WAF only — the token the voucher was issued for. Pair with `captcha_voucher`. */
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;
76
99
  }
77
100
 
78
101
  /** Extra options for {@link CapSkip.normal}. */
@@ -239,6 +262,68 @@ export interface FriendlyCaptchaOptions {
239
262
  [key: string]: unknown;
240
263
  }
241
264
 
265
+ /** Extra options for {@link CapSkip.amazonWaf}. */
266
+ export interface AmazonWafOptions {
267
+ /**
268
+ * Challenge page: `gokuProps.iv`. Send together with `context`, or leave both
269
+ * out and CapSkip reads them from the page at solve time.
270
+ */
271
+ iv?: string;
272
+ /** Challenge page: `gokuProps.context`. Send together with `iv`. */
273
+ context?: string;
274
+ /** Full https URL of the page's `challenge.js` on an `awswaf.com` host. */
275
+ challenge_script?: string;
276
+ /** Full https URL of the page's `challenge.js` (alias for `challenge_script`). */
277
+ challengeScript?: string;
278
+ /** Full https URL of the page's `captcha.js` on an `awswaf.com` host. */
279
+ captcha_script?: string;
280
+ /** Full https URL of the page's `captcha.js` (alias for `captcha_script`). */
281
+ captchaScript?: string;
282
+ /** Widget page: full https URL of `jsapi.js` on an `awswaf.com` host. */
283
+ jsapiScript?: string;
284
+ /** Widget page: full https URL of `jsapi.js` (alias for `jsapiScript`). */
285
+ jsapi_script?: string;
286
+ /** Poll timeout in seconds. Default: the client's `recaptchaTimeout`. */
287
+ timeout?: number;
288
+ /** `1` to request the raw JSON response from CapSkip. */
289
+ json?: number;
290
+ /** Proxy used for solving. */
291
+ proxy?: Proxy | string;
292
+ /** Proxy type when `proxy` is a bare string. */
293
+ proxytype?: string;
294
+ [key: string]: unknown;
295
+ }
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
+
242
327
  /** Options for the {@link CapSkip.solve} manual workflow. */
243
328
  export interface SolveOptions {
244
329
  /** Poll timeout in seconds (falls back to the configured default). */
@@ -320,6 +405,37 @@ export class CapSkip {
320
405
  url: string,
321
406
  options?: FriendlyCaptchaOptions,
322
407
  ): Promise<SolveResult>;
408
+ /**
409
+ * Solve an AWS WAF CAPTCHA.
410
+ *
411
+ * On a challenge page, `sitekey` is `gokuProps.key` and `iv` / `context` are
412
+ * `gokuProps.iv` / `gokuProps.context` (both or neither). On a widget page,
413
+ * `sitekey` is the widget's `apiKey`; pass `jsapiScript`. Script URLs must be
414
+ * https on an `awswaf.com` host. The result carries `captcha_voucher` and
415
+ * `existing_token`, which the site's `voucher` endpoint trades for the
416
+ * `aws-waf-token` cookie.
417
+ */
418
+ amazonWaf(
419
+ sitekey: string,
420
+ url: string,
421
+ options?: AmazonWafOptions,
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>;
323
439
  /** Submit then poll to completion. Used by the higher-level solve methods. */
324
440
  solve(options?: SolveOptions): Promise<SolveResult>;
325
441
  /** Submit a captcha without polling; resolves to the captcha id. */