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'
+ });
+ });
+});