Add unified captcha third-party services skill with official API workflow
这个提交包含在:
@@ -0,0 +1,140 @@
|
||||
---
|
||||
name: captcha-third-party-services
|
||||
description: Unified captcha workflow for 2Captcha, YesCaptcha, and Anti-Captcha. Use when users need official API-based task submission (`createTask`), polling (`getTaskResult`), balance checks (`getBalance`), provider fallback, or debugging captcha token pipelines for reCAPTCHA/hCaptcha/Turnstile/FunCaptcha and similar challenge types.
|
||||
---
|
||||
|
||||
# Captcha Third Party Services
|
||||
|
||||
## Overview
|
||||
|
||||
Use official APIs to create captcha tasks, poll completion, and return solve tokens with consistent behavior across providers.
|
||||
|
||||
Read `references/official-docs-and-analysis.md` when you need provider-specific details, method URLs, and compatibility notes.
|
||||
|
||||
## Inputs To Collect
|
||||
|
||||
- Provider: `2captcha`, `yescaptcha`, or `anti-captcha`
|
||||
- Captcha task type (for example `RecaptchaV2TaskProxyless`, `TurnstileTaskProxyless`, `HCaptchaTaskProxyless`)
|
||||
- Target metadata: `websiteURL`, `websiteKey` and task-specific fields
|
||||
- API key (runtime secret, not hardcoded into committed files)
|
||||
|
||||
## API Key Handling
|
||||
|
||||
Prefer environment variables:
|
||||
|
||||
- `CAPTCHA_2CAPTCHA_KEY`
|
||||
- `CAPTCHA_YESCAPTCHA_KEY`
|
||||
- `CAPTCHA_ANTI_CAPTCHA_KEY`
|
||||
|
||||
Only use direct `--api-key` arguments for one-off tests.
|
||||
|
||||
## Unified Workflow
|
||||
|
||||
1. Check balance before creating tasks.
|
||||
2. Build a `task` payload that matches provider-supported task schema.
|
||||
3. Submit `createTask`.
|
||||
4. Poll `getTaskResult` every 3-5 seconds until `status=ready` or timeout.
|
||||
5. Return `solution` token and timing/cost metadata.
|
||||
|
||||
## Provider Selection Strategy
|
||||
|
||||
- Start with preferred provider from user.
|
||||
- If provider returns permanent task validation errors, fix payload first.
|
||||
- If provider returns transient capacity/timeouts, fail over to next provider.
|
||||
- Keep task type and site parameters identical during failover to isolate provider variance.
|
||||
|
||||
Recommended fallback order:
|
||||
|
||||
1. `yescaptcha`
|
||||
2. `2captcha`
|
||||
3. `anti-captcha`
|
||||
|
||||
Adjust order using account balance, measured solve latency, and recent success rate.
|
||||
|
||||
## Use The Bundled CLI
|
||||
|
||||
Use `scripts/captcha_api_cli.py` for deterministic API calls.
|
||||
|
||||
### Balance
|
||||
|
||||
```bash
|
||||
python3 scripts/captcha_api_cli.py balance --provider 2captcha
|
||||
python3 scripts/captcha_api_cli.py balance --provider yescaptcha
|
||||
python3 scripts/captcha_api_cli.py balance --provider anti-captcha
|
||||
```
|
||||
|
||||
### Create Task
|
||||
|
||||
```bash
|
||||
python3 scripts/captcha_api_cli.py create-task \
|
||||
--provider 2captcha \
|
||||
--task-json '{"type":"RecaptchaV2TaskProxyless","websiteURL":"https://example.com","websiteKey":"SITE_KEY"}'
|
||||
```
|
||||
|
||||
### Poll Task Result
|
||||
|
||||
```bash
|
||||
python3 scripts/captcha_api_cli.py get-task-result \
|
||||
--provider 2captcha \
|
||||
--task-id 123456789
|
||||
```
|
||||
|
||||
### End-To-End Solve (Create + Poll)
|
||||
|
||||
```bash
|
||||
python3 scripts/captcha_api_cli.py solve \
|
||||
--provider yescaptcha \
|
||||
--task-json '{"type":"TurnstileTaskProxyless","websiteURL":"https://example.com","websiteKey":"SITE_KEY"}' \
|
||||
--poll-interval 3 \
|
||||
--timeout 180
|
||||
```
|
||||
|
||||
## Raw Curl Patterns
|
||||
|
||||
Use these when a user explicitly asks for direct HTTP examples.
|
||||
|
||||
### createTask
|
||||
|
||||
```bash
|
||||
curl -sS https://api.<provider-domain>/createTask \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"clientKey":"<API_KEY>",
|
||||
"task":{
|
||||
"type":"RecaptchaV2TaskProxyless",
|
||||
"websiteURL":"https://example.com",
|
||||
"websiteKey":"SITE_KEY"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
### getTaskResult
|
||||
|
||||
```bash
|
||||
curl -sS https://api.<provider-domain>/getTaskResult \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"clientKey":"<API_KEY>",
|
||||
"taskId":123456789
|
||||
}'
|
||||
```
|
||||
|
||||
### getBalance
|
||||
|
||||
```bash
|
||||
curl -sS https://api.<provider-domain>/getBalance \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"clientKey":"<API_KEY>"}'
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- `errorId != 0`: treat as API-level failure and inspect `errorCode`/`errorDescription`.
|
||||
- Stuck in `processing`: extend timeout or switch provider.
|
||||
- Invalid key/site params: validate task type and required fields from official docs.
|
||||
- Low balance: call `getBalance` and select another provider if needed.
|
||||
|
||||
## Compliance
|
||||
|
||||
- Use these services only for authorized security testing and legitimate automation.
|
||||
- Respect target website Terms of Service and applicable laws.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "Captcha Third-Party Services"
|
||||
short_description: "Unified workflows for 2Captcha, YesCaptcha, Anti-Captcha"
|
||||
default_prompt: "Use official APIs to create captcha tasks, poll results, and manage provider fallback across 2Captcha, YesCaptcha, and Anti-Captcha."
|
||||
@@ -0,0 +1,96 @@
|
||||
# Official Docs And Analysis
|
||||
|
||||
This file summarizes official API documentation for 2Captcha, YesCaptcha, and Anti-Captcha and maps them into one operational workflow.
|
||||
|
||||
## Official Sources
|
||||
|
||||
### 2Captcha
|
||||
|
||||
- API docs hub: https://2captcha.com/api-docs
|
||||
- `createTask`: https://2captcha.com/api-docs/create-task
|
||||
- `getTaskResult`: https://2captcha.com/api-docs/get-task-result
|
||||
- `getBalance`: https://2captcha.com/api-docs/get-balance
|
||||
|
||||
### YesCaptcha
|
||||
|
||||
- Dashboard: https://yescaptcha.com/dashboard.html
|
||||
- `createTask`: https://yescaptcha.atlassian.net/wiki/spaces/YESCAPTCHA/pages/7930129/createTask+submit+a+captcha+task
|
||||
- `getTaskResult`: https://yescaptcha.atlassian.net/wiki/spaces/YESCAPTCHA/pages/7930006/getTaskResult+Task+Result+Method
|
||||
- `getBalance`: https://yescaptcha.atlassian.net/wiki/spaces/YESCAPTCHA/pages/7930094/getBalance+balance+Method
|
||||
|
||||
### Anti-Captcha
|
||||
|
||||
- API docs hub: https://anti-captcha.com/apidoc
|
||||
- `createTask`: https://anti-captcha.com/apidoc/methods/createTask
|
||||
- `getTaskResult`: https://anti-captcha.com/apidoc/methods/getTaskResult
|
||||
- `getBalance`: https://anti-captcha.com/apidoc/methods/getBalance
|
||||
|
||||
## Endpoint Matrix
|
||||
|
||||
All three providers use `POST` + JSON and share the same core method names.
|
||||
|
||||
| Provider | createTask | getTaskResult | getBalance |
|
||||
|---|---|---|---|
|
||||
| 2Captcha | `https://api.2captcha.com/createTask` | `https://api.2captcha.com/getTaskResult` | `https://api.2captcha.com/getBalance` |
|
||||
| YesCaptcha | `https://api.yescaptcha.com/createTask` | `https://api.yescaptcha.com/getTaskResult` | `https://api.yescaptcha.com/getBalance` |
|
||||
| Anti-Captcha | `https://api.anti-captcha.com/createTask` | `https://api.anti-captcha.com/getTaskResult` | `https://api.anti-captcha.com/getBalance` |
|
||||
|
||||
## Shared Request Pattern
|
||||
|
||||
### createTask
|
||||
|
||||
```json
|
||||
{
|
||||
"clientKey": "YOUR_API_KEY",
|
||||
"task": {
|
||||
"type": "RecaptchaV2TaskProxyless",
|
||||
"websiteURL": "https://example.com",
|
||||
"websiteKey": "SITE_KEY"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### getTaskResult
|
||||
|
||||
```json
|
||||
{
|
||||
"clientKey": "YOUR_API_KEY",
|
||||
"taskId": 123456789
|
||||
}
|
||||
```
|
||||
|
||||
### getBalance
|
||||
|
||||
```json
|
||||
{
|
||||
"clientKey": "YOUR_API_KEY"
|
||||
}
|
||||
```
|
||||
|
||||
## Shared Response Pattern
|
||||
|
||||
- `errorId = 0`: request accepted/successful.
|
||||
- `errorId != 0`: inspect `errorCode` and `errorDescription`.
|
||||
- `getTaskResult.status`:
|
||||
- `processing`
|
||||
- `ready` with `solution`.
|
||||
|
||||
## Practical Differences
|
||||
|
||||
- **Domain and account pool differ**: task acceptance, queue depth, and solve latency vary by provider.
|
||||
- **Balance shape differs slightly**:
|
||||
- 2Captcha and Anti-Captcha commonly return `balance`.
|
||||
- YesCaptcha may also return additional balance fields (for example `softBalance` and invite-related fields).
|
||||
- **Task support overlap is high but not perfect**: always verify task type and required fields per provider docs before failover.
|
||||
|
||||
## Integration Recommendations
|
||||
|
||||
1. Keep one canonical `task` payload schema in your project.
|
||||
2. Swap only provider domain + API key for failover.
|
||||
3. Poll every 3-5 seconds; stop on timeout and switch provider.
|
||||
4. Log provider, task type, taskId, latency, and errorCode for reliability tuning.
|
||||
5. Never hardcode API keys into version-controlled files.
|
||||
|
||||
## Live Connectivity Check (2026-03-03)
|
||||
|
||||
`getBalance` was successfully called for all three providers (`errorId: 0`) during skill implementation on March 3, 2026.
|
||||
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Unified CLI for 2Captcha, YesCaptcha, and Anti-Captcha API methods."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
PROVIDERS = {
|
||||
"2captcha": {
|
||||
"endpoint": "https://api.2captcha.com",
|
||||
"env_var": "CAPTCHA_2CAPTCHA_KEY",
|
||||
},
|
||||
"yescaptcha": {
|
||||
"endpoint": "https://api.yescaptcha.com",
|
||||
"env_var": "CAPTCHA_YESCAPTCHA_KEY",
|
||||
},
|
||||
"anti-captcha": {
|
||||
"endpoint": "https://api.anti-captcha.com",
|
||||
"env_var": "CAPTCHA_ANTI_CAPTCHA_KEY",
|
||||
},
|
||||
}
|
||||
|
||||
PROVIDER_ALIASES = {
|
||||
"2captcha": "2captcha",
|
||||
"2-captcha": "2captcha",
|
||||
"yescaptcha": "yescaptcha",
|
||||
"yes-captcha": "yescaptcha",
|
||||
"anticaptcha": "anti-captcha",
|
||||
"anti-captcha": "anti-captcha",
|
||||
}
|
||||
|
||||
|
||||
class CliError(Exception):
|
||||
"""Raised for expected CLI errors."""
|
||||
|
||||
|
||||
def normalize_provider(provider: str) -> str:
|
||||
key = provider.strip().lower()
|
||||
normalized = PROVIDER_ALIASES.get(key)
|
||||
if not normalized:
|
||||
supported = ", ".join(sorted(PROVIDERS))
|
||||
raise CliError(f"Unsupported provider '{provider}'. Supported: {supported}")
|
||||
return normalized
|
||||
|
||||
|
||||
def resolve_api_key(provider: str, explicit_key: str | None) -> str:
|
||||
if explicit_key:
|
||||
return explicit_key
|
||||
env_var = PROVIDERS[provider]["env_var"]
|
||||
value = os.getenv(env_var, "").strip()
|
||||
if value:
|
||||
return value
|
||||
raise CliError(
|
||||
f"Missing API key for provider '{provider}'. "
|
||||
f"Set --api-key or environment variable {env_var}."
|
||||
)
|
||||
|
||||
|
||||
def call_api(provider: str, method: str, payload: Dict[str, Any], timeout: int = 60) -> Dict[str, Any]:
|
||||
base = PROVIDERS[provider]["endpoint"]
|
||||
url = f"{base}/{method}"
|
||||
body = json.dumps(payload).encode("utf-8")
|
||||
request = urllib.request.Request(
|
||||
url=url,
|
||||
data=body,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||
raw = response.read().decode("utf-8")
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", errors="replace")
|
||||
raise CliError(f"HTTP {exc.code} from {url}: {detail}") from exc
|
||||
except urllib.error.URLError as exc:
|
||||
raise CliError(f"Network error calling {url}: {exc}") from exc
|
||||
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise CliError(f"Invalid JSON response from {url}: {raw}") from exc
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def ensure_api_success(response: Dict[str, Any]) -> None:
|
||||
error_id = response.get("errorId")
|
||||
if error_id in (0, "0", None):
|
||||
return
|
||||
|
||||
code = response.get("errorCode", "UNKNOWN")
|
||||
desc = response.get("errorDescription", "")
|
||||
message = f"Provider API error (errorId={error_id}, errorCode={code})"
|
||||
if desc:
|
||||
message = f"{message}: {desc}"
|
||||
raise CliError(message)
|
||||
|
||||
|
||||
def load_task_payload(task_json: str | None, task_file: str | None) -> Dict[str, Any]:
|
||||
if not task_json and not task_file:
|
||||
raise CliError("Provide either --task-json or --task-file.")
|
||||
|
||||
if task_json and task_file:
|
||||
raise CliError("Use only one of --task-json or --task-file.")
|
||||
|
||||
if task_json:
|
||||
source = task_json
|
||||
else:
|
||||
path = Path(task_file or "")
|
||||
if not path.exists():
|
||||
raise CliError(f"Task file does not exist: {path}")
|
||||
source = path.read_text()
|
||||
|
||||
try:
|
||||
task = json.loads(source)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise CliError(f"Invalid task JSON: {exc}") from exc
|
||||
|
||||
if not isinstance(task, dict):
|
||||
raise CliError("Task JSON must be an object.")
|
||||
if "type" not in task:
|
||||
raise CliError("Task JSON must include a 'type' field.")
|
||||
return task
|
||||
|
||||
|
||||
def print_json(data: Dict[str, Any]) -> None:
|
||||
print(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True))
|
||||
|
||||
|
||||
def cmd_balance(args: argparse.Namespace) -> int:
|
||||
provider = normalize_provider(args.provider)
|
||||
key = resolve_api_key(provider, args.api_key)
|
||||
payload = {"clientKey": key}
|
||||
result = call_api(provider, "getBalance", payload)
|
||||
ensure_api_success(result)
|
||||
print_json({"provider": provider, "result": result})
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_create_task(args: argparse.Namespace) -> int:
|
||||
provider = normalize_provider(args.provider)
|
||||
key = resolve_api_key(provider, args.api_key)
|
||||
task = load_task_payload(args.task_json, args.task_file)
|
||||
|
||||
payload: Dict[str, Any] = {"clientKey": key, "task": task}
|
||||
if args.soft_id is not None:
|
||||
payload["softId"] = args.soft_id
|
||||
|
||||
result = call_api(provider, "createTask", payload)
|
||||
ensure_api_success(result)
|
||||
print_json({"provider": provider, "result": result})
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_get_task_result(args: argparse.Namespace) -> int:
|
||||
provider = normalize_provider(args.provider)
|
||||
key = resolve_api_key(provider, args.api_key)
|
||||
payload = {"clientKey": key, "taskId": args.task_id}
|
||||
|
||||
result = call_api(provider, "getTaskResult", payload)
|
||||
ensure_api_success(result)
|
||||
print_json({"provider": provider, "result": result})
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_solve(args: argparse.Namespace) -> int:
|
||||
provider = normalize_provider(args.provider)
|
||||
key = resolve_api_key(provider, args.api_key)
|
||||
task = load_task_payload(args.task_json, args.task_file)
|
||||
|
||||
create_payload: Dict[str, Any] = {"clientKey": key, "task": task}
|
||||
if args.soft_id is not None:
|
||||
create_payload["softId"] = args.soft_id
|
||||
|
||||
create_result = call_api(provider, "createTask", create_payload)
|
||||
ensure_api_success(create_result)
|
||||
|
||||
task_id = create_result.get("taskId")
|
||||
if not task_id:
|
||||
raise CliError(f"createTask succeeded but taskId missing: {create_result}")
|
||||
|
||||
deadline = time.time() + args.timeout
|
||||
|
||||
while time.time() < deadline:
|
||||
poll_payload = {"clientKey": key, "taskId": task_id}
|
||||
poll_result = call_api(provider, "getTaskResult", poll_payload)
|
||||
ensure_api_success(poll_result)
|
||||
|
||||
status = poll_result.get("status")
|
||||
if status == "ready":
|
||||
print_json(
|
||||
{
|
||||
"provider": provider,
|
||||
"taskId": task_id,
|
||||
"createTask": create_result,
|
||||
"getTaskResult": poll_result,
|
||||
}
|
||||
)
|
||||
return 0
|
||||
|
||||
if status != "processing":
|
||||
raise CliError(f"Unexpected getTaskResult status '{status}': {poll_result}")
|
||||
|
||||
time.sleep(args.poll_interval)
|
||||
|
||||
raise CliError(
|
||||
f"Timeout waiting for task {task_id} after {args.timeout} seconds "
|
||||
f"(poll interval {args.poll_interval}s)."
|
||||
)
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Unified captcha API CLI for 2Captcha, YesCaptcha, Anti-Captcha."
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
p_balance = subparsers.add_parser("balance", help="Call getBalance")
|
||||
p_balance.add_argument("--provider", required=True, help="2captcha|yescaptcha|anti-captcha")
|
||||
p_balance.add_argument("--api-key", help="Provider API key (optional if env var is set)")
|
||||
p_balance.set_defaults(func=cmd_balance)
|
||||
|
||||
p_create = subparsers.add_parser("create-task", help="Call createTask")
|
||||
p_create.add_argument("--provider", required=True, help="2captcha|yescaptcha|anti-captcha")
|
||||
p_create.add_argument("--api-key", help="Provider API key (optional if env var is set)")
|
||||
p_create.add_argument("--task-json", help="Inline JSON object for 'task'")
|
||||
p_create.add_argument("--task-file", help="Path to JSON file containing 'task' object")
|
||||
p_create.add_argument("--soft-id", type=int, help="Optional softId value")
|
||||
p_create.set_defaults(func=cmd_create_task)
|
||||
|
||||
p_result = subparsers.add_parser("get-task-result", help="Call getTaskResult")
|
||||
p_result.add_argument("--provider", required=True, help="2captcha|yescaptcha|anti-captcha")
|
||||
p_result.add_argument("--api-key", help="Provider API key (optional if env var is set)")
|
||||
p_result.add_argument("--task-id", required=True, type=int, help="Task ID from createTask")
|
||||
p_result.set_defaults(func=cmd_get_task_result)
|
||||
|
||||
p_solve = subparsers.add_parser("solve", help="Create task and poll until ready")
|
||||
p_solve.add_argument("--provider", required=True, help="2captcha|yescaptcha|anti-captcha")
|
||||
p_solve.add_argument("--api-key", help="Provider API key (optional if env var is set)")
|
||||
p_solve.add_argument("--task-json", help="Inline JSON object for 'task'")
|
||||
p_solve.add_argument("--task-file", help="Path to JSON file containing 'task' object")
|
||||
p_solve.add_argument("--soft-id", type=int, help="Optional softId value")
|
||||
p_solve.add_argument("--poll-interval", type=int, default=3, help="Polling interval in seconds")
|
||||
p_solve.add_argument("--timeout", type=int, default=180, help="Solve timeout in seconds")
|
||||
p_solve.set_defaults(func=cmd_solve)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
return args.func(args)
|
||||
except CliError as exc:
|
||||
print(f"ERROR: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
在新工单中引用
屏蔽一个用户