fix: improve Hotmail IMAP code extraction and OpenAI mail diagnostics
- 合并 PR #177 的核心改动:补齐 Hotmail helper 在 IMAP 场景下的完整正文提码与 OpenAI 邮件日志输出 - 本地补充修复:无 - 影响范围:scripts/hotmail_helper.py、tests/hotmail_helper_logging_test.py
This commit is contained in:
@@ -123,6 +123,48 @@ def log_info(message):
|
|||||||
print(f"[HotmailHelper] {message}", flush=True)
|
print(f"[HotmailHelper] {message}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def is_openai_sender(address):
|
||||||
|
sender = str(address or "").strip().lower()
|
||||||
|
return "openai" in sender
|
||||||
|
|
||||||
|
|
||||||
|
def get_message_body_content(message):
|
||||||
|
body = message.get("body") or {}
|
||||||
|
if not isinstance(body, dict):
|
||||||
|
return ""
|
||||||
|
return str(body.get("content") or "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def log_openai_messages(messages, transport=""):
|
||||||
|
for message in messages or []:
|
||||||
|
sender_info = message.get("from", {}).get("emailAddress", {}) or {}
|
||||||
|
sender = str(sender_info.get("address") or "").strip()
|
||||||
|
if not is_openai_sender(sender):
|
||||||
|
continue
|
||||||
|
|
||||||
|
sender_name = str(sender_info.get("name") or "").strip()
|
||||||
|
mailbox = str(message.get("mailbox") or "").strip() or "INBOX"
|
||||||
|
subject = str(message.get("subject") or "").strip()
|
||||||
|
transport_label = str(transport or "").strip() or "unknown"
|
||||||
|
base = (
|
||||||
|
f"transport={transport_label} mailbox={mailbox} sender={sender} "
|
||||||
|
f"senderName={sender_name or '-'} subject={subject}"
|
||||||
|
)
|
||||||
|
|
||||||
|
log_info(f"openai mail received {base}")
|
||||||
|
|
||||||
|
body_content = get_message_body_content(message)
|
||||||
|
if body_content:
|
||||||
|
log_info(f"openai mail full body start {base}")
|
||||||
|
print(body_content, flush=True)
|
||||||
|
log_info("openai mail full body end")
|
||||||
|
continue
|
||||||
|
|
||||||
|
preview = str(message.get("bodyPreview") or "").strip()
|
||||||
|
if preview:
|
||||||
|
log_info(f"openai mail preview {base} preview={compact_text(preview, 1000)}")
|
||||||
|
|
||||||
|
|
||||||
def get_proxy_debug_context():
|
def get_proxy_debug_context():
|
||||||
names = ["all_proxy", "http_proxy", "https_proxy", "ALL_PROXY", "HTTP_PROXY", "HTTPS_PROXY"]
|
names = ["all_proxy", "http_proxy", "https_proxy", "ALL_PROXY", "HTTP_PROXY", "HTTPS_PROXY"]
|
||||||
parts = []
|
parts = []
|
||||||
@@ -481,6 +523,9 @@ def normalize_message(message_id, raw_bytes, mailbox):
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bodyPreview": body[:500],
|
"bodyPreview": body[:500],
|
||||||
|
"body": {
|
||||||
|
"content": body,
|
||||||
|
},
|
||||||
"receivedDateTime": to_iso_string(timestamp_ms),
|
"receivedDateTime": to_iso_string(timestamp_ms),
|
||||||
"receivedTimestamp": timestamp_ms,
|
"receivedTimestamp": timestamp_ms,
|
||||||
}
|
}
|
||||||
@@ -680,6 +725,7 @@ def collect_messages(email_addr, client_id, refresh_token, mailboxes, top):
|
|||||||
try:
|
try:
|
||||||
log_info(f"message collection start transport={transport_name}")
|
log_info(f"message collection start transport={transport_name}")
|
||||||
result = collector(email_addr, client_id, refresh_token, mailboxes, top)
|
result = collector(email_addr, client_id, refresh_token, mailboxes, top)
|
||||||
|
log_openai_messages(result.get("messages") or [], transport=transport_name)
|
||||||
log_info(
|
log_info(
|
||||||
f"message collection success transport={transport_name} "
|
f"message collection success transport={transport_name} "
|
||||||
f"tokenEndpoint={result['token_payload'].get('token_endpoint', '')}"
|
f"tokenEndpoint={result['token_payload'].get('token_endpoint', '')}"
|
||||||
@@ -722,6 +768,10 @@ def select_latest_code(messages, sender_filters, subject_filters, exclude_codes,
|
|||||||
preview = str(message.get("bodyPreview", ""))
|
preview = str(message.get("bodyPreview", ""))
|
||||||
combined = " ".join([sender, subject.lower(), preview.lower()])
|
combined = " ".join([sender, subject.lower(), preview.lower()])
|
||||||
code = extract_code(" ".join([subject, preview, sender]))
|
code = extract_code(" ".join([subject, preview, sender]))
|
||||||
|
if not code:
|
||||||
|
body_content = get_message_body_content(message)
|
||||||
|
if body_content:
|
||||||
|
code = extract_code(" ".join([subject, body_content, sender]))
|
||||||
if not code or code in excluded:
|
if not code or code in excluded:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,107 @@ hotmail_helper = load_hotmail_helper()
|
|||||||
|
|
||||||
|
|
||||||
class HotmailHelperLoggingTest(unittest.TestCase):
|
class HotmailHelperLoggingTest(unittest.TestCase):
|
||||||
|
def test_select_latest_code_can_use_full_body_when_preview_is_truncated(self):
|
||||||
|
css_prefix = (
|
||||||
|
'Your temporary ChatGPT verification code '
|
||||||
|
'@font-face { font-family: "Söhne"; src: url(https://cdn.openai.com/common/fonts/soehne/soehne-buch.woff2) format("woff2"); } '
|
||||||
|
'.ExternalClass { width: 100%; } '
|
||||||
|
'#bodyTable { width: 560px; } '
|
||||||
|
'body { min-width: 100% !important; } '
|
||||||
|
) * 8
|
||||||
|
full_body = (
|
||||||
|
css_prefix
|
||||||
|
+ 'Enter this temporary verification code to continue: 272964 '
|
||||||
|
+ 'Please ignore this email if this was not you.'
|
||||||
|
)
|
||||||
|
message = {
|
||||||
|
"id": "imap-1",
|
||||||
|
"mailbox": "INBOX",
|
||||||
|
"subject": "Your temporary ChatGPT verification code",
|
||||||
|
"from": {
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "otp@tm1.openai.com",
|
||||||
|
"name": "OpenAI",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bodyPreview": full_body[:500],
|
||||||
|
"body": {
|
||||||
|
"content": full_body,
|
||||||
|
},
|
||||||
|
"receivedTimestamp": 200,
|
||||||
|
}
|
||||||
|
|
||||||
|
result = hotmail_helper.select_latest_code(
|
||||||
|
[message],
|
||||||
|
['openai', 'noreply', 'verify', 'auth', 'chatgpt', 'duckduckgo', 'forward'],
|
||||||
|
['verify', 'verification', 'code', '验证码', 'confirm', 'login'],
|
||||||
|
[],
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result["code"], "272964")
|
||||||
|
self.assertEqual(result["message"]["id"], "imap-1")
|
||||||
|
|
||||||
|
def test_log_openai_messages_logs_full_body_when_available(self):
|
||||||
|
messages = [{
|
||||||
|
"mailbox": "INBOX",
|
||||||
|
"subject": "Your verification code",
|
||||||
|
"from": {
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "account-security@openai.com",
|
||||||
|
"name": "OpenAI",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bodyPreview": "Use 123456 to continue.",
|
||||||
|
"body": {
|
||||||
|
"content": "Hello there\nUse 123456 to continue.",
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
output = io.StringIO()
|
||||||
|
with redirect_stdout(output):
|
||||||
|
hotmail_helper.log_openai_messages(messages, transport="imap")
|
||||||
|
|
||||||
|
rendered = output.getvalue()
|
||||||
|
self.assertIn(
|
||||||
|
"[HotmailHelper] openai mail received transport=imap mailbox=INBOX sender=account-security@openai.com senderName=OpenAI subject=Your verification code",
|
||||||
|
rendered,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"[HotmailHelper] openai mail full body start transport=imap mailbox=INBOX sender=account-security@openai.com senderName=OpenAI subject=Your verification code",
|
||||||
|
rendered,
|
||||||
|
)
|
||||||
|
self.assertIn("Hello there\nUse 123456 to continue.", rendered)
|
||||||
|
self.assertIn("[HotmailHelper] openai mail full body end", rendered)
|
||||||
|
|
||||||
|
def test_log_openai_messages_falls_back_to_preview_without_full_body(self):
|
||||||
|
messages = [{
|
||||||
|
"mailbox": "Junk",
|
||||||
|
"subject": "Verify your sign in",
|
||||||
|
"from": {
|
||||||
|
"emailAddress": {
|
||||||
|
"address": "noreply@tm.openai.com",
|
||||||
|
"name": "ChatGPT",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bodyPreview": "Use 654321 to continue.",
|
||||||
|
}]
|
||||||
|
|
||||||
|
output = io.StringIO()
|
||||||
|
with redirect_stdout(output):
|
||||||
|
hotmail_helper.log_openai_messages(messages, transport="graph")
|
||||||
|
|
||||||
|
rendered = output.getvalue()
|
||||||
|
self.assertIn(
|
||||||
|
"[HotmailHelper] openai mail received transport=graph mailbox=Junk sender=noreply@tm.openai.com senderName=ChatGPT subject=Verify your sign in",
|
||||||
|
rendered,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"[HotmailHelper] openai mail preview transport=graph mailbox=Junk sender=noreply@tm.openai.com senderName=ChatGPT subject=Verify your sign in preview=Use 654321 to continue.",
|
||||||
|
rendered,
|
||||||
|
)
|
||||||
|
self.assertNotIn("openai mail full body start", rendered)
|
||||||
|
|
||||||
def test_refresh_access_token_logs_invalid_grant_and_direct_connection_refused_separately(self):
|
def test_refresh_access_token_logs_invalid_grant_and_direct_connection_refused_separately(self):
|
||||||
failures = [
|
failures = [
|
||||||
{
|
{
|
||||||
@@ -38,7 +139,8 @@ class HotmailHelperLoggingTest(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
with mock.patch.object(hotmail_helper, "try_refresh_access_token", side_effect=failures):
|
with mock.patch.object(hotmail_helper, "try_refresh_access_token", side_effect=failures), \
|
||||||
|
mock.patch.object(hotmail_helper, "get_proxy_debug_context", return_value="direct"):
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
with redirect_stdout(output):
|
with redirect_stdout(output):
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
|
|||||||
Reference in New Issue
Block a user