From 94744bbfa9f183bab13a77ffc988a91bb2d5a36b Mon Sep 17 00:00:00 2001 From: cong <3135055939@qq.com> Date: Sat, 4 Apr 2026 00:45:21 +0800 Subject: [PATCH] fix(chatgpt): correct passwordless OTP request and reset email on retries --- platforms/chatgpt/oauth_client.py | 1 - .../refresh_token_registration_engine.py | 6 ++ tests/test_chatgpt_register.py | 98 +++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/platforms/chatgpt/oauth_client.py b/platforms/chatgpt/oauth_client.py index 3b14b6e..e5c20d3 100644 --- a/platforms/chatgpt/oauth_client.py +++ b/platforms/chatgpt/oauth_client.py @@ -684,7 +684,6 @@ class OAuthClient: try: kwargs = { - "json": {"email": email}, "headers": headers, "timeout": 30, "allow_redirects": False, diff --git a/platforms/chatgpt/refresh_token_registration_engine.py b/platforms/chatgpt/refresh_token_registration_engine.py index 36023ad..1027806 100644 --- a/platforms/chatgpt/refresh_token_registration_engine.py +++ b/platforms/chatgpt/refresh_token_registration_engine.py @@ -313,6 +313,7 @@ class RefreshTokenRegistrationEngine: def run(self) -> RegistrationResult: result = RegistrationResult(success=False, logs=self.logs) last_error = "" + fixed_email = str(self.email or "").strip() try: for attempt in range(self.max_retries): @@ -330,6 +331,11 @@ class RefreshTokenRegistrationEngine: self._log(f"整流程重试 {attempt + 1}/{self.max_retries} ...") time.sleep(1) + # 用户未显式指定邮箱时,每轮都必须使用本轮新购邮箱, + # 避免重试时误复用上一轮邮箱导致收不到验证码。 + if not fixed_email: + self.email = None + self._log("1. 创建邮箱...") if not self._create_email(): last_error = "创建邮箱失败" diff --git a/tests/test_chatgpt_register.py b/tests/test_chatgpt_register.py index 09b0290..3fcd4ff 100644 --- a/tests/test_chatgpt_register.py +++ b/tests/test_chatgpt_register.py @@ -145,6 +145,76 @@ class RefreshTokenRegistrationEngineTests(unittest.TestCase): login_kwargs = oauth_client.login_and_get_tokens.call_args.kwargs self.assertEqual(login_kwargs["login_source"], "existing_account_recovery") + @mock.patch("platforms.chatgpt.refresh_token_registration_engine.OAuthManager") + @mock.patch("platforms.chatgpt.refresh_token_registration_engine.OAuthClient") + @mock.patch("platforms.chatgpt.refresh_token_registration_engine.ChatGPTClient") + def test_run_retry_uses_newly_created_email_in_next_attempt( + self, + mock_chatgpt_client_cls, + mock_oauth_client_cls, + mock_oauth_manager_cls, + ): + class RotatingEmailService: + service_type = type("ST", (), {"value": "dummy"})() + + def __init__(self): + self.index = 0 + + def create_email(self): + self.index += 1 + return { + "email": f"user{self.index}@example.com", + "service_id": f"svc-{self.index}", + } + + def get_verification_code(self, **kwargs): + return "123456" + + register_client = mock.Mock() + register_client.device_id = "device-fixed" + register_client.ua = "UA" + register_client.sec_ch_ua = '"Chromium";v="136"' + register_client.impersonate = "chrome136" + register_client.register_complete_flow.side_effect = [ + (False, "network timeout"), + (True, "注册成功"), + ] + mock_chatgpt_client_cls.return_value = register_client + + oauth_client = mock.Mock() + oauth_client.login_and_get_tokens.return_value = { + "access_token": "at", + "refresh_token": "rt", + "id_token": "id-token", + "account_id": "acct-1", + } + oauth_client.last_workspace_id = "ws-1" + oauth_client._decode_oauth_session_cookie.return_value = { + "workspaces": [{"id": "ws-1"}] + } + oauth_client._get_cookie_value.return_value = "session-1" + mock_oauth_client_cls.return_value = oauth_client + + oauth_manager = mock.Mock() + oauth_manager.extract_account_info.return_value = { + "email": "user2@example.com", + "account_id": "acct-1", + } + mock_oauth_manager_cls.return_value = oauth_manager + + engine = RefreshTokenRegistrationEngine( + email_service=RotatingEmailService(), + proxy_url="http://127.0.0.1:7890", + callback_logger=lambda msg: None, + max_retries=2, + ) + result = engine.run() + + self.assertTrue(result.success) + call_args = register_client.register_complete_flow.call_args_list + self.assertEqual(call_args[0].args[0], "user1@example.com") + self.assertEqual(call_args[1].args[0], "user2@example.com") + class OAuthClientPasswordlessTests(unittest.TestCase): def _make_client(self): @@ -225,6 +295,34 @@ class OAuthClientPasswordlessTests(unittest.TestCase): follow_state.assert_called_once() handle_phone.assert_not_called() + def test_send_passwordless_login_otp_does_not_send_email_field(self): + client = self._make_client() + response = mock.Mock() + response.status_code = 200 + response.url = "https://auth.openai.com/api/accounts/passwordless/send-otp" + response.json.return_value = {"page": {"type": "email_otp_verification"}} + client.session.post = mock.Mock(return_value=response) + + expected_state = FlowState( + page_type="email_otp_verification", + continue_url="https://auth.openai.com/email-verification", + current_url="https://auth.openai.com/email-verification", + ) + with mock.patch.object( + client, + "_state_from_payload", + return_value=expected_state, + ): + state = client._send_passwordless_login_otp( + "user@example.com", + "device-fixed", + ) + + self.assertEqual(state, expected_state) + kwargs = client.session.post.call_args.kwargs + self.assertNotIn("json", kwargs) + self.assertNotIn("data", kwargs) + if __name__ == "__main__": unittest.main()