Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ jobs:
# to catch packaging errors early. Skipped if lint fails to save CI minutes.
test:
needs: lint
runs-on: ubuntu-latest
# Ubuntu 26.04 (the current ubuntu-latest) does not provide Python 3.9
# through actions/setup-python. Pin the matrix runner while Python 3.9 is
# part of the SDK's supported version range.
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
Expand Down
48 changes: 39 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,11 +499,9 @@ With proxy, use `GeeTestTask` (same proxy fields as reCAPTCHA v2).
<sup>[API method description.](https://captcha-solver.com/en/docs/captcha-types#yandex-smartcaptcha)</sup>

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.

`YandexSmartCaptchaTaskProxyless` / `YandexSmartCaptchaTask` -- token-based
challenge. For the image challenge instead, use `CoordinatesTask` with
[Coordinates](#coordinates-click-captcha) is available for generic click captchas.
`YandexSmartCaptchaTaskProxyless` / `YandexSmartCaptchaTask` -- token-based challenge.

| Parameter | Required | Description |
|---|---|---|
Expand Down Expand Up @@ -532,15 +530,45 @@ print(result["token"])
With proxy, use `YandexSmartCaptchaTask` (same proxy fields as reCAPTCHA v2,
plus the `https` option above).

#### Image challenge

Use `YandexSmartCaptchaImageTask` when SmartCaptcha shows a main image and a
separate strip indicating the symbols to click. The SDK sends API v2 type
`SmartCaptchaTask`; both images must be base64-encoded.

| Parameter | 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. |

**Response:** `coordinates` -- ordered `{"x": int, "y": int}` click positions.

```python
import base64
from captcha_solver_api import CaptchaClient
from captcha_solver_api.tasks import YandexSmartCaptchaImageTask

with open("captcha.png", "rb") as f:
image = base64.b64encode(f.read()).decode("utf-8")
with open("instructions.png", "rb") as f:
instructions = base64.b64encode(f.read()).decode("utf-8")

task = YandexSmartCaptchaImageTask(
image=image,
imgInstructions=instructions,
)
result = CaptchaClient("your_api_key").solve(task)
print(result["coordinates"])
```

### Coordinates (click captcha)

<sup>[API method description.](https://captcha-solver.com/en/docs/captcha-types#coordinates)</sup>

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.

`CoordinatesTask`. Used both for generic "click on X" captchas and for Yandex
SmartCaptcha's image challenge. No proxy variant -- the image is submitted directly.
`CoordinatesTask`. No proxy variant -- the image is submitted directly.

| Parameter | Required | Description |
|---|---|---|
Expand Down Expand Up @@ -718,7 +746,8 @@ Avoid importing it by name: it shadows Python's built-in `TimeoutError`.
See the dedicated [examples documentation](https://github.com/captcha-solver-api/python-sdk/blob/main/examples/README.md) for the full
sync/async example list, setup steps, expected results, and placeholder guidance.

- **Image/click captchas** (`image_to_text.py`, `coordinates.py`) run after installing the dependencies and setting a valid
- **Image/click captchas** (`image_to_text.py`, `coordinates.py`,
`yandex_smartcaptcha_image.py`) run after installing the dependencies and setting a valid
`CAPTCHA_API_KEY` -- they read sample images bundled in
[examples/assets](https://github.com/captcha-solver-api/python-sdk/tree/main/examples/assets), no target page needed.
`python-dotenv` is optional: install it only if you want the scripts to load a `.env` file.
Expand All @@ -743,6 +772,7 @@ export CAPTCHA_API_KEY=your_api_key
python examples/sync/balance.py
python examples/sync/image_to_text.py
python examples/sync/coordinates.py
python examples/sync/yandex_smartcaptcha_image.py
```

Live checks with real parameters have exercised all supported task types.
Expand Down
27 changes: 26 additions & 1 deletion captcha_solver_api/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,7 @@ def __init__(

class CoordinatesTask(BaseTask):
"""Coordinate-based (click) image captcha. No proxy variant -- the image
is submitted directly.
is submitted directly. Use this task for generic click captchas.

Args:
body: The captcha image, base64-encoded (no `data:image/...;base64,` prefix).
Expand Down Expand Up @@ -832,6 +832,31 @@ def __init__(
self.maxClicks = maxClicks


class YandexSmartCaptchaImageTask(BaseTask):
"""Image-based Yandex SmartCaptcha using API v2 ``SmartCaptchaTask``.

Args:
image: Main SmartCaptcha challenge image, base64-encoded.
imgInstructions: Click-order instruction image, base64-encoded.
comment: Optional text hint for the worker.

Returns (``solution`` from ``solve()``):
``coordinates`` -- an ordered list of ``{"x": int, "y": int}`` points.
"""

type = "SmartCaptchaTask"

def __init__(
self,
image: str,
imgInstructions: str,
comment: Optional[str] = None,
) -> None:
self.image = image
self.imgInstructions = imgInstructions
self.comment = comment


class TencentTaskProxyless(BaseTask):
"""Tencent captcha without proxy.

Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Each supported CAPTCHA type has a matching sync and async script:
| GeeTest v3 | [geetest_v3.py](sync/geetest_v3.py) | [geetest_v3.py](async/geetest_v3.py) | `challenge`, `validate`, `seccode` |
| GeeTest v4 | [geetest_v4.py](sync/geetest_v4.py) | [geetest_v4.py](async/geetest_v4.py) | `captcha_output` and related fields |
| Yandex SmartCaptcha token | [yandex_smartcaptcha.py](sync/yandex_smartcaptcha.py) | [yandex_smartcaptcha.py](async/yandex_smartcaptcha.py) | `token` |
| Yandex SmartCaptcha image | [yandex_smartcaptcha_image.py](sync/yandex_smartcaptcha_image.py) | [yandex_smartcaptcha_image.py](async/yandex_smartcaptcha_image.py) | `coordinates` |
| Coordinates / click captcha | [coordinates.py](sync/coordinates.py) | [coordinates.py](async/coordinates.py) | `coordinates` |
| Tencent | [tencent.py](sync/tencent.py) | [tencent.py](async/tencent.py) | `appid`, `ret`, `ticket`, `randstr` |

Expand Down
Binary file added examples/assets/yandex-smartcaptcha-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions examples/async/yandex_smartcaptcha_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Example: Solve an image-based Yandex SmartCaptcha asynchronously.

Prerequisites:
Set the CAPTCHA_API_KEY environment variable.
The sample captcha and instruction images are bundled in examples/assets/.
"""

import asyncio
import os
import sys
from base64 import b64encode

try:
from dotenv import load_dotenv
except ModuleNotFoundError as exc:
if exc.name != "dotenv":
raise
load_dotenv = None

repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
sys.path.append(repo_root)
if load_dotenv is not None:
load_dotenv(os.path.join(repo_root, ".env"))

from captcha_solver_api import AsyncCaptchaClient
from captcha_solver_api.tasks import YandexSmartCaptchaImageTask


def encode_image(filename: str) -> str:
"""Return an image from examples/assets as a Base64 string."""
path = os.path.join(repo_root, "examples", "assets", filename)
with open(path, "rb") as image_file:
return b64encode(image_file.read()).decode("utf-8")


async def main() -> None:
api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY")
async with AsyncCaptchaClient(api_key) as solver:
result = await solver.solve(
YandexSmartCaptchaImageTask(
image=encode_image("yandex-smartcaptcha-image.png"),
imgInstructions=encode_image("yandex-smartcaptcha-instructions.png"),
)
)
# Click the returned points on the captcha image in the listed order.
# Example: {"coordinates": [{"x": 73, "y": 92}, ...]}
print("result: " + str(result))


if __name__ == "__main__":
try:
asyncio.run(main())
except Exception as exc:
sys.exit(exc)
49 changes: 49 additions & 0 deletions examples/sync/yandex_smartcaptcha_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Example: Solve an image-based Yandex SmartCaptcha.

Prerequisites:
Set the CAPTCHA_API_KEY environment variable.
The sample captcha and instruction images are bundled in examples/assets/.
"""

import os
import sys
from base64 import b64encode

try:
from dotenv import load_dotenv
except ModuleNotFoundError as exc:
if exc.name != "dotenv":
raise
load_dotenv = None

repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
sys.path.append(repo_root)
if load_dotenv is not None:
load_dotenv(os.path.join(repo_root, ".env"))

from captcha_solver_api import CaptchaClient
from captcha_solver_api.tasks import YandexSmartCaptchaImageTask


def encode_image(filename: str) -> str:
"""Return an image from examples/assets as a Base64 string."""
path = os.path.join(repo_root, "examples", "assets", filename)
with open(path, "rb") as image_file:
return b64encode(image_file.read()).decode("utf-8")


api_key = os.getenv("CAPTCHA_API_KEY", "YOUR_API_KEY")
solver = CaptchaClient(api_key)

try:
result = solver.solve(
YandexSmartCaptchaImageTask(
image=encode_image("yandex-smartcaptcha-image.png"),
imgInstructions=encode_image("yandex-smartcaptcha-instructions.png"),
)
)
# Click the returned points on the captcha image in the listed order.
# Example: {"coordinates": [{"x": 73, "y": 92}, ...]}
print("result: " + str(result))
except Exception as exc:
sys.exit(exc)
2 changes: 2 additions & 0 deletions tests/sync/test_coordinates.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ def test_to_dict(self):
def test_with_click_limits(self):
task = CoordinatesTask(
body="base64string",
imgInstructions="base64instructions",
minClicks=1,
maxClicks=5,
)
result = task.to_dict()

assert result["imgInstructions"] == "base64instructions"
assert result["minClicks"] == 1
assert result["maxClicks"] == 5

Expand Down
23 changes: 23 additions & 0 deletions tests/sync/test_yandex_smartcaptcha_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Tests for YandexSmartCaptchaImageTask."""

from captcha_solver_api.tasks import YandexSmartCaptchaImageTask


class TestYandexSmartCaptchaImage:
def test_to_dict_uses_api_v2_task_type(self):
task = YandexSmartCaptchaImageTask(
image="captcha-image",
imgInstructions="instruction-image",
comment="click the symbols in the specified order",
)

assert task.to_dict() == {
"type": "SmartCaptchaTask",
"image": "captcha-image",
"imgInstructions": "instruction-image",
"comment": "click the symbols in the specified order",
}

def test_optional_comment_is_omitted(self):
task = YandexSmartCaptchaImageTask("captcha-image", "instruction-image")
assert "comment" not in task.to_dict()
Loading