capskip 1.3.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/CHANGELOG.md +41 -0
- package/README.md +51 -6
- package/docs/API_REFERENCE.md +118 -1
- package/examples/amazon_waf.js +97 -0
- package/package.json +4 -2
- package/src/apiParams.js +111 -0
- package/src/index.js +1 -1
- package/src/solver.js +76 -4
- package/types/index.d.ts +59 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,47 @@ 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.4.0] - 2026-11-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **AWS WAF support** via `amazonWaf(sitekey, url, options)`, sent as
|
|
13
|
+
`method=amazon_waf`. Only `sitekey` and `url` are required.
|
|
14
|
+
On a challenge page (the HTTP 405 page with `window.gokuProps`), `sitekey`,
|
|
15
|
+
`iv` and `context` are `gokuProps.key`, `.iv` and `.context`, and
|
|
16
|
+
`challenge_script` / `captcha_script` are the page's challenge.js and
|
|
17
|
+
captcha.js. On a widget page (`AwsWafCaptcha.renderCaptcha`), `sitekey` is the
|
|
18
|
+
widget's `apiKey` and `jsapiScript` the URL of jsapi.js.
|
|
19
|
+
An AWS WAF answer is **not a token**: the result expands into
|
|
20
|
+
`captcha_voucher` and `existing_token`, which the site's `voucher` endpoint
|
|
21
|
+
(beside challenge.js) trades for the `aws-waf-token` cookie. `code` keeps the
|
|
22
|
+
raw answer.
|
|
23
|
+
- Parameter aliases `challengeScript` for `challenge_script`, `captchaScript`
|
|
24
|
+
for `captcha_script`, and `jsapi_script` for `jsapiScript`.
|
|
25
|
+
- TypeScript definitions for `AmazonWafOptions`, plus the new `SolveResult`
|
|
26
|
+
fields.
|
|
27
|
+
- `examples/amazon_waf.js`.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- The Capy answer expansion is now a shared helper, used by Capy and AWS WAF
|
|
32
|
+
alike. Capy results are unchanged.
|
|
33
|
+
- The exported `version` constant now reports the package version; it had been
|
|
34
|
+
left at `1.1.0`.
|
|
35
|
+
|
|
36
|
+
### Notes
|
|
37
|
+
|
|
38
|
+
- `iv` and `context` are optional but travel together: one without the other is
|
|
39
|
+
refused locally, as the server would refuse it. Left out, CapSkip reads both
|
|
40
|
+
from the page at solve time. An optional passed as `''` is treated as not
|
|
41
|
+
sent, the way the server reads it.
|
|
42
|
+
- Every script URL must be `https` on an `awswaf.com` host, with no credentials,
|
|
43
|
+
no port other than 443, and a path ending in `challenge.js`, `captcha.js` or
|
|
44
|
+
`jsapi.js`. CapSkip posts the solution to the host the script names, so any
|
|
45
|
+
other script is refused before the request is made.
|
|
46
|
+
- AWS WAF uses `recaptchaTimeout`: a real browser mints the token the voucher is
|
|
47
|
+
issued for.
|
|
48
|
+
|
|
8
49
|
## [1.3.0] - 2026-09-22
|
|
9
50
|
|
|
10
51
|
### Added
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://github.com/capskip/capskip-node/actions/workflows/ci.yml)
|
|
7
7
|
|
|
8
|
-
**Solve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, GeeTest, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha and image captchas from Node.js.**
|
|
8
|
+
**Solve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, GeeTest, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF 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()`. 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()`. Nothing leaves your network, and there is no credit balance to keep an eye on.
|
|
23
23
|
|
|
24
24
|
## Supported captcha types
|
|
25
25
|
|
|
@@ -38,6 +38,8 @@ This SDK is a thin wrapper over that API, with the method names you would expect
|
|
|
38
38
|
| **Capy Puzzle solver** (slide puzzle) | `solver.capy(sitekey, url)` |
|
|
39
39
|
| **CaptchaFox solver** (widget) | `solver.captchafox(sitekey, url)` |
|
|
40
40
|
| **Friendly Captcha solver** (proof-of-work) | `solver.friendlyCaptcha(sitekey, url, { version })` |
|
|
41
|
+
| **AWS WAF solver** (challenge page) | `solver.amazonWaf(sitekey, url, { iv, context })` |
|
|
42
|
+
| AWS WAF (widget) | `solver.amazonWaf(sitekey, url, { jsapiScript })` |
|
|
41
43
|
|
|
42
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()`.
|
|
43
45
|
|
|
@@ -135,7 +137,7 @@ const solver = new CapSkip({
|
|
|
135
137
|
port: 8080, // CapSkip port from app settings
|
|
136
138
|
defaultTimeout: 120, // seconds — image captcha polling timeout
|
|
137
139
|
recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest / CaptchaFox /
|
|
138
|
-
// Friendly Captcha polling timeout
|
|
140
|
+
// Friendly Captcha / AWS WAF polling timeout
|
|
139
141
|
// (ALTCHA and Capy use defaultTimeout — neither is a browser solve)
|
|
140
142
|
pollingInterval: 5, // max seconds between res.php polls (starts at 0.25s, backs off to this)
|
|
141
143
|
});
|
|
@@ -313,6 +315,48 @@ a script URL, v1 is assumed. Pass `api_server: 'eu'` for a sitekey on the EU
|
|
|
313
315
|
data-residency tenant. A v2 token is roughly six kilobytes, so size whatever
|
|
314
316
|
carries it accordingly.
|
|
315
317
|
|
|
318
|
+
### AWS WAF
|
|
319
|
+
|
|
320
|
+
AWS WAF puts its CAPTCHA either on a **challenge page** — the HTTP 405
|
|
321
|
+
interstitial that sets `window.gokuProps` — or in a **widget** an ordinary page
|
|
322
|
+
renders with `AwsWafCaptcha.renderCaptcha`. Only `sitekey` and `url` are required.
|
|
323
|
+
|
|
324
|
+
```js
|
|
325
|
+
// Challenge page: sitekey, iv and context are gokuProps.key, .iv and .context
|
|
326
|
+
const result = await solver.amazonWaf(
|
|
327
|
+
'AQIDAHjcYuEXAMPLEghicBgQ/7bFaQZ+m5FKCMDnO+vTbNg96AHDh0IR5vgzHNceHYqZR+GOAAAAfjB8',
|
|
328
|
+
'https://example.com/protected',
|
|
329
|
+
{
|
|
330
|
+
iv: 'CgAFRDIjfgAAA5yY',
|
|
331
|
+
context: 'Rbp3r8fvbuhmrCqpUe8Mh1TLvs8OSKJZiSs2vCF4dt9rxVZI7xYJa2n5k/9r+XaXe7KUIu2I8nXTDHHkDvIOTDc2',
|
|
332
|
+
challenge_script: 'https://3f2a9c7d41b0.edge.sdk.awswaf.com/3f2a9c7d41b0/8e51d0c7a2f4/challenge.js',
|
|
333
|
+
},
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
// Widget page: sitekey is the widget's apiKey, plus the jsapi.js it loads
|
|
337
|
+
const result = await solver.amazonWaf(
|
|
338
|
+
'Xr9kEXAMPLE2bWQx1vTq8LmZ0aYc4dHs7nFp3gJt6uKw5eRi9AbCdEfGh=_0_1',
|
|
339
|
+
'https://example.com/protected',
|
|
340
|
+
{ jsapiScript: 'https://5b7e0a19c3d8.us-east-1.captcha-sdk.awswaf.com/5b7e0a19c3d8/jsapi.js' },
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
result.captcha_voucher, result.existing_token;
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
`iv` and `context` are optional and travel together: send both or neither. Left
|
|
347
|
+
out, CapSkip reads them from the page at solve time, which is the safer choice
|
|
348
|
+
when your copy of the page is more than a few minutes old.
|
|
349
|
+
|
|
350
|
+
The answer is not a token. POST `captcha_voucher` and `existing_token` as JSON
|
|
351
|
+
to the `voucher` endpoint beside the page's `challenge.js`; the response's
|
|
352
|
+
`token` is the `aws-waf-token` cookie. The pair is short-lived, so exchange it
|
|
353
|
+
immediately rather than storing it.
|
|
354
|
+
|
|
355
|
+
Every script URL (`challenge_script`, `captcha_script`, `jsapiScript`) must be an
|
|
356
|
+
https URL on an `awswaf.com` host. CapSkip posts the solution to the host the
|
|
357
|
+
script names, so anything else is refused before the request is made. See the
|
|
358
|
+
[AWS WAF API docs](https://capskip.com/api-docs/#aws-waf).
|
|
359
|
+
|
|
316
360
|
### With a proxy (every method except image captcha)
|
|
317
361
|
|
|
318
362
|
```js
|
|
@@ -411,7 +455,8 @@ GeeTest additionally expands its answer into `challenge`, `validate`, and
|
|
|
411
455
|
`seccode`, while `code` keeps the raw JSON string. CaptchaFox and Friendly
|
|
412
456
|
Captcha add `token`, the same string as `code`. Capy expands its answer into
|
|
413
457
|
`captchakey`, `challengekey` and `answer` — it returns an object rather than a
|
|
414
|
-
token, and `code` keeps the raw answer.
|
|
458
|
+
token, and `code` keeps the raw answer. AWS WAF likewise returns an object,
|
|
459
|
+
expanded into `captcha_voucher` and `existing_token`. ALTCHA adds `token` (the same
|
|
415
460
|
string as `code`) and `number`, the counter that solved it.
|
|
416
461
|
|
|
417
462
|
---
|
|
@@ -457,7 +502,7 @@ const result: SolveResult = await solver.recaptcha('...', 'https://example.com')
|
|
|
457
502
|
|
|
458
503
|
### How do I solve a captcha in Node.js?
|
|
459
504
|
|
|
460
|
-
Install the CapSkip desktop app, `npm install capskip`, then call the method that matches the widget — `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`, `capy()`, `captchafox()`, `friendlyCaptcha()` 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.
|
|
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.
|
|
461
506
|
|
|
462
507
|
### Is this a free captcha solver?
|
|
463
508
|
|
|
@@ -465,7 +510,7 @@ The SDK itself is MIT-licensed and free. Solving needs the CapSkip app, which is
|
|
|
465
510
|
|
|
466
511
|
### Which captchas can it solve?
|
|
467
512
|
|
|
468
|
-
reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, and image/text captchas. Not hCaptcha, and not FunCaptcha/Arkose.
|
|
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.
|
|
469
514
|
|
|
470
515
|
### Does it work with Puppeteer and Playwright?
|
|
471
516
|
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -9,7 +9,7 @@ POST http://<host>:<port>/in.php → submit captcha
|
|
|
9
9
|
GET http://<host>:<port>/res.php → poll result
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
The SDK only supports the
|
|
12
|
+
The SDK only supports the captcha types documented by CapSkip. Every solve
|
|
13
13
|
method returns a `Promise`.
|
|
14
14
|
|
|
15
15
|
---
|
|
@@ -27,6 +27,7 @@ method returns a `Promise`.
|
|
|
27
27
|
| Capy Puzzle (slide) | `capy()` | `capy` |
|
|
28
28
|
| CaptchaFox (widget) | `captchafox()` | `captchafox` |
|
|
29
29
|
| Friendly Captcha (proof-of-work) | `friendlyCaptcha()` | `friendly_captcha` |
|
|
30
|
+
| AWS WAF (challenge page or widget) | `amazonWaf()` | `amazon_waf` |
|
|
30
31
|
|
|
31
32
|
**Proxy** is supported for every method except image captcha. For ALTCHA the
|
|
32
33
|
proxy is used only for the `challengeUrl` fetch; for Capy, only for the
|
|
@@ -612,6 +613,106 @@ browser.
|
|
|
612
613
|
|
|
613
614
|
---
|
|
614
615
|
|
|
616
|
+
## 10. AWS WAF — `amazonWaf(sitekey, url, { ... })`
|
|
617
|
+
|
|
618
|
+
AWS WAF puts its CAPTCHA either on a challenge page — the HTTP 405 interstitial
|
|
619
|
+
that sets `window.gokuProps` — or in a widget an ordinary page renders with
|
|
620
|
+
`AwsWafCaptcha.renderCaptcha`. CapSkip solves both in a real browser. See the
|
|
621
|
+
[CapSkip API docs](https://capskip.com/api-docs/#aws-waf).
|
|
622
|
+
|
|
623
|
+
### POST `/in.php`
|
|
624
|
+
|
|
625
|
+
| Parameter | Type | Required | Description |
|
|
626
|
+
|---|---|---|---|
|
|
627
|
+
| `key` | string | Yes | CapSkip API key |
|
|
628
|
+
| `method` | string | Yes | `amazon_waf` |
|
|
629
|
+
| `sitekey` | string | Yes | Challenge page: `gokuProps.key`. Widget page: the widget's `apiKey` |
|
|
630
|
+
| `pageurl` | string | Yes | Full http or https URL of the protected page |
|
|
631
|
+
| `iv` | string | No | Challenge page: `gokuProps.iv`. Send together with `context` |
|
|
632
|
+
| `context` | string | No | Challenge page: `gokuProps.context`. Send together with `iv` |
|
|
633
|
+
| `challenge_script` | string | No | Full URL of the page's `challenge.js` |
|
|
634
|
+
| `captcha_script` | string | No | Full URL of the page's `captcha.js` |
|
|
635
|
+
| `jsapiScript` | string | No | Widget page: full URL of `jsapi.js` |
|
|
636
|
+
| `json` | int | No | `0` plain text, `1` JSON |
|
|
637
|
+
| `proxy` | string | No | Proxy address |
|
|
638
|
+
| `proxytype` | string | No | Proxy type |
|
|
639
|
+
|
|
640
|
+
The SDK also accepts `challengeScript`, `captchaScript` and `jsapi_script`, and
|
|
641
|
+
sends the documented spelling of each. An optional left `undefined`, `null` or
|
|
642
|
+
`''` is not sent — the server reads an empty value as absent.
|
|
643
|
+
|
|
644
|
+
### Challenge page or widget page
|
|
645
|
+
|
|
646
|
+
| | Challenge page | Widget page |
|
|
647
|
+
|---|---|---|
|
|
648
|
+
| Recognized by | HTTP 405 with `window.gokuProps` in an inline script | `AwsWafCaptcha.renderCaptcha(container, { apiKey, … })` |
|
|
649
|
+
| `sitekey` | `gokuProps.key` | the widget's `apiKey` |
|
|
650
|
+
| `iv` / `context` | `gokuProps.iv` / `gokuProps.context`, or neither | not sent |
|
|
651
|
+
| Scripts | `challenge_script`, `captcha_script` | `jsapiScript` |
|
|
652
|
+
|
|
653
|
+
`iv` and `context` come off the same page and only work as a pair, so the SDK
|
|
654
|
+
refuses one without the other. Both are optional: left out, CapSkip reads them
|
|
655
|
+
from `pageurl` at solve time, which is the safer choice when your copy of the
|
|
656
|
+
page is more than a few minutes old.
|
|
657
|
+
|
|
658
|
+
### Script URLs
|
|
659
|
+
|
|
660
|
+
CapSkip posts the solution to the host a script URL names, so it refuses any
|
|
661
|
+
script that is not on AWS WAF. The SDK applies the server's rule before the
|
|
662
|
+
request is made: the URL must be `https`, its host must end in `.awswaf.com`, it
|
|
663
|
+
must carry no credentials and no port other than 443, and its path must be one
|
|
664
|
+
or more segments followed by `challenge.js`, `captcha.js` or `jsapi.js`. A query
|
|
665
|
+
string is allowed. Anything else throws `ValidationException`, because CapSkip is
|
|
666
|
+
not an AWS WAF integration for scripts hosted elsewhere.
|
|
667
|
+
|
|
668
|
+
### GET `/res.php`
|
|
669
|
+
|
|
670
|
+
The answer is **not a token**. With `json=1` it is an object in `request`:
|
|
671
|
+
|
|
672
|
+
```json
|
|
673
|
+
{"status":1,"request":{"captcha_voucher":"…","existing_token":"…"}}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
and in plain text the same object as one line of JSON after `OK|`. The SDK
|
|
677
|
+
expands it into `captcha_voucher` and `existing_token`; see
|
|
678
|
+
[Return value](#return-value).
|
|
679
|
+
|
|
680
|
+
### Exchanging the voucher
|
|
681
|
+
|
|
682
|
+
The two values only work as a pair. POST them as JSON to the `voucher` endpoint
|
|
683
|
+
beside the page's `challenge.js` (the same directory, with `voucher` in place of
|
|
684
|
+
`challenge.js`). The response's `token` is the `aws-waf-token` cookie. Exchange
|
|
685
|
+
them immediately rather than storing them for later.
|
|
686
|
+
|
|
687
|
+
### SDK usage
|
|
688
|
+
|
|
689
|
+
```js
|
|
690
|
+
// Challenge page
|
|
691
|
+
const result = await solver.amazonWaf(
|
|
692
|
+
'AQIDAHjcYuEXAMPLEghicBgQ/7bFaQZ+m5FKCMDnO+vTbNg96AHDh0IR5vgzHNceHYqZR+GOAAAAfjB8',
|
|
693
|
+
'https://example.com/protected',
|
|
694
|
+
{
|
|
695
|
+
iv: 'CgAFRDIjfgAAA5yY',
|
|
696
|
+
context: 'Rbp3r8fvbuhmrCqpUe8Mh1TLvs8OSKJZiSs2vCF4dt9rxVZI7xYJa2n5k/9r+XaXe7KUIu2I8nXTDHHkDvIOTDc2',
|
|
697
|
+
challenge_script: 'https://3f2a9c7d41b0.edge.sdk.awswaf.com/3f2a9c7d41b0/8e51d0c7a2f4/challenge.js',
|
|
698
|
+
},
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
// Widget page
|
|
702
|
+
const result = await solver.amazonWaf(
|
|
703
|
+
'Xr9kEXAMPLE2bWQx1vTq8LmZ0aYc4dHs7nFp3gJt6uKw5eRi9AbCdEfGh=_0_1',
|
|
704
|
+
'https://example.com/protected',
|
|
705
|
+
{ jsapiScript: 'https://5b7e0a19c3d8.us-east-1.captcha-sdk.awswaf.com/5b7e0a19c3d8/jsapi.js' },
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
result.captcha_voucher, result.existing_token;
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
Uses `recaptchaTimeout`: a real browser mints the token the voucher is issued
|
|
712
|
+
for, and the puzzle rounds go through AWS on top of that.
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
615
716
|
## Return value
|
|
616
717
|
|
|
617
718
|
Every solve method resolves to:
|
|
@@ -663,6 +764,19 @@ target form takes (`code` keeps the raw answer):
|
|
|
663
764
|
}
|
|
664
765
|
```
|
|
665
766
|
|
|
767
|
+
AWS WAF returns an object too, expanded into the voucher pair the site's
|
|
768
|
+
`voucher` endpoint trades for the `aws-waf-token` cookie (`code` keeps the raw
|
|
769
|
+
answer):
|
|
770
|
+
|
|
771
|
+
```js
|
|
772
|
+
{
|
|
773
|
+
captchaId: '12345',
|
|
774
|
+
code: { captcha_voucher: '…', existing_token: '…' },
|
|
775
|
+
captcha_voucher: 'eyJ2b3VjaGVyIjoiRVhBTVBMRSJ9.9QwZcEXAMPLEvoucherJk4LmN0pQ',
|
|
776
|
+
existing_token: '7d4c2a91-EXAMPLE-4f0b-9e6d-3a1b5c8e2f70:EQoAeXAMPLEtokenAAAA:Wm9wc2E=',
|
|
777
|
+
}
|
|
778
|
+
```
|
|
779
|
+
|
|
666
780
|
GeeTest additionally expands its answer into `challenge`, `validate`, and
|
|
667
781
|
`seccode` (`code` keeps the raw JSON string):
|
|
668
782
|
|
|
@@ -697,6 +811,9 @@ Convenience aliases mapped before sending to CapSkip:
|
|
|
697
811
|
| `captchaKey` | `captchakey` |
|
|
698
812
|
| `moduleScript` | `module_script` |
|
|
699
813
|
| `nomoduleScript` / `noModuleScript` | `nomodule_script` |
|
|
814
|
+
| `challengeScript` | `challenge_script` |
|
|
815
|
+
| `captchaScript` | `captcha_script` |
|
|
816
|
+
| `jsapi_script` | `jsapiScript` |
|
|
700
817
|
| `proxy` object | `proxy` + `proxytype` strings |
|
|
701
818
|
|
|
702
819
|
```js
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Solve an AWS WAF CAPTCHA.
|
|
5
|
+
*
|
|
6
|
+
* AWS WAF shows up in two forms, and the values you need depend on which one
|
|
7
|
+
* the target page uses:
|
|
8
|
+
*
|
|
9
|
+
* * A challenge page -- the HTTP 405 interstitial that sets `window.gokuProps`
|
|
10
|
+
* in an inline script. `sitekey` is `gokuProps.key`; `iv` and `context` are
|
|
11
|
+
* `gokuProps.iv` and `gokuProps.context`. `challenge_script` and
|
|
12
|
+
* `captcha_script` are the full URLs of the page's challenge.js and
|
|
13
|
+
* captcha.js.
|
|
14
|
+
* * A widget page -- an ordinary page that calls
|
|
15
|
+
* `AwsWafCaptcha.renderCaptcha(container, { apiKey, ... })`. `sitekey` is
|
|
16
|
+
* that `apiKey`, and `jsapiScript` is the full URL of jsapi.js.
|
|
17
|
+
*
|
|
18
|
+
* Only `sitekey` and the page URL are required. `iv` and `context` travel
|
|
19
|
+
* together: send both or neither. Left out, CapSkip reads them from the page
|
|
20
|
+
* at solve time, which is the safer choice when your copy of the page is more
|
|
21
|
+
* than a few minutes old.
|
|
22
|
+
*
|
|
23
|
+
* Every script URL must be an https URL on an awswaf.com host. CapSkip posts
|
|
24
|
+
* the solution to the host the script names, so it refuses anything else
|
|
25
|
+
* before the request is made.
|
|
26
|
+
*
|
|
27
|
+
* Unlike reCAPTCHA or Turnstile, the answer is NOT a token you put in a form.
|
|
28
|
+
* It is a voucher pair that AWS trades for the `aws-waf-token` cookie.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const { CapSkip } = require('capskip');
|
|
32
|
+
|
|
33
|
+
const solver = new CapSkip({
|
|
34
|
+
apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
|
|
35
|
+
host: process.env.CAPSKIP_HOST || '127.0.0.1',
|
|
36
|
+
port: Number(process.env.CAPSKIP_PORT || 8080),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const PAGE_URL = 'https://example.com/protected';
|
|
40
|
+
|
|
41
|
+
// From the challenge page's window.gokuProps.
|
|
42
|
+
const SITEKEY = 'AQIDAHjcYuEXAMPLEghicBgQ/7bFaQZ+m5FKCMDnO+vTbNg96AHDh0IR5vgzHNceHYqZR+GOAAAAfjB8';
|
|
43
|
+
const IV = 'CgAFRDIjfgAAA5yY';
|
|
44
|
+
const CONTEXT = 'Rbp3r8fvbuhmrCqpUe8Mh1TLvs8OSKJZiSs2vCF4dt9rxVZI7xYJa2n5k/9r+XaXe7KUIu2I8nXTDHHkDvIOTDc2';
|
|
45
|
+
const CHALLENGE_JS = 'https://3f2a9c7d41b0.edge.sdk.awswaf.com/3f2a9c7d41b0/8e51d0c7a2f4/challenge.js';
|
|
46
|
+
|
|
47
|
+
async function main() {
|
|
48
|
+
const result = await solver.amazonWaf(SITEKEY, PAGE_URL, {
|
|
49
|
+
iv: IV,
|
|
50
|
+
context: CONTEXT,
|
|
51
|
+
challenge_script: CHALLENGE_JS,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log('Captcha ID: ', result.captchaId);
|
|
55
|
+
console.log('Voucher: ', `${result.captcha_voucher.slice(0, 32)}...`);
|
|
56
|
+
console.log('Existing token:', `${result.existing_token.slice(0, 32)}...`);
|
|
57
|
+
|
|
58
|
+
// --- A widget page -------------------------------------------------------
|
|
59
|
+
// The widget's apiKey as the sitekey, and the jsapi.js it loads. No iv or
|
|
60
|
+
// context: a widget page has none.
|
|
61
|
+
//
|
|
62
|
+
// await solver.amazonWaf(
|
|
63
|
+
// 'Xr9kEXAMPLE2bWQx1vTq8LmZ0aYc4dHs7nFp3gJt6uKw5eRi9AbCdEfGh=_0_1',
|
|
64
|
+
// PAGE_URL,
|
|
65
|
+
// { jsapiScript: 'https://5b7e0a19c3d8.us-east-1.captcha-sdk.awswaf.com/5b7e0a19c3d8/jsapi.js' },
|
|
66
|
+
// );
|
|
67
|
+
//
|
|
68
|
+
// --- With a per-request proxy --------------------------------------------
|
|
69
|
+
// await solver.amazonWaf(SITEKEY, PAGE_URL, {
|
|
70
|
+
// proxy: { type: 'HTTP', uri: 'login:password@1.2.3.4:8080' },
|
|
71
|
+
// });
|
|
72
|
+
|
|
73
|
+
// Exchange the pair for the aws-waf-token cookie: POST both values as JSON
|
|
74
|
+
// to the `voucher` endpoint that sits beside the page's challenge.js. The
|
|
75
|
+
// response's `token` is the cookie value. Do this immediately -- the voucher
|
|
76
|
+
// is short-lived, so do not store the pair for a later request.
|
|
77
|
+
const voucherUrl = new URL('voucher', CHALLENGE_JS).href;
|
|
78
|
+
console.log('Voucher URL: ', voucherUrl);
|
|
79
|
+
//
|
|
80
|
+
// const response = await fetch(voucherUrl, {
|
|
81
|
+
// method: 'POST',
|
|
82
|
+
// headers: { 'Content-Type': 'application/json' },
|
|
83
|
+
// body: JSON.stringify({
|
|
84
|
+
// captcha_voucher: result.captcha_voucher,
|
|
85
|
+
// existing_token: result.existing_token,
|
|
86
|
+
// }),
|
|
87
|
+
// });
|
|
88
|
+
// const { token } = await response.json();
|
|
89
|
+
//
|
|
90
|
+
// // Then request the protected page with the cookie:
|
|
91
|
+
// await fetch(PAGE_URL, { headers: { Cookie: `aws-waf-token=${token}` } });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
main().catch((error) => {
|
|
95
|
+
console.error(`${error.name}: ${error.message}`);
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capskip",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Captcha solver for Node.js: solve reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest and image captchas with CapSkip, a local unlimited captcha solver with no per-solve fees.",
|
|
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.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"cloudflare-turnstile",
|
|
40
40
|
"geetest",
|
|
41
41
|
"geetest-solver",
|
|
42
|
+
"aws-waf",
|
|
43
|
+
"aws-waf-captcha",
|
|
42
44
|
"image-captcha",
|
|
43
45
|
"ocr",
|
|
44
46
|
"2captcha-alternative",
|
package/src/apiParams.js
CHANGED
|
@@ -46,6 +46,19 @@ const FRIENDLY_CAPTCHA_SUBMIT = new Set([
|
|
|
46
46
|
'api_server', 'useragent', 'json', 'proxy', 'proxytype',
|
|
47
47
|
]);
|
|
48
48
|
|
|
49
|
+
const AMAZON_WAF_SUBMIT = new Set([
|
|
50
|
+
'method', 'sitekey', 'pageurl', 'iv', 'context', 'challenge_script',
|
|
51
|
+
'captcha_script', 'jsapiScript', 'json', 'proxy', 'proxytype',
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
// The script parameters of an AWS WAF task. CapSkip POSTs the solution to the
|
|
55
|
+
// host these name, so it refuses any that is not a genuine AWS WAF one.
|
|
56
|
+
const AMAZON_WAF_SCRIPTS = ['challenge_script', 'captcha_script', 'jsapiScript'];
|
|
57
|
+
|
|
58
|
+
// The server's own test: one or more [A-Za-z0-9_-] path segments, then one of
|
|
59
|
+
// the three AWS WAF script files.
|
|
60
|
+
const AMAZON_WAF_SCRIPT_PATH = /^(\/[A-Za-z0-9_-]+)+\/(challenge|captcha|jsapi)\.js$/;
|
|
61
|
+
|
|
49
62
|
// CapSkip solves the puzzle family only. `avatar` is a different challenge
|
|
50
63
|
// behind a different endpoint; the server refuses it at submit time rather than
|
|
51
64
|
// answering it with a puzzle answer, which would bill for a solve the target
|
|
@@ -81,6 +94,12 @@ const PARAM_ALIASES = {
|
|
|
81
94
|
moduleScript: 'module_script',
|
|
82
95
|
nomoduleScript: 'nomodule_script',
|
|
83
96
|
noModuleScript: 'nomodule_script',
|
|
97
|
+
// AWS WAF: the API documents challenge_script / captcha_script in snake case
|
|
98
|
+
// and jsapiScript in camel case, as 2Captcha does. Accept the other spelling
|
|
99
|
+
// of each, which is what createTask-style callers arrive with.
|
|
100
|
+
challengeScript: 'challenge_script',
|
|
101
|
+
captchaScript: 'captcha_script',
|
|
102
|
+
jsapi_script: 'jsapiScript',
|
|
84
103
|
};
|
|
85
104
|
|
|
86
105
|
function has(obj, key) {
|
|
@@ -353,6 +372,90 @@ function validateFriendlyCaptchaSubmit(params) {
|
|
|
353
372
|
}
|
|
354
373
|
}
|
|
355
374
|
|
|
375
|
+
/**
|
|
376
|
+
* True for a script URL CapSkip will accept on an AWS WAF task.
|
|
377
|
+
*
|
|
378
|
+
* Mirrors the server: https, a host under .awswaf.com, no credentials or
|
|
379
|
+
* explicit port, and a path ending in challenge.js, captcha.js or jsapi.js.
|
|
380
|
+
* The server only applies the check once the task is queued, so failing here
|
|
381
|
+
* saves a submit that could never solve.
|
|
382
|
+
*/
|
|
383
|
+
function isAmazonWafScriptUrl(url) {
|
|
384
|
+
let parsed;
|
|
385
|
+
try {
|
|
386
|
+
parsed = new URL(String(url).trim());
|
|
387
|
+
} catch (error) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
// WHATWG URL already lowercases the scheme and host, and drops a :443 on an
|
|
391
|
+
// https URL, so `port` is empty for both the default and an explicit 443.
|
|
392
|
+
return parsed.protocol === 'https:'
|
|
393
|
+
&& parsed.hostname.toLowerCase().endsWith('.awswaf.com')
|
|
394
|
+
&& parsed.username === ''
|
|
395
|
+
&& parsed.password === ''
|
|
396
|
+
&& (parsed.port === '' || parsed.port === '443')
|
|
397
|
+
&& AMAZON_WAF_SCRIPT_PATH.test(parsed.pathname);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Drop unset optionals, and the empty ones too: an AWS WAF optional left as ''
|
|
402
|
+
* means "not sent" to the server, exactly like an absent one, so it is dropped
|
|
403
|
+
* rather than tripping the iv/context pairing.
|
|
404
|
+
*/
|
|
405
|
+
function normalizeAmazonWafSubmit(params) {
|
|
406
|
+
const out = {};
|
|
407
|
+
for (const [key, value] of Object.entries(dropUnset(params))) {
|
|
408
|
+
if (value !== '') {
|
|
409
|
+
out[key] = value;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return out;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function validateAmazonWafSubmit(params) {
|
|
416
|
+
// Without sitekey the server answers ERROR_BAD_PARAMETERS and without
|
|
417
|
+
// pageurl ERROR_PAGEURL. Fail locally so a missing value costs no round-trip.
|
|
418
|
+
for (const key of ['sitekey', 'pageurl']) {
|
|
419
|
+
if (!params[key]) {
|
|
420
|
+
throw new ValidationException(`'${key}' is required for Amazon WAF.`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const pageurl = String(params.pageurl).trim();
|
|
425
|
+
if (!/^https?:\/\//i.test(pageurl)) {
|
|
426
|
+
throw new ValidationException(
|
|
427
|
+
`'pageurl' must be a full http or https URL for Amazon WAF, got '${pageurl}'.`,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// iv and context come off the same challenge page and only work as a pair;
|
|
432
|
+
// the server refuses one without the other with ERROR_BAD_PARAMETERS.
|
|
433
|
+
if (Boolean(params.iv) !== Boolean(params.context)) {
|
|
434
|
+
throw new ValidationException(
|
|
435
|
+
"Amazon WAF 'iv' and 'context' go together: send both (a challenge "
|
|
436
|
+
+ 'page) or neither (a widget page, or let CapSkip read them from pageurl).',
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
for (const key of AMAZON_WAF_SCRIPTS) {
|
|
441
|
+
const value = params[key];
|
|
442
|
+
if (value && !isAmazonWafScriptUrl(value)) {
|
|
443
|
+
throw new ValidationException(
|
|
444
|
+
`'${key}' must be the https URL of an AWS WAF script on an awswaf.com `
|
|
445
|
+
+ `host (challenge.js, captcha.js or jsapi.js), got '${value}'. CapSkip `
|
|
446
|
+
+ 'refuses any other script, because it is not an AWS WAF integration.',
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const unknown = unknownKeys(params, AMAZON_WAF_SUBMIT);
|
|
452
|
+
if (unknown.length > 0) {
|
|
453
|
+
throw new ValidationException(
|
|
454
|
+
`Unsupported parameters for Amazon WAF: ${reprList(unknown)}.`,
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
356
459
|
function validateProxyType(params) {
|
|
357
460
|
const proxytype = params.proxytype;
|
|
358
461
|
if (proxytype === undefined || proxytype === null || proxytype === '') {
|
|
@@ -390,6 +493,9 @@ function prepareSubmitParams(params, captchaType, version = 'v2') {
|
|
|
390
493
|
} else if (captchaType === 'friendly_captcha') {
|
|
391
494
|
prepared = dropUnset(prepared);
|
|
392
495
|
validateFriendlyCaptchaSubmit(prepared);
|
|
496
|
+
} else if (captchaType === 'amazon_waf') {
|
|
497
|
+
prepared = normalizeAmazonWafSubmit(prepared);
|
|
498
|
+
validateAmazonWafSubmit(prepared);
|
|
393
499
|
}
|
|
394
500
|
|
|
395
501
|
// Skipped for 'normal', which rejects proxy outright with a clearer message.
|
|
@@ -410,6 +516,8 @@ module.exports = {
|
|
|
410
516
|
CAPY_SUBMIT,
|
|
411
517
|
CAPTCHAFOX_SUBMIT,
|
|
412
518
|
FRIENDLY_CAPTCHA_SUBMIT,
|
|
519
|
+
AMAZON_WAF_SUBMIT,
|
|
520
|
+
AMAZON_WAF_SCRIPTS,
|
|
413
521
|
CAPY_VERSIONS,
|
|
414
522
|
FRIENDLY_CAPTCHA_VERSIONS,
|
|
415
523
|
PROXY_TYPES,
|
|
@@ -426,6 +534,9 @@ module.exports = {
|
|
|
426
534
|
validateCapySubmit,
|
|
427
535
|
validateCaptchaFoxSubmit,
|
|
428
536
|
validateFriendlyCaptchaSubmit,
|
|
537
|
+
isAmazonWafScriptUrl,
|
|
538
|
+
normalizeAmazonWafSubmit,
|
|
539
|
+
validateAmazonWafSubmit,
|
|
429
540
|
validateProxyType,
|
|
430
541
|
prepareSubmitParams,
|
|
431
542
|
};
|
package/src/index.js
CHANGED
package/src/solver.js
CHANGED
|
@@ -222,7 +222,7 @@ function applyAltchaSolution(result) {
|
|
|
222
222
|
const CAPY_FIELDS = ['captchakey', 'challengekey', 'answer', 'respKey'];
|
|
223
223
|
|
|
224
224
|
/**
|
|
225
|
-
*
|
|
225
|
+
* Copy `fields` out of an answer that is an object rather than a token.
|
|
226
226
|
*
|
|
227
227
|
* `code` keeps the raw answer -- an object when polled with json=1, where the
|
|
228
228
|
* server puts it straight into `request`, or the JSON string it sends after
|
|
@@ -232,7 +232,7 @@ const CAPY_FIELDS = ['captchakey', 'challengekey', 'answer', 'respKey'];
|
|
|
232
232
|
* If the answer does not parse, the result is returned untouched rather than
|
|
233
233
|
* masking the server's reply.
|
|
234
234
|
*/
|
|
235
|
-
function
|
|
235
|
+
function expandObjectAnswer(result, fields) {
|
|
236
236
|
let payload = result.solution;
|
|
237
237
|
delete result.solution;
|
|
238
238
|
|
|
@@ -253,7 +253,7 @@ function applyCapySolution(result) {
|
|
|
253
253
|
return result;
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
for (const field of
|
|
256
|
+
for (const field of fields) {
|
|
257
257
|
if (Object.prototype.hasOwnProperty.call(payload, field)) {
|
|
258
258
|
result[field] = payload[field];
|
|
259
259
|
}
|
|
@@ -262,6 +262,21 @@ function applyCapySolution(result) {
|
|
|
262
262
|
return result;
|
|
263
263
|
}
|
|
264
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
|
+
|
|
265
280
|
/**
|
|
266
281
|
* Expose a single-token answer as `token`, named for the form field it fills.
|
|
267
282
|
*
|
|
@@ -309,7 +324,10 @@ function parseSubmitResponse(response) {
|
|
|
309
324
|
throw new ApiException(`cannot recognize response ${response}`);
|
|
310
325
|
}
|
|
311
326
|
|
|
312
|
-
/**
|
|
327
|
+
/**
|
|
328
|
+
* Client for the CapSkip local captcha solver (image, reCAPTCHA, Turnstile,
|
|
329
|
+
* GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, AWS WAF).
|
|
330
|
+
*/
|
|
313
331
|
class CapSkip {
|
|
314
332
|
constructor({
|
|
315
333
|
apiKey = 'capskip',
|
|
@@ -553,6 +571,55 @@ class CapSkip {
|
|
|
553
571
|
return applyTokenSolution(result);
|
|
554
572
|
}
|
|
555
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
|
+
|
|
556
623
|
async solve(options = {}) {
|
|
557
624
|
const {
|
|
558
625
|
timeout = 0,
|
|
@@ -653,6 +720,9 @@ class CapSkip {
|
|
|
653
720
|
if (method === 'friendly_captcha') {
|
|
654
721
|
return prepareSubmitParams(params, 'friendly_captcha');
|
|
655
722
|
}
|
|
723
|
+
if (method === 'amazon_waf') {
|
|
724
|
+
return prepareSubmitParams(params, 'amazon_waf');
|
|
725
|
+
}
|
|
656
726
|
return applyProxy(applyParamAliases(params));
|
|
657
727
|
}
|
|
658
728
|
}
|
|
@@ -667,5 +737,7 @@ module.exports = {
|
|
|
667
737
|
applyGeetestSolution,
|
|
668
738
|
applyAltchaSolution,
|
|
669
739
|
applyCapySolution,
|
|
740
|
+
applyAmazonWafSolution,
|
|
741
|
+
expandObjectAnswer,
|
|
670
742
|
applyTokenSolution,
|
|
671
743
|
};
|
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 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,8 +36,8 @@ 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
|
|
40
|
-
* object, expanded into the
|
|
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
41
|
*/
|
|
42
42
|
code: string | Record<string, unknown>;
|
|
43
43
|
/**
|
|
@@ -73,6 +73,15 @@ export interface SolveResult {
|
|
|
73
73
|
* 2Captcha's documented response; it carries nothing.
|
|
74
74
|
*/
|
|
75
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;
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
/** Extra options for {@link CapSkip.normal}. */
|
|
@@ -239,6 +248,38 @@ export interface FriendlyCaptchaOptions {
|
|
|
239
248
|
[key: string]: unknown;
|
|
240
249
|
}
|
|
241
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
|
+
|
|
242
283
|
/** Options for the {@link CapSkip.solve} manual workflow. */
|
|
243
284
|
export interface SolveOptions {
|
|
244
285
|
/** Poll timeout in seconds (falls back to the configured default). */
|
|
@@ -320,6 +361,21 @@ export class CapSkip {
|
|
|
320
361
|
url: string,
|
|
321
362
|
options?: FriendlyCaptchaOptions,
|
|
322
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>;
|
|
323
379
|
/** Submit then poll to completion. Used by the higher-level solve methods. */
|
|
324
380
|
solve(options?: SolveOptions): Promise<SolveResult>;
|
|
325
381
|
/** Submit a captcha without polling; resolves to the captcha id. */
|