fix(unsub): List-Unsubscribe 头兜底 + mailto 退订按钮放行
两封推广邮件无退订按钮,两个独立根因:
1. Cloudflare 邮件 (msg_id=1364):程序只喂纯文本给 AI,纯文本里
'Unsubscribe' 无 URL;邮件头 List-Unsubscribe 的权威退订链接被丢弃。
-> 解析 List-Unsubscribe 头 (RFC 2369) 存入 Email.list_unsubscribe,
注入 AI 提示词;显示层新增 _ensure_unsub_link 兜底:AI 漏提取时
直接用头部 http(s)/mailto 链接补退订按钮(不依赖 AI)。
2. InferX 邮件 (msg_id=1363):AI 提取到 mailto:team@inferx.net?subject=Unsubscribe,
但 _link_buttons 只放行 http 协议把它过滤掉。
-> URL 校验放行 mailto:(退订场景常见)。
send_summary / tg_send_and_mark 均应用兜底并写回 summary_data.links,
回调恢复视图也能显示退订按钮。新增 6 个测试,共 14 个全部通过。
This commit is contained in:
5 files changed
+120
-14
No files matched your search
@@ -1,6 +1,11 @@
|
||||
"""测试摘要键盘渲染:推广邮件的退订链接按钮必须显示且排最前。"""
|
||||
|
||||
from src.tg_bot import _is_unsub_link, _link_buttons, _summary_keyboard
|
||||
from src.tg_bot import (
|
||||
_ensure_unsub_link,
|
||||
_is_unsub_link,
|
||||
_link_buttons,
|
||||
_summary_keyboard,
|
||||
)
|
||||
|
||||
|
||||
def _rows_of(kb: dict) -> list[list[dict]]:
|
||||
@@ -102,4 +107,43 @@ def test_oversized_url_filtered():
|
||||
url = "https://example.com/" + "x" * 2100
|
||||
links = [{"text": "退订", "url": url}]
|
||||
rows = _link_buttons(links)
|
||||
assert rows == []
|
||||
assert rows == []
|
||||
|
||||
|
||||
def test_mailto_unsub_link_rendered():
|
||||
"""mailto: 退订链接(如 InferX 的 mailto:team@inferx.net?subject=Unsubscribe)必须渲染"""
|
||||
links = [{"text": "退订", "url": "mailto:team@inferx.net?subject=Unsubscribe"}]
|
||||
kb = _summary_keyboard(can_reply=False, is_promotion=True, links=links)
|
||||
rows = kb["inline_keyboard"]
|
||||
assert rows[0][0]["url"] == "mailto:team@inferx.net?subject=Unsubscribe"
|
||||
assert "🔕" in rows[0][0]["text"]
|
||||
assert _is_unsub_link(links[0])
|
||||
|
||||
|
||||
def test_ensure_unsub_link_http_header_fallback():
|
||||
"""AI 没提取到退订链接时,用 List-Unsubscribe 头的 http 链接兜底"""
|
||||
header = "https://links.iterable.com/s/uh/tKjpy4SHCKvbzE2votNyZEJDvlMdeodVyWFExNs310ugJ_RvZtoZq4gGLQ0BXMSL2lf3kEZsmr1XJmrODIFHpEwcypo3LR096pZ7jsfBynZBgGWz171Wc1E6ZIIC5Lu0e-Stq6p5-_Ad3CnNlUWrW1tnrLSeiGsOiQG_zMYKVbtfQZgvfm3QYfD9lQ/Cey29KU8yOzIUltaEJA8EMRFf-FKMZQy/24"
|
||||
links = _ensure_unsub_link([], header)
|
||||
assert links == [{"text": "退订", "url": header}]
|
||||
kb = _summary_keyboard(can_reply=False, is_promotion=True, links=links)
|
||||
assert kb["inline_keyboard"][0][0]["url"] == header
|
||||
|
||||
|
||||
def test_ensure_unsub_link_mailto_header_fallback():
|
||||
"""List-Unsubscribe 头只有 mailto 时也兜底"""
|
||||
header = "mailto:unsubscribe+19693436+25822730@unsubscribe.iterable.com"
|
||||
links = _ensure_unsub_link([], header)
|
||||
assert links == [{"text": "退订", "url": header}]
|
||||
|
||||
|
||||
def test_ensure_unsub_link_no_duplicate_when_ai_extracted():
|
||||
"""AI 已提取退订链接时,不重复添加头部链接"""
|
||||
ai_links = [{"text": "取消订阅", "url": "https://r.openai.com/asm/unsubscribe/?x=1"}]
|
||||
links = _ensure_unsub_link(ai_links, "https://links.example.com/unsub")
|
||||
assert links == ai_links
|
||||
|
||||
|
||||
def test_ensure_unsub_link_ignores_invalid_header():
|
||||
"""头部无有效链接时不兜底"""
|
||||
assert _ensure_unsub_link([], "") == []
|
||||
assert _ensure_unsub_link([], "javascript:alert(1)") == []
|
||||
Reference in New Issue
Block a user