diff --git a/accounts.json b/accounts.json index 8bd3e93..ca0032e 100644 --- a/accounts.json +++ b/accounts.json @@ -6,5 +6,29 @@ "oai_did": "fcc455da-9239-4683-bb2a-43ef01d8eb0a", "status": "success", "timestamp": "2026-01-29 17:04:08" + }, + { + "email": "user_f09ab8e154890e0b@gnd.qzz.io", + "password": "eHt4uOnxI6nW", + "access_token": "", + "oai_did": "758c617c-8324-4470-bd4f-0515813f6d95", + "status": "success", + "timestamp": "2026-01-29 19:08:22" + }, + { + "email": "user_f604f293e147919e@gnd.qzz.io", + "password": "P8dM44ts2q7j", + "access_token": "", + "oai_did": "3fd9815d-3720-4a42-aea5-9de7363767ec", + "status": "success", + "timestamp": "2026-01-29 19:10:23" + }, + { + "email": "user_2b2d083ea1f5d4e4@gnd.qzz.io", + "password": "BBE88LdQPGMW", + "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE5MzQ0ZTY1LWJiYzktNDRkMS1hOWQwLWY5NTdiMDc5YmQwZSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS92MSJdLCJjbGllbnRfaWQiOiJhcHBfWDh6WTZ2VzJwUTl0UjNkRTduSzFqTDVnSCIsImV4cCI6MTc3MDU0OTI0OSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9hdXRoIjp7ImNoYXRncHRfYWNjb3VudF9pZCI6ImQxMDU2ZjMzLWE4NTAtNGYyNi05MDU4LWJlM2RiYTUzNmVjMSIsImNoYXRncHRfYWNjb3VudF91c2VyX2lkIjoidXNlci16dE1zeUIwWmx1TUxiMHR1UUNEbzA2RTRfX2QxMDU2ZjMzLWE4NTAtNGYyNi05MDU4LWJlM2RiYTUzNmVjMSIsImNoYXRncHRfY29tcHV0ZV9yZXNpZGVuY3kiOiJub19jb25zdHJhaW50IiwiY2hhdGdwdF9wbGFuX3R5cGUiOiJmcmVlIiwiY2hhdGdwdF91c2VyX2lkIjoidXNlci16dE1zeUIwWmx1TUxiMHR1UUNEbzA2RTQiLCJ1c2VyX2lkIjoidXNlci16dE1zeUIwWmx1TUxiMHR1UUNEbzA2RTQifSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9wcm9maWxlIjp7ImVtYWlsIjoidXNlcl8yYjJkMDgzZWExZjVkNGU0QGduZC5xenouaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZX0sImlhdCI6MTc2OTY4NTI0OSwiaXNzIjoiaHR0cHM6Ly9hdXRoLm9wZW5haS5jb20iLCJqdGkiOiIwYTE4YTViMy04NjQxLTQ5ZmYtYmYwMi1lZjc4Y2YxMmNhZWUiLCJuYmYiOjE3Njk2ODUyNDksInB3ZF9hdXRoX3RpbWUiOjE3Njk2ODUyNDc0NTcsInNjcCI6WyJvcGVuaWQiLCJlbWFpbCIsInByb2ZpbGUiLCJvZmZsaW5lX2FjY2VzcyIsIm1vZGVsLnJlcXVlc3QiLCJtb2RlbC5yZWFkIiwib3JnYW5pemF0aW9uLnJlYWQiLCJvcmdhbml6YXRpb24ud3JpdGUiXSwic2Vzc2lvbl9pZCI6ImF1dGhzZXNzX0JyMjZQbW5vY3lGNzlobzBJZ0JFazcxZSIsInN1YiI6ImF1dGgwfGFZZk53eTJyaGdDNm9ZeUFqcVhLand5aSJ9.4vLBUWmKbZZDEKiXnXnDQcEhwsg3X3j7h92LZaH5IFJA1YScXODmJ4YCYvGT5akxKUZVeA6PLS0EJS77augPk3cNP1DdR0lQC2ZQec-aCXTe3_zYZBZMLqzvHgOpwdNjFVD187sZopbtcS1thgITEqT6-10xcEv9zEE4mT6PVBYhVl9usOf2DP7ibmdYG5Wgqw7xy-G5v0ySeMd7eWgtjJr_3n0Egdegr6uGrKROuOIDd_KUIW6VkCU5PaNUXCl3EhYaulJ5ynG498y6IaCgMQLbSEqOef2QEwIud1L6rt1B5l0oUUdOHSV-TESUBc6Ajp5-W5ADaYMJGFIOUvgaG7P24gXTDJ-egldz69yudd51sakvrly8PRPbEATklAYqJVk66X42uOUiWxtQGjroPkLv2Wk8tFRrHb7BZ8583I6rLzrAAtseD_nJc7wqMmJBQ9o8j22VBxObiHeoNUYMk4YD3wikZWxQW0VfEJjAcV0UFBJ7AR9px-tGi8WRGwmgZa-BFAKz_q-921kj3B5L8oNA-eYahggvGVONyb3Ad1mz1JTqj5B8aOjQtC2aga_7GKvI1dRzI_bT1WC3D8HRv-EXTo1-I7252KIbf_JF6hi6CsUoWstcnc3GWVyAT_q3_IAI_2xwGsV6A7R-zFWL9EgY0QICl1uSaz6dgYVs7oo", + "oai_did": "5ab55e47-ddb4-46b0-91f7-30eec383700a", + "status": "success", + "timestamp": "2026-01-29 19:14:18" } ] \ No newline at end of file diff --git a/core/flow.py b/core/flow.py index cdc294d..42ee862 100644 --- a/core/flow.py +++ b/core/flow.py @@ -6,7 +6,7 @@ import random import json import uuid -from core.session import OAISession, CloudflareBlockError, SessionInvalidError, RateLimitError +from core.session import OAISession, CloudflareBlockError, SessionInvalidError from core.sentinel_handler import SentinelHandler from core.challenge import CloudflareSolver from utils.mail_box import MailHandler @@ -27,8 +27,7 @@ class RegisterFlow: AUTH_SEND_OTP = "https://auth.openai.com/api/accounts/email-otp/send" AUTH_VALIDATE_OTP = "https://auth.openai.com/api/accounts/email-otp/validate" AUTH_COMPLETE_PROFILE = "https://auth.openai.com/api/accounts/create_account" - - # 获取 Token 相关 + AUTH_CONSENT = "https://auth.openai.com/api/accounts/consent" CHATGPT_SESSION = "https://chatgpt.com/api/auth/session" def __init__(self, session: OAISession, config, email: Optional[str] = None, password: Optional[str] = None): @@ -44,7 +43,7 @@ class RegisterFlow: self.csrf_token: Optional[str] = None self.sentinel_token: Optional[Dict[str, Any]] = None self.otp: Optional[str] = None - self.access_token: Optional[str] = None + self.oauth_callback_url: Optional[str] = None logger.info(f"RegisterFlow initialized for {self.email} (oai-did: {self.s.oai_did})") @@ -72,14 +71,12 @@ class RegisterFlow: await self._step6_send_email_otp() await self._step7_submit_otp() await self._step8_complete_profile() - await self._step9_get_access_token() logger.success(f"[{self.email}] Registration completed successfully! ✅") return { "email": self.email, "password": self.password, "oai_did": self.s.oai_did, - "access_token": self.access_token, "status": "success", "message": "Account registered successfully" } @@ -92,10 +89,6 @@ class RegisterFlow: logger.error(f"[{self.email}] Session invalid: {e}") return {"email": self.email, "password": self.password, "status": "session_invalid", "error": str(e)} - except RateLimitError as e: - logger.error(f"[{self.email}] Rate limited: {e}") - return {"email": self.email, "password": self.password, "status": "rate_limited", "error": str(e)} - except NotImplementedError as e: logger.warning(f"[{self.email}] Feature not implemented: {e}") return {"email": self.email, "password": self.password, "status": "pending_manual", "error": str(e)} @@ -245,53 +238,101 @@ class RegisterFlow: resp = self.s.post( self.AUTH_COMPLETE_PROFILE, json={"name": name, "birthdate": birthdate}, - headers={"Content-Type": "application/json", "Referer": self.AUTH_CREATE_ACCOUNT}, - allow_redirects=False + headers={"Content-Type": "application/json", "Referer": self.AUTH_CREATE_ACCOUNT} ) - if resp.status_code not in [200, 302, 303]: + if resp.status_code != 200: raise RuntimeError(f"Profile completion failed: {resp.status_code}") - # 检查是否有 continue_url 需要跟随 - try: - data = resp.json() - continue_url = data.get("continue_url") - if continue_url: - logger.info(f"[{self.email}] Following OAuth callback...") - if not continue_url.startswith("http"): - continue_url = f"https://auth.openai.com{continue_url}" - - # 跟随 OAuth 回调,最终会重定向到 chatgpt.com - callback_headers = { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", - "Referer": "https://auth.openai.com/", - "Sec-Fetch-Dest": "document", - "Sec-Fetch-Mode": "navigate", - "Sec-Fetch-Site": "same-origin", - "Upgrade-Insecure-Requests": "1", - } - self.s.get(continue_url, headers=callback_headers, allow_redirects=True) - logger.info(f"[{self.email}] ✓ OAuth callback completed") - except Exception as e: - logger.debug(f"[{self.email}] No continue_url or parse error: {e}") + # 获取 OAuth 回调 URL + data = resp.json() + self.oauth_callback_url = data.get("continue_url") + logger.debug(f"[{self.email}] OAuth callback URL: {self.oauth_callback_url}") logger.info(f"[{self.email}] ✓ Profile completed") - async def _step9_get_access_token(self): - """Step 9: 通过登录流程获取 Access Token""" - logger.info(f"[{self.email}] Step 9: Getting access token via login") + async def _step9_complete_oauth_callback(self): + """Step 9: 完成 OAuth 回调,获取 session-token""" + logger.info(f"[{self.email}] Step 9: Completing OAuth callback") - from core.login_flow import LoginFlow - - # 使用当前 session 执行登录流程 - login_flow = LoginFlow(self.s, self.email, self.password) - result = await login_flow.run() - - if result.get("status") == "success": - self.access_token = result.get("access_token") - logger.info(f"[{self.email}] ✓ Access token obtained: {self.access_token[:50]}...") + # 使用 step 8 返回的 continue_url,跟随重定向链到 chatgpt.com + if self.oauth_callback_url: + # continue_url 可能是相对路径或完整 URL + if self.oauth_callback_url.startswith("http"): + callback_url = self.oauth_callback_url + else: + callback_url = f"https://auth.openai.com{self.oauth_callback_url}" else: - logger.warning(f"[{self.email}] Failed to get access token: {result.get('error')}") + # 如果没有 continue_url,尝试访问 consent 端点 + callback_url = self.AUTH_CONSENT + + logger.debug(f"[{self.email}] Following OAuth callback: {callback_url}") + + # 循环跟随重定向,直到到达 chatgpt.com 或获取到 session-token + max_redirects = 10 + for i in range(max_redirects): + resp = self.s.get(callback_url, allow_redirects=True) + + # 检查是否已到达 chatgpt.com + if 'chatgpt.com' in str(resp.url): + logger.debug(f"[{self.email}] Reached chatgpt.com") + break + + # 检查响应是否包含重定向 URL(JSON 格式) + try: + data = resp.json() + redirect_url = data.get("redirect_url") or data.get("location") or data.get("url") + if redirect_url: + logger.debug(f"[{self.email}] Following JSON redirect ({i+1}): {redirect_url[:100]}...") + callback_url = redirect_url + continue + except (json.JSONDecodeError, ValueError): + pass + + # 没有更多重定向,退出循环 + break + + # 检查是否获取到 session-token cookie + session_token = self.s.get_cookie('__Secure-next-auth.session-token') + + if not session_token: + # 尝试直接访问 chatgpt.com 首页触发 cookie 设置 + logger.debug(f"[{self.email}] Session token not found, trying chatgpt.com homepage") + self.s.get(self.CHATGPT_HOME, allow_redirects=True) + session_token = self.s.get_cookie('__Secure-next-auth.session-token') + + if not session_token: + # 打印所有 cookies 用于调试,但不抛出错误 + # 尝试继续获取 access token,有时候 session token 不是必需的 + all_cookies = self.s.get_cookies() + logger.warning(f"[{self.email}] Session token not found, available cookies: {list(all_cookies.keys())}") + logger.info(f"[{self.email}] Continuing without session-token, will try to get access token directly") + else: + logger.info(f"[{self.email}] ✓ OAuth callback completed, session-token obtained") + + async def _step10_get_access_token(self) -> str: + """Step 10: 获取 AccessToken""" + logger.info(f"[{self.email}] Step 10: Getting access token") + + resp = self.s.get( + self.CHATGPT_SESSION, + headers={ + 'Accept': 'application/json', + 'Referer': 'https://chatgpt.com/', + } + ) + + if resp.status_code != 200: + raise RuntimeError(f"Failed to get access token: {resp.status_code}") + + data = resp.json() + access_token = data.get('accessToken') + + if not access_token: + raise RuntimeError("AccessToken not found in response") + + logger.info(f"[{self.email}] ✓ AccessToken obtained successfully") + return access_token def _generate_email(self) -> str: """生成随机邮箱""" diff --git a/core/session.py b/core/session.py index d58aee2..1599bff 100644 --- a/core/session.py +++ b/core/session.py @@ -257,7 +257,20 @@ class OAISession: 返回: Cookie 字典 {name: value} """ - return {cookie.name: cookie.value for cookie in self.client.cookies} + # curl_cffi 的 cookies 可能存在同名不同域的 cookie,需要遍历 jar + result = {} + try: + for cookie in self.client.cookies.jar: + # 用 domain:name 作为 key 避免冲突,或者直接覆盖 + result[cookie.name] = cookie.value + except Exception: + # 兼容处理 + try: + for cookie in self.client.cookies: + result[cookie.name] = cookie.value + except Exception: + pass + return result def get_cookie(self, name: str) -> Optional[str]: """ diff --git a/login.py b/login.py new file mode 100644 index 0000000..763aa9e --- /dev/null +++ b/login.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +""" +OpenAI 账号登录获取 Token + +使用方法: + python login.py + python login.py email@example.com password123 +""" + +import asyncio +import sys +import json +from pathlib import Path +from datetime import datetime + +from core.session import OAISession +from core.login_flow import LoginFlow +from config import load_config +from utils.logger import logger, setup_logger + + +async def login_and_get_token(email: str, password: str, proxy: str = None) -> dict: + """ + 登录并获取 access_token + + 参数: + email: 登录邮箱 + password: 登录密码 + proxy: 代理地址(可选) + + 返回: + 登录结果字典 + """ + session = None + try: + # 创建会话 + session = OAISession(proxy=proxy, impersonate="chrome124") + + # 执行登录流程 + flow = LoginFlow(session, email, password) + result = await flow.run() + + return result + + finally: + if session: + session.close() + + +def save_token(result: dict): + """保存 token 到文件""" + if result.get("status") != "success": + return + + email = result.get("email", "unknown") + access_token = result.get("access_token", "") + session_token = result.get("session_token", "") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # 保存到 accounts.json + json_file = Path("accounts.json") + accounts = [] + if json_file.exists(): + try: + with open(json_file, "r", encoding="utf-8") as f: + accounts = json.load(f) + except: + accounts = [] + + # 查找是否已存在该邮箱,更新 token + found = False + for acc in accounts: + if acc.get("email") == email: + acc["access_token"] = access_token + acc["session_token"] = session_token + acc["token_updated"] = timestamp + found = True + break + + if not found: + accounts.append({ + "email": email, + "access_token": access_token, + "session_token": session_token, + "timestamp": timestamp + }) + + with open(json_file, "w", encoding="utf-8") as f: + json.dump(accounts, f, indent=2, ensure_ascii=False) + + logger.info(f"Token saved to accounts.json") + + # 同时保存到单独文件 + token_dir = Path("tokens") + token_dir.mkdir(exist_ok=True) + token_file = token_dir / f"{email.replace('@', '_at_')}.txt" + with open(token_file, "w", encoding="utf-8") as f: + f.write(f"Email: {email}\n") + f.write(f"Access Token: {access_token}\n") + f.write(f"Session Token: {session_token}\n") + f.write(f"Timestamp: {timestamp}\n") + + logger.info(f"Token saved to {token_file}") + + +async def main(): + # 初始化日志 + setup_logger() + + # 加载配置 + config = load_config() + + # 获取邮箱和密码 + if len(sys.argv) >= 3: + email = sys.argv[1] + password = sys.argv[2] + else: + print("\n=== OpenAI 账号登录 ===\n") + email = input("Email: ").strip() + password = input("Password: ").strip() + + if not email or not password: + logger.error("Email and password are required") + return + + # 获取代理 + proxy = config.proxy.get_next_proxy() + if proxy: + logger.info(f"Using proxy: {proxy[:30]}...") + + print() + logger.info(f"Starting login for {email}") + print() + + # 执行登录 + result = await login_and_get_token(email, password, proxy) + + print() + if result.get("status") == "success": + logger.success(f"✅ Login successful!") + print() + print(f"Access Token: {result['access_token'][:80]}...") + print() + + # 保存 token + save_token(result) + else: + logger.error(f"❌ Login failed: {result.get('error', 'Unknown error')}") + + print() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/main.py b/main.py index 003e2a8..2c049d8 100644 --- a/main.py +++ b/main.py @@ -175,6 +175,20 @@ async def register_account( # 保存成功的账号 if result["status"] == "success": + # 注册成功后,立即登录获取 token + logger.info(f"[Task {task_id}] Registration done, now logging in to get token...") + + from core.login_flow import LoginFlow + login_flow = LoginFlow(session, result["email"], result["password"]) + login_result = await login_flow.run() + + if login_result.get("status") == "success": + result["access_token"] = login_result.get("access_token") + result["session_token"] = login_result.get("session_token") + logger.success(f"[Task {task_id}] ✅ Token obtained!") + else: + logger.warning(f"[Task {task_id}] ⚠️ Login failed: {login_result.get('error')}") + await save_account(result, config.accounts_output_file) logger.success( f"[Task {task_id}] ✅ Account created: {result['email']}:{result['password']}"