From 9b71a3cd7d8c199882d6db4930afba3fc7d79ae8 Mon Sep 17 00:00:00 2001 From: dariyaanis Date: Sat, 10 Oct 2026 00:59:04 +0300 Subject: [PATCH] feat: add Tencent captcha example --- .env.example | 4 ++ README.md | 40 ++++++++++++++-- examples/tencent.py | 113 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 examples/tencent.py diff --git a/.env.example b/.env.example index 7bd38a7..2338724 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/README.md b/README.md index c735784..6b8c696 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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) @@ -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 | @@ -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 @@ -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`, @@ -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 diff --git a/examples/tencent.py b/examples/tencent.py new file mode 100644 index 0000000..42a0045 --- /dev/null +++ b/examples/tencent.py @@ -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()