This commit is contained in:
2026-01-25 05:40:08 +08:00
parent af161cca4f
commit 32e926c4af
9 changed files with 1495 additions and 13 deletions

View File

@@ -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()