diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b30cfc4..360da34 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: # Matches engines.node: ">=18" in package.json - node-version: [18, 20, 22] + node-version: [18, 20, 22, 24] steps: - uses: actions/checkout@v4 @@ -51,11 +51,10 @@ jobs: integration: name: Integration (real API) - # Only on schedule and manual dispatch. Deliberately not run on - # pull_request: the tests spend real account balance, and secrets are - # unavailable in PRs from forks anyway -- the tests would silently skip - # and produce a false-green result. - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + # Run for same-repository pull requests as well as scheduled and manual + # checks. GitHub does not expose repository secrets to pull requests from + # forks, so those runs fail at the explicit secret check below instead of + # producing a false-green result. runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index 9edfe7d..6e1bff9 100644 --- a/README.md +++ b/README.md @@ -87,14 +87,13 @@ const task = new Tasks.RecaptchaV2Proxyless({ websiteURL: 'https://example.com/login', websiteKey: 'YOUR_WEBSITE_KEY' }); -const result = await captchaSolver.solve(task); -console.log(result.gRecaptchaResponse); +captchaSolver.solve(task) + .then((solution) => console.log(solution.gRecaptchaResponse)) + .catch(console.error); ``` -Runnable versions of every example below live in [examples/async](examples/async) (async/await -style) and [examples/sync](examples/sync) (promise-chain style, `.then()/.catch()`) -- both call -the same `CaptchaClient`, since JavaScript has no blocking HTTP client to mirror `requests` vs. -`httpx` the way the Python SDK does. See [Running the examples](#running-the-examples). +Runnable Promise-chain versions of every example below live in [examples](examples). +Each file contains one API operation. See [Running the examples](#running-the-examples). ## Build Faster with AI @@ -119,16 +118,19 @@ on the target page. |---|---|---| | reCAPTCHA v2 | ✅ | ✅ | | reCAPTCHA v2 Enterprise | ✅ | ✅ | -| reCAPTCHA v3 | ✅ | ❌ | +| reCAPTCHA v3 | ✅ | — | | Cloudflare Turnstile | ✅ | ✅ | | GeeTest v3 | ✅ | ✅ | | GeeTest v4 | ✅ | ✅ | -| Image to Text | ✅ | ❌ | +| Image to Text | ✅ | — | | Yandex SmartCaptcha | ✅ | ✅ | -| Yandex SmartCaptcha Image | ✅ | ❌ | -| Coordinates (click captcha) | ✅ | ❌ | +| Yandex SmartCaptcha Image | ✅ | — | +| Coordinates (click captcha) | ✅ | — | | Tencent | ✅ | ✅ | +`—` means that this CAPTCHA type does not have a separate proxy task method; +the supported task is used as shown in the Proxyless column. + ### Other supported API task types Use `GenericTask` for any task type supported by the API that does not have a dedicated convenience class: @@ -140,7 +142,9 @@ const task = new Tasks.GenericTask({ websiteKey: 'YOUR_WEBSITE_KEY' }); -const solution = await captchaSolver.solve(task); +captchaSolver.solve(task) + .then(console.log) + .catch(console.error); ``` `GenericTask` preserves every supplied API field and removes only `null` and `undefined` values. @@ -202,8 +206,7 @@ account's currency. Rejects with `ApiError`, `NetworkError`. Each section below covers one captcha type end-to-end: task fields, the `solution` shape, a runnable example, and a link to the full spec. Optional fields left unset are omitted from the request. Every code block matches a runnable file under -[examples/async](examples/async) (and its [examples/sync](examples/sync) counterpart) --- swap the placeholder `websiteURL`/`websiteKey`/etc. for values from your own +[examples](examples) -- swap the placeholder `websiteURL`/`websiteKey`/etc. for values from your own target page before running. See [Running the examples](#running-the-examples). Types with a `*` counterpart (as opposed to `*Proxyless`) also accept `proxyType` / @@ -229,7 +232,9 @@ Choose the proxy variant when the solving session must use your own IP address. | `userAgent` | no | User-Agent to solve with. Recommended to match the agent submitting the token. | | `cookies` | no | Session cookies to use while solving, if the page requires them. | -**Response:** `gRecaptchaResponse` -- submit as `g-recaptcha-response`. +**Response fields** + +- `gRecaptchaResponse` — submit this token as `g-recaptcha-response`. ```javascript import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; @@ -238,8 +243,9 @@ const task = new Tasks.RecaptchaV2Proxyless({ websiteURL: 'https://example.com/login', websiteKey: 'YOUR_WEBSITE_KEY' }); -const result = await captchaSolver.solve(task); -console.log(result.gRecaptchaResponse); +captchaSolver.solve(task) + .then((solution) => console.log(solution.gRecaptchaResponse)) + .catch(console.error); ``` With proxy, use `Tasks.RecaptchaV2` instead: @@ -271,7 +277,13 @@ as reCAPTCHA v2, plus: | `enterprisePayload` | no | Extra parameters passed to `grecaptcha.enterprise.render` on the page, e.g. `{ s: '...' }`. | | `apiDomain` | no | Defaults to `google.com`. | -**Response:** `gRecaptchaResponse`. +> **Important:** If the page passes values such as `s` to +> `grecaptcha.enterprise.render`, include them in `enterprisePayload`. A token +> created without the page's payload can be rejected by the target website. + +**Response fields** + +- `gRecaptchaResponse` — submit this token to the Enterprise reCAPTCHA callback. ```javascript import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; @@ -280,8 +292,9 @@ const task = new Tasks.RecaptchaV2EnterpriseProxyless({ websiteURL: 'https://example.com/login', websiteKey: 'YOUR_WEBSITE_KEY' }); -const result = await captchaSolver.solve(task); -console.log(result.gRecaptchaResponse); +captchaSolver.solve(task) + .then((solution) => console.log(solution.gRecaptchaResponse)) + .catch(console.error); ``` With proxy, use `Tasks.RecaptchaV2Enterprise` (same proxy fields as reCAPTCHA v2). @@ -303,7 +316,9 @@ this task only without a customer proxy. | `isEnterprise` | no | Set to `true` for reCAPTCHA v3 Enterprise. | | `apiDomain` | no | Alternative domain used to load the reCAPTCHA script. | -**Response:** `gRecaptchaResponse`. +**Response fields** + +- `gRecaptchaResponse` — submit this token to the reCAPTCHA v3 callback. ```javascript import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; @@ -316,8 +331,9 @@ const task = new Tasks.RecaptchaV3Proxyless({ pageAction: 'homepage' }); -const result = await captchaSolver.solve(task); -console.log(result.gRecaptchaResponse); +captchaSolver.solve(task) + .then((solution) => console.log(solution.gRecaptchaResponse)) + .catch(console.error); ``` ### Cloudflare Turnstile @@ -338,10 +354,15 @@ that the target page expects in `cf-turnstile-response`. | `pagedata` | no | Value of the `chlPageData` parameter, needed for some Cloudflare challenge pages beyond the basic widget. Note the lowercase, single-word spelling -- unlike every other field on this page, the real API does not accept `pageData`. | | `userAgent` | no | Current browser User-Agent. Pass it for Cloudflare Challenge pages; it is not needed for a standalone widget. | -**Response:** `token` -- submit as `cf-turnstile-response`. For Cloudflare Challenge -pages, pass `action`, `data`, `pagedata`, and the current browser `userAgent` in the -task. Keep the browser User-Agent unchanged when invoking the callback. The solution -may also contain `userAgent`; do not switch the browser to this returned value. +**Response fields** + +- `token` — submit this value as `cf-turnstile-response`. +- `userAgent` — the User-Agent reported by the solving service, when present. + +> **Important:** For Cloudflare Challenge pages, pass `action`, `data`, +> `pagedata`, and the current browser `userAgent` in the task. Keep that browser +> User-Agent unchanged when invoking the callback; do not switch it to the +> returned value. ```javascript import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; @@ -350,8 +371,9 @@ const task = new Tasks.TurnstileProxyless({ websiteURL: 'https://example.com/login', websiteKey: 'YOUR_WEBSITE_KEY' }); -const result = await captchaSolver.solve(task); -console.log(result.token); +captchaSolver.solve(task) + .then((solution) => console.log(solution.token)) + .catch(console.error); ``` With proxy, use `Tasks.Turnstile` (same proxy fields as reCAPTCHA v2). @@ -377,7 +399,12 @@ browser session involved. | `comment` | no | Free-text hint for the worker. | | `imgInstructions` | no | Optional supplementary instruction image, base64-encoded. | -**Response:** `text` -- the recognized text/answer. +> **Important:** Pass only the raw base64 data in `body` and +> `imgInstructions`. Remove prefixes such as `data:image/png;base64,`. + +**Response fields** + +- `text` — the recognized text or calculated answer. ```javascript import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; @@ -390,8 +417,9 @@ const task = new Tasks.ImageToText({ minLength: 4, maxLength: 6 }); -const result = await captchaSolver.solve(task); -console.log(result.text); +captchaSolver.solve(task) + .then((solution) => console.log(solution.text)) + .catch(console.error); ``` ### GeeTest (v3 & v4) @@ -415,23 +443,30 @@ pass the values collected from the target page before creating the task. | `userAgent` | no | User-Agent to solve with. | | `risk_type` | no | Dynamic, single-use value included in the page's captcha-loading request, if present. Note the snake_case name -- unlike every other field here, the real API does not accept `riskType`. | -**Response:** v3 -- `challenge`, `validate`, `seccode`. v4 -- `captcha_id`, `lot_number`, `pass_token`, `gen_time`, `captcha_output`. +**Response fields** + +- GeeTest v3: `challenge`, `validate`, and `seccode`. +- GeeTest v4: `captcha_id`, `lot_number`, `pass_token`, `gen_time`, and + `captcha_output`. + +> **Important:** Fetch a fresh v3 `challenge` for every task. Do not reuse a +> challenge value from an earlier request. Docs: [v3 ↗](https://captcha-solver.com/en/docs/captcha-types#geetest-v3), [v4 ↗](https://captcha-solver.com/en/docs/captcha-types#geetest-v4) ```javascript import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; -const captchaSolver = new CaptchaClient({ clientKey: 'your_api_key', timeout: 300000, pollingInterval: 10000 }); +const captchaSolver = new CaptchaClient({ clientKey: 'your_api_key', timeout: 300000 }); const task = new Tasks.GeeTestProxyless({ websiteURL: 'https://example.com/login', gt: 'f2ae6cadcf7886856696c46d84d109d1', challenge: '12345678abc90123d45678e90123f45g6' // dynamic -- fetch a fresh one per request }); -const result = await captchaSolver.solve(task); -console.log(result.validate); -console.log(result.seccode); +captchaSolver.solve(task) + .then((solution) => console.log(solution.validate, solution.seccode)) + .catch(console.error); ``` `challenge` is session-specific and expires quickly, so it can't be hardcoded into a -static example -- see [examples/async/geetest_v3.js](examples/async/geetest_v3.js) for +static example -- see [examples/geetest_v3.js](examples/geetest_v3.js) for where the fetch belongs in the flow. ```javascript @@ -440,8 +475,9 @@ const task = new Tasks.GeeTestProxyless({ version: 4, initParameters: { captcha_id: 'YOUR_CAPTCHA_ID' } }); -const result = await captchaSolver.solve(task); -console.log(result.captcha_output); +captchaSolver.solve(task) + .then((solution) => console.log(solution.captcha_output)) + .catch(console.error); ``` With proxy, use `Tasks.GeeTest` (same proxy fields as reCAPTCHA v2). @@ -466,7 +502,9 @@ challenge. Proxy variant note: `proxyType` also accepts `'https'` for this captcha type only (in addition to `http`/`socks4`/`socks5`). -**Response:** `token`. +**Response fields** + +- `token` — submit this value to the SmartCaptcha callback. ```javascript import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; @@ -475,8 +513,9 @@ const task = new Tasks.YandexSmartCaptchaTaskProxyless({ websiteURL: 'https://example.com/login', websiteKey: 'YOUR_WEBSITE_KEY' }); -const result = await captchaSolver.solve(task); -console.log(result.token); +captchaSolver.solve(task) + .then((solution) => console.log(solution.token)) + .catch(console.error); ``` With proxy, use `Tasks.YandexSmartCaptchaTask` (same proxy fields as reCAPTCHA v2, @@ -499,16 +538,18 @@ already ordered for clicking. import fs from 'node:fs'; import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; +const captchaSolver = new CaptchaClient({ clientKey: 'your_api_key' }); const task = new Tasks.YandexSmartCaptchaImageTask({ image: fs.readFileSync('captcha.png').toString('base64'), imgInstructions: fs.readFileSync('instructions.png').toString('base64') }); -const result = await new CaptchaClient({ clientKey: 'your_api_key' }).solve(task); -console.log(result.coordinates); +captchaSolver.solve(task) + .then((solution) => console.log(solution.coordinates)) + .catch(console.error); ``` -See the runnable [async](examples/async/yandex_smartcaptcha_image.js) and -[promise-chain](examples/sync/yandex_smartcaptcha_image.js) examples. +See the runnable example in +[examples/yandex_smartcaptcha_image.js](examples/yandex_smartcaptcha_image.js). ### Coordinates (click captcha) @@ -525,7 +566,9 @@ Use this method to identify points that a worker should click in a generic image | `minClicks` | no | Minimum number of clicks expected (default `1`). | | `maxClicks` | no | Maximum number of clicks allowed. | -**Response:** `coordinates` -- an array of `{ x, y }` pixel positions to click, in order. +**Response fields** + +- `coordinates` — an ordered array of `{ x, y }` pixel positions to click. ```javascript import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; @@ -536,11 +579,11 @@ const task = new Tasks.CoordinatesTask({ body: body, comment: 'click on the green apple' }); -const result = await captchaSolver.solve(task); -console.log(result.coordinates); // [{ x: 358, y: 268 }] +captchaSolver.solve(task) + .then((solution) => console.log(solution.coordinates)) + .catch(console.error); ``` -mode" block in [examples/async/coordinates.js](examples/async/coordinates.js) (or -[examples/sync/coordinates.js](examples/sync/coordinates.js)). +example in [examples/coordinates_traffic_lights.js](examples/coordinates_traffic_lights.js). ### Tencent @@ -557,7 +600,14 @@ values required by the target page. | `appId` | yes | Value of the `appId` parameter found in the page source. | | `captchaScript` | no | URL of the Tencent captcha script, if the page uses a non-default one. | -**Response:** `appid`, `ret`, `ticket`, `randstr` -- pass all four into the page's Tencent captcha callback. +**Response fields** + +- `appid` — Tencent application ID. +- `ret` — Tencent result code. +- `ticket` — verification ticket. +- `randstr` — verification nonce. + +Pass all four values to the page's Tencent CAPTCHA callback. ```javascript import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; @@ -566,8 +616,9 @@ const task = new Tasks.TencentTaskProxyless({ websiteURL: 'https://example.com/register', appId: 'YOUR_APP_ID' }); -const result = await captchaSolver.solve(task); -console.log(result.ticket); +captchaSolver.solve(task) + .then((solution) => console.log(solution.ticket)) + .catch(console.error); ``` With proxy, use `Tasks.TencentTask` (same proxy fields as reCAPTCHA v2). @@ -579,8 +630,9 @@ With proxy, use `Tasks.TencentTask` (same proxy fields as reCAPTCHA v2). ```javascript import { CaptchaClient } from '@captcha-solver-api/javascript-sdk'; const captchaSolver = new CaptchaClient({ clientKey: 'your_api_key' }); -const balance = await captchaSolver.getBalance(); -console.log(`Balance: ${balance}`); +captchaSolver.getBalance() + .then((balance) => console.log(`Balance: ${balance}`)) + .catch(console.error); ``` ### Custom timeout and polling @@ -599,7 +651,9 @@ Pass `languagePool` as the second argument to `solve()` (or `createTask()`) to p worker pool by language. Accepted values: `'en'` or `'ru'`. ```javascript -const result = await captchaSolver.solve(task, 'en'); +captchaSolver.solve(task, 'en') + .then(console.log) + .catch(console.error); ``` ### Solving multiple captchas in parallel @@ -614,7 +668,9 @@ const captchaSolver = new CaptchaClient({ clientKey: 'your_api_key' }); const task1 = captchaSolver.solve(new Tasks.RecaptchaV2Proxyless({ websiteURL: 'https://site1.com', websiteKey: 'key1' })); const task2 = captchaSolver.solve(new Tasks.TurnstileProxyless({ websiteURL: 'https://site2.com', websiteKey: 'key2' })); -const results = await Promise.allSettled([task1, task2]); +Promise.allSettled([task1, task2]) + .then(console.log) + .catch(console.error); ``` This completes in roughly the time of the slowest single captcha, not the sum of all of them. @@ -623,9 +679,7 @@ This completes in roughly the time of the slowest single captcha, not the sum of ```javascript import { CaptchaClient, ApiError, TimeoutError, NetworkError, ValidationError } from '@captcha-solver-api/javascript-sdk'; const captchaSolver = new CaptchaClient({ clientKey: 'your_api_key' }); -try { - const result = await captchaSolver.solve(task); -} catch (error) { +captchaSolver.solve(task).catch((error) => { if (error instanceof ValidationError) { console.log(`Invalid input: ${error.message}`); } else if (error instanceof ApiError) { @@ -635,7 +689,7 @@ try { } else if (error instanceof NetworkError) { console.log(`Network error: ${error.message}`); } -} +}); ``` ### TypeScript @@ -650,12 +704,13 @@ import type { TurnstileSolution } from '@captcha-solver-api/javascript-sdk'; const captchaSolver = new CaptchaClient({ clientKey: 'your_api_key' }); -const result = await captchaSolver.solve(new Tasks.TurnstileProxyless({ +captchaSolver.solve(new Tasks.TurnstileProxyless({ websiteURL: 'https://example.com/login', websiteKey: 'YOUR_WEBSITE_KEY' -})); -result.token; // string -result.userAgent; // string +})).then((result) => { + result.token; // string + result.userAgent; // string +}); // The same shape is exported by name if you need to pass it around. const stored: TurnstileSolution = result; @@ -689,9 +744,9 @@ matches this README. ```bash export CAPTCHA_API_KEY=your_api_key -node examples/async/balance.js -node examples/async/image_to_text.js -node examples/async/coordinates.js +node examples/balance.js +node examples/image_to_text.js +node examples/coordinates.js ``` Full details, an example index, and a per-type breakdown of what each script diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..dbee401 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,28 @@ +# Security Policy + +## Supported versions + +Security fixes are provided for the latest `1.x` release of the SDK. + +| Version | Supported | +|---|---| +| 1.x | Yes | +| < 1.0 | No | + +## Reporting a vulnerability + +Please do not report security vulnerabilities in public GitHub issues. + +Use GitHub's private vulnerability reporting instead: + +1. Open the repository's **Security** tab. +2. Select **Advisories**. +3. Select **Report a vulnerability**. + +Include the affected SDK version, a clear description of the issue, steps to +reproduce it, and its potential impact. If possible, include a minimal proof of +concept and any suggested mitigation. + +We will acknowledge the report, investigate it, and coordinate disclosure with +the reporter. Please allow time for a fix to be prepared and released before +publishing details. diff --git a/examples/README.md b/examples/README.md index afbb024..fa2f97f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,192 +1,55 @@ # Examples -Runnable scripts for every captcha type the SDK supports. Each file is standalone: it builds a -`CaptchaClient`, creates one or two tasks, waits for the solution and prints it. Nothing here is -imported by the library itself — the directory exists to be read and executed. - -The same 11 scenarios are provided twice, once per calling style: - -| Suite | Style | Look here if | -|---|---|---| -| [async/](async) | `async/await` | your code is `await`-based (the common case) | -| [sync/](sync) | `.then()/.catch()` | you chain promises instead of awaiting them | - -Both suites call the same `CaptchaClient`. The SDK exposes a single promise-based API, and the split -only demonstrates two ways of consuming it — JavaScript has no blocking HTTP client, so there is no -true synchronous variant the way the Python SDK has `requests` vs. `httpx`. - -## Table of contents - -- [Setup](#setup) -- [Running an example](#running-an-example) -- [Example index](#example-index) -- [What each example covers](#what-each-example-covers) -- [Before you run](#before-you-run) +Every file is a standalone Promise-chain example and contains exactly one API +operation. Examples with a customer proxy use the `_proxy.js` suffix. ## Setup -**1. Install dependencies.** The scripts use `dotenv`, which is a devDependency of the repo: +Install dependencies and provide your API key: ```bash npm install ``` -**2. Provide an API key.** Copy the template and fill in your key: - -```bash -cp .env.example .env -``` - -``` +```text CAPTCHA_API_KEY=your_api_key_here ``` -Every script starts with `import 'dotenv/config'` and reads `process.env.CAPTCHA_API_KEY`, falling -back to the literal `'YOUR_API_KEY'` when the variable is missing. That fallback keeps the script -importable, but the API will reject it — set a real key before expecting results. - -**3. Node.js 18 or newer.** The examples rely on global `fetch` and, in `async/`, on top-level -`await`. - -> The scripts import the SDK by package name, exactly as consumer applications do: -> `import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk';` - -## Running an example - -Run from the repository root: +Run an example from the repository root: ```bash -node examples/async/balance.js -node examples/sync/recaptcha_v2.js +node examples/recaptcha_v2.js ``` -`balance.js` is the one file that works as-is — it only needs a valid key. Every other example ships -with placeholder values (`https://example.com/login`, `6Le-xxxxxxxxx`, …) that you must replace with -values from your own target page. See [Before you run](#before-you-run). - -Most files contain **two independent blocks** — a proxyless one and a with-proxy one — that both run -on execution. Comment out the block you don't need, or the script will submit two tasks and spend -twice the balance. +Replace placeholder URLs, site keys, application IDs, dynamic values, and +proxy credentials before running token CAPTCHA examples. ## Example index -| Captcha type | async | sync | Task classes | Solution fields | -|---|---|---|---|---| -| reCAPTCHA v2 | [recaptcha_v2.js](async/recaptcha_v2.js) | [recaptcha_v2.js](sync/recaptcha_v2.js) | `RecaptchaV2Proxyless`, `RecaptchaV2` | `gRecaptchaResponse` | -| reCAPTCHA v2 Enterprise | [recaptcha_v2_enterprise.js](async/recaptcha_v2_enterprise.js) | [recaptcha_v2_enterprise.js](sync/recaptcha_v2_enterprise.js) | `RecaptchaV2EnterpriseProxyless`, `RecaptchaV2Enterprise` | `gRecaptchaResponse` | -| Cloudflare Turnstile | [turnstile.js](async/turnstile.js) | [turnstile.js](sync/turnstile.js) | `TurnstileProxyless`, `Turnstile` | `token` | -| GeeTest v3 | [geetest_v3.js](async/geetest_v3.js) | [geetest_v3.js](sync/geetest_v3.js) | `GeeTestProxyless`, `GeeTest` | `challenge`, `validate`, `seccode` | -| GeeTest v4 | [geetest_v4.js](async/geetest_v4.js) | [geetest_v4.js](sync/geetest_v4.js) | `GeeTestProxyless`, `GeeTest` | `captcha_id`, `lot_number`, `pass_token`, `gen_time`, `captcha_output` | -| Yandex SmartCaptcha | [yandex_smartcaptcha.js](async/yandex_smartcaptcha.js) | [yandex_smartcaptcha.js](sync/yandex_smartcaptcha.js) | `YandexSmartCaptchaTaskProxyless`, `YandexSmartCaptchaTask` | `token` | -| Yandex SmartCaptcha Image | [yandex_smartcaptcha_image.js](async/yandex_smartcaptcha_image.js) | [yandex_smartcaptcha_image.js](sync/yandex_smartcaptcha_image.js) | `YandexSmartCaptchaImageTask` | `coordinates` | -| Tencent | [tencent.js](async/tencent.js) | [tencent.js](sync/tencent.js) | `TencentTaskProxyless`, `TencentTask` | `appid`, `ret`, `ticket`, `randstr` | -| Image to Text | [image_to_text.js](async/image_to_text.js) | [image_to_text.js](sync/image_to_text.js) | `ImageToText` | `text` | -| Coordinates (click) | [coordinates.js](async/coordinates.js) | [coordinates.js](sync/coordinates.js) | `CoordinatesTask` | `coordinates` | -| Account balance | [balance.js](async/balance.js) | [balance.js](sync/balance.js) | — (`getBalance()`) | number | - -## What each example covers - -### reCAPTCHA v2 - -The baseline example, and the best one to read first. Shows the two shapes every proxy-capable type -follows: `RecaptchaV2Proxyless` (the service uses its own IPs) and `RecaptchaV2` with the five proxy -fields — `proxyType`, `proxyAddress`, `proxyPort`, `proxyLogin`, `proxyPassword`. Also documents the -two optional client settings, `timeout` and `pollingInterval`, and the `isInvisible` flag for -invisible widgets. - -### reCAPTCHA v2 Enterprise - -Same two shapes as v2, with `enterprisePayload` added. If the target site passes extra parameters to -`grecaptcha.enterprise.render()`, they must be forwarded in that object — otherwise the returned -token is rejected by the site even though the API call succeeded. - - -### Cloudflare Turnstile - -Proxyless and with-proxy variants. The important detail is in the comments: there's no `userAgent` -input for this type -- the worker picks its own while solving and returns it as `result.userAgent` -instead, and the token is tied to that fingerprint, so you must submit it with that exact User-Agent. -For Cloudflare Challenge pages, `action`, `data` (the `data-cdata` attribute) and `pagedata` (the -`chlPageData` parameter -- lowercase, unlike every other field here) also have to be extracted from -the page and passed along. - -### GeeTest v3 - -The only example that performs a **request of its own before solving**. The `challenge` value is -session-specific and must be fresh for every task, so the script fetches one first and then builds -the task. The fetch URL is a deliberate placeholder — see [Before you run](#before-you-run). Uses -`timeout: 300000` and `pollingInterval: 10000`, since GeeTest is among the slowest types. `version` -is omitted because v3 is the default. - -### GeeTest v4 - -Same task classes as v3, different identification: v4 drops `gt`/`challenge` entirely and identifies -the widget by `captcha_id` inside `initParameters`, with `version: 4` set explicitly. Solution shape -differs from v3 as well — `captcha_output` and friends instead of `validate`/`seccode`. - -### Yandex SmartCaptcha - -Covers the **token** challenge, proxyless and with proxy. `websiteKey` is the sitekey from the page -a different task type — it lives in the coordinates example below. - -### Tencent - -Proxyless and with proxy. `appId` is read from the page source. `captchaScript` is only needed when -the site loads the widget from a non-default script URL. - -### Image to Text - -Three blocks, no proxy variant, all active. *Basic* submits the shipped letters-only sample -(`text-captcha.png`) with matching character-set hints. *Advanced* solves a different image entirely -— a real math captcha (`captcha-math.png`) with its own instruction image (`captcha-math-instructions.png`) -— using the rest of the hints that speed up recognition: `phrase`, `math`, `comment`, -`imgInstructions`. *With language pool* shows that `languagePool` is the **second argument to -`solve()`**, not a task field: `solve(task, 'en')` picks an English-speaking worker pool (`'en'` or -`'ru'`). - -### Coordinates (click captcha) - -Three blocks, no proxy variant, all active. *Basic* passes the shipped grid captcha -(`coordinates-captcha.png`) plus a `comment` telling the worker what to click — it repeats the -instruction printed on the image itself, since nothing guarantees the worker reads that. *Advanced* -solves a different image — real traffic-lights photo (`traffic-lights.png`) with its own instruction -image (`traffic-lights-instructions.png`) and `minClicks`/`maxClicks` limits. *Yandex SmartCaptcha -token challenge. - -### Account balance - -The shortest script: one `getBalance()` call returning the available amount as a number. It needs no -target page and no placeholder edits, which makes it the fastest way to confirm that your key and -network access work. - -## Before you run - -Every example except `balance.js` needs something replaced first. - -| Example | What you must supply | -|---|---| -| `balance.js` | Nothing — runs as-is with a valid key | -| `turnstile.js` | Real `websiteURL` and `websiteKey`; for Challenge pages also `action`, `data`, `pagedata` | -| `yandex_smartcaptcha.js` | Real `websiteURL` and `websiteKey` | -| `tencent.js` | Real `websiteURL` and `appId` | -| `geetest_v3.js` | Real `websiteURL` and `gt`, **plus a real init endpoint** (see below) | -| `geetest_v4.js` | Real `websiteURL` and `captcha_id` | -| `image_to_text.js` | Nothing — all sample images ship in [assets/](assets/) | -| `coordinates.js` | Nothing — all sample images ship in [assets/](assets/) | - -**Both image examples run entirely as-is**, with a valid key and nothing else — every block, -[assets/](assets/) and are read through `new URL('../assets/…', import.meta.url)`, which resolves -against the script rather than the working directory — so `node examples/async/coordinates.js` works -from the repository root just as well as from anywhere else. Point the read at your own file to solve -a different image. - -**`geetest_v3.js` will not run end-to-end as-is.** It fetches -`https://target-site.com/path/to/geetest/init`, which is a placeholder, not a live endpoint; the -script exits with code 1 when that fetch fails. Point it at your real target page — or wherever it -exposes a fresh `challenge` — before running. The block is there to show *where* that fetch belongs -in the flow. - -Proxy credentials (`1.2.3.4:8080`, `user`/`password`) are placeholders too. If you only want the -proxyless path, comment out the with-proxy block rather than leaving it to fail. - -Full API reference: https://captcha-solver.com/en/docs/captcha-types +| CAPTCHA type | Without customer proxy | With customer proxy | +|---|---|---| +| reCAPTCHA v2 | [`recaptcha_v2.js`](recaptcha_v2.js) | [`recaptcha_v2_proxy.js`](recaptcha_v2_proxy.js) | +| reCAPTCHA v2 Enterprise | [`recaptcha_v2_enterprise.js`](recaptcha_v2_enterprise.js) | [`recaptcha_v2_enterprise_proxy.js`](recaptcha_v2_enterprise_proxy.js) | +| reCAPTCHA v3 | [`recaptcha_v3.js`](recaptcha_v3.js) | No separate proxy task | +| Cloudflare Turnstile | [`turnstile.js`](turnstile.js) | [`turnstile_proxy.js`](turnstile_proxy.js) | +| GeeTest v3 | [`geetest_v3.js`](geetest_v3.js) | [`geetest_v3_proxy.js`](geetest_v3_proxy.js) | +| GeeTest v4 | [`geetest_v4.js`](geetest_v4.js) | [`geetest_v4_proxy.js`](geetest_v4_proxy.js) | +| Yandex SmartCaptcha | [`yandex_smartcaptcha.js`](yandex_smartcaptcha.js) | [`yandex_smartcaptcha_proxy.js`](yandex_smartcaptcha_proxy.js) | +| Yandex SmartCaptcha Image | [`yandex_smartcaptcha_image.js`](yandex_smartcaptcha_image.js) | No separate proxy task | +| Tencent | [`tencent.js`](tencent.js) | [`tencent_proxy.js`](tencent_proxy.js) | +| Image to Text | [`image_to_text.js`](image_to_text.js) | No separate proxy task | +| Coordinates | [`coordinates.js`](coordinates.js) | No separate proxy task | +| Account balance | [`balance.js`](balance.js) | Not applicable | + +Additional one-scenario examples: + +- [`image_to_text_math.js`](image_to_text_math.js) — math CAPTCHA with an instruction image. +- [`image_to_text_language_pool.js`](image_to_text_language_pool.js) — select the English worker pool. +- [`coordinates_traffic_lights.js`](coordinates_traffic_lights.js) — click coordinates with an instruction image. + +> **Important:** `geetest_v3.js` and `geetest_v3_proxy.js` contain a placeholder +> initialization endpoint. Replace it with the target website's endpoint and +> fetch a fresh `challenge` for every task. + +> **Important:** Proxy examples contain placeholder proxy details. Replace all +> of them with your own proxy settings before running a script. diff --git a/examples/async/README.md b/examples/async/README.md deleted file mode 100644 index 189bff8..0000000 --- a/examples/async/README.md +++ /dev/null @@ -1,154 +0,0 @@ -# Examples — async/await - -The 11 SDK scenarios written in `async/await` style. Every file uses **top-level `await`** (possible -because the package is ESM, `"type": "module"`) and wraps each solve in its own `try/catch`, so a -failing block prints the error and the next block still runs. - -Setup, API key and the general gotchas are documented once in the [parent README](../README.md) — -read that first if you haven't. The identical scenarios in promise-chain style live in -[../sync/](../sync). - -Because every block is awaited at the top level, the blocks in a file execute **sequentially**: a -file with a proxyless block and a with-proxy block submits two tasks, one after the other, and -spends balance twice. Comment out the one you don't need. - -## Table of contents - -- [File index](#file-index) -- [What each file does](#what-each-file-does) - -## File index - -| File | What it does | Blocks in the file | -|---|---|---| -| [balance.js](balance.js) | Reads the account balance | single call | -| [recaptcha_v2.js](recaptcha_v2.js) | Solves reCAPTCHA v2 | Proxyless · With proxy | -| [recaptcha_v2_enterprise.js](recaptcha_v2_enterprise.js) | Solves reCAPTCHA v2 Enterprise | Proxyless · With proxy | -| [turnstile.js](turnstile.js) | Solves Cloudflare Turnstile | Proxyless · With proxy | -| [geetest_v3.js](geetest_v3.js) | Fetches a fresh `challenge`, then solves GeeTest v3 | Fetch · Proxyless · With proxy | -| [geetest_v4.js](geetest_v4.js) | Solves GeeTest v4 via `captcha_id` | Proxyless · With proxy | -| [yandex_smartcaptcha.js](yandex_smartcaptcha.js) | Solves Yandex SmartCaptcha (token challenge) | Proxyless · With proxy | -| [yandex_smartcaptcha_image.js](yandex_smartcaptcha_image.js) | Solves Yandex SmartCaptcha image challenge | single task | -| [tencent.js](tencent.js) | Solves a Tencent captcha | Proxyless · With proxy | -| [image_to_text.js](image_to_text.js) | Recognises text on a captcha image | Basic · Advanced · With language pool | - -## What each file does - -### balance.js - -[Source code](balance.js) · [API documentation](https://captcha-solver.com/en/docs/methods#post-getbalance) - -```javascript -const balance = await captchaSolver.getBalance(); -``` - -One call, returning the available amount as a number. No task, no target page, no placeholders — -the only file here that runs correctly without editing anything, which makes it the quickest check -that your key works. - -### recaptcha_v2.js - -[Source code](recaptcha_v2.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#recaptcha-v2) - -The reference file for the proxyless/with-proxy pattern that most other examples repeat. -`RecaptchaV2Proxyless` solves through the service's own IPs; `RecaptchaV2` adds `proxyType`, -`proxyAddress`, `proxyPort`, `proxyLogin` and `proxyPassword`. The client constructor is annotated -with the two optional settings — `timeout` (default 120000 ms) and `pollingInterval` (default -5000 ms) — and the task shows `isInvisible` for invisible widgets. Solution: `gRecaptchaResponse`. - -### recaptcha_v2_enterprise.js - -[Source code](recaptcha_v2_enterprise.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#recaptcha-v2-enterprise) - -Same two blocks, plus `enterprisePayload`. Enterprise widgets are rendered through -`grecaptcha.enterprise.render()`, and any extra parameters the site passes there must be forwarded -in that object — omit them and the site rejects an otherwise valid token. Solution: -`gRecaptchaResponse`. - - - -A single block: v3 has no with-proxy variant. `minScore` is required and controls how hard the task -is — `0.3` fastest, `0.7` balanced, `0.9` highest and slowest — so the client is created with -`timeout: 180000`. `pageAction` should mirror the action the site sets in `grecaptcha.execute()`. -Commented-out lines show `isEnterprise` and `apiDomain` for sites loading from `recaptcha.net`. -Solution: `gRecaptchaResponse`. - -### turnstile.js - -[Source code](turnstile.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#cloudflare-turnstile) - -Proxyless and with-proxy blocks. The commented optional fields matter more here than elsewhere: -`action`, `data` (`data-cdata`) and `pagedata` (`chlPageData`, lowercase unlike the rest) are required for Cloudflare Challenge -pages. Note the User-Agent rule spelled out in the comments — there's no `userAgent` input field; -the worker picks its own and returns it as `result.userAgent`, and the browser or bot submitting -the token must send that exact one back. Solution: `token` (plus `userAgent`). - -### geetest_v3.js - -[Source code](geetest_v3.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#geetest-v3) - -The only file that makes a request of its own before solving. `challenge` is session-specific and -must be fresh per task, so the script awaits a `fetch` to the target's init endpoint, destructures -`challenge` out of the JSON, and only then builds the task; if that fetch throws, it logs and calls -`process.exit(1)`. - -That URL — `https://target-site.com/path/to/geetest/init` — is a **placeholder**, so the file does -not run end-to-end unmodified. It marks where the fetch belongs in the flow; point it at your real -source of `challenge` first. - -The client uses `timeout: 300000` and `pollingInterval: 10000`, GeeTest being among the slowest -types. `version` is omitted since v3 is the default. Solution: `challenge`, `validate`, `seccode`. - -### geetest_v4.js - -[Source code](geetest_v4.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#geetest-v4) - -Same `GeeTestProxyless`/`GeeTest` classes as v3, but v4 identifies the widget differently: no -`gt`, no `challenge`, instead `version: 4` and `captcha_id` inside `initParameters`. No pre-fetch is -needed. Same extended timeouts. Solution: `captcha_id`, `lot_number`, `pass_token`, `gen_time`, -`captcha_output`. - -### yandex_smartcaptcha.js - -[Source code](yandex_smartcaptcha.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#yandex-smartcaptcha) - -The **token** challenge, proxyless and with proxy. `websiteKey` is the sitekey from the page source -[coordinates.js](coordinates.js) instead — different task class entirely. Solution: `token`. - -### tencent.js - -[Source code](tencent.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#tencent) - -Proxyless and with proxy. `appId` comes from the page source; `captchaScript` is only needed when -the site loads the widget from a non-default script URL. Solution: `appid`, `ret`, `ticket`, -`randstr`. - -### image_to_text.js - -[Source code](image_to_text.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#image-to-text) - -Three blocks, no proxy variant, all active. *Basic* sends the shipped `text-captcha.png` sample with -the character-set hints that match it — `numeric: 2` (letters), `minLength`/`maxLength`. *Advanced* -solves a different image entirely, `captcha-math.png`, using the rest of the hints — `phrase`, -`math`, `comment`, and `captcha-math-instructions.png` passed as `imgInstructions`. *With language -pool* demonstrates the one API detail that is easy to get wrong: `languagePool` is the **second -argument to `solve()`**, not a task field — `await captchaSolver.solve(task, 'en')`, accepting `'en'` -or `'ru'`. - -Reads its images via `new URL(..., import.meta.url)`, so the path holds **regardless of the working -directory** and the script runs as-is. Solution: `text`. - -### coordinates.js - -[Source code](coordinates.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#coordinates) - -Three blocks, no proxy variant, all active. *Basic* submits the shipped grid captcha -(`coordinates-captcha.png`) plus a `comment` telling the worker what to click. *Advanced* solves a -different image, a real traffic-lights photo (`traffic-lights.png`), with -`traffic-lights-instructions.png` as `imgInstructions` and `minClicks`/`maxClicks` limits. *Yandex -SmartCaptcha screenshot (`yandex-smartcaptcha-sample.jpg`) whose click-order hint is baked into the -image itself, so no separate `imgInstructions` is passed — this is how the image variant of Yandex -SmartCaptcha is solved. - -Reads its images via `new URL(..., import.meta.url)`, so the path holds **regardless of the working -directory** and the script runs as-is. Solution: `coordinates`, an array of `{ x, y }` points. diff --git a/examples/async/balance.js b/examples/async/balance.js deleted file mode 100644 index dcc6e05..0000000 --- a/examples/async/balance.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Example: Get account balance. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Returns the current available balance of your account. - */ - -import 'dotenv/config'; -import { CaptchaClient } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// Get the current account balance. -// Returns a float with the available amount in your account currency. -try { - const balance = await captchaSolver.getBalance(); - console.log('Balance:', balance); -} catch (error) { - console.error(error); -} diff --git a/examples/async/coordinates.js b/examples/async/coordinates.js deleted file mode 100644 index 4e91a30..0000000 --- a/examples/async/coordinates.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Example: Solve a click-based image captcha using CoordinatesTask. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. That is all: the sample - * captcha ships with the repository, in examples/assets/. - * Point the read below at your own file to solve a different image, and use - * comment to tell the worker what to click on it. - * No proxy is required. The image is submitted directly to the service. - */ - -import 'dotenv/config'; -import fs from 'fs'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// Read and encode the captcha image to base64. -// The body must be a pure base64 string without the data:image/...;base64, prefix. -// The path is resolved against this file rather than the working directory, so -// the example runs from anywhere -- including from the repository root. -const body = fs.readFileSync(new URL('../assets/coordinates-captcha.png', import.meta.url)).toString('base64'); - -// --- Basic example --- -// Solves a simple click-based captcha with a hint for the worker. The comment -// repeats the instruction printed on the sample image, because nothing -// guarantees the worker reads the text baked into the picture. -try { - const task = new Tasks.CoordinatesTask({ - body: body, // Base64-encoded captcha image (required) - comment: 'click on all squares with street signs' // Text hint for the worker - }); - const result = await captchaSolver.solve(task); - // Solution contains { coordinates: [{ x: 358, y: 268 }] } - console.log('result:', result); -} catch (error) { - console.error(error); -} - -// --- Advanced example --- -// Solves a captcha (examples/assets/traffic-lights.png) with instruction image -// (examples/assets/traffic-lights-instructions.png) and click count limits. -// Uses its own body -- the basic example's sample image above isn't traffic lights. -try { - const trafficBody = fs.readFileSync(new URL('../assets/traffic-lights.png', import.meta.url)).toString('base64'); - const imgInstructions = fs.readFileSync( - new URL('../assets/traffic-lights-instructions.png', import.meta.url) - ).toString('base64'); - - const task = new Tasks.CoordinatesTask({ - body: trafficBody, // Base64-encoded captcha image - comment: 'click on all traffic lights', // Text hint for the worker - imgInstructions: imgInstructions, // Optional instruction image - minClicks: 1, // Minimum number of clicks (default 1) - maxClicks: 3 // Maximum number of clicks allowed - }); - const result = await captchaSolver.solve(task); - console.log('result:', result); -} catch (error) { - console.error(error); -} diff --git a/examples/async/geetest_v3.js b/examples/async/geetest_v3.js deleted file mode 100644 index 1363776..0000000 --- a/examples/async/geetest_v3.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Example: Solve a GeeTest v3 challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL, gt, and challenge with values from your target page. - * Important: the challenge value is dynamic. Fetch a fresh one for each request. - * - * NOTE: "https://target-site.com/path/to/geetest/init" below is a PLACEHOLDER, not a - * real endpoint -- this script will not run end-to-end as-is. Replace it with a request - * to your actual target page (or wherever it exposes a fresh `challenge` value) before - * running this example. It's here only to illustrate where that fetch belongs in the flow. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -// GeeTest tasks may take longer. Increase timeout if needed. -const captchaSolver = new CaptchaClient({ clientKey: apiKey, timeout: 300000, pollingInterval: 10000 }); - -// Fetch a fresh challenge value from the target page. -// In production, extract this from the page's initGeetest call or network requests. -// "target-site.com" is a placeholder -- point this at your real target before running. -let challenge; -try { - const initResponse = await fetch('https://target-site.com/path/to/geetest/init'); - ({ challenge } = await initResponse.json()); -} catch (error) { - console.error('Failed to fetch a fresh challenge value:', error); - process.exit(1); -} - -// --- Proxyless example --- -// v3 is the default version, so the version field can be omitted. -try { - const task = new Tasks.GeeTestProxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with GeeTest - gt: 'f2ae6cadcf7886856696c46d84d109d1', // Public key of the GeeTest widget - challenge: challenge // Session-specific value, must be fresh - // Optional fields - // geetestApiServerSubdomain: 'api-na.geetest.com', // Custom API subdomain - // initParameters: {...}, // Extra params from initGeetest call - // risk_type: 'slide', // Dynamic value from the page's captcha-loading request, if present (note: snake_case, not riskType) - // userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', - }); - const result = await captchaSolver.solve(task); - // Solution contains { challenge, validate, seccode } - console.log('result:', result); -} catch (error) { - console.error(error); -} - -// --- With proxy example --- -try { - const task = new Tasks.GeeTest({ - websiteURL: 'https://example.com/login', - gt: 'f2ae6cadcf7886856696c46d84d109d1', - challenge: challenge, - proxyType: 'http', // http, socks4, or socks5 - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) - }); - const result = await captchaSolver.solve(task); - console.log('result:', result); -} catch (error) { - console.error(error); -} diff --git a/examples/async/geetest_v4.js b/examples/async/geetest_v4.js deleted file mode 100644 index 26d7a76..0000000 --- a/examples/async/geetest_v4.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Example: Solve a GeeTest v4 challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and captcha_id with values from your target page. - * GeeTest v4 drops gt/challenge entirely. It uses captcha_id instead. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -// GeeTest v4 tasks may take longer. Increase timeout if needed. -const captchaSolver = new CaptchaClient({ clientKey: apiKey, timeout: 300000, pollingInterval: 10000 }); - -// --- Proxyless example --- -// v4 drops gt/challenge. The widget is identified by captcha_id inside initParameters. -try { - const task = new Tasks.GeeTestProxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with GeeTest v4 - version: 4, // Required: must be 4 for this version - initParameters: { // Required: must contain captcha_id - captcha_id: 'e392e1d7fd421dc63325744d5a2b9c73' // Static site identifier - } - // Optional fields - // risk_type: 'slide', // Dynamic value from the page's captcha-loading request, if present (note: snake_case, not riskType) - // userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', - }); - const result = await captchaSolver.solve(task); - // Solution contains { captcha_id, lot_number, pass_token, gen_time, captcha_output } - console.log('result:', result); -} catch (error) { - console.error(error); -} - -// --- With proxy example --- -try { - const task = new Tasks.GeeTest({ - websiteURL: 'https://example.com/login', - version: 4, - initParameters: { - captcha_id: 'e392e1d7fd421dc63325744d5a2b9c73' - }, - proxyType: 'http', // http, socks4, or socks5 - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) - }); - const result = await captchaSolver.solve(task); - console.log('result:', result); -} catch (error) { - console.error(error); -} diff --git a/examples/async/image_to_text.js b/examples/async/image_to_text.js deleted file mode 100644 index dca3672..0000000 --- a/examples/async/image_to_text.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Example: Solve an Image to Text challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. That is all: the sample - * captcha ships with the repository, in examples/assets/. - * Point the read below at your own file to solve a different image. - * Use optional fields to give hints to the worker for faster solving. - */ - -import 'dotenv/config'; -import fs from 'fs'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -// Image to Text tasks are usually fast. Default timeout is fine. -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// Read and encode the captcha image to base64. -// The body must be a pure base64 string without the data:image/...;base64, prefix. -// The path is resolved against this file rather than the working directory, so -// the example runs from anywhere -- including from the repository root. -const body = fs.readFileSync(new URL('../assets/text-captcha.png', import.meta.url)).toString('base64'); - -// --- Basic example --- -// Solves a simple image captcha with character set hints. The hints below match -// the sample image, which is letters and no digits -- adjust them for your own. -try { - const task = new Tasks.ImageToText({ - body: body, // Base64-encoded image (required) - numeric: 2, // 2 = letters only - minLength: 4, // Minimum expected answer length - maxLength: 6 // Maximum expected answer length - }); - const result = await captchaSolver.solve(task); - // Solution contains { text: "aB3fX9" } - console.log('result:', result); -} catch (error) { - console.error(error); -} - -// --- Advanced example --- -// Solves a math captcha (examples/assets/captcha-math.png) with comment and -// instruction image (examples/assets/captcha-math-instructions.png). Uses its -// own body -- the basic example's sample image above isn't a math captcha. -try { - const mathBody = fs.readFileSync(new URL('../assets/captcha-math.png', import.meta.url)).toString('base64'); - const imgInstructions = fs.readFileSync( - new URL('../assets/captcha-math-instructions.png', import.meta.url) - ).toString('base64'); - - const task = new Tasks.ImageToText({ - body: mathBody, // Base64-encoded captcha image - // Optional fields (pass only if needed by the captcha type) - phrase: false, // true if answer has multiple words - case_: true, // true if answer is case-sensitive - numeric: 0, // 0 = not specified, 1 = digits, 2 = letters, 3 = any with digits, 4 = any with letters - math: true, // true if image is a math expression to solve - minLength: 1, // Minimum answer length - maxLength: 10, // Maximum answer length - comment: 'Enter the result of the equation', // Text hint for the worker - imgInstructions: imgInstructions // Optional instruction image for the worker - }); - const result = await captchaSolver.solve(task); - console.log('result:', result); -} catch (error) { - console.error(error); -} - -// --- With language pool --- -// The languagePool parameter selects the worker pool by language. -// Pass it as the second argument to solve(), not inside the task. -// Accepted values: "en" (English) or "ru" (Russian). -try { - const task = new Tasks.ImageToText({ - body: body, - numeric: 2, - minLength: 4, - maxLength: 6 - }); - const result = await captchaSolver.solve(task, 'en'); // Picks English-speaking worker pool - console.log('result:', result); -} catch (error) { - console.error(error); -} diff --git a/examples/async/recaptcha_v2.js b/examples/async/recaptcha_v2.js deleted file mode 100644 index 4254b23..0000000 --- a/examples/async/recaptcha_v2.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Example: Solve a reCAPTCHA v2 challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and websiteKey with values from your target page. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -// Create a solver instance with your API key. -// Optional: timeout (ms to wait for solution, default 120000) -// Optional: pollingInterval (ms between status checks, default 5000) -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// --- Proxyless example --- -// Solves reCAPTCHA v2 without a proxy. The service uses its own IP addresses. -try { - const task = new Tasks.RecaptchaV2Proxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with captcha - websiteKey: '6Le-xxxxxxxxxxxxxxxxxxxxxxxxxxxx', // data-sitekey attribute value - isInvisible: false // Set true for invisible reCAPTCHA - }); - const result = await captchaSolver.solve(task); - // Solution contains { gRecaptchaResponse: "03AGdBq..." } - console.log('result:', result); -} catch (error) { - console.error(error); -} - -// --- With proxy example --- -// Solves reCAPTCHA v2 through your own proxy. Required when the target site is -// geo-restricted or you need session consistency. -try { - const task = new Tasks.RecaptchaV2({ - websiteURL: 'https://example.com/login', - websiteKey: '6Le-xxxxxxxxxxxxxxxxxxxxxxxxxxxx', - // --- Proxy parameters --- - proxyType: 'http', // http, socks4, or socks5 - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) - }); - const result = await captchaSolver.solve(task); - console.log('result:', result); -} catch (error) { - console.error(error); -} diff --git a/examples/async/recaptcha_v2_enterprise.js b/examples/async/recaptcha_v2_enterprise.js deleted file mode 100644 index 2669ffc..0000000 --- a/examples/async/recaptcha_v2_enterprise.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Example: Solve a reCAPTCHA v2 Enterprise challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and websiteKey with values from your target page. - * If the site uses enterprisePayload, extract and pass it or the token may be rejected. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// --- Proxyless example --- -// Enterprise captchas are loaded via the reCAPTCHA Enterprise API. If the site -// passes extra parameters to grecaptcha.enterprise.render(), pass them as -// enterprisePayload or the token will be rejected. -try { - const task = new Tasks.RecaptchaV2EnterpriseProxyless({ - websiteURL: 'https://example.com/login', - websiteKey: '6Le-xxxxxxxxxxxxxxxxxxxxxxxxxxxx', - isInvisible: false - // Optional fields (pass only if the target site requires them): - // enterprisePayload: { s: 'value-from-page' }, - // apiDomain: 'recaptcha.net', - // userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', - // cookies: 'session=abc123; token=xyz789', - }); - const result = await captchaSolver.solve(task); - // Solution contains { gRecaptchaResponse: "03AGdBq..." } - console.log('result:', result); -} catch (error) { - console.error(error); -} - -// --- With proxy example --- -try { - const task = new Tasks.RecaptchaV2Enterprise({ - websiteURL: 'https://example.com/login', - websiteKey: '6Le-xxxxxxxxxxxxxxxxxxxxxxxxxxxx', - proxyType: 'http', - proxyAddress: '1.2.3.4', - proxyPort: 8080, - proxyLogin: 'user', - proxyPassword: 'password', - isInvisible: false - }); - const result = await captchaSolver.solve(task); - console.log('result:', result); -} catch (error) { - console.error(error); -} diff --git a/examples/async/recaptcha_v3.js b/examples/async/recaptcha_v3.js deleted file mode 100644 index 2d2a9a7..0000000 --- a/examples/async/recaptcha_v3.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Example: Solve reCAPTCHA v3 without a customer proxy. - * Set CAPTCHA_API_KEY and replace the page-specific values before running. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const captchaSolver = new CaptchaClient({ - clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' -}); - -const task = new Tasks.RecaptchaV3Proxyless({ - websiteURL: 'https://example.com/login', - websiteKey: 'YOUR_WEBSITE_KEY', - minScore: 0.3, - pageAction: 'homepage' -}); - -try { - const result = await captchaSolver.solve(task); - console.log('result:', result); -} catch (error) { - console.error(error); -} diff --git a/examples/async/tencent.js b/examples/async/tencent.js deleted file mode 100644 index 670026d..0000000 --- a/examples/async/tencent.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Example: Solve a Tencent captcha challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and appId with values from your target page. - * Pass captchaScript if the site uses a non-default script URL. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// --- Proxyless example --- -// The service's own proxies are used to solve the captcha. -// appId is found in the page source code. captchaScript is optional if the site uses the default. -try { - const task = new Tasks.TencentTaskProxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with captcha - appId: '190014885' // appId from page source code (required) - // Optional fields: - // captchaScript: 'https://turing.captcha.qcloud.com/TCaptcha.js', - }); - const result = await captchaSolver.solve(task); - // Solution contains { appid, ret, ticket, randstr } - // Pass all four values together into the page's captcha callback as-is. - console.log('result:', result); -} catch (error) { - console.error(error); -} - -// --- With proxy example --- -// Use when the target site is geo-restricted or you need a consistent session. -try { - const task = new Tasks.TencentTask({ - websiteURL: 'https://example.com/login', - appId: '190014885', - // --- Proxy parameters --- - proxyType: 'http', // http, socks4, or socks5 - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) - }); - const result = await captchaSolver.solve(task); - console.log('result:', result); -} catch (error) { - console.error(error); -} diff --git a/examples/async/turnstile.js b/examples/async/turnstile.js deleted file mode 100644 index 06c0271..0000000 --- a/examples/async/turnstile.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Example: Solve a Cloudflare Turnstile challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and websiteKey with values from your target page. - * For Cloudflare Challenge pages, also extract and pass action, data, pagedata, - * and the current browser userAgent. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// --- Proxyless example --- -// For a Cloudflare Challenge page, pass the current browser User-Agent together -// with action, data, and pagedata. Do not change the browser User-Agent after solving. -try { - const task = new Tasks.TurnstileProxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with Turnstile - websiteKey: '0x4AAAAAAAxxxxxxxxxxxxxxxx', // data-sitekey attribute value - // Optional fields (pass only if the target site sets them) - // action: 'login', // Value of data-action attribute - // data: 'custom-cdata-value', // Value of data-cdata attribute - // pagedata: 'chl-page-data-value', // Value of chlPageData parameter - // userAgent: 'Mozilla/5.0 ...', // Current browser User-Agent - }); - const result = await captchaSolver.solve(task); - // Solution contains { token: "0.zxcv...", userAgent: "Mozilla/5.0 ..." } - console.log('result:', result); -} catch (error) { - console.error(error); -} - -// --- With proxy example --- -try { - const task = new Tasks.Turnstile({ - websiteURL: 'https://example.com/login', - websiteKey: '0x4AAAAAAAxxxxxxxxxxxxxxxx', - // --- Proxy parameters --- - proxyType: 'http', // http, socks4, or socks5 - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) - // --- Optional fields --- - // action: 'login', - // data: 'custom-cdata-value', - // pagedata: 'chl-page-data-value', - }); - const result = await captchaSolver.solve(task); - console.log('result:', result); -} catch (error) { - console.error(error); -} diff --git a/examples/async/yandex_smartcaptcha.js b/examples/async/yandex_smartcaptcha.js deleted file mode 100644 index a937413..0000000 --- a/examples/async/yandex_smartcaptcha.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Example: Solve a Yandex SmartCaptcha challenge (token-based). - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and websiteKey with values from your target page. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// --- Proxyless example --- -// The service's own proxies are used to solve the captcha. -// websiteKey is the sitekey value from the page code or captcha iframe. -try { - const task = new Tasks.YandexSmartCaptchaTaskProxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with captcha - websiteKey: 'FEXfAbHQsToo97VidNVk3j4dC74nGW1DgdxK4OoR' // sitekey from page code or iframe - // Optional fields: - // userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', - // cookies: 'session=abc123; token=xyz789', - }); - const result = await captchaSolver.solve(task); - // Solution contains { token: "dV9xNjYyNTU3NjkxO4k9OTQuNVMuMjkuMjM9..." } - console.log('result:', result); -} catch (error) { - console.error(error); -} - -// --- With proxy example --- -// Note: this is the only captcha type where an https proxy is accepted. -try { - const task = new Tasks.YandexSmartCaptchaTask({ - websiteURL: 'https://example.com/login', - websiteKey: 'FEXfAbHQsToo97VidNVk3j4dC74nGW1DgdxK4OoR', - // --- Proxy parameters --- - proxyType: 'http', // http, https, socks4, or socks5 (https is accepted only for this type) - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) - }); - const result = await captchaSolver.solve(task); - console.log('result:', result); -} catch (error) { - console.error(error); -} diff --git a/examples/async/yandex_smartcaptcha_image.js b/examples/async/yandex_smartcaptcha_image.js deleted file mode 100644 index e878647..0000000 --- a/examples/async/yandex_smartcaptcha_image.js +++ /dev/null @@ -1,26 +0,0 @@ -/** Solve the image-based Yandex SmartCaptcha challenge. */ -import 'dotenv/config'; -import fs from 'node:fs'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const captchaSolver = new CaptchaClient({ - clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' -}); - -const image = fs.readFileSync( - new URL('../assets/yandex-smartcaptcha-image.png', import.meta.url) -).toString('base64'); -const imgInstructions = fs.readFileSync( - new URL('../assets/yandex-smartcaptcha-instructions.png', import.meta.url) -).toString('base64'); - -try { - const task = new Tasks.YandexSmartCaptchaImageTask({ - image, - imgInstructions - }); - const result = await captchaSolver.solve(task); - console.log('Coordinates in click order:', result.coordinates); -} catch (error) { - console.error(error); -} diff --git a/examples/sync/balance.js b/examples/balance.js similarity index 100% rename from examples/sync/balance.js rename to examples/balance.js diff --git a/examples/coordinates.js b/examples/coordinates.js new file mode 100644 index 0000000..fb6c748 --- /dev/null +++ b/examples/coordinates.js @@ -0,0 +1,13 @@ +import 'dotenv/config'; +import fs from 'fs'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.CoordinatesTask({ + body: fs.readFileSync(new URL('./assets/coordinates-captcha.png', import.meta.url)).toString('base64'), + comment: 'Click on the green apple' +}); + +client.solve(task) + .then((solution) => console.log(solution.coordinates)) + .catch(console.error); diff --git a/examples/coordinates_traffic_lights.js b/examples/coordinates_traffic_lights.js new file mode 100644 index 0000000..00ee1ab --- /dev/null +++ b/examples/coordinates_traffic_lights.js @@ -0,0 +1,15 @@ +import 'dotenv/config'; +import fs from 'fs'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.CoordinatesTask({ + body: fs.readFileSync(new URL('./assets/traffic-lights.png', import.meta.url)).toString('base64'), + imgInstructions: fs.readFileSync(new URL('./assets/traffic-lights-instructions.png', import.meta.url)).toString('base64'), + minClicks: 1, + maxClicks: 5 +}); + +client.solve(task) + .then((solution) => console.log(solution.coordinates)) + .catch(console.error); diff --git a/examples/geetest_v3.js b/examples/geetest_v3.js new file mode 100644 index 0000000..12223f2 --- /dev/null +++ b/examples/geetest_v3.js @@ -0,0 +1,14 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY', timeout: 300000 }); + +fetch('https://target-site.com/path/to/geetest/init') + .then((response) => response.json()) + .then(({ challenge }) => client.solve(new Tasks.GeeTestProxyless({ + websiteURL: 'https://example.com/login', + gt: 'YOUR_GT_KEY', + challenge + }))) + .then((solution) => console.log(solution)) + .catch(console.error); diff --git a/examples/geetest_v3_proxy.js b/examples/geetest_v3_proxy.js new file mode 100644 index 0000000..21b4bbc --- /dev/null +++ b/examples/geetest_v3_proxy.js @@ -0,0 +1,17 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY', timeout: 300000 }); + +fetch('https://target-site.com/path/to/geetest/init') + .then((response) => response.json()) + .then(({ challenge }) => client.solve(new Tasks.GeeTest({ + websiteURL: 'https://example.com/login', + gt: 'YOUR_GT_KEY', + challenge, + proxyType: 'http', + proxyAddress: '1.2.3.4', + proxyPort: 8080 + }))) + .then((solution) => console.log(solution)) + .catch(console.error); diff --git a/examples/geetest_v4.js b/examples/geetest_v4.js new file mode 100644 index 0000000..8c1084c --- /dev/null +++ b/examples/geetest_v4.js @@ -0,0 +1,13 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY', timeout: 300000 }); +const task = new Tasks.GeeTestProxyless({ + websiteURL: 'https://example.com/login', + version: 4, + initParameters: { captcha_id: 'YOUR_CAPTCHA_ID' } +}); + +client.solve(task) + .then((solution) => console.log(solution)) + .catch(console.error); diff --git a/examples/geetest_v4_proxy.js b/examples/geetest_v4_proxy.js new file mode 100644 index 0000000..1ff188e --- /dev/null +++ b/examples/geetest_v4_proxy.js @@ -0,0 +1,16 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY', timeout: 300000 }); +const task = new Tasks.GeeTest({ + websiteURL: 'https://example.com/login', + version: 4, + initParameters: { captcha_id: 'YOUR_CAPTCHA_ID' }, + proxyType: 'http', + proxyAddress: '1.2.3.4', + proxyPort: 8080 +}); + +client.solve(task) + .then((solution) => console.log(solution)) + .catch(console.error); diff --git a/examples/image_to_text.js b/examples/image_to_text.js new file mode 100644 index 0000000..ca2bd02 --- /dev/null +++ b/examples/image_to_text.js @@ -0,0 +1,15 @@ +import 'dotenv/config'; +import fs from 'fs'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.ImageToText({ + body: fs.readFileSync(new URL('./assets/text-captcha.png', import.meta.url)).toString('base64'), + numeric: 2, + minLength: 4, + maxLength: 6 +}); + +client.solve(task) + .then((solution) => console.log(solution.text)) + .catch(console.error); diff --git a/examples/image_to_text_language_pool.js b/examples/image_to_text_language_pool.js new file mode 100644 index 0000000..db08a44 --- /dev/null +++ b/examples/image_to_text_language_pool.js @@ -0,0 +1,12 @@ +import 'dotenv/config'; +import fs from 'fs'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.ImageToText({ + body: fs.readFileSync(new URL('./assets/text-captcha.png', import.meta.url)).toString('base64') +}); + +client.solve(task, 'en') + .then((solution) => console.log(solution.text)) + .catch(console.error); diff --git a/examples/image_to_text_math.js b/examples/image_to_text_math.js new file mode 100644 index 0000000..8c7dcaa --- /dev/null +++ b/examples/image_to_text_math.js @@ -0,0 +1,15 @@ +import 'dotenv/config'; +import fs from 'fs'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.ImageToText({ + body: fs.readFileSync(new URL('./assets/captcha-math.png', import.meta.url)).toString('base64'), + math: true, + comment: 'Enter the result of the equation', + imgInstructions: fs.readFileSync(new URL('./assets/captcha-math-instructions.png', import.meta.url)).toString('base64') +}); + +client.solve(task) + .then((solution) => console.log(solution.text)) + .catch(console.error); diff --git a/examples/recaptcha_v2.js b/examples/recaptcha_v2.js new file mode 100644 index 0000000..2e7b01f --- /dev/null +++ b/examples/recaptcha_v2.js @@ -0,0 +1,12 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.RecaptchaV2Proxyless({ + websiteURL: 'https://example.com/login', + websiteKey: 'YOUR_WEBSITE_KEY' +}); + +client.solve(task) + .then((solution) => console.log(solution.gRecaptchaResponse)) + .catch(console.error); diff --git a/examples/recaptcha_v2_enterprise.js b/examples/recaptcha_v2_enterprise.js new file mode 100644 index 0000000..dd7c6e3 --- /dev/null +++ b/examples/recaptcha_v2_enterprise.js @@ -0,0 +1,12 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.RecaptchaV2EnterpriseProxyless({ + websiteURL: 'https://example.com/login', + websiteKey: 'YOUR_WEBSITE_KEY' +}); + +client.solve(task) + .then((solution) => console.log(solution.gRecaptchaResponse)) + .catch(console.error); diff --git a/examples/recaptcha_v2_enterprise_proxy.js b/examples/recaptcha_v2_enterprise_proxy.js new file mode 100644 index 0000000..6817580 --- /dev/null +++ b/examples/recaptcha_v2_enterprise_proxy.js @@ -0,0 +1,15 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.RecaptchaV2Enterprise({ + websiteURL: 'https://example.com/login', + websiteKey: 'YOUR_WEBSITE_KEY', + proxyType: 'http', + proxyAddress: '1.2.3.4', + proxyPort: 8080 +}); + +client.solve(task) + .then((solution) => console.log(solution.gRecaptchaResponse)) + .catch(console.error); diff --git a/examples/recaptcha_v2_proxy.js b/examples/recaptcha_v2_proxy.js new file mode 100644 index 0000000..2dc1745 --- /dev/null +++ b/examples/recaptcha_v2_proxy.js @@ -0,0 +1,17 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.RecaptchaV2({ + websiteURL: 'https://example.com/login', + websiteKey: 'YOUR_WEBSITE_KEY', + proxyType: 'http', + proxyAddress: '1.2.3.4', + proxyPort: 8080, + proxyLogin: 'user', + proxyPassword: 'password' +}); + +client.solve(task) + .then((solution) => console.log(solution.gRecaptchaResponse)) + .catch(console.error); diff --git a/examples/sync/recaptcha_v3.js b/examples/recaptcha_v3.js similarity index 100% rename from examples/sync/recaptcha_v3.js rename to examples/recaptcha_v3.js diff --git a/examples/sync/README.md b/examples/sync/README.md deleted file mode 100644 index 30eaad4..0000000 --- a/examples/sync/README.md +++ /dev/null @@ -1,162 +0,0 @@ -# Examples — promise chains - -The same 11 SDK scenarios as [../async/](../async), written with `.then()/.catch()` instead of -`await`. Nothing about the SDK changes between the two suites: `CaptchaClient` returns promises, and -these files simply consume them without `await`. - -Setup, API key and the general gotchas are documented once in the [parent README](../README.md) — -read that first if you haven't. - -Two consequences of dropping `await` are worth knowing before you run anything here: - -- **Tasks are named, not inline.** Each block declares its task in a `const` (`proxylessTask`, - `proxyTask`, `basicTask`, …) and then passes it to `captchaSolver.solve(...)`, which keeps the - promise chain readable. -- **Blocks start concurrently.** In `async/`, a top-level `await` makes the proxyless block finish - before the with-proxy block begins. Here nothing blocks, so both `solve()` calls are submitted - almost simultaneously and their results arrive in whatever order the API returns them. Two tasks, - twice the balance — comment out the block you don't need. - -Errors are handled per chain with a trailing `.catch()`; a failing block does not stop the others. - -## Table of contents - -- [File index](#file-index) -- [What each file does](#what-each-file-does) - -## File index - -| File | What it does | Blocks in the file | -|---|---|---| -| [balance.js](balance.js) | Reads the account balance | single call | -| [recaptcha_v2.js](recaptcha_v2.js) | Solves reCAPTCHA v2 | Proxyless · With proxy | -| [recaptcha_v2_enterprise.js](recaptcha_v2_enterprise.js) | Solves reCAPTCHA v2 Enterprise | Proxyless · With proxy | -| [turnstile.js](turnstile.js) | Solves Cloudflare Turnstile | Proxyless · With proxy | -| [geetest_v3.js](geetest_v3.js) | Fetches a fresh `challenge`, then solves GeeTest v3 | one nested chain | -| [geetest_v4.js](geetest_v4.js) | Solves GeeTest v4 via `captcha_id` | Proxyless · With proxy | -| [yandex_smartcaptcha.js](yandex_smartcaptcha.js) | Solves Yandex SmartCaptcha (token challenge) | Proxyless · With proxy | -| [yandex_smartcaptcha_image.js](yandex_smartcaptcha_image.js) | Solves Yandex SmartCaptcha image challenge | single chain | -| [tencent.js](tencent.js) | Solves a Tencent captcha | Proxyless · With proxy | -| [image_to_text.js](image_to_text.js) | Recognises text on a captcha image | Basic · Advanced · With language pool | - -## What each file does - -### balance.js - -[Source code](balance.js) · [API documentation](https://captcha-solver.com/en/docs/methods#post-getbalance) - -```javascript -captchaSolver.getBalance() - .then((balance) => console.log('Balance:', balance)) -``` - -One call resolving to the available amount as a number. No task, no target page, no placeholders — -the only file here that runs correctly without editing anything, and therefore the quickest check -that your key works. - -### recaptcha_v2.js - -[Source code](recaptcha_v2.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#recaptcha-v2) - -The reference file for the proxyless/with-proxy pattern most other examples repeat. -`RecaptchaV2Proxyless` solves through the service's own IPs; `RecaptchaV2` adds `proxyType`, -`proxyAddress`, `proxyPort`, `proxyLogin` and `proxyPassword`. The client constructor is annotated -with the two optional settings — `timeout` (default 120000 ms) and `pollingInterval` (default -5000 ms) — and the task shows `isInvisible` for invisible widgets. Solution: `gRecaptchaResponse`. - -### recaptcha_v2_enterprise.js - -[Source code](recaptcha_v2_enterprise.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#recaptcha-v2-enterprise) - -Same two chains, plus `enterprisePayload`. Enterprise widgets render through -`grecaptcha.enterprise.render()`, and any extra parameters the site passes there must be forwarded -in that object — omit them and the site rejects an otherwise valid token. Solution: -`gRecaptchaResponse`. - - - -A single chain: v3 has no with-proxy variant. `minScore` is required and controls how hard the task -is — `0.3` fastest, `0.7` balanced, `0.9` highest and slowest — so the client is created with -`timeout: 180000`. `pageAction` should mirror the action the site sets in `grecaptcha.execute()`. -Commented-out lines show `isEnterprise` and `apiDomain` for sites loading from `recaptcha.net`. -Solution: `gRecaptchaResponse`. - -### turnstile.js - -[Source code](turnstile.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#cloudflare-turnstile) - -Proxyless and with-proxy chains. The commented optional fields matter more here than elsewhere: -`action`, `data` (`data-cdata`) and `pagedata` (`chlPageData`, lowercase unlike the rest) are required for Cloudflare Challenge -pages. Note the User-Agent rule spelled out in the comments — there's no `userAgent` input field; -the worker picks its own and returns it as `result.userAgent`, and the browser or bot submitting -the token must send that exact one back. Solution: `token` (plus `userAgent`). - -### geetest_v3.js - -[Source code](geetest_v3.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#geetest-v3) - -Structurally the odd one out. `challenge` is session-specific and must be fresh per task, so the -file opens with a `fetch(...).then(response => response.json())` chain and **nests both solve blocks -inside** the `.then(({ challenge }) => { … })` callback — the tasks cannot be built until the value -arrives. This is the clearest place to see what `await` buys you: the async version expresses the -same dependency as three flat statements. - -That URL — `https://target-site.com/path/to/geetest/init` — is a **placeholder**, so the file does -not run end-to-end unmodified. It marks where the fetch belongs in the flow; point it at your real -source of `challenge` first. - -The client uses `timeout: 300000` and `pollingInterval: 10000`, GeeTest being among the slowest -types. `version` is omitted since v3 is the default. Solution: `challenge`, `validate`, `seccode`. - -### geetest_v4.js - -[Source code](geetest_v4.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#geetest-v4) - -Same `GeeTestProxyless`/`GeeTest` classes as v3, but v4 identifies the widget differently: no `gt`, -no `challenge`, instead `version: 4` and `captcha_id` inside `initParameters`. No pre-fetch is -needed, so this file keeps the flat two-chain layout. Same extended timeouts. Solution: -`captcha_id`, `lot_number`, `pass_token`, `gen_time`, `captcha_output`. - -### yandex_smartcaptcha.js - -[Source code](yandex_smartcaptcha.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#yandex-smartcaptcha) - -The **token** challenge, proxyless and with proxy. `websiteKey` is the sitekey from the page source -[coordinates.js](coordinates.js) instead — different task class entirely. Solution: `token`. - -### tencent.js - -[Source code](tencent.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#tencent) - -Proxyless and with proxy. `appId` comes from the page source; `captchaScript` is only needed when -the site loads the widget from a non-default script URL. Solution: `appid`, `ret`, `ticket`, -`randstr`. - -### image_to_text.js - -[Source code](image_to_text.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#image-to-text) - -Three chains, no proxy variant, all active. *Basic* (`basicTask`) sends the shipped -`text-captcha.png` sample with the character-set hints that match it — `numeric: 2` (letters), -`minLength`/`maxLength`. *Advanced* (`advancedTask`) solves a different image entirely, -`captcha-math.png`, using the rest of the hints — `phrase`, `math`, `comment`, and -`captcha-math-instructions.png` passed as `imgInstructions`. *With language pool* -(`languagePoolTask`) demonstrates the one API detail that is easy to get wrong: `languagePool` is -the **second argument to `solve()`**, not a task field — -`captchaSolver.solve(languagePoolTask, 'en')`, accepting `'en'` or `'ru'`. - -Reads its images via `new URL(..., import.meta.url)`, so the path holds **regardless of the working -directory** and the script runs as-is. Solution: `text`. - -### coordinates.js - -[Source code](coordinates.js) · [API documentation](https://captcha-solver.com/en/docs/captcha-types#coordinates) - -Three chains, no proxy variant, all active. *Basic* (`basicTask`) submits the shipped grid captcha -(`coordinates-captcha.png`) plus a `comment` telling the worker what to click. *Advanced* -(`advancedTask`) solves a different image, a real traffic-lights photo (`traffic-lights.png`), with -`traffic-lights-instructions.png` as `imgInstructions` and `minClicks`/`maxClicks` limits. - -Reads its images via -`new URL(..., import.meta.url)`, so the path holds **regardless of the working directory** and the -script runs as-is. Solution: `coordinates`, an array of `{ x, y }` points. diff --git a/examples/sync/coordinates.js b/examples/sync/coordinates.js deleted file mode 100644 index 8d7180d..0000000 --- a/examples/sync/coordinates.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Example: Solve a click-based image captcha using CoordinatesTask. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. That is all: the sample - * captcha ships with the repository, in examples/assets/. - * Point the read below at your own file to solve a different image, and use - * comment to tell the worker what to click on it. - * No proxy is required. The image is submitted directly to the service. - */ - -import 'dotenv/config'; -import fs from 'fs'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// Read and encode the captcha image to base64. -// The body must be a pure base64 string without the data:image/...;base64, prefix. -// The path is resolved against this file rather than the working directory, so -// the example runs from anywhere -- including from the repository root. -const body = fs.readFileSync(new URL('../assets/coordinates-captcha.png', import.meta.url)).toString('base64'); - -// --- Basic example --- -// Solves a simple click-based captcha with a hint for the worker. The comment -// repeats the instruction printed on the sample image, because nothing -// guarantees the worker reads the text baked into the picture. -const basicTask = new Tasks.CoordinatesTask({ - body: body, // Base64-encoded captcha image (required) - comment: 'click on all squares with street signs' // Text hint for the worker -}); - -captchaSolver.solve(basicTask) - .then((result) => { - // Solution contains { coordinates: [{ x: 358, y: 268 }] } - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); - -// --- Advanced example --- -// Solves a captcha (examples/assets/traffic-lights.png) with instruction image -// (examples/assets/traffic-lights-instructions.png) and click count limits. -// Uses its own body -- the basic example's sample image above isn't traffic lights. -const trafficBody = fs.readFileSync(new URL('../assets/traffic-lights.png', import.meta.url)).toString('base64'); -const imgInstructions = fs.readFileSync( - new URL('../assets/traffic-lights-instructions.png', import.meta.url) -).toString('base64'); - -const advancedTask = new Tasks.CoordinatesTask({ - body: trafficBody, // Base64-encoded captcha image - comment: 'click on all traffic lights', // Text hint for the worker - imgInstructions: imgInstructions, // Optional instruction image - minClicks: 1, // Minimum number of clicks (default 1) - maxClicks: 3 // Maximum number of clicks allowed -}); - -captchaSolver.solve(advancedTask) - .then((result) => { - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); diff --git a/examples/sync/geetest_v3.js b/examples/sync/geetest_v3.js deleted file mode 100644 index 0840845..0000000 --- a/examples/sync/geetest_v3.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Example: Solve a GeeTest v3 challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL, gt, and challenge with values from your target page. - * Important: the challenge value is dynamic. Fetch a fresh one for each request. - * - * NOTE: "https://target-site.com/path/to/geetest/init" below is a PLACEHOLDER, not a - * real endpoint -- this script will not run end-to-end as-is. Replace it with a request - * to your actual target page (or wherever it exposes a fresh `challenge` value) before - * running this example. It's here only to illustrate where that fetch belongs in the flow. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -// GeeTest tasks may take longer. Increase timeout if needed. -const captchaSolver = new CaptchaClient({ clientKey: apiKey, timeout: 300000, pollingInterval: 10000 }); - -// Fetch a fresh challenge value from the target page. -// In production, extract this from the page's initGeetest call or network requests. -// "target-site.com" is a placeholder -- point this at your real target before running. -fetch('https://target-site.com/path/to/geetest/init') - .then((response) => response.json()) - .then(({ challenge }) => { - // --- Proxyless example --- - // v3 is the default version, so the version field can be omitted. - const proxylessTask = new Tasks.GeeTestProxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with GeeTest - gt: 'f2ae6cadcf7886856696c46d84d109d1', // Public key of the GeeTest widget - challenge: challenge // Session-specific value, must be fresh - // Optional fields - // geetestApiServerSubdomain: 'api-na.geetest.com', // Custom API subdomain - // initParameters: {...}, // Extra params from initGeetest call - // risk_type: 'slide', // Dynamic value from the page's captcha-loading request, if present (note: snake_case, not riskType) - // userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', - }); - - captchaSolver.solve(proxylessTask) - .then((result) => { - // Solution contains { challenge, validate, seccode } - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); - - // --- With proxy example --- - const proxyTask = new Tasks.GeeTest({ - websiteURL: 'https://example.com/login', - gt: 'f2ae6cadcf7886856696c46d84d109d1', - challenge: challenge, - proxyType: 'http', // http, socks4, or socks5 - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) - }); - - captchaSolver.solve(proxyTask) - .then((result) => { - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); - }) - .catch((error) => { - console.error(error); - }); diff --git a/examples/sync/geetest_v4.js b/examples/sync/geetest_v4.js deleted file mode 100644 index 822147b..0000000 --- a/examples/sync/geetest_v4.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Example: Solve a GeeTest v4 challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and captcha_id with values from your target page. - * GeeTest v4 drops gt/challenge entirely. It uses captcha_id instead. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -// GeeTest v4 tasks may take longer. Increase timeout if needed. -const captchaSolver = new CaptchaClient({ clientKey: apiKey, timeout: 300000, pollingInterval: 10000 }); - -// --- Proxyless example --- -// v4 drops gt/challenge. The widget is identified by captcha_id inside initParameters. -const proxylessTask = new Tasks.GeeTestProxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with GeeTest v4 - version: 4, // Required: must be 4 for this version - initParameters: { // Required: must contain captcha_id - captcha_id: 'e392e1d7fd421dc63325744d5a2b9c73' // Static site identifier - } - // Optional fields - // risk_type: 'slide', // Dynamic value from the page's captcha-loading request, if present (note: snake_case, not riskType) - // userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', -}); - -captchaSolver.solve(proxylessTask) - .then((result) => { - // Solution contains { captcha_id, lot_number, pass_token, gen_time, captcha_output } - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); - -// --- With proxy example --- -const proxyTask = new Tasks.GeeTest({ - websiteURL: 'https://example.com/login', - version: 4, - initParameters: { - captcha_id: 'e392e1d7fd421dc63325744d5a2b9c73' - }, - proxyType: 'http', // http, socks4, or socks5 - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) -}); - -captchaSolver.solve(proxyTask) - .then((result) => { - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); diff --git a/examples/sync/image_to_text.js b/examples/sync/image_to_text.js deleted file mode 100644 index fc638c6..0000000 --- a/examples/sync/image_to_text.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Example: Solve an Image to Text challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. That is all: the sample - * captcha ships with the repository, in examples/assets/. - * Point the read below at your own file to solve a different image. - * Use optional fields to give hints to the worker for faster solving. - */ - -import 'dotenv/config'; -import fs from 'fs'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -// Image to Text tasks are usually fast. Default timeout is fine. -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// Read and encode the captcha image to base64. -// The body must be a pure base64 string without the data:image/...;base64, prefix. -// The path is resolved against this file rather than the working directory, so -// the example runs from anywhere -- including from the repository root. -const body = fs.readFileSync(new URL('../assets/text-captcha.png', import.meta.url)).toString('base64'); - -// --- Basic example --- -// Solves a simple image captcha with character set hints. The hints below match -// the sample image, which is letters and no digits -- adjust them for your own. -const basicTask = new Tasks.ImageToText({ - body: body, // Base64-encoded image (required) - numeric: 2, // 2 = letters only - minLength: 4, // Minimum expected answer length - maxLength: 6 // Maximum expected answer length -}); - -captchaSolver.solve(basicTask) - .then((result) => { - // Solution contains { text: "aB3fX9" } - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); - -// --- Advanced example --- -// Solves a math captcha (examples/assets/captcha-math.png) with comment and -// instruction image (examples/assets/captcha-math-instructions.png). Uses its -// own body -- the basic example's sample image above isn't a math captcha. -const mathBody = fs.readFileSync(new URL('../assets/captcha-math.png', import.meta.url)).toString('base64'); -const imgInstructions = fs.readFileSync( - new URL('../assets/captcha-math-instructions.png', import.meta.url) -).toString('base64'); - -const advancedTask = new Tasks.ImageToText({ - body: mathBody, // Base64-encoded captcha image - // Optional fields (pass only if needed by the captcha type) - phrase: false, // true if answer has multiple words - case_: true, // true if answer is case-sensitive - numeric: 0, // 0 = not specified, 1 = digits, 2 = letters, 3 = any with digits, 4 = any with letters - math: true, // true if image is a math expression to solve - minLength: 1, // Minimum answer length - maxLength: 10, // Maximum answer length - comment: 'Enter the result of the equation', // Text hint for the worker - imgInstructions: imgInstructions // Optional instruction image for the worker -}); - -captchaSolver.solve(advancedTask) - .then((result) => { - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); - -// --- With language pool --- -// The languagePool parameter selects the worker pool by language. -// Pass it as the second argument to solve(), not inside the task. -// Accepted values: "en" (English) or "ru" (Russian). -const languagePoolTask = new Tasks.ImageToText({ - body: body, - numeric: 2, - minLength: 4, - maxLength: 6 -}); - -captchaSolver.solve(languagePoolTask, 'en') // Picks English-speaking worker pool - .then((result) => { - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); diff --git a/examples/sync/recaptcha_v2.js b/examples/sync/recaptcha_v2.js deleted file mode 100644 index 1de91df..0000000 --- a/examples/sync/recaptcha_v2.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Example: Solve a reCAPTCHA v2 challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and websiteKey with values from your target page. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -// Create a solver instance with your API key. -// Optional: timeout (ms to wait for solution, default 120000) -// Optional: pollingInterval (ms between status checks, default 5000) -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// --- Proxyless example --- -// Solves reCAPTCHA v2 without a proxy. The service uses its own IP addresses. -const proxylessTask = new Tasks.RecaptchaV2Proxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with captcha - websiteKey: '6Le-xxxxxxxxxxxxxxxxxxxxxxxxxxxx', // data-sitekey attribute value - isInvisible: false // Set true for invisible reCAPTCHA -}); - -captchaSolver.solve(proxylessTask) - .then((result) => { - // Solution contains { gRecaptchaResponse: "03AGdBq..." } - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); - -// --- With proxy example --- -// Solves reCAPTCHA v2 through your own proxy. Required when the target site is -// geo-restricted or you need session consistency. -const proxyTask = new Tasks.RecaptchaV2({ - websiteURL: 'https://example.com/login', - websiteKey: '6Le-xxxxxxxxxxxxxxxxxxxxxxxxxxxx', - // --- Proxy parameters --- - proxyType: 'http', // http, socks4, or socks5 - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) -}); - -captchaSolver.solve(proxyTask) - .then((result) => { - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); diff --git a/examples/sync/recaptcha_v2_enterprise.js b/examples/sync/recaptcha_v2_enterprise.js deleted file mode 100644 index 083c82f..0000000 --- a/examples/sync/recaptcha_v2_enterprise.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Example: Solve a reCAPTCHA v2 Enterprise challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and websiteKey with values from your target page. - * If the site uses enterprisePayload, extract and pass it or the token may be rejected. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// --- Proxyless example --- -// Enterprise captchas are loaded via the reCAPTCHA Enterprise API. If the site -// passes extra parameters to grecaptcha.enterprise.render(), pass them as -// enterprisePayload or the token will be rejected. -const proxylessTask = new Tasks.RecaptchaV2EnterpriseProxyless({ - websiteURL: 'https://example.com/login', - websiteKey: '6Le-xxxxxxxxxxxxxxxxxxxxxxxxxxxx', - isInvisible: false - // Optional fields (pass only if the target site requires them): - // enterprisePayload: { s: 'value-from-page' }, - // apiDomain: 'recaptcha.net', - // userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', - // cookies: 'session=abc123; token=xyz789', -}); - -captchaSolver.solve(proxylessTask) - .then((result) => { - // Solution contains { gRecaptchaResponse: "03AGdBq..." } - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); - -// --- With proxy example --- -const proxyTask = new Tasks.RecaptchaV2Enterprise({ - websiteURL: 'https://example.com/login', - websiteKey: '6Le-xxxxxxxxxxxxxxxxxxxxxxxxxxxx', - proxyType: 'http', - proxyAddress: '1.2.3.4', - proxyPort: 8080, - proxyLogin: 'user', - proxyPassword: 'password', - isInvisible: false -}); - -captchaSolver.solve(proxyTask) - .then((result) => { - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); diff --git a/examples/sync/tencent.js b/examples/sync/tencent.js deleted file mode 100644 index c56ec98..0000000 --- a/examples/sync/tencent.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Example: Solve a Tencent captcha challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and appId with values from your target page. - * Pass captchaScript if the site uses a non-default script URL. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// --- Proxyless example --- -// The service's own proxies are used to solve the captcha. -// appId is found in the page source code. captchaScript is optional if the site uses the default. -const proxylessTask = new Tasks.TencentTaskProxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with captcha - appId: '190014885' // appId from page source code (required) - // Optional fields: - // captchaScript: 'https://turing.captcha.qcloud.com/TCaptcha.js', -}); - -captchaSolver.solve(proxylessTask) - .then((result) => { - // Solution contains { appid, ret, ticket, randstr } - // Pass all four values together into the page's captcha callback as-is. - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); - -// --- With proxy example --- -// Use when the target site is geo-restricted or you need a consistent session. -const proxyTask = new Tasks.TencentTask({ - websiteURL: 'https://example.com/login', - appId: '190014885', - // --- Proxy parameters --- - proxyType: 'http', // http, socks4, or socks5 - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) -}); - -captchaSolver.solve(proxyTask) - .then((result) => { - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); diff --git a/examples/sync/turnstile.js b/examples/sync/turnstile.js deleted file mode 100644 index 8eaea0b..0000000 --- a/examples/sync/turnstile.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Example: Solve a Cloudflare Turnstile challenge. - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and websiteKey with values from your target page. - * For Cloudflare Challenge pages, also extract and pass action, data, pagedata, - * and the current browser userAgent. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// --- Proxyless example --- -// For a Cloudflare Challenge page, pass the current browser User-Agent together -// with action, data, and pagedata. Do not change the browser User-Agent after solving. -const proxylessTask = new Tasks.TurnstileProxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with Turnstile - websiteKey: '0x4AAAAAAAxxxxxxxxxxxxxxxx', // data-sitekey attribute value - // Optional fields (pass only if the target site sets them) - // action: 'login', // Value of data-action attribute - // data: 'custom-cdata-value', // Value of data-cdata attribute - // pagedata: 'chl-page-data-value', // Value of chlPageData parameter - // userAgent: 'Mozilla/5.0 ...', // Current browser User-Agent -}); - -captchaSolver.solve(proxylessTask) - .then((result) => { - // Solution contains { token: "0.zxcv...", userAgent: "Mozilla/5.0 ..." } - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); - -// --- With proxy example --- -const proxyTask = new Tasks.Turnstile({ - websiteURL: 'https://example.com/login', - websiteKey: '0x4AAAAAAAxxxxxxxxxxxxxxxx', - // --- Proxy parameters --- - proxyType: 'http', // http, socks4, or socks5 - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) - // --- Optional fields --- - // action: 'login', - // data: 'custom-cdata-value', - // pagedata: 'chl-page-data-value', -}); - -captchaSolver.solve(proxyTask) - .then((result) => { - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); diff --git a/examples/sync/yandex_smartcaptcha.js b/examples/sync/yandex_smartcaptcha.js deleted file mode 100644 index dfc0c52..0000000 --- a/examples/sync/yandex_smartcaptcha.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Example: Solve a Yandex SmartCaptcha challenge (token-based). - * - * Prerequisites: - * Set the CAPTCHA_API_KEY environment variable. - * Replace websiteURL and websiteKey with values from your target page. - */ - -import 'dotenv/config'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const apiKey = process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY'; - -const captchaSolver = new CaptchaClient({ clientKey: apiKey }); - -// --- Proxyless example --- -// The service's own proxies are used to solve the captcha. -// websiteKey is the sitekey value from the page code or captcha iframe. -const proxylessTask = new Tasks.YandexSmartCaptchaTaskProxyless({ - websiteURL: 'https://example.com/login', // Full URL of the page with captcha - websiteKey: 'FEXfAbHQsToo97VidNVk3j4dC74nGW1DgdxK4OoR' // sitekey from page code or iframe - // Optional fields: - // userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...', - // cookies: 'session=abc123; token=xyz789', -}); - -captchaSolver.solve(proxylessTask) - .then((result) => { - // Solution contains { token: "dV9xNjYyNTU3NjkxO4k9OTQuNVMuMjkuMjM9..." } - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); - -// --- With proxy example --- -// Note: this is the only captcha type where an https proxy is accepted. -const proxyTask = new Tasks.YandexSmartCaptchaTask({ - websiteURL: 'https://example.com/login', - websiteKey: 'FEXfAbHQsToo97VidNVk3j4dC74nGW1DgdxK4OoR', - // --- Proxy parameters --- - proxyType: 'http', // http, https, socks4, or socks5 (https is accepted only for this type) - proxyAddress: '1.2.3.4', // Proxy IP address - proxyPort: 8080, // Proxy port - proxyLogin: 'user', // Login for proxy authorization (optional) - proxyPassword: 'password' // Password for proxy authorization (optional) -}); - -captchaSolver.solve(proxyTask) - .then((result) => { - console.log('result:', result); - }) - .catch((error) => { - console.error(error); - }); diff --git a/examples/sync/yandex_smartcaptcha_image.js b/examples/sync/yandex_smartcaptcha_image.js deleted file mode 100644 index e56f4c3..0000000 --- a/examples/sync/yandex_smartcaptcha_image.js +++ /dev/null @@ -1,28 +0,0 @@ -/** Solve the image-based Yandex SmartCaptcha challenge using promises. */ -import 'dotenv/config'; -import fs from 'node:fs'; -import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; - -const captchaSolver = new CaptchaClient({ - clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' -}); - -const image = fs.readFileSync( - new URL('../assets/yandex-smartcaptcha-image.png', import.meta.url) -).toString('base64'); -const imgInstructions = fs.readFileSync( - new URL('../assets/yandex-smartcaptcha-instructions.png', import.meta.url) -).toString('base64'); - -const task = new Tasks.YandexSmartCaptchaImageTask({ - image, - imgInstructions -}); - -captchaSolver.solve(task) - .then((result) => { - console.log('Coordinates in click order:', result.coordinates); - }) - .catch((error) => { - console.error(error); - }); diff --git a/examples/tencent.js b/examples/tencent.js new file mode 100644 index 0000000..e827ff4 --- /dev/null +++ b/examples/tencent.js @@ -0,0 +1,12 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.TencentTaskProxyless({ + websiteURL: 'https://example.com/register', + appId: 'YOUR_APP_ID' +}); + +client.solve(task) + .then((solution) => console.log(solution)) + .catch(console.error); diff --git a/examples/tencent_proxy.js b/examples/tencent_proxy.js new file mode 100644 index 0000000..b92f239 --- /dev/null +++ b/examples/tencent_proxy.js @@ -0,0 +1,15 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.TencentTask({ + websiteURL: 'https://example.com/register', + appId: 'YOUR_APP_ID', + proxyType: 'http', + proxyAddress: '1.2.3.4', + proxyPort: 8080 +}); + +client.solve(task) + .then((solution) => console.log(solution)) + .catch(console.error); diff --git a/examples/turnstile.js b/examples/turnstile.js new file mode 100644 index 0000000..ad66730 --- /dev/null +++ b/examples/turnstile.js @@ -0,0 +1,12 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.TurnstileProxyless({ + websiteURL: 'https://example.com/login', + websiteKey: 'YOUR_WEBSITE_KEY' +}); + +client.solve(task) + .then((solution) => console.log(solution.token)) + .catch(console.error); diff --git a/examples/turnstile_proxy.js b/examples/turnstile_proxy.js new file mode 100644 index 0000000..31fb852 --- /dev/null +++ b/examples/turnstile_proxy.js @@ -0,0 +1,15 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.Turnstile({ + websiteURL: 'https://example.com/login', + websiteKey: 'YOUR_WEBSITE_KEY', + proxyType: 'http', + proxyAddress: '1.2.3.4', + proxyPort: 8080 +}); + +client.solve(task) + .then((solution) => console.log(solution.token)) + .catch(console.error); diff --git a/examples/yandex_smartcaptcha.js b/examples/yandex_smartcaptcha.js new file mode 100644 index 0000000..3397175 --- /dev/null +++ b/examples/yandex_smartcaptcha.js @@ -0,0 +1,12 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.YandexSmartCaptchaTaskProxyless({ + websiteURL: 'https://example.com/login', + websiteKey: 'YOUR_WEBSITE_KEY' +}); + +client.solve(task) + .then((solution) => console.log(solution.token)) + .catch(console.error); diff --git a/examples/yandex_smartcaptcha_image.js b/examples/yandex_smartcaptcha_image.js new file mode 100644 index 0000000..714063a --- /dev/null +++ b/examples/yandex_smartcaptcha_image.js @@ -0,0 +1,13 @@ +import 'dotenv/config'; +import fs from 'fs'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.YandexSmartCaptchaImageTask({ + image: fs.readFileSync(new URL('./assets/yandex-smartcaptcha-image.png', import.meta.url)).toString('base64'), + imgInstructions: fs.readFileSync(new URL('./assets/yandex-smartcaptcha-instructions.png', import.meta.url)).toString('base64') +}); + +client.solve(task) + .then((solution) => console.log(solution.coordinates)) + .catch(console.error); diff --git a/examples/yandex_smartcaptcha_proxy.js b/examples/yandex_smartcaptcha_proxy.js new file mode 100644 index 0000000..c3250d6 --- /dev/null +++ b/examples/yandex_smartcaptcha_proxy.js @@ -0,0 +1,15 @@ +import 'dotenv/config'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +const client = new CaptchaClient({ clientKey: process.env.CAPTCHA_API_KEY || 'YOUR_API_KEY' }); +const task = new Tasks.YandexSmartCaptchaTask({ + websiteURL: 'https://example.com/login', + websiteKey: 'YOUR_WEBSITE_KEY', + proxyType: 'http', + proxyAddress: '1.2.3.4', + proxyPort: 8080 +}); + +client.solve(task) + .then((solution) => console.log(solution.token)) + .catch(console.error); diff --git a/package-lock.json b/package-lock.json index c4a9784..6f3057f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@captcha-solver-api/javascript-sdk", - "version": "1.0.1", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@captcha-solver-api/javascript-sdk", - "version": "1.0.1", + "version": "1.1.0", "license": "MIT", "devDependencies": { "dotenv": "^16.4.0", @@ -2065,9 +2065,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3f06ed3..aca194e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@captcha-solver-api/javascript-sdk", - "version": "1.0.1", - "description": "Official JavaScript SDK for the Captcha Solver API. Solve reCAPTCHA v2, Cloudflare Turnstile, GeeTest, Tencent, and image captchas.", + "version": "1.1.0", + "description": "Official JavaScript SDK for the Captcha Solver API. Solve reCAPTCHA v2, Cloudflare Turnstile, GeeTest, Yandex SmartCaptcha, Tencent, and image captchas.", "main": "./dist/index.cjs", "type": "module", "exports": { @@ -38,6 +38,7 @@ "recaptcha-v2", "cloudflare-turnstile", "geetest", + "yandex-smartcaptcha", "tencent-captcha", "image-to-text", "captcha-recognition", diff --git a/src/client.ts b/src/client.ts index fda67c0..537af5e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -58,7 +58,9 @@ export type SolutionOf = T extends BaseTask ? S : Record console.log(gRecaptchaResponse)) + * .catch(console.error); * ``` */ export class CaptchaClient { @@ -75,7 +77,7 @@ export class CaptchaClient { * @param options - Client options; only `clientKey` is required. * @throws {ValidationError} If `clientKey` is empty. */ - constructor({ clientKey, baseUrl = 'https://api.captcha-solver.com', timeout = 120000, pollingInterval = 5000 }: CaptchaClientOptions) { + constructor({ clientKey, baseUrl = 'https://api.captcha-solver.com', timeout = 120000, pollingInterval = 5000 }: CaptchaClientOptions = {} as CaptchaClientOptions) { if (!clientKey) throw new ValidationError('clientKey is required'); this.clientKey = clientKey; this.baseUrl = baseUrl; this.timeout = timeout; this.pollingInterval = pollingInterval; } @@ -120,6 +122,12 @@ export class CaptchaClient { * @throws {ApiError} If the API rejected the task. * @throws {TimeoutError} If the request exceeded {@link timeout}. * @throws {NetworkError} On a transport failure. + * @example + * ```ts + * client.createTask(task) + * .then((taskId) => console.log(taskId)) + * .catch(console.error); + * ``` */ async createTask(task: BaseTask, languagePool: string | null = null): Promise { const payload: Record = { clientKey: this.clientKey, task: task.toDict() }; if (languagePool !== null) payload.languagePool = languagePool; @@ -137,6 +145,12 @@ export class CaptchaClient { * @throws {ApiError} If the task failed on the API side. * @throws {TimeoutError} If the request exceeded {@link timeout}. * @throws {NetworkError} On a transport failure. + * @example + * ```ts + * client.getTaskResult(taskId) + * .then(console.log) + * .catch(console.error); + * ``` */ getTaskResult(taskId: number): Promise { return this._request('/getTaskResult', { clientKey: this.clientKey, taskId }); } @@ -147,6 +161,12 @@ export class CaptchaClient { * @throws {ApiError} If the API key is invalid. * @throws {TimeoutError} If the request exceeded {@link timeout}. * @throws {NetworkError} On a transport failure. + * @example + * ```ts + * client.getBalance() + * .then((balance) => console.log(balance)) + * .catch(console.error); + * ``` */ async getBalance(): Promise { return Number((await this._request('/getBalance', { clientKey: this.clientKey })).balance); } @@ -164,6 +184,10 @@ export class CaptchaClient { * @throws {TimeoutError} If no solution arrived within {@link timeout}. * @throws {ApiError} If the API rejected the task or the task failed while solving. * @throws {NetworkError} On a transport failure. + * @example + * ```ts + * client.solve(task).then(console.log).catch(console.error); + * ``` */ async solve>(task: T, languagePool: string | null = null): Promise> { const taskId = await this.createTask(task, languagePool); const startedAt = Date.now(); diff --git a/src/tasks.ts b/src/tasks.ts index c27d615..0740c7b 100644 --- a/src/tasks.ts +++ b/src/tasks.ts @@ -1,5 +1,52 @@ import { ValidationError } from './exceptions.js'; +const REQUIRED_TASK_PARAMS: Record = { + RecaptchaV2TaskProxyless: ['websiteURL', 'websiteKey'], + RecaptchaV2Task: ['websiteURL', 'websiteKey', 'proxyType', 'proxyAddress', 'proxyPort'], + RecaptchaV2EnterpriseTaskProxyless: ['websiteURL', 'websiteKey'], + RecaptchaV2EnterpriseTask: ['websiteURL', 'websiteKey', 'proxyType', 'proxyAddress', 'proxyPort'], + RecaptchaV3TaskProxyless: ['websiteURL', 'websiteKey', 'minScore'], + TurnstileTaskProxyless: ['websiteURL', 'websiteKey'], + TurnstileTask: ['websiteURL', 'websiteKey', 'proxyType', 'proxyAddress', 'proxyPort'], + GeeTestTaskProxyless: ['websiteURL'], + GeeTestTask: ['websiteURL', 'proxyType', 'proxyAddress', 'proxyPort'], + ImageToTextTask: ['body'], + YandexSmartCaptchaTaskProxyless: ['websiteURL', 'websiteKey'], + YandexSmartCaptchaTask: ['websiteURL', 'websiteKey', 'proxyType', 'proxyAddress', 'proxyPort'], + CoordinatesTask: ['body'], + SmartCaptchaTask: ['image', 'imgInstructions'], + TencentTaskProxyless: ['websiteURL', 'appId'], + TencentTask: ['websiteURL', 'appId', 'proxyType', 'proxyAddress', 'proxyPort'] +}; + +function isMissing(value: unknown): boolean { + return value === null || value === undefined || (typeof value === 'string' && value.trim() === ''); +} + +function validateTaskParams(params: Record): void { + const type = params.type; + if (typeof type !== 'string') return; + + const required = [...(REQUIRED_TASK_PARAMS[type] || [])]; + if (type === 'GeeTestTask' || type === 'GeeTestTaskProxyless') { + if (params.version === 4) { + required.push('initParameters'); + } else { + required.push('gt', 'challenge'); + } + } + + const missing = required.filter((name) => isMissing(params[name])); + if (!missing.length && params.version === 4) { + const initParameters = params.initParameters as Record; + if (isMissing(initParameters.captcha_id)) missing.push('initParameters.captcha_id'); + } + + if (missing.length) { + throw new ValidationError(`${type} requires: ${missing.join(', ')}`); + } +} + /* ------------------------------------------------------------------ */ /* Shared parameter fragments */ /* ------------------------------------------------------------------ */ @@ -40,8 +87,9 @@ export interface TurnstileSolution { /** Token to submit as `cf-turnstile-response`. */ token: string; /** - * User-Agent the worker actually solved with. The token is tied to it, - * so submit the token with this exact User-Agent. + * User-Agent reported by the solving service, when present. For Cloudflare + * Challenge pages, keep using the browser User-Agent sent in the task; do + * not replace it with this returned value. */ userAgent: string; [key: string]: unknown; @@ -134,6 +182,7 @@ export class BaseTask = Record) { + validateTaskParams(params); this._params = params; } @@ -180,7 +229,7 @@ export class GenericTask extends BaseTask { * @param params - Task payload; `type` is required. * @throws {ValidationError} If `type` is missing. */ - constructor({ type, ...params }: GenericTaskParams) { + constructor({ type, ...params }: GenericTaskParams = {} as GenericTaskParams) { if (!type) throw new ValidationError('task type is required'); super({ type, ...params }); } @@ -214,11 +263,12 @@ export interface RecaptchaV2Params extends RecaptchaV2ProxylessParams, ProxyPara /** * reCAPTCHA v2 without a proxy (`RecaptchaV2TaskProxyless`). * Solution: {@link RecaptchaSolution}. + * @example `new Tasks.RecaptchaV2Proxyless({ websiteURL, websiteKey })` * * @see https://captcha-solver.com/en/docs/captcha-types#recaptcha-v2 */ export class RecaptchaV2Proxyless extends BaseTask { - constructor({ websiteURL, websiteKey, isInvisible = null, recaptchaDataSValue = null, apiDomain = null, cookies = null, userAgent = null }: RecaptchaV2ProxylessParams) { + constructor({ websiteURL, websiteKey, isInvisible = null, recaptchaDataSValue = null, apiDomain = null, cookies = null, userAgent = null }: RecaptchaV2ProxylessParams = {} as RecaptchaV2ProxylessParams) { super({ type: 'RecaptchaV2TaskProxyless', websiteURL, websiteKey, isInvisible, recaptchaDataSValue, apiDomain, cookies, userAgent }); } } @@ -226,11 +276,12 @@ export class RecaptchaV2Proxyless extends BaseTask { /** * reCAPTCHA v2 solved through your own proxy (`RecaptchaV2Task`). * Solution: {@link RecaptchaSolution}. + * @example `new Tasks.RecaptchaV2({ websiteURL, websiteKey, proxyType, proxyAddress, proxyPort })` * * @see https://captcha-solver.com/en/docs/captcha-types#recaptcha-v2 */ export class RecaptchaV2 extends BaseTask { - constructor({ websiteURL, websiteKey, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null, isInvisible = null, recaptchaDataSValue = null, apiDomain = null, cookies = null, userAgent = null }: RecaptchaV2Params) { + constructor({ websiteURL, websiteKey, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null, isInvisible = null, recaptchaDataSValue = null, apiDomain = null, cookies = null, userAgent = null }: RecaptchaV2Params = {} as RecaptchaV2Params) { super({ type: 'RecaptchaV2Task', websiteURL, websiteKey, proxyType, proxyAddress, proxyPort, proxyLogin, proxyPassword, isInvisible, recaptchaDataSValue, apiDomain, cookies, userAgent }); } } @@ -266,11 +317,12 @@ export interface RecaptchaV2EnterpriseParams extends RecaptchaV2EnterpriseProxyl /** * reCAPTCHA v2 Enterprise without a proxy (`RecaptchaV2EnterpriseTaskProxyless`). * Solution: {@link RecaptchaSolution}. + * @example `new Tasks.RecaptchaV2EnterpriseProxyless({ websiteURL, websiteKey })` * * @see https://captcha-solver.com/en/docs/captcha-types#recaptcha-v2-enterprise */ export class RecaptchaV2EnterpriseProxyless extends BaseTask { - constructor({ websiteURL, websiteKey, enterprisePayload = null, isInvisible = null, apiDomain = null, cookies = null, userAgent = null }: RecaptchaV2EnterpriseProxylessParams) { + constructor({ websiteURL, websiteKey, enterprisePayload = null, isInvisible = null, apiDomain = null, cookies = null, userAgent = null }: RecaptchaV2EnterpriseProxylessParams = {} as RecaptchaV2EnterpriseProxylessParams) { super({ type: 'RecaptchaV2EnterpriseTaskProxyless', websiteURL, websiteKey, enterprisePayload, isInvisible, apiDomain, cookies, userAgent }); } } @@ -278,11 +330,12 @@ export class RecaptchaV2EnterpriseProxyless extends BaseTask /** * reCAPTCHA v2 Enterprise solved through your own proxy (`RecaptchaV2EnterpriseTask`). * Solution: {@link RecaptchaSolution}. + * @example `new Tasks.RecaptchaV2Enterprise({ websiteURL, websiteKey, proxyType, proxyAddress, proxyPort })` * * @see https://captcha-solver.com/en/docs/captcha-types#recaptcha-v2-enterprise */ export class RecaptchaV2Enterprise extends BaseTask { - constructor({ websiteURL, websiteKey, enterprisePayload = null, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null, isInvisible = null, apiDomain = null, cookies = null, userAgent = null }: RecaptchaV2EnterpriseParams) { + constructor({ websiteURL, websiteKey, enterprisePayload = null, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null, isInvisible = null, apiDomain = null, cookies = null, userAgent = null }: RecaptchaV2EnterpriseParams = {} as RecaptchaV2EnterpriseParams) { super({ type: 'RecaptchaV2EnterpriseTask', websiteURL, websiteKey, enterprisePayload, proxyType, proxyAddress, proxyPort, proxyLogin, proxyPassword, isInvisible, apiDomain, cookies, userAgent }); } } @@ -311,11 +364,12 @@ export interface RecaptchaV3ProxylessParams { * Score-based reCAPTCHA v3 (`RecaptchaV3TaskProxyless`). * The API supports this type only without a customer proxy. * Solution: {@link RecaptchaSolution}. + * @example `new Tasks.RecaptchaV3Proxyless({ websiteURL, websiteKey, minScore: 0.3 })` * * @see https://captcha-solver.com/en/docs/captcha-types#recaptcha-v3 */ export class RecaptchaV3Proxyless extends BaseTask { - constructor({ websiteURL, websiteKey, minScore, pageAction = null, isEnterprise = null, apiDomain = null }: RecaptchaV3ProxylessParams) { + constructor({ websiteURL, websiteKey, minScore, pageAction = null, isEnterprise = null, apiDomain = null }: RecaptchaV3ProxylessParams = {} as RecaptchaV3ProxylessParams) { super({ type: 'RecaptchaV3TaskProxyless', websiteURL, websiteKey, minScore, pageAction, isEnterprise, apiDomain }); } } @@ -353,11 +407,12 @@ export interface TurnstileParams extends TurnstileProxylessParams, ProxyParams { /** * Cloudflare Turnstile without a proxy (`TurnstileTaskProxyless`). * Solution: {@link TurnstileSolution}. + * @example `new Tasks.TurnstileProxyless({ websiteURL, websiteKey })` * * @see https://captcha-solver.com/en/docs/captcha-types#cloudflare-turnstile */ export class TurnstileProxyless extends BaseTask { - constructor({ websiteURL, websiteKey, action = null, data = null, pagedata = null, userAgent = null }: TurnstileProxylessParams) { + constructor({ websiteURL, websiteKey, action = null, data = null, pagedata = null, userAgent = null }: TurnstileProxylessParams = {} as TurnstileProxylessParams) { super({ type: 'TurnstileTaskProxyless', websiteURL, websiteKey, action, data, pagedata, userAgent }); } } @@ -365,11 +420,12 @@ export class TurnstileProxyless extends BaseTask { /** * Cloudflare Turnstile solved through your own proxy (`TurnstileTask`). * Solution: {@link TurnstileSolution}. + * @example `new Tasks.Turnstile({ websiteURL, websiteKey, proxyType, proxyAddress, proxyPort })` * * @see https://captcha-solver.com/en/docs/captcha-types#cloudflare-turnstile */ export class Turnstile extends BaseTask { - constructor({ websiteURL, websiteKey, action = null, data = null, pagedata = null, userAgent = null, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null }: TurnstileParams) { + constructor({ websiteURL, websiteKey, action = null, data = null, pagedata = null, userAgent = null, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null }: TurnstileParams = {} as TurnstileParams) { super({ type: 'TurnstileTask', websiteURL, websiteKey, action, data, pagedata, userAgent, proxyType, proxyAddress, proxyPort, proxyLogin, proxyPassword }); } } @@ -414,12 +470,13 @@ export interface GeeTestParams extends GeeTestProxylessParams, ProxyParams {} * GeeTest v3 / v4 without a proxy (`GeeTestTaskProxyless`). * Pass `gt` + `challenge` for v3, or `version: 4` + `initParameters.captcha_id` for v4. * Solution: {@link GeeTestSolution}. + * @example `new Tasks.GeeTestProxyless({ websiteURL, gt, challenge })` * * @see https://captcha-solver.com/en/docs/captcha-types#geetest-v3 * @see https://captcha-solver.com/en/docs/captcha-types#geetest-v4 */ export class GeeTestProxyless extends BaseTask { - constructor({ websiteURL, gt = null, challenge = null, version = null, initParameters = null, geetestApiServerSubdomain = null, userAgent = null, risk_type = null }: GeeTestProxylessParams) { + constructor({ websiteURL, gt = null, challenge = null, version = null, initParameters = null, geetestApiServerSubdomain = null, userAgent = null, risk_type = null }: GeeTestProxylessParams = {} as GeeTestProxylessParams) { super({ type: 'GeeTestTaskProxyless', websiteURL, gt, challenge, version, initParameters, geetestApiServerSubdomain, userAgent, risk_type }); } } @@ -427,12 +484,13 @@ export class GeeTestProxyless extends BaseTask { /** * GeeTest v3 / v4 solved through your own proxy (`GeeTestTask`). * Solution: {@link GeeTestSolution}. + * @example `new Tasks.GeeTest({ websiteURL, gt, challenge, proxyType, proxyAddress, proxyPort })` * * @see https://captcha-solver.com/en/docs/captcha-types#geetest-v3 * @see https://captcha-solver.com/en/docs/captcha-types#geetest-v4 */ export class GeeTest extends BaseTask { - constructor({ websiteURL, gt = null, challenge = null, version = null, initParameters = null, geetestApiServerSubdomain = null, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null, userAgent = null, risk_type = null }: GeeTestParams) { + constructor({ websiteURL, gt = null, challenge = null, version = null, initParameters = null, geetestApiServerSubdomain = null, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null, userAgent = null, risk_type = null }: GeeTestParams = {} as GeeTestParams) { super({ type: 'GeeTestTask', websiteURL, gt, challenge, version, initParameters, geetestApiServerSubdomain, proxyType, proxyAddress, proxyPort, proxyLogin, proxyPassword, userAgent, risk_type }); } } @@ -477,11 +535,12 @@ export interface ImageToTextParams { * Text / number / math recognition in an image captcha (`ImageToTextTask`). * The image is submitted directly, so there is no proxy variant. * Solution: {@link ImageToTextSolution}. + * @example `new Tasks.ImageToText({ body: imageBase64 })` * * @see https://captcha-solver.com/en/docs/captcha-types#image-to-text */ export class ImageToText extends BaseTask { - constructor({ body, phrase = null, case_ = null, numeric = null, math = null, minLength = null, maxLength = null, comment = null, imgInstructions = null }: ImageToTextParams) { + constructor({ body, phrase = null, case_ = null, numeric = null, math = null, minLength = null, maxLength = null, comment = null, imgInstructions = null }: ImageToTextParams = {} as ImageToTextParams) { super({ type: 'ImageToTextTask', body, phrase, case: case_, numeric, math, minLength, maxLength, comment, imgInstructions }); } } @@ -510,13 +569,14 @@ export interface YandexSmartCaptchaTaskParams extends YandexSmartCaptchaTaskProx /** * Token-based Yandex SmartCaptcha without a proxy (`YandexSmartCaptchaTaskProxyless`). - * For the image challenge use {@link CoordinatesTask} instead. + * For the image challenge use {@link YandexSmartCaptchaImageTask} instead. * Solution: {@link YandexSmartCaptchaSolution}. + * @example `new Tasks.YandexSmartCaptchaTaskProxyless({ websiteURL, websiteKey })` * * @see https://captcha-solver.com/en/docs/captcha-types#yandex-smartcaptcha */ export class YandexSmartCaptchaTaskProxyless extends BaseTask { - constructor({ websiteURL, websiteKey, userAgent = null, cookies = null }: YandexSmartCaptchaTaskProxylessParams) { + constructor({ websiteURL, websiteKey, userAgent = null, cookies = null }: YandexSmartCaptchaTaskProxylessParams = {} as YandexSmartCaptchaTaskProxylessParams) { super({ type: 'YandexSmartCaptchaTaskProxyless', websiteURL, websiteKey, userAgent, cookies }); } } @@ -524,11 +584,12 @@ export class YandexSmartCaptchaTaskProxyless extends BaseTask { - constructor({ websiteURL, websiteKey, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null, userAgent = null, cookies = null }: YandexSmartCaptchaTaskParams) { + constructor({ websiteURL, websiteKey, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null, userAgent = null, cookies = null }: YandexSmartCaptchaTaskParams = {} as YandexSmartCaptchaTaskParams) { super({ type: 'YandexSmartCaptchaTask', websiteURL, websiteKey, proxyType, proxyAddress, proxyPort, proxyLogin, proxyPassword, userAgent, cookies }); } } @@ -554,11 +615,12 @@ export interface CoordinatesTaskParams { /** * Click captcha: the worker marks points in an image (`CoordinatesTask`). * Solution: {@link CoordinatesSolution}. + * @example `new Tasks.CoordinatesTask({ body: imageBase64, comment: 'Click the buses' })` * * @see https://captcha-solver.com/en/docs/captcha-types#coordinates */ export class CoordinatesTask extends BaseTask { - constructor({ body, comment = null, imgInstructions = null, minClicks = null, maxClicks = null }: CoordinatesTaskParams) { + constructor({ body, comment = null, imgInstructions = null, minClicks = null, maxClicks = null }: CoordinatesTaskParams = {} as CoordinatesTaskParams) { super({ type: 'CoordinatesTask', body, comment, imgInstructions, minClicks, maxClicks }); } } @@ -587,7 +649,7 @@ export interface YandexSmartCaptchaImageTaskParams { * @see https://captcha-solver.com/en/docs/captcha-types#yandex-smartcaptcha */ export class YandexSmartCaptchaImageTask extends BaseTask { - constructor({ image, imgInstructions, comment = null }: YandexSmartCaptchaImageTaskParams) { + constructor({ image, imgInstructions, comment = null }: YandexSmartCaptchaImageTaskParams = {} as YandexSmartCaptchaImageTaskParams) { super({ type: 'SmartCaptchaTask', image, imgInstructions, comment }); } } @@ -612,11 +674,12 @@ export interface TencentTaskParams extends TencentTaskProxylessParams, ProxyPara /** * Tencent Captcha without a proxy (`TencentTaskProxyless`). * Solution: {@link TencentSolution}. + * @example `new Tasks.TencentTaskProxyless({ websiteURL, appId })` * * @see https://captcha-solver.com/en/docs/captcha-types#tencent */ export class TencentTaskProxyless extends BaseTask { - constructor({ websiteURL, appId, captchaScript = null }: TencentTaskProxylessParams) { + constructor({ websiteURL, appId, captchaScript = null }: TencentTaskProxylessParams = {} as TencentTaskProxylessParams) { super({ type: 'TencentTaskProxyless', websiteURL, appId, captchaScript }); } } @@ -624,11 +687,12 @@ export class TencentTaskProxyless extends BaseTask { /** * Tencent Captcha solved through your own proxy (`TencentTask`). * Solution: {@link TencentSolution}. + * @example `new Tasks.TencentTask({ websiteURL, appId, proxyType, proxyAddress, proxyPort })` * * @see https://captcha-solver.com/en/docs/captcha-types#tencent */ export class TencentTask extends BaseTask { - constructor({ websiteURL, appId, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null, captchaScript = null }: TencentTaskParams) { + constructor({ websiteURL, appId, proxyType, proxyAddress, proxyPort, proxyLogin = null, proxyPassword = null, captchaScript = null }: TencentTaskParams = {} as TencentTaskParams) { super({ type: 'TencentTask', websiteURL, appId, proxyType, proxyAddress, proxyPort, proxyLogin, proxyPassword, captchaScript }); } } diff --git a/src/version.ts b/src/version.ts index 809a5f0..190fd5e 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ /** SDK version, sent to the API in the `X-SDK` header. Must match `version` in package.json. */ -export const __version__ = '1.0.1'; +export const __version__ = '1.1.0'; diff --git a/tests/README.md b/tests/README.md index dee11c3..de11a5d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -53,6 +53,7 @@ tests/ ├── turnstile.test.js ├── geetest_v4.test.js ├── yandex_smartcaptcha.test.js + ├── yandex_smartcaptcha_image.test.js └── tencent.test.js ``` @@ -182,6 +183,7 @@ The only set that talks to the real API at `https://api.captcha-solver.com`. One | `turnstile.test.js` | `token` | `TURNSTILE_URL`, `TURNSTILE_SITE_KEY` | 130 s | **yes** | | `geetest_v4.test.js` | `captcha_output`, `lot_number`, `pass_token` | `GEETEST_V4_URL`, `GEETEST_V4_CAPTCHA_ID` | 310 s | **yes** | | `yandex_smartcaptcha.test.js` | `token` | `YANDEX_SMARTCAPTCHA_URL`, `YANDEX_SMARTCAPTCHA_SITE_KEY` | 130 s | **yes** | +| `yandex_smartcaptcha_image.test.js` | `coordinates` in click order | — (images ship with the repo) | 130 s | **yes** | | `tencent.test.js` | `ticket`, `randstr` | `TENCENT_URL`, `TENCENT_APP_ID` | 130 s | **yes** | The split follows **cost, not captcha type**. `balance.test.js` costs nothing and depends on nothing, which makes it a fine smoke test for "the key is alive, the network is up": @@ -200,9 +202,9 @@ The identifier differs per vendor: a sitekey for reCAPTCHA, Turnstile and Yandex The practical consequence: **a fresh clone has no working integration check other than the balance one**. That is the price of the decision, not an oversight. Put your own targets in `.env` (it is gitignored); the template with the variable names is [.env.example](../.env.example). -The exception is the two image-based tests, `image_to_text.test.js` and `coordinates.test.js`. They need neither a page nor a widget identifier, only an image, and the images ship with the repository — [text-captcha.png](../examples/assets/text-captcha.png) and [coordinates-captcha.png](../examples/assets/coordinates-captcha.png). Both work out of the box; a key is all it takes. +The exception is the three image-based tests, `image_to_text.test.js`, `coordinates.test.js` and `yandex_smartcaptcha_image.test.js`. They need neither a page nor a widget identifier, only an image, and the images ship with the repository — [text-captcha.png](../examples/assets/text-captcha.png) and [coordinates-captcha.png](../examples/assets/coordinates-captcha.png), [yandex-smartcaptcha-image.png](../examples/assets/yandex-smartcaptcha-image.png) with [yandex-smartcaptcha-instructions.png](../examples/assets/yandex-smartcaptcha-instructions.png). All of them work out of the box; a key is all it takes. -The tests deliberately have no fixtures directory of their own: the very same images are what the `image_to_text.js` and `coordinates.js` examples feed to the API, and one shared copy cannot drift from a second one. The path in the test is resolved through `new URL(..., import.meta.url)`, i.e. relative to the test file itself rather than the working directory. +The tests deliberately have no fixtures directory of their own: the very same images are what the `image_to_text.js`, `coordinates.js` and `yandex_smartcaptcha_image.js` examples feed to the API, and one shared copy cannot drift from a second one. The path in the test is resolved through `new URL(..., import.meta.url)`, i.e. relative to the test file itself rather than the working directory. In `coordinates.test.js` the image bounds are not hard-coded: width and height are read from the PNG's own IHDR chunk, so the "point lies inside the image" assertion stays correct even if the sample is swapped out. @@ -210,7 +212,7 @@ The image used to be passed through an `IMAGE_TO_TEXT_BASE64` variable in `.env. ### What is not covered here -- **GeeTest v3.** It needs a fresh session-bound `challenge` scraped from the target page immediately before the task is created. That cannot be driven from static configuration; it would take a scraper, as in [examples/async/geetest_v3.js](../examples/async/geetest_v3.js). +- **GeeTest v3.** It needs a fresh session-bound `challenge` scraped from the target page immediately before the task is created. That cannot be driven from static configuration; it would take a scraper, as in [examples/geetest_v3.js](../examples/geetest_v3.js). - **The proxy variants and reCAPTCHA v2 Enterprise.** They need a working proxy and a site with an Enterprise widget respectively. - **Cloudflare Challenge pages** in `turnstile.test.js` — only the ordinary widget is covered. Challenge pages need fresh `action`, `data` and `pagedata` pulled off the page. @@ -417,7 +419,7 @@ The configuration is [.github/workflows/tests.yml](../.github/workflows/tests.ym ### What the nightly run actually checks -The `integration` job passes only `CAPTCHA_API_KEY` to the tests. It has no target variables, so among the paid suites everything skips except the two image-based ones — `image_to_text.test.js` and `coordinates.test.js`: they need no targets, and with a live key they **really do hit the API and spend balance**. `recaptcha_v2.test.js` used to work off a hard-coded Google demo page; after targets were dropped from the repository (see [above](#why-the-repository-carries-no-targets)) that is no longer the case. +The `integration` job passes only `CAPTCHA_API_KEY` to the tests. It has no target variables, so among the paid suites everything skips except the three image-based ones — `image_to_text.test.js`, `coordinates.test.js` and `yandex_smartcaptcha_image.test.js`: they need no targets, and with a live key they **really do hit the API and spend balance**. `recaptcha_v2.test.js` used to work off a hard-coded Google demo page; after targets were dropped from the repository (see [above](#why-the-repository-carries-no-targets)) that is no longer the case. So **every night costs two tasks**, one per image. That is the price of the nightly run checking `solve()` at all, rather than the balance alone, which is all it used to check. If that spend is unwanted, the simplest fix is to narrow the step to specific files: `npm test -- tests/integration/balance.test.js`. diff --git a/tests/integration/coordinates.test.js b/tests/integration/coordinates.test.js index 21ec678..828e62b 100644 --- a/tests/integration/coordinates.test.js +++ b/tests/integration/coordinates.test.js @@ -20,21 +20,13 @@ import { Tasks } from '@captcha-solver-api/javascript-sdk'; import { describeIntegration, createClient } from './helpers.js'; const imagePath = new URL('../../examples/assets/coordinates-captcha.png', import.meta.url); -const yandexImagePath = new URL( - '../../examples/assets/yandex-smartcaptcha-image.png', - import.meta.url -); -const yandexInstructionsPath = new URL( - '../../examples/assets/yandex-smartcaptcha-instructions.png', - import.meta.url -); // The sample is a 4x4 grid captcha whose own header reads "Select all squares // with street signs". The comment repeats that instruction for the worker, // because nothing guarantees they read the text baked into the image. const comment = 'click on all squares with street signs'; -describeIntegration('Image coordinate tasks against the real API', () => { +describeIntegration('Coordinates against the real API', () => { test('solve returns the clicked points', async () => { const image = fs.readFileSync(imagePath); @@ -66,28 +58,4 @@ describeIntegration('Image coordinate tasks against the real API', () => { } }, 130000); - test('Yandex SmartCaptcha image returns ordered click coordinates', async () => { - const image = fs.readFileSync(yandexImagePath); - const instructions = fs.readFileSync(yandexInstructionsPath); - const task = new Tasks.YandexSmartCaptchaImageTask({ - image: image.toString('base64'), - imgInstructions: instructions.toString('base64') - }); - - const solution = await createClient().solve(task); - - expect(solution.coordinates).toHaveLength(5); - - const width = image.readUInt32BE(16); - const height = image.readUInt32BE(20); - for (const point of solution.coordinates) { - expect(typeof point.x).toBe('number'); - expect(typeof point.y).toBe('number'); - expect(point.x).toBeGreaterThanOrEqual(0); - expect(point.y).toBeGreaterThanOrEqual(0); - expect(point.x).toBeLessThanOrEqual(width); - expect(point.y).toBeLessThanOrEqual(height); - } - }, 130000); - }); diff --git a/tests/integration/geetest_v4.test.js b/tests/integration/geetest_v4.test.js index 54e9164..95e6984 100644 --- a/tests/integration/geetest_v4.test.js +++ b/tests/integration/geetest_v4.test.js @@ -9,7 +9,7 @@ * v4 is covered but v3 is not, and that is a deliberate gap: v3 needs a fresh, * session-specific `challenge` fetched from the target immediately before the * task is created. A hardcoded one is stale on arrival, so a v3 test would - * have to scrape the target first -- see examples/async/geetest_v3.js for what + * have to scrape the target first -- see examples/geetest_v3.js for what * that looks like. v4 identifies the widget by captcha_id, which is stable. */ diff --git a/tests/integration/yandex_smartcaptcha_image.test.js b/tests/integration/yandex_smartcaptcha_image.test.js new file mode 100644 index 0000000..88b2c42 --- /dev/null +++ b/tests/integration/yandex_smartcaptcha_image.test.js @@ -0,0 +1,54 @@ +/** + * Integration: solve an image-based Yandex SmartCaptcha end to end and get + * back the click coordinates in order. + * + * SPENDS REAL BALANCE on every run. + * + * Needs no target page: the main image and the instruction strip ship with the + * repository in examples/assets/, the same files the + * yandex_smartcaptcha_image.js example uses. Paths are resolved against this + * file, not the working directory, so the suite runs from anywhere. + */ + +import fs from 'node:fs'; + +import { Tasks } from '@captcha-solver-api/javascript-sdk'; +import { describeIntegration, createClient } from './helpers.js'; + +const imagePath = new URL( + '../../examples/assets/yandex-smartcaptcha-image.png', + import.meta.url +); +const instructionsPath = new URL( + '../../examples/assets/yandex-smartcaptcha-instructions.png', + import.meta.url +); + +describeIntegration('Yandex SmartCaptcha image against the real API', () => { + test('solve returns ordered click coordinates', async () => { + const image = fs.readFileSync(imagePath); + const instructions = fs.readFileSync(instructionsPath); + const task = new Tasks.YandexSmartCaptchaImageTask({ + image: image.toString('base64'), + imgInstructions: instructions.toString('base64') + }); + + const solution = await createClient().solve(task); + + // The instruction strip in the sample shows five symbols. + expect(solution.coordinates).toHaveLength(5); + + // Bounds come from the PNG's IHDR chunk (bytes 16..24), so swapping the + // sample cannot silently invalidate them. + const width = image.readUInt32BE(16); + const height = image.readUInt32BE(20); + for (const point of solution.coordinates) { + expect(typeof point.x).toBe('number'); + expect(typeof point.y).toBe('number'); + expect(point.x).toBeGreaterThanOrEqual(0); + expect(point.y).toBeGreaterThanOrEqual(0); + expect(point.x).toBeLessThanOrEqual(width); + expect(point.y).toBeLessThanOrEqual(height); + } + }, 130000); +}); diff --git a/tests/unit/async/client.test.js b/tests/unit/async/client.test.js index 8558f62..13d717b 100644 --- a/tests/unit/async/client.test.js +++ b/tests/unit/async/client.test.js @@ -25,6 +25,7 @@ describe('CaptchaClient (async)', () => { test('throws ValidationError when clientKey is missing', () => { expect(() => new CaptchaClient({})).toThrow(ValidationError); + expect(() => new CaptchaClient()).toThrow(ValidationError); }); test('sends the SDK version header', async () => { @@ -35,7 +36,7 @@ describe('CaptchaClient (async)', () => { }); await client.getBalance(); - expect(requestOptions.headers['X-SDK']).toBe('javascript-sdk/1.0.1'); + expect(requestOptions.headers['X-SDK']).toBe('javascript-sdk/1.1.0'); }); test('aborts an HTTP request after timeout', async () => { diff --git a/tests/unit/sync/client.test.js b/tests/unit/sync/client.test.js index 90acac9..f107285 100644 --- a/tests/unit/sync/client.test.js +++ b/tests/unit/sync/client.test.js @@ -26,6 +26,7 @@ describe('CaptchaClient (sync-style)', () => { test('throws ValidationError when clientKey is missing', () => { expect(() => new CaptchaClient({})).toThrow(ValidationError); + expect(() => new CaptchaClient()).toThrow(ValidationError); }); test('creates task and returns taskId', () => { diff --git a/tests/unit/task-validation.test.js b/tests/unit/task-validation.test.js new file mode 100644 index 0000000..e7b34cd --- /dev/null +++ b/tests/unit/task-validation.test.js @@ -0,0 +1,55 @@ +import { Tasks, ValidationError } from '@captcha-solver-api/javascript-sdk'; + +describe('built-in task validation', () => { + test('missing constructor arguments produce SDK validation errors', () => { + expect(() => new Tasks.ImageToText()).toThrow(ValidationError); + expect(() => new Tasks.YandexSmartCaptchaImageTask()).toThrow( + 'SmartCaptchaTask requires: image, imgInstructions' + ); + expect(() => new Tasks.RecaptchaV2Proxyless()).toThrow( + 'RecaptchaV2TaskProxyless requires: websiteURL, websiteKey' + ); + }); + + test.each([ + ['RecaptchaV2Proxyless', { websiteURL: 'https://example.com' }, 'websiteKey'], + ['RecaptchaV2', { websiteURL: 'https://example.com', websiteKey: 'key' }, 'proxyType, proxyAddress, proxyPort'], + ['RecaptchaV2EnterpriseProxyless', { websiteKey: 'key' }, 'websiteURL'], + ['RecaptchaV3Proxyless', { websiteURL: 'https://example.com', websiteKey: 'key' }, 'minScore'], + ['TurnstileProxyless', { websiteURL: '', websiteKey: 'key' }, 'websiteURL'], + ['ImageToText', {}, 'body'], + ['YandexSmartCaptchaTaskProxyless', { websiteURL: 'https://example.com' }, 'websiteKey'], + ['CoordinatesTask', { body: ' ' }, 'body'], + ['YandexSmartCaptchaImageTask', {}, 'image, imgInstructions'], + ['YandexSmartCaptchaImageTask', { image: 'image', imgInstructions: '' }, 'imgInstructions'], + ['TencentTaskProxyless', { websiteURL: 'https://example.com' }, 'appId'] + ])('%s reports missing required parameters', (className, params, expected) => { + expect(() => new Tasks[className](params)).toThrow(ValidationError); + expect(() => new Tasks[className](params)).toThrow(expected); + }); + + test('GeeTest v3 requires gt and challenge', () => { + expect(() => new Tasks.GeeTestProxyless({ + websiteURL: 'https://example.com', + gt: 'gt' + })).toThrow('GeeTestTaskProxyless requires: challenge'); + }); + + test('GeeTest v4 requires initParameters.captcha_id', () => { + expect(() => new Tasks.GeeTestProxyless({ + websiteURL: 'https://example.com', + version: 4, + initParameters: {} + })).toThrow('GeeTestTaskProxyless requires: initParameters.captcha_id'); + }); + + test('valid required parameters are accepted', () => { + expect(new Tasks.Turnstile({ + websiteURL: 'https://example.com', + websiteKey: 'key', + proxyType: 'http', + proxyAddress: '127.0.0.1', + proxyPort: 8080 + }).toDict()).toMatchObject({ type: 'TurnstileTask', proxyPort: 8080 }); + }); +});