""" 雨云 (Rainyun) 业务接口适配器 (Rainyun Captcha Adapter) 将腾讯天御点选验证码纯协议客户端无缝注入雨云核心业务流程: 1. 任务签到与积分领取: POST /user/reward/tasks (带 vticket 与 vrandstr) 2. 账号密码登录: POST /user/login (带 vticket 与 vrandstr) """ import json import logging from typing import Dict, Optional, Tuple import httpx from tcaptcha.client import TencentCaptchaClient logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger("rainyun_adapter") class RainyunAdapter: API_BASE = "https://api.v2.rainyun.com" RAINYUN_AID = "2039519451" def __init__( self, api_key: Optional[str] = None, cookie: Optional[str] = None, timeout: float = 15.0, ): self.api_key = api_key self.cookie = cookie self.timeout = timeout self.captcha_client = TencentCaptchaClient(aid=self.RAINYUN_AID) headers = { "User-Agent": ( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36" ), "Referer": "https://app.rainyun.com/", "Origin": "https://app.rainyun.com", "Accept": "application/json, text/plain, */*", } if self.api_key: headers["X-Api-Key"] = self.api_key if self.cookie: headers["Cookie"] = self.cookie self.http_client = httpx.Client( base_url=self.API_BASE, trust_env=False, headers=headers, timeout=self.timeout, ) def get_captcha_ticket(self) -> Tuple[Optional[str], Optional[str]]: """调用纯协议验证码求解管线,获取有效 ticket 与 randstr""" logger.info("正在调用腾讯天御验证码求解客户端...") res = self.captcha_client.solve() if res["success"]: logger.info(f"验证码求解成功 (耗时: {res['elapsed_ms']}ms, randstr: {res['randstr']})") return res["ticket"], res["randstr"] else: logger.error(f"验证码求解失败: {res['error']}") return None, None def claim_reward_task(self, task_name: str = "加入用户群") -> Dict: """ 带验证码提交雨云积分任务 接口: POST https://api.v2.rainyun.com/user/reward/tasks """ ticket, randstr = self.get_captcha_ticket() if not ticket or not randstr: return {"code": -1, "message": "验证码获取失败"} payload = { "task_name": task_name, "verifyCode": "", "vticket": ticket, "vrandstr": randstr, } logger.info(f"正在提交雨云任务: {task_name}...") resp = self.http_client.post("/user/reward/tasks", json=payload) try: res_json = resp.json() logger.info(f"雨云任务接口返回: {res_json}") return res_json except Exception as e: logger.error(f"雨云任务接口解析失败: {e}") return {"code": resp.status_code, "text": resp.text} def login(self, field: str, password: str) -> Dict: """ 雨云用户登录 (自动注入验证码凭证) 接口: POST https://api.v2.rainyun.com/user/login """ ticket, randstr = self.get_captcha_ticket() if not ticket or not randstr: return {"code": -1, "message": "验证码获取失败"} payload = { "field": field, "password": password, "verifyCode": "", "vticket": ticket, "vrandstr": randstr, } logger.info(f"正在发起雨云登录: {field}...") resp = self.http_client.post("/user/login", json=payload) try: return resp.json() except Exception as e: return {"code": resp.status_code, "text": resp.text}