Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ CAPTCHA_API_KEY=your_api_key
TARGET_URL=
BROWSER_USER_AGENT=

# Required for the Tencent example. Leave the trigger blank if the page opens it automatically.
TENCENT_TRIGGER_SELECTOR=
TENCENT_SUCCESS_SELECTOR=

# Required only for examples whose filename ends with _proxy.py.
PROXY_TYPE=http
PROXY_ADDRESS=1.2.3.4
Expand Down
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ They show how to detect captcha parameters on a live page, create the correct AP
task, receive a solution, and apply it in a Selenium browser session.

The repository includes reCAPTCHA v2 and v3, callback-based reCAPTCHA,
Cloudflare Turnstile and Challenge pages, image recognition, coordinate captchas,
and proxy-based flows. SeleniumBase manages Chrome while standard Selenium APIs
handle elements, waits, scripts, and mouse actions.
Cloudflare Turnstile and Challenge pages, Tencent CAPTCHA, image recognition,
coordinate captchas, and proxy-based flows. SeleniumBase manages Chrome while
standard Selenium APIs handle elements, waits, scripts, and mouse actions.

## Contents

Expand All @@ -19,6 +19,7 @@ handle elements, waits, scripts, and mouse actions.
- [Available examples](#available-examples)
- [reCAPTCHA examples](#recaptcha-examples)
- [Cloudflare examples](#cloudflare-examples)
- [Tencent CAPTCHA](#tencent-captcha)
- [Image and coordinate examples](#image-and-coordinate-examples)
- [Proxy configuration](#proxy-configuration)
- [How the examples work](#how-the-examples-work)
Expand Down Expand Up @@ -117,6 +118,7 @@ The API key is never printed.
| reCAPTCHA v3 | [`recaptcha_v3_extended_js_script.py`](examples/recaptcha_v3_extended_js_script.py) | Compatibility entry point for script-based discovery |
| Cloudflare Turnstile | [`cloudflare_turnstile.py`](examples/cloudflare_turnstile.py) | Solve an embedded widget and submit its form |
| Cloudflare Challenge | [`cloudflare_challenge_page.py`](examples/cloudflare_challenge_page.py) | Intercept dynamic parameters and invoke the captured callback |
| Tencent CAPTCHA | [`tencent.py`](examples/tencent.py) | Capture `appId` and return the complete solution to the original callback |
| Image captcha | [`normal_captcha_screenshot.py`](examples/normal_captcha_screenshot.py) | Capture the element as a screenshot |
| Image captcha | [`normal_captcha_canvas.py`](examples/normal_captcha_canvas.py) | Extract the original image through canvas |
| Image captcha + hints | [`normal_captcha_screenshot_params.py`](examples/normal_captcha_screenshot_params.py) | Supply numeric and length constraints |
Expand Down Expand Up @@ -198,6 +200,29 @@ python examples/cloudflare_challenge_page.py
If the page already reports a successful challenge, the example exits without
creating another paid task.

## Tencent CAPTCHA

[`tencent.py`](examples/tencent.py) installs an interception script before page
navigation. It captures the page's `TencentCaptcha` constructor call, creates a
`TencentTaskProxyless`, and passes the complete solution to the original callback.

The example does not include a target site. Set your own page and the element
that confirms successful verification:

```dotenv
TARGET_URL=https://your-site.example/tencent-captcha
TENCENT_TRIGGER_SELECTOR=#open-captcha
TENCENT_SUCCESS_SELECTOR=.verification-success
```

Leave `TENCENT_TRIGGER_SELECTOR` blank when the page opens Tencent CAPTCHA
automatically. `TENCENT_SUCCESS_SELECTOR` is required so the example verifies
that the page accepted the answer.

```bash
python examples/tencent.py
```

## Image and coordinate examples

### Image captcha
Expand Down Expand Up @@ -263,7 +288,8 @@ Every script follows the same reusable sequence:
2. Extract fresh captcha parameters or image data from the loaded page.
3. Create the matching task from `captcha_solver_api.tasks`.
4. Call `CaptchaClient.solve()` and wait for the solution.
5. Apply the returned token, text, or coordinates in the same browser session.
5. Apply the returned token, text, coordinates, or callback data in the same
browser session.
6. Wait for the page's success state.

The shared helpers in [`examples/common.py`](examples/common.py) load `.env`,
Expand Down Expand Up @@ -303,6 +329,12 @@ The interception script must run before the refreshed page initializes Turnstile
Use the provided Challenge example as the entry point so it can register the CDP
script and perform the required refresh.

### Tencent parameters are not captured

The target page must construct `TencentCaptcha` after the interception script is
installed. Set `TENCENT_TRIGGER_SELECTOR` to the control that opens the captcha,
or leave it blank when the page opens the captcha during load.

### A proxy example times out

Confirm that the proxy accepts connections, that its credentials are valid, and
Expand Down
113 changes: 113 additions & 0 deletions examples/tencent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""Solve Tencent CAPTCHA on a user-configured page."""

import os
import time

from captcha_solver_api.tasks import TencentTaskProxyless
from common import create_client, logged_example, required_env, wait_for_css
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from seleniumbase import Driver

TENCENT_INTERCEPT_SCRIPT = r"""
(() => {
const records = new Map();
const wrappers = new WeakMap();
let constructor;
let sequence = 0;

const wrap = (Original, scriptUrl) => {
if (typeof Original !== 'function') return Original;
if (wrappers.has(Original)) return wrappers.get(Original);
const Wrapped = new Proxy(Original, {
construct(Target, args, newTarget) {
const offset = typeof args[1] === 'function' ? 0 : 1;
const appId = args[offset];
const callback = args[offset + 1];
if (!appId || typeof callback !== 'function') {
return Reflect.construct(Target, args, newTarget);
}
const id = `tencent-${++sequence}`;
const params = {
id,
websiteURL: location.href,
appId: String(appId),
captchaScript: scriptUrl || [...document.scripts]
.map(script => script.src)
.find(src => /\/TCaptcha-global\.js(?:\?|$)/i.test(src))
};
const nativeArgs = [...args];
nativeArgs[offset + 1] = () => {};
const instance = Reflect.construct(Target, nativeArgs, newTarget);
instance.show = () => {};
records.set(id, {params, callback, submitted: false});
return instance;
}
});
wrappers.set(Original, Wrapped);
wrappers.set(Wrapped, Wrapped);
return Wrapped;
};

Object.defineProperty(window, '__captchaSolverTencent', {value: {
pending: () => [...records.values()]
.filter(record => !record.submitted)
.map(record => record.params),
submit(id, solution) {
const record = records.get(id);
if (!record || record.submitted) throw new Error('Tencent CAPTCHA callback is missing.');
record.submitted = true;
record.callback(solution);
}
}});

const existing = window.TencentCaptcha;
Object.defineProperty(window, 'TencentCaptcha', {
configurable: true,
enumerable: true,
get: () => constructor,
set: value => { constructor = wrap(value, document.currentScript?.src); }
});
if (existing) window.TencentCaptcha = existing;
})();
"""


@logged_example
def main() -> None:
target_url = required_env("TARGET_URL")
success_selector = required_env("TENCENT_SUCCESS_SELECTOR")

with Driver(browser="chrome", headless=False) as driver, create_client() as client:
driver.execute_cdp_cmd(
"Page.addScriptToEvaluateOnNewDocument", {"source": TENCENT_INTERCEPT_SCRIPT}
)
driver.get(target_url)

trigger_selector = os.getenv("TENCENT_TRIGGER_SELECTOR")
if trigger_selector:
WebDriverWait(driver, 30).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, trigger_selector))
).click()

params = WebDriverWait(driver, 30).until(
lambda browser: browser.execute_script(
"return window.__captchaSolverTencent?.pending()[0] || null"
)
)
record_id = params.pop("id")
solution = client.solve(TencentTaskProxyless(**params))
driver.execute_script(
"window.__captchaSolverTencent.submit(arguments[0], arguments[1])",
record_id,
solution,
)
message = wait_for_css(driver, success_selector)
WebDriverWait(driver, 30).until(EC.visibility_of(message))
print(message.text)
time.sleep(5)


if __name__ == "__main__":
main()
Loading