From 32e926c4af4e36ac99935bb79c964ce24b1a47ae Mon Sep 17 00:00:00 2001 From: kyx236 Date: Sun, 25 Jan 2026 05:40:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + api_register.py | 624 ++++++++++++++++++++++++++++++++++++++++++ auto_gpt_team.py | 405 ++++++++++++++++++++++++++- browser_automation.py | 107 +++++++- config.toml.example | 9 + pyproject.toml | 1 + requirements.txt | 3 + telegram_bot.py | 266 +++++++++++++++++- uv.lock | 91 ++++++ 9 files changed, 1495 insertions(+), 13 deletions(-) create mode 100644 api_register.py diff --git a/.gitignore b/.gitignore index 5edfdb1..cad2b74 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ nul .claude/settings.local.json autogptplus_drission.py +autogptplus_drission_oai.py +accounts.json diff --git a/api_register.py b/api_register.py new file mode 100644 index 0000000..541b5c8 --- /dev/null +++ b/api_register.py @@ -0,0 +1,624 @@ +""" +ChatGPT API 注册模块 (协议模式) +- 使用 curl_cffi 通过 API 快速完成注册 +- 支持 Cookie 注入到浏览器完成支付 +""" + +import time +import random +import re +import uuid +from urllib.parse import unquote + +try: + from curl_cffi import requests as curl_requests + CURL_CFFI_AVAILABLE = True +except ImportError: + CURL_CFFI_AVAILABLE = False + curl_requests = None + +import requests + + +def log_status(step, message): + """日志输出""" + timestamp = time.strftime("%H:%M:%S") + print(f"[{timestamp}] [{step}] {message}") + + +def log_progress(message): + """进度输出""" + print(f" -> {message}") + + +def request_with_retry(func, *args, max_retries=3, **kwargs): + """带重试的请求""" + for i in range(max_retries): + try: + return func(*args, **kwargs) + except Exception as e: + if i == max_retries - 1: + raise e + time.sleep(1) + + +class ChatGPTAPIRegister: + """ChatGPT API 注册类 (协议模式)""" + + def __init__(self, proxy=None): + """初始化 + + Args: + proxy: 代理地址,如 "http://127.0.0.1:7890" + """ + if not CURL_CFFI_AVAILABLE: + raise ImportError("协议模式需要安装 curl_cffi: pip install curl_cffi") + + self.session = curl_requests.Session( + impersonate="edge", + verify=False, + proxies={"http": proxy, "https": proxy} if proxy else {} + ) + self.auth_session_logging_id = str(uuid.uuid4()) + self.oai_did = "" + self.csrf_token = "" + self.authorize_url = "" + self.access_token = "" + + def init_session(self) -> bool: + """初始化会话,获取必要的 cookies 和 tokens""" + try: + resp = request_with_retry(self.session.get, "https://chatgpt.com") + if resp.status_code != 200: + log_progress(f"[X] 初始化失败: HTTP {resp.status_code}") + return False + + self.oai_did = self.session.cookies.get("oai-did") + csrf_cookie = self.session.cookies.get("__Host-next-auth.csrf-token") + + if csrf_cookie: + self.csrf_token = unquote(csrf_cookie).split("|")[0] + else: + log_progress("[X] 未获取到 CSRF token") + return False + + # 访问登录页面 + request_with_retry( + self.session.get, + f"https://chatgpt.com/auth/login?openaicom-did={self.oai_did}" + ) + return True + except Exception as e: + log_progress(f"[X] 初始化异常: {e}") + return False + + def get_authorize_url(self, email: str) -> bool: + """获取授权 URL""" + try: + url = f"https://chatgpt.com/api/auth/signin/openai?prompt=login&ext-oai-did={self.oai_did}&auth_session_logging_id={self.auth_session_logging_id}&screen_hint=login_or_signup&login_hint={email}" + payload = { + "callbackUrl": "https://chatgpt.com/", + "csrfToken": self.csrf_token, + "json": "true" + } + resp = request_with_retry( + self.session.post, url, data=payload, + headers={"Origin": "https://chatgpt.com"} + ) + data = resp.json() + + if data.get("url") and "auth.openai.com" in data["url"]: + self.authorize_url = data["url"] + return True + + log_progress(f"[X] 授权 URL 无效: {data}") + return False + except Exception as e: + log_progress(f"[X] 获取授权 URL 异常: {e}") + return False + + def start_authorize(self) -> bool: + """启动授权流程""" + try: + resp = request_with_retry( + self.session.get, self.authorize_url, allow_redirects=True + ) + return "create-account" in resp.url or "log-in" in resp.url + except Exception as e: + log_progress(f"[X] 启动授权异常: {e}") + return False + + def register(self, email: str, password: str) -> bool: + """注册账户""" + try: + resp = request_with_retry( + self.session.post, + "https://auth.openai.com/api/accounts/user/register", + json={"password": password, "username": email}, + headers={ + "Content-Type": "application/json", + "Origin": "https://auth.openai.com" + } + ) + if resp.status_code == 200: + return True + log_progress(f"[X] 注册失败: {resp.status_code} - {resp.text[:200]}") + return False + except Exception as e: + log_progress(f"[X] 注册异常: {e}") + return False + + def send_verification_email(self) -> bool: + """发送验证邮件""" + try: + resp = request_with_retry( + self.session.get, + "https://auth.openai.com/api/accounts/email-otp/send", + allow_redirects=True + ) + return resp.status_code == 200 + except Exception as e: + log_progress(f"[X] 发送验证邮件异常: {e}") + return False + + def validate_otp(self, otp_code: str) -> bool: + """验证 OTP 码""" + try: + resp = request_with_retry( + self.session.post, + "https://auth.openai.com/api/accounts/email-otp/validate", + json={"code": otp_code}, + headers={ + "Content-Type": "application/json", + "Origin": "https://auth.openai.com" + } + ) + if resp.status_code == 200: + return True + log_progress(f"[X] OTP 验证失败: {resp.status_code} - {resp.text[:200]}") + return False + except Exception as e: + log_progress(f"[X] OTP 验证异常: {e}") + return False + + def create_account(self, name: str, birthdate: str) -> bool: + """创建账户 (填写姓名和生日) + + Args: + name: 姓名 + birthdate: 生日,格式 "YYYY-MM-DD" + """ + try: + resp = request_with_retry( + self.session.post, + "https://auth.openai.com/api/accounts/create_account", + json={"name": name, "birthdate": birthdate}, + headers={ + "Content-Type": "application/json", + "Origin": "https://auth.openai.com" + } + ) + if resp.status_code != 200: + log_progress(f"[X] 创建账户失败: {resp.status_code} - {resp.text[:200]}") + return False + + data = resp.json() + continue_url = data.get("continue_url") + if continue_url: + request_with_retry(self.session.get, continue_url, allow_redirects=True) + return True + except Exception as e: + log_progress(f"[X] 创建账户异常: {e}") + return False + + def login(self, email: str, password: str) -> bool: + """使用密码登录 (复用注册时建立的会话)""" + try: + resp = request_with_retry( + self.session.post, + "https://auth.openai.com/api/accounts/password/verify", + json={"username": email, "password": password}, + headers={ + "Content-Type": "application/json", + "Origin": "https://auth.openai.com" + } + ) + + if resp.status_code == 200: + data = resp.json() + continue_url = data.get("continue_url") + if continue_url: + request_with_retry(self.session.get, continue_url, allow_redirects=True) + return True + + log_progress(f"[X] 登录失败: {resp.status_code} - {resp.text[:200]}") + return False + except Exception as e: + log_progress(f"[X] 登录异常: {e}") + return False + + def get_session_token(self) -> str: + """获取 access token""" + try: + resp = request_with_retry(self.session.get, "https://chatgpt.com/api/auth/session") + if resp.status_code == 200: + data = resp.json() + token = data.get("accessToken") + if token: + self.access_token = token + return token + log_progress(f"[X] Session 响应无 token: {str(data)[:200]}") + else: + log_progress(f"[X] Session 请求失败: {resp.status_code}") + return "" + except Exception as e: + log_progress(f"[X] 获取 token 异常: {e}") + return "" + + def get_checkout_url(self) -> str: + """通过 API 获取支付页 URL""" + try: + token = self.access_token or self.get_session_token() + if not token: + log_progress("[X] 无法获取 access token") + return "" + + payload = { + "plan_name": "chatgptteamplan", + "team_plan_data": { + "workspace_name": "Sepa", + "price_interval": "month", + "seat_quantity": 5 + }, + "billing_details": { + "country": "DE", + "currency": "EUR" + }, + "promo_campaign": { + "promo_campaign_id": "team-1-month-free", + "is_coupon_from_query_param": True + }, + "checkout_ui_mode": "redirect" + } + + resp = request_with_retry( + self.session.post, + "https://chatgpt.com/backend-api/payments/checkout", + json=payload, + headers={ + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + "Origin": "https://chatgpt.com" + } + ) + + if resp.status_code == 200: + data = resp.json() + checkout_url = data.get("url") + if checkout_url: + return checkout_url + log_progress(f"[X] 响应无 URL: {resp.text[:200]}") + else: + log_progress(f"[X] 获取支付页失败: {resp.status_code} - {resp.text[:200]}") + return "" + except Exception as e: + log_progress(f"[X] 获取支付页异常: {e}") + return "" + + def get_cookies(self) -> list: + """获取所有 cookies 用于注入浏览器""" + cookies = [] + for cookie in self.session.cookies.jar: + cookies.append({ + "name": cookie.name, + "value": cookie.value, + "domain": cookie.domain, + "path": cookie.path or "/", + "secure": cookie.secure, + }) + return cookies + + +def get_verification_code_api(target_email: str, mail_api_base: str, mail_api_token: str, max_retries: int = 90) -> str: + """通过 API 获取验证码 + + Args: + target_email: 目标邮箱 + mail_api_base: 邮件 API 地址 + mail_api_token: 邮件 API Token + max_retries: 最大重试次数 + + Returns: + str: 验证码,失败返回空字符串 + """ + log_status("API监听", "正在监听邮件...") + headers = {"Authorization": mail_api_token, "Content-Type": "application/json"} + start_time = time.time() + + for i in range(max_retries): + elapsed = int(time.time() - start_time) + try: + url = f"{mail_api_base}/api/public/emailList" + payload = {"toEmail": target_email, "timeSort": "desc", "size": 20} + resp = requests.post(url, headers=headers, json=payload, timeout=10) + + if resp.status_code == 200: + data = resp.json() + if data.get('code') == 200: + mails = data.get('data', []) + if mails: + for mail in mails: + html_body = mail.get('content') or mail.get('text') or str(mail) + code_match = re.search(r'\b(\d{6})\b', html_body) + if code_match: + code = code_match.group(1) + log_status("捕获", f"[OK] 提取到验证码: {code}") + return code + except Exception: + pass + + if i % 5 == 0: + print(f" [监听中] 已耗时 {elapsed}秒...") + time.sleep(2) + + log_status("超时", "[X] 未能获取验证码") + return "" + + +def api_register_flow( + email: str, + password: str, + real_name: str, + birthdate: str, + mail_api_base: str, + mail_api_token: str, + proxy: str = None, + progress_callback=None +) -> ChatGPTAPIRegister: + """执行 API 注册流程 + + Args: + email: 邮箱 + password: 密码 + real_name: 姓名 + birthdate: 生日 (YYYY-MM-DD) + mail_api_base: 邮件 API 地址 + mail_api_token: 邮件 API Token + proxy: 代理地址 + progress_callback: 进度回调 + + Returns: + ChatGPTAPIRegister: 成功返回 reg 对象,失败返回 None + """ + def log_cb(msg): + if progress_callback: + progress_callback(msg) + log_progress(msg) + + reg = ChatGPTAPIRegister(proxy=proxy) + + try: + log_status("API注册", "初始化会话...") + if not reg.init_session(): + log_cb("[X] 初始化失败") + return None + log_cb("[OK] 会话初始化成功") + + log_status("API注册", "获取授权 URL...") + if not reg.get_authorize_url(email): + log_cb("[X] 获取授权 URL 失败") + return None + log_cb("[OK] 授权 URL 获取成功") + + log_status("API注册", "开始授权流程...") + if not reg.start_authorize(): + log_cb("[X] 授权流程启动失败") + return None + log_cb("[OK] 授权流程已启动") + + log_status("API注册", "注册账户...") + if not reg.register(email, password): + log_cb("[X] 注册失败") + return None + log_cb("[OK] 账户注册成功") + + log_status("API注册", "发送验证邮件...") + if not reg.send_verification_email(): + log_cb("[X] 发送验证邮件失败") + return None + log_cb("[OK] 验证邮件已发送") + + # 获取验证码 + otp_code = get_verification_code_api(email, mail_api_base, mail_api_token) + if not otp_code: + log_cb("[X] 未能获取验证码") + return None + + log_status("API注册", f"验证 OTP: {otp_code}") + if not reg.validate_otp(otp_code): + log_cb("[X] OTP 验证失败") + return None + log_cb("[OK] OTP 验证成功") + + log_status("API注册", "创建账户...") + if not reg.create_account(real_name, birthdate): + log_cb("[X] 创建账户失败") + return None + log_cb("[OK] 账户创建成功") + + # 验证 session 是否有效 + token = reg.get_session_token() + if token: + log_cb(f"[OK] Session 有效,Token: {token[:30]}...") + else: + log_cb("[!] 注册完成但 session 可能未完全建立") + + return reg + + except Exception as e: + log_status("错误", f"注册异常: {e}") + return None + + +def api_login_flow( + email: str, + password: str, + proxy: str = None, + progress_callback=None +) -> ChatGPTAPIRegister: + """执行 API 登录流程 + + Args: + email: 邮箱 + password: 密码 + proxy: 代理地址 + progress_callback: 进度回调 + + Returns: + ChatGPTAPIRegister: 成功返回 reg 对象,失败返回 None + """ + def log_cb(msg): + if progress_callback: + progress_callback(msg) + log_progress(msg) + + reg = ChatGPTAPIRegister(proxy=proxy) + + try: + log_status("API登录", "初始化会话...") + if not reg.init_session(): + log_cb("[X] 初始化失败") + return None + log_cb("[OK] 初始化成功") + + log_status("API登录", "获取授权 URL...") + if not reg.get_authorize_url(email): + log_cb("[X] 获取授权 URL 失败") + return None + log_cb("[OK] 获取授权 URL 成功") + + log_status("API登录", "开始授权流程...") + if not reg.start_authorize(): + log_cb("[X] 授权流程失败") + return None + log_cb("[OK] 授权流程成功") + + log_status("API登录", "密码验证...") + if not reg.login(email, password): + log_cb("[X] 登录失败") + return None + log_cb("[OK] 登录成功") + + # 获取 token + token = reg.get_session_token() + if token: + log_status("API登录", f"Token: {token[:50]}...") + + return reg + + except Exception as e: + log_status("错误", f"登录异常: {e}") + return None + + +def is_api_mode_available() -> bool: + """检查协议模式是否可用""" + return CURL_CFFI_AVAILABLE + + +def api_register_account_only( + email: str, + password: str, + real_name: str, + birthdate: str, + get_verification_code_func, + proxy: str = None, + progress_callback=None +) -> bool: + """仅执行 API 注册流程(不含支付,用于邀请邮箱注册) + + Args: + email: 邮箱 + password: 密码 + real_name: 姓名 + birthdate: 生日 (YYYY-MM-DD) + get_verification_code_func: 获取验证码的函数,签名: func(email) -> str + proxy: 代理地址 + progress_callback: 进度回调 + + Returns: + bool: 是否注册成功 + """ + def log_cb(msg): + if progress_callback: + progress_callback(msg) + log_progress(msg) + + if not CURL_CFFI_AVAILABLE: + log_status("错误", "协议模式不可用,请安装 curl_cffi") + return False + + reg = ChatGPTAPIRegister(proxy=proxy) + + try: + log_status("API注册", "初始化会话...") + if not reg.init_session(): + log_cb("[X] 初始化失败") + return False + log_cb("[OK] 会话初始化成功") + + log_status("API注册", "获取授权 URL...") + if not reg.get_authorize_url(email): + log_cb("[X] 获取授权 URL 失败") + return False + log_cb("[OK] 授权 URL 获取成功") + + log_status("API注册", "开始授权流程...") + if not reg.start_authorize(): + log_cb("[X] 授权流程启动失败") + return False + log_cb("[OK] 授权流程已启动") + + log_status("API注册", "注册账户...") + if not reg.register(email, password): + log_cb("[X] 注册失败") + return False + log_cb("[OK] 账户注册成功") + + log_status("API注册", "发送验证邮件...") + if not reg.send_verification_email(): + log_cb("[X] 发送验证邮件失败") + return False + log_cb("[OK] 验证邮件已发送") + + # 使用传入的函数获取验证码 + log_status("API注册", "等待验证码...") + otp_code = get_verification_code_func(email) + if not otp_code: + log_cb("[X] 未能获取验证码") + return False + + log_status("API注册", f"验证 OTP: {otp_code}") + if not reg.validate_otp(otp_code): + log_cb("[X] OTP 验证失败") + return False + log_cb("[OK] OTP 验证成功") + + log_status("API注册", "创建账户...") + if not reg.create_account(real_name, birthdate): + log_cb("[X] 创建账户失败") + return False + log_cb("[OK] 账户创建成功") + + # 验证 session 是否有效 + token = reg.get_session_token() + if token: + log_cb(f"[OK] 注册完成,Token: {token[:30]}...") + return True + else: + log_cb("[!] 注册完成但 session 可能未完全建立") + return True # 仍然返回成功,因为注册流程已完成 + + except Exception as e: + log_status("错误", f"注册异常: {e}") + return False diff --git a/auto_gpt_team.py b/auto_gpt_team.py index 68d5d22..9aeee9f 100644 --- a/auto_gpt_team.py +++ b/auto_gpt_team.py @@ -2,7 +2,11 @@ Author: muyyg Project: Subscription Automation (DrissionPage Version) Created: 2026-01-12 -Version: 3.0-drission +Version: 3.1-hybrid (支持协议模式) + +模式说明: +- browser: 浏览器自动化模式 (默认),全程使用 DrissionPage 浏览器自动化 +- api: 协议模式,使用 API 快速完成注册,仅支付环节使用浏览器 """ import time @@ -13,10 +17,27 @@ import sys import os import platform import subprocess +import tempfile import requests from pathlib import Path from DrissionPage import ChromiumPage, ChromiumOptions +# 导入协议模式模块 +try: + from api_register import ( + ChatGPTAPIRegister, + api_register_flow, + api_login_flow, + is_api_mode_available, + get_verification_code_api, + ) + API_MODE_AVAILABLE = is_api_mode_available() +except ImportError: + API_MODE_AVAILABLE = False + ChatGPTAPIRegister = None + api_register_flow = None + api_login_flow = None + # ================= 配置加载 ================= try: import tomllib @@ -63,6 +84,13 @@ SEPA_IBANS = _autogptplus.get("sepa_ibans", []) # 6. 随机指纹开关 RANDOM_FINGERPRINT = _autogptplus.get("random_fingerprint", True) +# 7. 注册模式: "browser" (浏览器自动化) 或 "api" (协议模式) +# 默认使用 API 模式(更快),如果 curl_cffi 不可用则自动回退到浏览器模式 +REGISTER_MODE = _autogptplus.get("register_mode", "api") + +# 8. 协议模式代理 (仅协议模式使用) +API_PROXY = _autogptplus.get("api_proxy", "") + # ================= 浏览器指纹 ================= FINGERPRINTS = [ # NVIDIA 显卡 @@ -1198,6 +1226,166 @@ def run_payment_flow(page, email, step_callback=None): log_status("错误", f"[X] 支付流程异常: {e}") return None + +def browser_pay_with_cookies(reg, email: str, proxy: str = None, headless: bool = True, step_callback=None): + """使用 API 会话的 cookies 注入浏览器完成支付 (协议模式专用) + + Args: + reg: ChatGPTAPIRegister 对象 + email: 邮箱 + proxy: 代理地址 + headless: 是否无头模式 + step_callback: 步骤回调 + + Returns: + dict: {"token": ..., "account_id": ...} 或 None + """ + def step_cb(step): + if step_callback: + step_callback(step) + + step_cb("获取支付页 URL...") + + # 通过 API 获取支付页 URL + checkout_url = reg.get_checkout_url() + if not checkout_url: + log_status("失败", "无法获取支付页 URL") + return None + + log_progress(f"[OK] 支付页: {checkout_url[:60]}...") + + # 获取 cookies + cookies = reg.get_cookies() + log_status("Cookie", f"获取到 {len(cookies)} 个 cookies") + + # 启动浏览器 + step_cb("启动浏览器...") + temp_user_data = tempfile.mkdtemp(prefix="chrome_api_") + + # 检测操作系统 + is_linux = platform.system() == "Linux" + + # 获取随机指纹 + fingerprint = None + if RANDOM_FINGERPRINT: + fingerprint = get_random_fingerprint() + else: + fingerprint = { + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", + "platform": "Win32", + "webgl_vendor": "Google Inc. (NVIDIA)", + "webgl_renderer": "ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0)", + "screen": {"width": 1920, "height": 1080} + } + + co = ChromiumOptions() + co.set_argument('--no-first-run') + co.set_argument('--no-default-browser-check') + co.set_argument(f'--user-data-dir={temp_user_data}') + co.set_argument('--disable-blink-features=AutomationControlled') + co.set_argument('--disable-infobars') + co.set_argument('--disable-dev-shm-usage') + co.set_argument('--no-sandbox') + co.set_argument(f'--user-agent={fingerprint["user_agent"]}') + + screen = fingerprint.get("screen", {"width": 1920, "height": 1080}) + + if headless: + co.set_argument('--headless=new') + co.set_argument(f'--window-size={screen["width"]},{screen["height"]}') + else: + co.set_argument(f'--window-size={screen["width"]},{screen["height"]}') + + if proxy: + co.set_argument(f'--proxy-server={proxy}') + + if is_linux: + co.set_argument('--disable-software-rasterizer') + co.set_argument('--disable-extensions') + co.set_argument('--disable-setuid-sandbox') + co.set_argument('--single-process') + co.set_argument('--remote-debugging-port=0') + chrome_paths = [ + '/usr/bin/google-chrome', + '/usr/bin/google-chrome-stable', + '/usr/bin/chromium-browser', + '/usr/bin/chromium', + '/snap/bin/chromium', + ] + for chrome_path in chrome_paths: + if os.path.exists(chrome_path): + co.set_browser_path(chrome_path) + break + else: + co.auto_port(True) + co.set_local_port(random.randint(19222, 29999)) + + log_status("浏览器", f"正在启动 ({'无头' if headless else '有头'}模式)...") + page = ChromiumPage(co) + + try: + # 注入指纹 + if RANDOM_FINGERPRINT: + inject_fingerprint(page, fingerprint) + + # 先访问 chatgpt.com 注入 cookies + step_cb("注入登录状态...") + log_status("Cookie", "注入登录状态...") + page.get("https://chatgpt.com") + + injected_count = 0 + for cookie in cookies: + try: + if 'chatgpt.com' in cookie.get('domain', ''): + page.set.cookies({ + 'name': cookie['name'], + 'value': cookie['value'], + 'domain': cookie['domain'].lstrip('.'), + 'path': cookie.get('path', '/'), + }) + injected_count += 1 + except: + pass + + log_progress(f"[OK] 已注入 {injected_count} 个 cookies") + + # 刷新页面确保 cookies 生效 + time.sleep(1) + page.refresh() + time.sleep(1) + + # 直接跳转到支付页 + step_cb("跳转到支付页...") + log_status("订阅", "跳转到支付页...") + page.get(checkout_url) + + try: + page.wait.url_change('pay.openai.com', timeout=15) + log_progress("✓ 已跳转到支付页") + except: + time.sleep(2) + + # 执行支付流程 + step_cb("执行 SEPA 支付...") + result = run_payment_flow(page, email, step_cb) + return result + + except Exception as e: + log_status("错误", f"浏览器流程异常: {e}") + return None + finally: + try: + page.quit() + except: + pass + # 清理临时目录 + try: + import shutil + shutil.rmtree(temp_user_data, ignore_errors=True) + except: + pass + + def fetch_account_id(page, access_token: str) -> str: """通过 API 获取 account_id (使用 requests 直接请求,更快)""" log_status("获取", "正在获取 account_id...") @@ -2013,5 +2201,220 @@ def run_single_registration(progress_callback=None, step_callback=None) -> dict: cleanup_chrome_processes() +def run_single_registration_api(progress_callback=None, step_callback=None, proxy: str = None) -> dict: + """执行单次注册流程 - 协议模式 (API + Cookie 注入浏览器) + + Args: + progress_callback: 进度回调函数 (message: str) + step_callback: 步骤回调函数 (step: str) + proxy: 代理地址 + + Returns: + dict: {"success": bool, "account": str, "password": str, "token": str, "account_id": str, "error": str} + """ + def log_cb(msg): + if progress_callback: + progress_callback(msg) + log_progress(msg) + + def step_cb(step): + if step_callback: + step_callback(step) + + # 检查协议模式是否可用 + if not API_MODE_AVAILABLE: + return {"success": False, "error": "协议模式不可用,请安装 curl_cffi: pip install curl_cffi"} + + # 检查必要配置 + if not MAIL_API_TOKEN or not MAIL_API_BASE: + return {"success": False, "error": "配置错误: 请在 config.toml 中配置 [autogptplus] 段"} + + # 检查域名 + domains = get_email_domains() + if not domains: + return {"success": False, "error": "没有可用的邮箱域名,请先通过 /domain_add 导入"} + + # 检查 IBAN + ibans = get_sepa_ibans() + if not ibans: + return {"success": False, "error": "没有可用的 IBAN,请先通过 /iban_add 导入"} + + step_cb("生成账号信息...") + + # 生成账号信息 + random_str = ''.join(random.choices(string.ascii_lowercase + string.digits, k=15)) + email_domain = random.choice(domains) + email = f"{random_str}{email_domain}" + password = ''.join(random.choices(string.ascii_uppercase, k=2)) + \ + ''.join(random.choices(string.ascii_lowercase, k=8)) + \ + ''.join(random.choices(string.digits, k=2)) + \ + random.choice('!@#$%') + real_name = f"{random.choice(FIRST_NAMES)} {random.choice(LAST_NAMES)}" + + # 生成生日 + year = random.randint(2000, 2004) + month = random.randint(1, 12) + if month in [1, 3, 5, 7, 8, 10, 12]: + max_day = 31 + elif month in [4, 6, 9, 11]: + max_day = 30 + else: + max_day = 29 if year % 4 == 0 else 28 + day = random.randint(1, max_day) + birthdate = f"{year}-{month:02d}-{day:02d}" + + log_status("初始化", f"生成账号: {email}") + log_status("初始化", f"设置密码: {password}") + log_status("初始化", f"姓名: {real_name} | 生日: {birthdate}") + log_status("模式", "协议模式 (API + Cookie 注入)") + + # 使用配置的代理或传入的代理 + use_proxy = proxy or API_PROXY or None + if use_proxy: + log_status("代理", f"使用代理: {use_proxy}") + + try: + # 阶段 1: API 注册 + step_cb("API 快速注册...") + log_status("阶段 1", "========== API 快速注册 ==========") + + reg = api_register_flow( + email=email, + password=password, + real_name=real_name, + birthdate=birthdate, + mail_api_base=MAIL_API_BASE, + mail_api_token=MAIL_API_TOKEN, + proxy=use_proxy, + progress_callback=log_cb + ) + + if not reg: + log_status("失败", "API 注册失败") + return {"success": False, "error": "API 注册失败", "account": email, "password": password} + + log_status("完成", "[OK] API 注册成功!") + + # 阶段 2: Cookie 注入浏览器 + 支付 + step_cb("Cookie 注入浏览器...") + log_status("阶段 2", "========== Cookie 注入浏览器 + 订阅支付 ==========") + + result = browser_pay_with_cookies( + reg=reg, + email=email, + proxy=use_proxy, + headless=True, + step_callback=step_cb + ) + + if result and result.get("stopped"): + log_status("停止", "⚠ 注册被用户停止") + return {"success": False, "error": "用户停止", "stopped": True, "account": email, "password": password} + elif result and result.get("token"): + step_cb("注册成功!") + log_status("完成", "✓ 全部流程完成!") + return { + "success": True, + "account": email, + "password": password, + "token": result["token"], + "account_id": result.get("account_id", "") + } + + # 如果 Cookie 注入失败,尝试 API 登录方式 + log_status("重试", "Cookie 注入失败,尝试 API 登录...") + step_cb("尝试 API 登录...") + + reg2 = api_login_flow( + email=email, + password=password, + proxy=use_proxy, + progress_callback=log_cb + ) + + if reg2: + result = browser_pay_with_cookies( + reg=reg2, + email=email, + proxy=use_proxy, + headless=True, + step_callback=step_cb + ) + + if result and result.get("token"): + step_cb("注册成功!") + log_status("完成", "✓ 全部流程完成!") + return { + "success": True, + "account": email, + "password": password, + "token": result["token"], + "account_id": result.get("account_id", "") + } + + log_status("失败", "注册成功但支付/获取token失败") + return {"success": False, "error": "支付流程失败", "account": email, "password": password} + + except Exception as e: + error_msg = str(e) + if _is_connection_lost(error_msg) or _is_shutdown_requested(): + log_status("停止", "⚠ 注册被用户停止") + return {"success": False, "error": "用户停止", "stopped": True, "account": email, "password": password} + log_status("错误", f"注册异常: {e}") + return {"success": False, "error": str(e), "account": email, "password": password} + finally: + cleanup_chrome_processes() + + +def run_single_registration_auto(progress_callback=None, step_callback=None, mode: str = None) -> dict: + """自动选择模式执行注册 + + Args: + progress_callback: 进度回调 + step_callback: 步骤回调 + mode: 强制指定模式 ("browser" / "api"),None 则使用配置 + + Returns: + dict: 注册结果 + """ + use_mode = mode or REGISTER_MODE + + if use_mode == "api": + if not API_MODE_AVAILABLE: + log_status("警告", "协议模式不可用,回退到浏览器模式") + return run_single_registration(progress_callback, step_callback) + return run_single_registration_api(progress_callback, step_callback) + else: + return run_single_registration(progress_callback, step_callback) + + +def get_register_mode() -> str: + """获取当前注册模式""" + return REGISTER_MODE + + +def set_register_mode(mode: str) -> bool: + """设置注册模式 (运行时) + + Args: + mode: "browser" 或 "api" + + Returns: + bool: 是否设置成功 + """ + global REGISTER_MODE + if mode in ("browser", "api"): + if mode == "api" and not API_MODE_AVAILABLE: + return False + REGISTER_MODE = mode + return True + return False + + +def is_api_mode_supported() -> bool: + """检查协议模式是否支持""" + return API_MODE_AVAILABLE + + if __name__ == "__main__": run_main_process() diff --git a/browser_automation.py b/browser_automation.py index eef05b7..7134cc3 100644 --- a/browser_automation.py +++ b/browser_automation.py @@ -1,6 +1,7 @@ # ==================== 浏览器自动化模块 ==================== # 处理 OpenAI 注册、Codex 授权等浏览器自动化操作 # 使用 DrissionPage 替代 Selenium +# 支持协议模式 (API) 和浏览器模式 import time import random @@ -34,6 +35,17 @@ from s2a_service import ( ) from logger import log +# 导入协议模式模块 +try: + from api_register import ( + api_register_account_only, + is_api_mode_available as _is_api_mode_available, + ) + API_MODE_AVAILABLE = _is_api_mode_available() +except ImportError: + API_MODE_AVAILABLE = False + api_register_account_only = None + # 进度更新 (Telegram Bot 使用) try: from bot_notifier import progress_update @@ -945,6 +957,91 @@ def is_logged_in(page, timeout: int = 5) -> bool: return False +def register_openai_account_api(email: str, password: str, proxy: str = None) -> bool: + """使用协议模式 (API) 注册 OpenAI 账号 + + Args: + email: 邮箱地址 + password: 密码 + proxy: 代理地址 (可选) + + Returns: + bool: 是否成功 + """ + if not API_MODE_AVAILABLE: + log.warning("协议模式不可用,回退到浏览器模式") + return None # 返回 None 表示需要回退 + + log.info(f"[API模式] 开始注册 OpenAI 账号: {email}", icon="account") + + # 生成随机姓名和生日 + random_name = get_random_name() + birthday = get_random_birthday() + birthdate = f"{birthday['year']}-{birthday['month']}-{birthday['day']}" + + log.step(f"姓名: {random_name}, 生日: {birthdate}") + + # 定义获取验证码的函数 + def get_code(target_email): + progress_update(phase="注册", step="等待验证码...") + log.step("等待验证码邮件...") + code, error, email_time = unified_get_verification_code(target_email) + if code: + log.success(f"获取到验证码: {code}") + return code + + # 执行 API 注册 + try: + result = api_register_account_only( + email=email, + password=password, + real_name=random_name, + birthdate=birthdate, + get_verification_code_func=get_code, + proxy=proxy, + progress_callback=lambda msg: log.step(msg) + ) + + if result: + log.success(f"[API模式] 注册完成: {email}") + return True + else: + log.warning("[API模式] 注册失败,可能需要回退到浏览器模式") + return False + + except Exception as e: + log.error(f"[API模式] 注册异常: {e}") + return False + + +def register_openai_account_auto(page, email: str, password: str, use_api: bool = True, proxy: str = None) -> bool: + """自动选择模式注册 OpenAI 账号 + + 优先使用 API 模式,失败则回退到浏览器模式 + + Args: + page: 浏览器实例 (用于浏览器模式回退) + email: 邮箱地址 + password: 密码 + use_api: 是否优先使用 API 模式 + proxy: 代理地址 (API 模式使用) + + Returns: + bool: 是否成功 + """ + # 如果启用 API 模式且可用 + if use_api and API_MODE_AVAILABLE: + result = register_openai_account_api(email, password, proxy) + if result is True: + return True + elif result is False: + log.warning("API 模式注册失败,回退到浏览器模式...") + # result is None 表示 API 模式不可用,直接使用浏览器模式 + + # 使用浏览器模式 + return register_openai_account(page, email, password) + + def register_openai_account(page, email: str, password: str) -> bool: """使用浏览器注册 OpenAI 账号 @@ -1976,12 +2073,13 @@ def login_and_authorize_with_otp(email: str) -> tuple[bool, dict]: return False, None -def register_and_authorize(email: str, password: str) -> tuple: +def register_and_authorize(email: str, password: str, use_api_register: bool = True) -> tuple: """完整流程: 注册 OpenAI + Codex 授权 (带重试机制) Args: email: 邮箱地址 password: 密码 + use_api_register: 是否优先使用 API 模式注册 (默认 True) Returns: tuple: (register_success, codex_data) @@ -1992,8 +2090,11 @@ def register_and_authorize(email: str, password: str) -> tuple: with browser_context_with_retry(max_browser_retries=2) as ctx: for attempt in ctx.attempts(): try: - # 注册 OpenAI - register_result = register_openai_account(ctx.page, email, password) + # 注册 OpenAI (优先使用 API 模式) + register_result = register_openai_account_auto( + ctx.page, email, password, + use_api=use_api_register + ) # 检查是否是域名黑名单错误 if register_result == "domain_blacklisted": diff --git a/config.toml.example b/config.toml.example index fc0821e..1b555e1 100644 --- a/config.toml.example +++ b/config.toml.example @@ -234,3 +234,12 @@ email_domains = ["@example.com", "@example.org"] sepa_ibans = [] # 是否启用随机指纹 (User-Agent, WebGL, 分辨率等) random_fingerprint = true + +# 注册模式选择: +# - "api": 协议模式 (默认),使用 API 快速完成注册,仅支付环节使用浏览器 +# 协议模式更快,需要安装 curl_cffi: pip install curl_cffi +# - "browser": 浏览器自动化模式,全程使用 DrissionPage 浏览器自动化 +register_mode = "api" + +# 协议模式代理 (仅协议模式使用,格式: http://127.0.0.1:7890) +api_proxy = "" diff --git a/pyproject.toml b/pyproject.toml index 31b584b..48181f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "OpenAI Team 账号自动批量注册 & CRS 入库工具" readme = "README.md" requires-python = ">=3.12" dependencies = [ + "curl-cffi>=0.14.0", "drissionpage>=4.1.1.2", "python-telegram-bot[job-queue]>=22.5", "requests>=2.32.5", diff --git a/requirements.txt b/requirements.txt index 898208d..45c5b21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,6 @@ requests>=2.32.5 rich>=14.2.0 setuptools>=80.9.0 tomli>=2.3.0 + +# 协议模式依赖 (可选,用于 API 快速注册) +curl_cffi>=0.7.0 diff --git a/telegram_bot.py b/telegram_bot.py index bd521cb..4925087 100644 --- a/telegram_bot.py +++ b/telegram_bot.py @@ -2862,7 +2862,7 @@ class ProvisionerBot: async def _run_team_registration(self, chat_id: int, count: int, output_type: str): """执行 GPT Team 注册任务""" - from auto_gpt_team import run_single_registration, cleanup_chrome_processes + from auto_gpt_team import run_single_registration_auto, cleanup_chrome_processes, get_register_mode import json import threading @@ -2870,6 +2870,10 @@ class ProvisionerBot: success_count = 0 fail_count = 0 + # 获取当前注册模式 + current_mode = get_register_mode() + mode_display = "🌐 协议模式" if current_mode == "api" else "🖥️ 浏览器模式" + # 当前步骤 (用于显示) current_step = ["初始化..."] current_account = [""] @@ -2884,6 +2888,7 @@ class ProvisionerBot: progress_msg = await self.app.bot.send_message( chat_id, f"🚀 开始注册\n\n" + f"模式: {mode_display}\n" f"进度: 0/{count}\n" f"{'▱' * 20}", parse_mode="HTML" @@ -2908,6 +2913,7 @@ class ProvisionerBot: text = ( f"🚀 注册中...\n\n" + f"模式: {mode_display}\n" f"进度: {success_count + fail_count}/{count}\n" f"{progress_bar}\n\n" f"✅ 成功: {success_count}\n" @@ -2947,7 +2953,7 @@ class ProvisionerBot: import functools def run_with_callback(): - return run_single_registration( + return run_single_registration_auto( progress_callback=None, step_callback=step_callback ) @@ -3094,7 +3100,16 @@ class ProvisionerBot: def _get_autogptplus_main_keyboard(self): """获取 AutoGPTPlus 主菜单键盘""" - return InlineKeyboardMarkup([ + # 检查协议模式是否可用 + try: + from auto_gpt_team import is_api_mode_supported, get_register_mode + api_supported = is_api_mode_supported() + current_mode = get_register_mode() + except ImportError: + api_supported = False + current_mode = "browser" + + keyboard = [ [ InlineKeyboardButton("📋 查看配置", callback_data="autogptplus:config"), InlineKeyboardButton("🔑 设置 Token", callback_data="autogptplus:set_token"), @@ -3111,10 +3126,20 @@ class ProvisionerBot: InlineKeyboardButton("📧 测试邮件", callback_data="autogptplus:test_email"), InlineKeyboardButton("🔄 测试 API", callback_data="autogptplus:test_api"), ], - [ - InlineKeyboardButton("🚀 开始注册", callback_data="autogptplus:register"), - ], + ] + + # 添加注册模式选择按钮 + mode_icon = "🌐" if current_mode == "api" else "🖥️" + mode_text = "协议模式" if current_mode == "api" else "浏览器模式" + keyboard.append([ + InlineKeyboardButton(f"⚙️ 注册模式: {mode_icon} {mode_text}", callback_data="autogptplus:select_mode"), ]) + + keyboard.append([ + InlineKeyboardButton("🚀 开始注册", callback_data="autogptplus:register"), + ]) + + return InlineKeyboardMarkup(keyboard) async def callback_autogptplus(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """处理 AutoGPTPlus 回调""" @@ -3148,6 +3173,11 @@ class ProvisionerBot: await self._toggle_autogptplus_fingerprint(query) elif action == "stats": await self._show_autogptplus_stats(query) + + elif action == "select_mode": + await self._show_autogptplus_mode_selection(query) + elif action == "set_mode": + await self._set_autogptplus_mode(query, sub_action) elif action == "register": await self._start_autogptplus_register(query, context) elif action == "back": @@ -3203,7 +3233,8 @@ class ProvisionerBot: try: from auto_gpt_team import ( MAIL_API_TOKEN, MAIL_API_BASE, EMAIL_DOMAINS, - SEPA_IBANS, RANDOM_FINGERPRINT, get_email_domains, get_sepa_ibans + SEPA_IBANS, RANDOM_FINGERPRINT, get_email_domains, get_sepa_ibans, + REGISTER_MODE, API_PROXY, is_api_mode_supported ) # 脱敏显示 Token @@ -3226,6 +3257,19 @@ class ProvisionerBot: # 随机指纹状态 fingerprint_status = "✅ 已开启" if RANDOM_FINGERPRINT else "❌ 已关闭" + # 注册模式 + api_supported = is_api_mode_supported() + if REGISTER_MODE == "api": + mode_display = "🌐 协议模式 (API)" + else: + mode_display = "🖥️ 浏览器模式" + + if not api_supported: + mode_display += " (协议模式不可用)" + + # 代理配置 + proxy_display = API_PROXY if API_PROXY else "未配置" + lines = [ "📋 AutoGPTPlus 配置", "", @@ -3240,8 +3284,10 @@ class ProvisionerBot: "💳 SEPA IBAN", f" 数量: {len(ibans)} 个", "", - "🎭 随机指纹", - f" 状态: {fingerprint_status}", + "⚙️ 注册设置", + f" 模式: {mode_display}", + f" 随机指纹: {fingerprint_status}", + f" API 代理: {proxy_display}", ] # 配置状态检查 @@ -3731,6 +3777,208 @@ class ProvisionerBot: reply_markup=reply_markup ) + async def _show_autogptplus_mode_selection(self, query): + """显示注册模式选择界面""" + try: + from auto_gpt_team import is_api_mode_supported, get_register_mode + + api_supported = is_api_mode_supported() + current_mode = get_register_mode() + + lines = [ + "⚙️ 选择注册模式\n", + "请选择 ChatGPT Team 注册使用的方式:\n", + ] + + # 浏览器模式说明 + browser_check = "✅" if current_mode == "browser" else "⬜" + lines.append(f"{browser_check} 🖥️ 浏览器模式") + lines.append("全程使用 DrissionPage 浏览器自动化") + lines.append("• 兼容性好,无需额外依赖") + lines.append("• 速度较慢,资源占用较高") + lines.append("") + + # 协议模式说明 + api_check = "✅" if current_mode == "api" else "⬜" + api_status = "" if api_supported else " (不可用)" + lines.append(f"{api_check} 🌐 协议模式{api_status}") + lines.append("使用 API 快速注册,仅支付环节用浏览器") + lines.append("• 速度快,资源占用少") + lines.append("• 需要 curl_cffi 依赖") + if not api_supported: + lines.append("• 请安装: pip install curl_cffi") + + # 构建按钮 + keyboard = [] + + # 浏览器模式按钮 + browser_icon = "✅" if current_mode == "browser" else "🖥️" + keyboard.append([ + InlineKeyboardButton( + f"{browser_icon} 浏览器模式", + callback_data="autogptplus:set_mode:browser" + ) + ]) + + # 协议模式按钮 + if api_supported: + api_icon = "✅" if current_mode == "api" else "🌐" + keyboard.append([ + InlineKeyboardButton( + f"{api_icon} 协议模式 (推荐)", + callback_data="autogptplus:set_mode:api" + ) + ]) + else: + keyboard.append([ + InlineKeyboardButton( + "🌐 协议模式 (需安装依赖)", + callback_data="autogptplus:set_mode:api_unavailable" + ) + ]) + + keyboard.append([ + InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back") + ]) + + reply_markup = InlineKeyboardMarkup(keyboard) + + await query.edit_message_text( + "\n".join(lines), + parse_mode="HTML", + reply_markup=reply_markup + ) + + except ImportError as e: + keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.edit_message_text( + f"❌ 模块导入失败\n\n{e}", + parse_mode="HTML", + reply_markup=reply_markup + ) + except Exception as e: + keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.edit_message_text( + f"❌ 获取配置失败\n\n{e}", + parse_mode="HTML", + reply_markup=reply_markup + ) + + async def _set_autogptplus_mode(self, query, mode: str): + """设置注册模式""" + import tomli_w + + try: + from auto_gpt_team import is_api_mode_supported, get_register_mode, set_register_mode + + # 处理协议模式不可用的情况 + if mode == "api_unavailable": + keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:select_mode")]] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.edit_message_text( + "❌ 协议模式不可用\n\n" + "需要安装 curl_cffi 依赖:\n" + "pip install curl_cffi\n\n" + "安装后重启程序即可使用协议模式", + parse_mode="HTML", + reply_markup=reply_markup + ) + return + + # 检查是否已经是当前模式 + current_mode = get_register_mode() + if mode == current_mode: + await query.answer(f"当前已是{'协议' if mode == 'api' else '浏览器'}模式", show_alert=False) + return + + # 检查协议模式是否可用 + if mode == "api" and not is_api_mode_supported(): + keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:select_mode")]] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.edit_message_text( + "❌ 协议模式不可用\n\n" + "需要安装 curl_cffi 依赖:\n" + "pip install curl_cffi\n\n" + "安装后重启程序即可使用协议模式", + parse_mode="HTML", + reply_markup=reply_markup + ) + return + + # 读取当前配置 + with open(CONFIG_FILE, "rb") as f: + import tomllib + config = tomllib.load(f) + + # 确保 autogptplus section 存在 + if "autogptplus" not in config: + config["autogptplus"] = {} + + # 更新配置 + config["autogptplus"]["register_mode"] = mode + + # 写回文件 + with open(CONFIG_FILE, "wb") as f: + tomli_w.dump(config, f) + + # 更新运行时配置 + set_register_mode(mode) + + # 重新加载模块 + import importlib + import auto_gpt_team + importlib.reload(auto_gpt_team) + + # 显示成功消息 + if mode == "api": + mode_name = "🌐 协议模式" + mode_desc = ( + "使用 API 快速完成注册流程,仅支付环节使用浏览器\n\n" + "特点:\n" + "• 注册速度更快\n" + "• 资源占用更少\n" + "• 更稳定可靠" + ) + else: + mode_name = "🖥️ 浏览器模式" + mode_desc = ( + "全程使用 DrissionPage 浏览器自动化\n\n" + "特点:\n" + "• 兼容性更好\n" + "• 无需额外依赖\n" + "• 可视化调试方便" + ) + + keyboard = [[InlineKeyboardButton("◀️ 返回主菜单", callback_data="autogptplus:back")]] + reply_markup = InlineKeyboardMarkup(keyboard) + + await query.edit_message_text( + f"✅ 注册模式已设置\n\n" + f"当前模式: {mode_name}\n\n" + f"{mode_desc}", + parse_mode="HTML", + reply_markup=reply_markup + ) + + except ImportError as e: + keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.edit_message_text( + f"❌ 模块导入失败\n\n{e}", + parse_mode="HTML", + reply_markup=reply_markup + ) + except Exception as e: + keyboard = [[InlineKeyboardButton("◀️ 返回", callback_data="autogptplus:back")]] + reply_markup = InlineKeyboardMarkup(keyboard) + await query.edit_message_text( + f"❌ 设置失败\n\n{e}", + parse_mode="HTML", + reply_markup=reply_markup + ) + async def _show_autogptplus_stats(self, query): """显示统计信息""" try: diff --git a/uv.lock b/uv.lock index 4e3c5cf..ba7de27 100644 --- a/uv.lock +++ b/uv.lock @@ -36,6 +36,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.4" @@ -123,6 +180,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/58/257350f7db99b4ae12b614a36256d9cc870d71d9e451e79c2dc3b23d7c3c/cssselect-1.3.0-py3-none-any.whl", hash = "sha256:56d1bf3e198080cc1667e137bc51de9cadfca259f03c2d4e09037b3e01e30f0d", size = 18786, upload-time = "2025-03-10T09:30:28.048Z" }, ] +[[package]] +name = "curl-cffi" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/c9/0067d9a25ed4592b022d4558157fcdb6e123516083700786d38091688767/curl_cffi-0.14.0.tar.gz", hash = "sha256:5ffbc82e59f05008ec08ea432f0e535418823cda44178ee518906a54f27a5f0f", size = 162633, upload-time = "2025-12-16T03:25:07.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/f0/0f21e9688eaac85e705537b3a87a5588d0cefb2f09d83e83e0e8be93aa99/curl_cffi-0.14.0-cp39-abi3-macosx_14_0_arm64.whl", hash = "sha256:e35e89c6a69872f9749d6d5fda642ed4fc159619329e99d577d0104c9aad5893", size = 3087277, upload-time = "2025-12-16T03:24:49.607Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a3/0419bd48fce5b145cb6a2344c6ac17efa588f5b0061f212c88e0723da026/curl_cffi-0.14.0-cp39-abi3-macosx_15_0_x86_64.whl", hash = "sha256:5945478cd28ad7dfb5c54473bcfb6743ee1d66554d57951fdf8fc0e7d8cf4e45", size = 5804650, upload-time = "2025-12-16T03:24:51.518Z" }, + { url = "https://files.pythonhosted.org/packages/e2/07/a238dd062b7841b8caa2fa8a359eb997147ff3161288f0dd46654d898b4d/curl_cffi-0.14.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c42e8fa3c667db9ccd2e696ee47adcd3cd5b0838d7282f3fc45f6c0ef3cfdfa7", size = 8231918, upload-time = "2025-12-16T03:24:52.862Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d2/ce907c9b37b5caf76ac08db40cc4ce3d9f94c5500db68a195af3513eacbc/curl_cffi-0.14.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:060fe2c99c41d3cb7f894de318ddf4b0301b08dca70453d769bd4e74b36b8483", size = 8654624, upload-time = "2025-12-16T03:24:54.579Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ae/6256995b18c75e6ef76b30753a5109e786813aa79088b27c8eabb1ef85c9/curl_cffi-0.14.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b158c41a25388690dd0d40b5bc38d1e0f512135f17fdb8029868cbc1993d2e5b", size = 8010654, upload-time = "2025-12-16T03:24:56.507Z" }, + { url = "https://files.pythonhosted.org/packages/fb/10/ff64249e516b103cb762e0a9dca3ee0f04cf25e2a1d5d9838e0f1273d071/curl_cffi-0.14.0-cp39-abi3-manylinux_2_28_i686.whl", hash = "sha256:1439fbef3500fb723333c826adf0efb0e2e5065a703fb5eccce637a2250db34a", size = 7781969, upload-time = "2025-12-16T03:24:57.885Z" }, + { url = "https://files.pythonhosted.org/packages/51/76/d6f7bb76c2d12811aa7ff16f5e17b678abdd1b357b9a8ac56310ceccabd5/curl_cffi-0.14.0-cp39-abi3-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e7176f2c2d22b542e3cf261072a81deb018cfa7688930f95dddef215caddb469", size = 7969133, upload-time = "2025-12-16T03:24:59.261Z" }, + { url = "https://files.pythonhosted.org/packages/23/7c/cca39c0ed4e1772613d3cba13091c0e9d3b89365e84b9bf9838259a3cd8f/curl_cffi-0.14.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:03f21ade2d72978c2bb8670e9b6de5260e2755092b02d94b70b906813662998d", size = 9080167, upload-time = "2025-12-16T03:25:00.946Z" }, + { url = "https://files.pythonhosted.org/packages/75/03/a942d7119d3e8911094d157598ae0169b1c6ca1bd3f27d7991b279bcc45b/curl_cffi-0.14.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:58ebf02de64ee5c95613209ddacb014c2d2f86298d7080c0a1c12ed876ee0690", size = 9520464, upload-time = "2025-12-16T03:25:02.922Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/78900e9b0833066d2274bda75cba426fdb4cef7fbf6a4f6a6ca447607bec/curl_cffi-0.14.0-cp39-abi3-win_amd64.whl", hash = "sha256:6e503f9a103f6ae7acfb3890c843b53ec030785a22ae7682a22cc43afb94123e", size = 1677416, upload-time = "2025-12-16T03:25:04.902Z" }, + { url = "https://files.pythonhosted.org/packages/5c/7c/d2ba86b0b3e1e2830bd94163d047de122c69a8df03c5c7c36326c456ad82/curl_cffi-0.14.0-cp39-abi3-win_arm64.whl", hash = "sha256:2eed50a969201605c863c4c31269dfc3e0da52916086ac54553cfa353022425c", size = 1425067, upload-time = "2025-12-16T03:25:06.454Z" }, +] + [[package]] name = "datarecorder" version = "3.6.2" @@ -337,6 +417,7 @@ name = "oai-team-auto-provisioner" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "curl-cffi" }, { name = "drissionpage" }, { name = "python-telegram-bot", extra = ["job-queue"] }, { name = "requests" }, @@ -348,6 +429,7 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "curl-cffi", specifier = ">=0.14.0" }, { name = "drissionpage", specifier = ">=4.1.1.2" }, { name = "python-telegram-bot", extras = ["job-queue"], specifier = ">=22.5" }, { name = "requests", specifier = ">=2.32.5" }, @@ -395,6 +477,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, ] +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + [[package]] name = "pygments" version = "2.19.2"