diff --git a/README.md b/README.md index 55c7326..9edfe7d 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ on the target page. | GeeTest v4 | ✅ | ✅ | | Image to Text | ✅ | ❌ | | Yandex SmartCaptcha | ✅ | ✅ | +| Yandex SmartCaptcha Image | ✅ | ❌ | | Coordinates (click captcha) | ✅ | ❌ | | Tencent | ✅ | ✅ | @@ -450,10 +451,10 @@ With proxy, use `Tasks.GeeTest` (same proxy fields as reCAPTCHA v2). [API method description.](https://captcha-solver.com/en/docs/captcha-types#yandex-smartcaptcha) Use this method to solve the token-based Yandex SmartCaptcha and obtain a token -for the widget on the target page. Use the coordinates method for image challenges. +for the widget on the target page. Use the dedicated image method for image challenges. `Tasks.YandexSmartCaptchaTaskProxyless` / `Tasks.YandexSmartCaptchaTask` -- token-based -challenge. For the image challenge instead, use `Tasks.CoordinatesTask` with +challenge. | Field | Required | Description | |---|---|---| @@ -481,15 +482,41 @@ console.log(result.token); With proxy, use `Tasks.YandexSmartCaptchaTask` (same proxy fields as reCAPTCHA v2, plus the `https` option above). +#### Image challenge + +Use `Tasks.YandexSmartCaptchaImageTask` when SmartCaptcha shows a main image and +a separate strip indicating the symbols to click. Encode both images as base64; +the SDK sends API v2 type `SmartCaptchaTask`, and the returned coordinates are +already ordered for clicking. + +| Field | Required | Description | +|---|---|---| +| `image` | yes | Main captcha image, base64-encoded. | +| `imgInstructions` | yes | Click-order instruction image, base64-encoded. | +| `comment` | no | Optional text hint for the worker. | + +```javascript +import fs from 'node:fs'; +import { CaptchaClient, Tasks } from '@captcha-solver-api/javascript-sdk'; + +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); +``` + +See the runnable [async](examples/async/yandex_smartcaptcha_image.js) and +[promise-chain](examples/sync/yandex_smartcaptcha_image.js) examples. + ### Coordinates (click captcha) [API method description.](https://captcha-solver.com/en/docs/captcha-types#coordinates) -Use this method to identify points that a worker should click in an image. It -supports generic click captchas and the image version of Yandex SmartCaptcha. +Use this method to identify points that a worker should click in a generic image. -`Tasks.CoordinatesTask`. Used both for generic "click on X" captchas and for -directly. +`Tasks.CoordinatesTask`. | Field | Required | Description | |---|---|---| diff --git a/examples/README.md b/examples/README.md index 044fbd6..afbb024 100644 --- a/examples/README.md +++ b/examples/README.md @@ -78,6 +78,7 @@ twice the balance. | 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` | diff --git a/examples/assets/yandex-smartcaptcha-image.png b/examples/assets/yandex-smartcaptcha-image.png new file mode 100644 index 0000000..92addc3 Binary files /dev/null and b/examples/assets/yandex-smartcaptcha-image.png differ diff --git a/examples/assets/yandex-smartcaptcha-instructions.png b/examples/assets/yandex-smartcaptcha-instructions.png new file mode 100644 index 0000000..80cf090 Binary files /dev/null and b/examples/assets/yandex-smartcaptcha-instructions.png differ diff --git a/examples/async/README.md b/examples/async/README.md index 9015c41..189bff8 100644 --- a/examples/async/README.md +++ b/examples/async/README.md @@ -28,6 +28,7 @@ spends balance twice. Comment out the one you don't need. | [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 | diff --git a/examples/async/yandex_smartcaptcha_image.js b/examples/async/yandex_smartcaptcha_image.js new file mode 100644 index 0000000..e878647 --- /dev/null +++ b/examples/async/yandex_smartcaptcha_image.js @@ -0,0 +1,26 @@ +/** 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/README.md b/examples/sync/README.md index 88fd009..30eaad4 100644 --- a/examples/sync/README.md +++ b/examples/sync/README.md @@ -35,6 +35,7 @@ Errors are handled per chain with a trailing `.catch()`; a failing block does no | [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 | @@ -154,10 +155,7 @@ directory** and the script runs as-is. Solution: `text`. 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. *Yandex -SmartCaptcha image mode* (`yandexTask`) reuses the same `CoordinatesTask` with -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. +`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 diff --git a/examples/sync/yandex_smartcaptcha_image.js b/examples/sync/yandex_smartcaptcha_image.js new file mode 100644 index 0000000..e56f4c3 --- /dev/null +++ b/examples/sync/yandex_smartcaptcha_image.js @@ -0,0 +1,28 @@ +/** 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/src/index.ts b/src/index.ts index 5620434..320b475 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,6 +30,7 @@ export type { YandexSmartCaptchaTaskProxylessParams, YandexSmartCaptchaTaskParams, CoordinatesTaskParams, + YandexSmartCaptchaImageTaskParams, TencentTaskProxylessParams, TencentTaskParams } from './tasks.js'; diff --git a/src/tasks.ts b/src/tasks.ts index 7851c95..c27d615 100644 --- a/src/tasks.ts +++ b/src/tasks.ts @@ -553,7 +553,6 @@ export interface CoordinatesTaskParams { /** * Click captcha: the worker marks points in an image (`CoordinatesTask`). - * Used for generic "click on X" captchas and the image version of Yandex SmartCaptcha. * Solution: {@link CoordinatesSolution}. * * @see https://captcha-solver.com/en/docs/captcha-types#coordinates @@ -564,6 +563,35 @@ export class CoordinatesTask extends BaseTask { } } +/* ------------------------------------------------------------------ */ +/* Yandex SmartCaptcha image */ +/* ------------------------------------------------------------------ */ + +/** Parameters of {@link YandexSmartCaptchaImageTask}. */ +export interface YandexSmartCaptchaImageTaskParams { + /** The main SmartCaptcha challenge image, base64-encoded. */ + image: string; + /** The SmartCaptcha click-order instruction image, base64-encoded. */ + imgInstructions: string; + /** Optional text hint shown to the worker. */ + comment?: string | null; +} + +/** + * Image-based Yandex SmartCaptcha challenge. + * + * Serialized as the API v2 `SmartCaptchaTask`. Coordinates are returned in + * the order in which the symbols must be clicked. + * Solution: {@link CoordinatesSolution}. + * + * @see https://captcha-solver.com/en/docs/captcha-types#yandex-smartcaptcha + */ +export class YandexSmartCaptchaImageTask extends BaseTask { + constructor({ image, imgInstructions, comment = null }: YandexSmartCaptchaImageTaskParams) { + super({ type: 'SmartCaptchaTask', image, imgInstructions, comment }); + } +} + /* ------------------------------------------------------------------ */ /* Tencent */ /* ------------------------------------------------------------------ */ diff --git a/tests/integration/coordinates.test.js b/tests/integration/coordinates.test.js index 828e62b..21ec678 100644 --- a/tests/integration/coordinates.test.js +++ b/tests/integration/coordinates.test.js @@ -20,13 +20,21 @@ 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('Coordinates against the real API', () => { +describeIntegration('Image coordinate tasks against the real API', () => { test('solve returns the clicked points', async () => { const image = fs.readFileSync(imagePath); @@ -58,4 +66,28 @@ describeIntegration('Coordinates 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/unit/public-api.test.js b/tests/unit/public-api.test.js index 8b62af7..3f6066f 100644 --- a/tests/unit/public-api.test.js +++ b/tests/unit/public-api.test.js @@ -30,6 +30,7 @@ const TASK_CLASSES = [ 'YandexSmartCaptchaTaskProxyless', 'YandexSmartCaptchaTask', 'CoordinatesTask', + 'YandexSmartCaptchaImageTask', 'TencentTaskProxyless', 'TencentTask' ]; diff --git a/tests/unit/yandex_smartcaptcha_image.test.js b/tests/unit/yandex_smartcaptcha_image.test.js new file mode 100644 index 0000000..e9705cf --- /dev/null +++ b/tests/unit/yandex_smartcaptcha_image.test.js @@ -0,0 +1,18 @@ +import { Tasks } from '@captcha-solver-api/javascript-sdk'; + +describe('YandexSmartCaptchaImageTask', () => { + test('serializes as an API v2 SmartCaptchaTask with both images', () => { + const task = new Tasks.YandexSmartCaptchaImageTask({ + image: 'captcha-image', + imgInstructions: 'instruction-image', + comment: 'click the symbols in the specified order' + }); + + expect(task.toDict()).toEqual({ + type: 'SmartCaptchaTask', + image: 'captcha-image', + imgInstructions: 'instruction-image', + comment: 'click the symbols in the specified order' + }); + }); +});