capskip 1.2.0 → 1.4.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
@@ -217,6 +217,90 @@ function applyAltchaSolution(result) {
217
217
  return result;
218
218
  }
219
219
 
220
+ // A Capy solution is not a token. It is three values that together go into the
221
+ // target form, under the capy_ prefixed names the widget would have filled in.
222
+ const CAPY_FIELDS = ['captchakey', 'challengekey', 'answer', 'respKey'];
223
+
224
+ /**
225
+ * Copy `fields` out of an answer that is an object rather than a token.
226
+ *
227
+ * `code` keeps the raw answer -- an object when polled with json=1, where the
228
+ * server puts it straight into `request`, or the JSON string it sends after
229
+ * `OK|` in plain-text mode -- so callers that forward it verbatim (or that were
230
+ * written against another solver's API) keep working.
231
+ *
232
+ * If the answer does not parse, the result is returned untouched rather than
233
+ * masking the server's reply.
234
+ */
235
+ function expandObjectAnswer(result, fields) {
236
+ let payload = result.solution;
237
+ delete result.solution;
238
+
239
+ if (payload === null || typeof payload !== 'object') {
240
+ const { code } = result;
241
+ if (code !== null && typeof code === 'object') {
242
+ payload = code;
243
+ } else {
244
+ try {
245
+ payload = JSON.parse(code || '');
246
+ } catch (error) {
247
+ return result;
248
+ }
249
+ }
250
+ }
251
+
252
+ if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) {
253
+ return result;
254
+ }
255
+
256
+ for (const field of fields) {
257
+ if (Object.prototype.hasOwnProperty.call(payload, field)) {
258
+ result[field] = payload[field];
259
+ }
260
+ }
261
+
262
+ return result;
263
+ }
264
+
265
+ /** Expand the Capy answer into `captchakey` / `challengekey` / `answer`. */
266
+ function applyCapySolution(result) {
267
+ return expandObjectAnswer(result, CAPY_FIELDS);
268
+ }
269
+
270
+ // An AWS WAF solution is two values that only work as a pair: the voucher AWS
271
+ // issued for the answered puzzle, and the token it was issued for. The site's
272
+ // own /voucher endpoint trades them for the aws-waf-token cookie.
273
+ const AMAZON_WAF_FIELDS = ['captcha_voucher', 'existing_token'];
274
+
275
+ /** Expand the AWS WAF answer into `captcha_voucher` / `existing_token`. */
276
+ function applyAmazonWafSolution(result) {
277
+ return expandObjectAnswer(result, AMAZON_WAF_FIELDS);
278
+ }
279
+
280
+ /**
281
+ * Expose a single-token answer as `token`, named for the form field it fills.
282
+ *
283
+ * `code` keeps the raw answer so callers that forward it verbatim keep working;
284
+ * `token` is the same string. The server's createTask-shaped `solution` object
285
+ * carries that same string, so it is consumed here rather than handed back as a
286
+ * second copy -- but it is read first when `request` came through empty, so a
287
+ * client is never left without the token the poll actually carried.
288
+ */
289
+ function applyTokenSolution(result) {
290
+ const { solution } = result;
291
+ delete result.solution;
292
+
293
+ let code = result.code;
294
+
295
+ if (!code && solution !== null && typeof solution === 'object') {
296
+ code = solution.token || '';
297
+ result.code = code;
298
+ }
299
+
300
+ result.token = code || '';
301
+ return result;
302
+ }
303
+
220
304
  // CapSkip's in.php returns OK|<id> by default, or {"status":1,"request":"<id>"}
221
305
  // when the submit carried json=1. Accept both so submitting with json=1 works.
222
306
  function parseSubmitResponse(response) {
@@ -240,7 +324,10 @@ function parseSubmitResponse(response) {
240
324
  throw new ApiException(`cannot recognize response ${response}`);
241
325
  }
242
326
 
243
- /** Client for the CapSkip local captcha solver (image, reCAPTCHA, Turnstile, GeeTest v3, ALTCHA). */
327
+ /**
328
+ * Client for the CapSkip local captcha solver (image, reCAPTCHA, Turnstile,
329
+ * GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF).
330
+ */
244
331
  class CapSkip {
245
332
  constructor({
246
333
  apiKey = 'capskip',
@@ -359,6 +446,180 @@ class CapSkip {
359
446
  return applyAltchaSolution(result);
360
447
  }
361
448
 
449
+ /**
450
+ * Solve a Capy Puzzle captcha.
451
+ *
452
+ * `sitekey` is the site's public Capy key, conventionally prefixed `PUZZLE_`;
453
+ * it is sent as the `captchakey` the API documents. Pass `api_server` when the
454
+ * widget script points somewhere other than `https://jp.api.capy.me`.
455
+ *
456
+ * The result is not a token. It carries `captchakey`, `challengekey` and
457
+ * `answer`, which go into the target form's `capy_captchakey`,
458
+ * `capy_challengekey` and `capy_answer` fields, plus the raw answer as `code`.
459
+ * Submit `answer` verbatim -- it is the drag path the widget would have
460
+ * recorded, so trimming or re-encoding it invalidates the solve.
461
+ *
462
+ * The challenge key is single-use and short-lived, so submit promptly rather
463
+ * than caching the three values for a later request.
464
+ */
465
+ async capy(sitekey, url, options = {}) {
466
+ // An unset optional is dropped rather than sent as undefined, so
467
+ // `capy(key, url, { apiServer: undefined })` behaves as if it were omitted.
468
+ const given = {};
469
+ for (const [key, value] of Object.entries(options)) {
470
+ if (value !== undefined && value !== null) {
471
+ given[key] = value;
472
+ }
473
+ }
474
+
475
+ // A Capy solve is one HTTP fetch plus pixel math, not a browser session, so
476
+ // it keeps the default timeout. It is held back to roughly two seconds
477
+ // before the answer is released -- Capy refuses answers that arrive faster
478
+ // than a human could have produced them -- which the default absorbs.
479
+ const result = await this.solve({
480
+ captchakey: sitekey,
481
+ url,
482
+ ...given,
483
+ method: 'capy',
484
+ poll_json: 1,
485
+ });
486
+ return applyCapySolution(result);
487
+ }
488
+
489
+ /**
490
+ * Solve a CaptchaFox challenge.
491
+ *
492
+ * `sitekey` is the public key the widget renders with, conventionally prefixed
493
+ * `sk_`, and `url` has to be the page the widget actually runs on: CaptchaFox
494
+ * checks it against the domains the key is registered for and refuses a
495
+ * mismatch permanently rather than intermittently.
496
+ *
497
+ * Pass `api_server` only when the target page does not load the default
498
+ * widget. A page loading the MAM package expects a `MAM_` prefixed token, and
499
+ * sending the wrong source still succeeds -- it just returns a token in a
500
+ * format the site will not accept, which reads as a silent verification
501
+ * failure rather than an error.
502
+ *
503
+ * The result carries the token as both `code` and `token`, for the form's
504
+ * `cf-captcha-response` field, and `userAgent` when the solve reported one.
505
+ * That User-Agent is the browser's own, not any you sent, so submit the token
506
+ * under it.
507
+ */
508
+ async captchafox(sitekey, url, options = {}) {
509
+ const given = {};
510
+ for (const [key, value] of Object.entries(options)) {
511
+ if (value !== undefined && value !== null) {
512
+ given[key] = value;
513
+ }
514
+ }
515
+
516
+ // A real browser session, like reCAPTCHA and GeeTest, and longer again when
517
+ // an interactive challenge is drawn -- so it gets the longer of the two
518
+ // timeouts unless the caller asked for a specific one.
519
+ const result = await this.solve({
520
+ timeout: this.recaptchaTimeout,
521
+ sitekey,
522
+ url,
523
+ ...given,
524
+ method: 'captchafox',
525
+ poll_json: 1,
526
+ });
527
+ return applyTokenSolution(result);
528
+ }
529
+
530
+ /**
531
+ * Solve a Friendly Captcha proof-of-work challenge.
532
+ *
533
+ * Two different protocols ship under this name and a sitekey does not tell you
534
+ * which one a site uses, so say which: pass `version: 'v1'` or `version: 'v2'`,
535
+ * or pass `moduleScript` with the src of the widget's `type="module"` script
536
+ * tag and let CapSkip read the version off the build the site actually loads.
537
+ * With neither, v1 is assumed. Solving the wrong version returns a well-formed
538
+ * token the target site rejects, with nothing to indicate the version was the
539
+ * problem.
540
+ *
541
+ * Pass `api_server: 'eu'` for a sitekey on the EU data-residency tenant; both
542
+ * tenants mint a token for the same sitekey, so the wrong one is only caught
543
+ * by the site's own verification.
544
+ *
545
+ * The result carries the token as both `code` and `token`. It goes into
546
+ * `frc-captcha-solution` on v1 and `frc-captcha-response` on v2 -- the field
547
+ * names differ, which is what catches an integration moved from one to the
548
+ * other. A v2 token is roughly six kilobytes, so size whatever carries it
549
+ * accordingly.
550
+ */
551
+ async friendlyCaptcha(sitekey, url, options = {}) {
552
+ const given = {};
553
+ for (const [key, value] of Object.entries(options)) {
554
+ if (value !== undefined && value !== null) {
555
+ given[key] = value;
556
+ }
557
+ }
558
+
559
+ // Proof-of-work, but not the millisecond kind ALTCHA does: the service sets
560
+ // the difficulty per request and raises it for addresses it has seen a lot
561
+ // of, and v2 always solves in a browser. Both make solve time variable
562
+ // enough to want the longer timeout.
563
+ const result = await this.solve({
564
+ timeout: this.recaptchaTimeout,
565
+ sitekey,
566
+ url,
567
+ ...given,
568
+ method: 'friendly_captcha',
569
+ poll_json: 1,
570
+ });
571
+ return applyTokenSolution(result);
572
+ }
573
+
574
+ /**
575
+ * Solve an AWS WAF (Amazon WAF) CAPTCHA.
576
+ *
577
+ * Only `sitekey` and `url` are required:
578
+ *
579
+ * - On a challenge page (the HTTP 405 page with `window.gokuProps`), `sitekey`
580
+ * is `gokuProps.key`, and `iv` and `context` are `gokuProps.iv` and
581
+ * `gokuProps.context`. Send both or neither -- left out, CapSkip reads them
582
+ * from `url` at solve time, which is the safer choice when your copy of the
583
+ * page is more than a few minutes old. `challenge_script` and
584
+ * `captcha_script` are the full URLs of the page's challenge.js and
585
+ * captcha.js.
586
+ * - On a widget page (`AwsWafCaptcha.renderCaptcha`), `sitekey` is the
587
+ * widget's `apiKey`; pass `jsapiScript` with the full URL of jsapi.js and no
588
+ * `iv` / `context`.
589
+ *
590
+ * Every script URL must be an https URL on an awswaf.com host; anything else
591
+ * is refused before the request is sent.
592
+ *
593
+ * The result carries `captcha_voucher` and `existing_token`, plus the raw
594
+ * answer as `code`. The two only work as a pair: POST them as JSON to the
595
+ * `voucher` endpoint next to the page's challenge.js and the response's
596
+ * `token` is the aws-waf-token cookie. Exchange them as soon as they arrive
597
+ * rather than storing them for later.
598
+ */
599
+ async amazonWaf(sitekey, url, options = {}) {
600
+ // An unset optional is dropped rather than sent as undefined; an empty one
601
+ // is dropped during validation, since the server reads '' as "not sent".
602
+ const given = {};
603
+ for (const [key, value] of Object.entries(options)) {
604
+ if (value !== undefined && value !== null) {
605
+ given[key] = value;
606
+ }
607
+ }
608
+
609
+ // A real browser mints the token the voucher is issued for, and the puzzle
610
+ // rounds go through AWS on top of that, so it gets the longer of the two
611
+ // timeouts unless the caller asked for a specific one.
612
+ const result = await this.solve({
613
+ timeout: this.recaptchaTimeout,
614
+ sitekey,
615
+ url,
616
+ ...given,
617
+ method: 'amazon_waf',
618
+ poll_json: 1,
619
+ });
620
+ return applyAmazonWafSolution(result);
621
+ }
622
+
362
623
  async solve(options = {}) {
363
624
  const {
364
625
  timeout = 0,
@@ -450,6 +711,18 @@ class CapSkip {
450
711
  if (method === 'altcha') {
451
712
  return prepareSubmitParams(params, 'altcha');
452
713
  }
714
+ if (method === 'capy') {
715
+ return prepareSubmitParams(params, 'capy');
716
+ }
717
+ if (method === 'captchafox') {
718
+ return prepareSubmitParams(params, 'captchafox');
719
+ }
720
+ if (method === 'friendly_captcha') {
721
+ return prepareSubmitParams(params, 'friendly_captcha');
722
+ }
723
+ if (method === 'amazon_waf') {
724
+ return prepareSubmitParams(params, 'amazon_waf');
725
+ }
453
726
  return applyProxy(applyParamAliases(params));
454
727
  }
455
728
  }
@@ -463,4 +736,8 @@ module.exports = {
463
736
  applyPollResult,
464
737
  applyGeetestSolution,
465
738
  applyAltchaSolution,
739
+ applyCapySolution,
740
+ applyAmazonWafSolution,
741
+ expandObjectAnswer,
742
+ applyTokenSolution,
466
743
  };
package/types/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Type definitions for the CapSkip Node.js SDK.
2
2
 
3
- /** Proxy passed to reCAPTCHA / Turnstile / GeeTest solves. */
3
+ /** Proxy passed to any solve except image captcha. */
4
4
  export interface Proxy {
5
5
  /** Proxy type: `HTTP`, `HTTPS`, `SOCKS5`, or `SOCKS5H`. */
6
6
  type: string;
@@ -18,7 +18,11 @@ export interface CapSkipOptions {
18
18
  port?: number;
19
19
  /** Seconds to poll an image captcha before timing out. Default `120`. */
20
20
  defaultTimeout?: number;
21
- /** Seconds to poll reCAPTCHA / Turnstile / GeeTest before timing out. Default `300`. */
21
+ /**
22
+ * Seconds to poll reCAPTCHA / Turnstile / GeeTest / CaptchaFox / Friendly
23
+ * Captcha / AWS WAF before timing out. Default `300`. ALTCHA and Capy use
24
+ * `defaultTimeout` instead — neither is a browser solve.
25
+ */
22
26
  recaptchaTimeout?: number;
23
27
  /** Max seconds between polls; starts at `0.25` and backs off to this. Default `5`. */
24
28
  pollingInterval?: number;
@@ -32,10 +36,15 @@ export interface SolveResult {
32
36
  * The solution: recognized text for images, a token otherwise. For GeeTest
33
37
  * this is the raw JSON string CapSkip returns — prefer the parsed
34
38
  * `challenge` / `validate` / `seccode` fields below. For ALTCHA it is the
35
- * base64 token, also exposed as `token`.
39
+ * base64 token, also exposed as `token`. For Capy and AWS WAF it is the raw
40
+ * answer object, expanded into the fields below.
41
+ */
42
+ code: string | Record<string, unknown>;
43
+ /**
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.
36
47
  */
37
- code: string;
38
- /** Turnstile only — the User-Agent to use when submitting the token. */
39
48
  userAgent?: string;
40
49
  /** GeeTest only — the `geetest_challenge` value to post back. */
41
50
  challenge?: string;
@@ -50,6 +59,29 @@ export interface SolveResult {
50
59
  token?: string;
51
60
  /** ALTCHA only — the counter that solved the challenge. */
52
61
  number?: number;
62
+ /** Capy only — the value for the target form's `capy_captchakey` field. */
63
+ captchakey?: string;
64
+ /** Capy only — the value for the target form's `capy_challengekey` field. */
65
+ challengekey?: string;
66
+ /**
67
+ * Capy only — the value for the target form's `capy_answer` field. The drag
68
+ * path the widget would have recorded; submit it verbatim.
69
+ */
70
+ answer?: string;
71
+ /**
72
+ * Capy only — empty for a puzzle solve. Present for shape compatibility with
73
+ * 2Captcha's documented response; it carries nothing.
74
+ */
75
+ respKey?: string;
76
+ /**
77
+ * AWS WAF only — the voucher AWS issued for the answered puzzle. Only valid
78
+ * together with `existing_token`: POST both as JSON to the `voucher` endpoint
79
+ * beside the page's challenge.js, whose response `token` is the
80
+ * `aws-waf-token` cookie. Exchange immediately.
81
+ */
82
+ captcha_voucher?: string;
83
+ /** AWS WAF only — the token the voucher was issued for. Pair with `captcha_voucher`. */
84
+ existing_token?: string;
53
85
  }
54
86
 
55
87
  /** Extra options for {@link CapSkip.normal}. */
@@ -141,6 +173,113 @@ export interface AltchaOptions {
141
173
  [key: string]: unknown;
142
174
  }
143
175
 
176
+ /** Extra options for {@link CapSkip.capy}. */
177
+ export interface CapyOptions {
178
+ /** Root of the Capy API the key lives behind (alias for `api_server`). */
179
+ apiServer?: string;
180
+ /** Root of the Capy API the key lives behind. Default `https://jp.api.capy.me`. */
181
+ api_server?: string;
182
+ /**
183
+ * The challenge family. Only `"puzzle"` (the default) is solved — `"avatar"`
184
+ * is refused before the request is made.
185
+ */
186
+ version?: string;
187
+ /** User-Agent to send with the puzzle request (alias for `useragent`). */
188
+ userAgent?: string;
189
+ /** User-Agent to send with the puzzle request. */
190
+ useragent?: string;
191
+ /** `1` to request the raw JSON response from CapSkip. */
192
+ json?: number;
193
+ /** Proxy — used for the puzzle-image fetch, the only request a Capy solve makes. */
194
+ proxy?: Proxy | string;
195
+ /** Proxy type when `proxy` is a bare string. */
196
+ proxytype?: string;
197
+ [key: string]: unknown;
198
+ }
199
+
200
+ /** Extra options for {@link CapSkip.captchafox}. */
201
+ export interface CaptchaFoxOptions {
202
+ /** Widget entry point (alias for `api_server`). */
203
+ apiServer?: string;
204
+ /**
205
+ * Widget entry point. Default `https://cdn.captchafox.com/`. The MAM package
206
+ * path returns a `MAM_` prefixed token instead.
207
+ */
208
+ api_server?: string;
209
+ /** Accepted for compatibility and not applied (alias for `useragent`). */
210
+ userAgent?: string;
211
+ /** Accepted for compatibility and not applied — CapSkip uses its own browser. */
212
+ useragent?: string;
213
+ /** `1` to request the raw JSON response from CapSkip. */
214
+ json?: number;
215
+ /** Proxy used for solving. */
216
+ proxy?: Proxy | string;
217
+ /** Proxy type when `proxy` is a bare string. */
218
+ proxytype?: string;
219
+ [key: string]: unknown;
220
+ }
221
+
222
+ /** Extra options for {@link CapSkip.friendlyCaptcha}. */
223
+ export interface FriendlyCaptchaOptions {
224
+ /** Protocol version: `"v1"` (default) or `"v2"`; a bare `1` or `2` works too. */
225
+ version?: string | number;
226
+ /** `src` of the widget script tag carrying `type="module"` (alias). */
227
+ moduleScript?: string;
228
+ /** `src` of the widget script tag carrying `type="module"`. */
229
+ module_script?: string;
230
+ /** `src` of the widget script tag carrying `nomodule` (alias). */
231
+ nomoduleScript?: string;
232
+ /** `src` of the widget script tag carrying `nomodule`. */
233
+ nomodule_script?: string;
234
+ /** Data residency tenant (alias for `api_server`). */
235
+ apiServer?: string;
236
+ /** Data residency tenant: `"global"` (default), `"eu"`, or a full URL. */
237
+ api_server?: string;
238
+ /** User-Agent to send with the request (alias for `useragent`). */
239
+ userAgent?: string;
240
+ /** User-Agent to send with the request. */
241
+ useragent?: string;
242
+ /** `1` to request the raw JSON response from CapSkip. */
243
+ json?: number;
244
+ /** Proxy used for solving. */
245
+ proxy?: Proxy | string;
246
+ /** Proxy type when `proxy` is a bare string. */
247
+ proxytype?: string;
248
+ [key: string]: unknown;
249
+ }
250
+
251
+ /** Extra options for {@link CapSkip.amazonWaf}. */
252
+ export interface AmazonWafOptions {
253
+ /**
254
+ * Challenge page: `gokuProps.iv`. Send together with `context`, or leave both
255
+ * out and CapSkip reads them from the page at solve time.
256
+ */
257
+ iv?: string;
258
+ /** Challenge page: `gokuProps.context`. Send together with `iv`. */
259
+ context?: string;
260
+ /** Full https URL of the page's `challenge.js` on an `awswaf.com` host. */
261
+ challenge_script?: string;
262
+ /** Full https URL of the page's `challenge.js` (alias for `challenge_script`). */
263
+ challengeScript?: string;
264
+ /** Full https URL of the page's `captcha.js` on an `awswaf.com` host. */
265
+ captcha_script?: string;
266
+ /** Full https URL of the page's `captcha.js` (alias for `captcha_script`). */
267
+ captchaScript?: string;
268
+ /** Widget page: full https URL of `jsapi.js` on an `awswaf.com` host. */
269
+ jsapiScript?: string;
270
+ /** Widget page: full https URL of `jsapi.js` (alias for `jsapiScript`). */
271
+ jsapi_script?: string;
272
+ /** Poll timeout in seconds. Default: the client's `recaptchaTimeout`. */
273
+ timeout?: number;
274
+ /** `1` to request the raw JSON response from CapSkip. */
275
+ json?: number;
276
+ /** Proxy used for solving. */
277
+ proxy?: Proxy | string;
278
+ /** Proxy type when `proxy` is a bare string. */
279
+ proxytype?: string;
280
+ [key: string]: unknown;
281
+ }
282
+
144
283
  /** Options for the {@link CapSkip.solve} manual workflow. */
145
284
  export interface SolveOptions {
146
285
  /** Poll timeout in seconds (falls back to the configured default). */
@@ -188,6 +327,55 @@ export class CapSkip {
188
327
  * wins. Challenges expire fast, so fetch one immediately before calling.
189
328
  */
190
329
  altcha(url: string, options?: AltchaOptions): Promise<SolveResult>;
330
+ /**
331
+ * Solve a Capy Puzzle captcha.
332
+ *
333
+ * `sitekey` is the site's public Capy key, conventionally prefixed `PUZZLE_`,
334
+ * and is sent as the `captchakey` the API documents. The result is not a
335
+ * token: it carries `captchakey`, `challengekey` and `answer`, all three of
336
+ * which go into the target form.
337
+ */
338
+ capy(sitekey: string, url: string, options?: CapyOptions): Promise<SolveResult>;
339
+ /**
340
+ * Solve a CaptchaFox challenge.
341
+ *
342
+ * `url` has to be the page the widget actually runs on — CaptchaFox checks it
343
+ * against the domains the key is registered for. The result carries `token`
344
+ * for the form's `cf-captcha-response` field.
345
+ */
346
+ captchafox(
347
+ sitekey: string,
348
+ url: string,
349
+ options?: CaptchaFoxOptions,
350
+ ): Promise<SolveResult>;
351
+ /**
352
+ * Solve a Friendly Captcha proof-of-work challenge.
353
+ *
354
+ * Two different protocols ship under this name and a sitekey does not say
355
+ * which, so pass `version`, or pass `moduleScript` and let CapSkip read it off
356
+ * the build the site loads. The token goes into `frc-captcha-solution` on v1
357
+ * or `frc-captcha-response` on v2.
358
+ */
359
+ friendlyCaptcha(
360
+ sitekey: string,
361
+ url: string,
362
+ options?: FriendlyCaptchaOptions,
363
+ ): Promise<SolveResult>;
364
+ /**
365
+ * Solve an AWS WAF CAPTCHA.
366
+ *
367
+ * On a challenge page, `sitekey` is `gokuProps.key` and `iv` / `context` are
368
+ * `gokuProps.iv` / `gokuProps.context` (both or neither). On a widget page,
369
+ * `sitekey` is the widget's `apiKey`; pass `jsapiScript`. Script URLs must be
370
+ * https on an `awswaf.com` host. The result carries `captcha_voucher` and
371
+ * `existing_token`, which the site's `voucher` endpoint trades for the
372
+ * `aws-waf-token` cookie.
373
+ */
374
+ amazonWaf(
375
+ sitekey: string,
376
+ url: string,
377
+ options?: AmazonWafOptions,
378
+ ): Promise<SolveResult>;
191
379
  /** Submit then poll to completion. Used by the higher-level solve methods. */
192
380
  solve(options?: SolveOptions): Promise<SolveResult>;
193
381
  /** Submit a captcha without polling; resolves to the captcha id. */