Repo .git was damaged (refs/ missing, index truncated, object store near-empty). Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak, then full worktree committed as a rollback baseline.
84 lines
11 KiB
Markdown
84 lines
11 KiB
Markdown
# 2026-09-07
|
||
|
||
## notify_domain_change.py 语言列探测 v3(langprobe)
|
||
- 上轮 information_schema 探测在线上误报(探测说 language 存在、真实查询报列不存在,疑似跨 schema 同名表干扰)。
|
||
- 本轮重写为「真实试跑探测」`detect_lang_column()`:依次执行 `SELECT ... language / lang / NULL`(LIMIT 1),哪个能跑通用哪个;NULL 兜底必然成功,不会再因列缺失 FATAL。
|
||
- `fetch_recipients(cfg, args, lang_expr=...)` 与 `main` 调用链对齐;版本号提升为 `2026-09-06.langprobe.v3`。
|
||
- 6 场景 mock 验证全过:language 报错→降级 lang;全缺失→NULL 回退 default_lang(ja);显式 --lang-column;自动探测拼 SQL 正确。
|
||
- 测试脚本两处自身 bug(dict 键名 language 非 lang;mock 对 NULL 查询仍返回 'en')已修正后 ALL_PASS。
|
||
|
||
## v4(rfc5322.schema):QQ 邮箱 550 退信 + 统计全 0 两个问题
|
||
- 550 退信根因:`From: 狼的提问箱 <tamabox@...>` 中文显示名未做 RFC2047 编码,QQ(mx3.qq.com)回 550 "From header is missing or invalid"。修复:`formataddr((str(Header(显示名, utf-8)), 地址))` 编码 From,Subject 同样用 Header 编码,并补 Message-ID。
|
||
- 统计全 0 根因:psql 未限定 schema,search_path 命中了别的 schema 里的同名空 users 表(也解释了 v2 information_schema 探测误报 language 存在)。修复:新增 `resolve_users_table()` 枚举所有 schema 的 users 表并逐个 COUNT,选有数据(其次带 language 列)的 schema,全部查询用 `"schema".users` 限定;main 打印 `[DB 连接]` 与 `[users 表]` 诊断行,全空时警告。
|
||
- 5 场景 mock 验证 ALL_PASS(schema 定位/语言探测/counts/全空回退/From+Subject 编码与 Message-ID)。版本 `2026-09-07.rfc5322.schema.v4`。
|
||
|
||
## 新项目 mail-broadcast/(站内信群发工具)
|
||
- 用户确认 v4 后收件正常、数据正确,但邮件里提问箱链接显示成站点首页而非 `/_/个性域名`;要求新建独立可复用项目、四语言模板拆成独立 HTML。
|
||
- 新建 `mail-broadcast/`:`broadcast.py`(通用化:去掉硬编码域名,`--site-url`/`--box-prefix`/`--var k=v` 自定义占位符;缺失占位符置空并上报)+ `templates/{zh-CN,zh-TW,en,ja}.{html,subject.txt}`(plain 可选,缺省由 HTML 自动抽取纯文本,HTMLParser 实现)+ README.md。
|
||
- 保留 v4 全部 DB/SMTP 能力:schema 定位、语言列试跑探测、断点续发、分群限速、SMTP 拒收捕获、RFC2047 From/Subject、Message-ID。语言回退保持「空→zh-CN、未知→default_lang→zh-CN」。
|
||
- 预览区直接打印每个收件人解析后的 box_link,方便发现链接问题。
|
||
- 7 场景 mock 测试 ALL_PASS(含四语言渲染 box_link、缺失占位符上报、From 编码)。
|
||
|
||
## params.ini 参数文件 + --interactive 交互模式
|
||
- 用户新需求:运行时直接读参数文件,或运行后再交互。
|
||
- 新增 `params.ini`([site] site_url/box_prefix + [vars] 自定义占位符 + [send] to/delay/group_pause),默认自动读取脚本同目录 params.ini,`--params`/`--no-params` 可控;优先级 CLI > 文件 > 默认。
|
||
- 安全边界:参数文件不提供 --send/--yes,正式发送必须命令行显式指定 + 终端确认。
|
||
- `--interactive/-i`:逐项确认 site_url/box_prefix,自动扫描模板占位符(collect_template_placeholders 排除内置)逐个提问(模板加新占位符会被自动发现),可选测试邮箱,可选保存回 params.ini(save_params)。
|
||
- 踩坑:测试脚本两次自身 bug(回答顺序没按字母序、Args 类属性 vs 实例属性);跑挂的测试曾把 params.ini 写坏,已恢复标准值。7 场景 ALL_PASS。
|
||
|
||
## conf 路径纳入 params.ini 与交互模式
|
||
- params.ini 新增 `[path] config`(预填 ./auto/conf/app.ini);`-c` 默认值改为 None,解析优先级:CLI > params.ini > 环境变量 TAMABOX_CONFIG_PATH > ./conf/app.ini。
|
||
- `--interactive` 第一步先问配置文件路径(在 load_config 之前),保存回 params.ini 时含 config 路径(save_params 签名加 config 参数)。
|
||
- 5 场景测试 ALL_PASS(params 读取/优先级/save round-trip 含 config/交互完整流程/标准值恢复)。
|
||
|
||
## 交互选运行方式 + 模板对齐原程序邮件样式
|
||
- 用户新需求:① send 之类的决策放进交互里而不是命令行;② 模板配色样式参照原程序 templates/mail/。
|
||
- run_interactive 重构:占位符问完后进入「运行方式」选择(1 演练/2 测试/3 群发,默认回车=演练);模式 2 问测试邮箱、模式 3 可问 limit(试水)并可选择是否保留最终确认(答 n → args.yes=True)。args.send/to/yes/limit 就地写入,交互全程无需 --send/--to/--yes。
|
||
- 四语言模板重写为原程序 Google 风格:白底 #FFFFFF、#dadce0 细边框 8px 圆角卡片(max-width 516px)、标题 24px 带下边框、正文 Roboto 14px rgba(0,0,0,0.87)、次要信息 rgba(0,0,0,0.54)、新域名高亮框 #e8f0fe/#c6dafc、按钮 #4184F3 蓝底白字、页脚 11px rgba(0,0,0,0.54)。新增 `{{year}}` 内置占位符(ctx 用 datetime.now),run_interactive 的 builtin 集合同步排除。
|
||
- 测试踩坑:render() 返回 (结果, missing) 元组需解包;群发路径交互共 8 次输入。24 项检查 ALL_PASS(四语言 box_link/按钮链接/配色/#year/占位符完整 + 4 交互场景 + From 头 ASCII)。
|
||
|
||
## 默认交互模式 + 单文件通用模板(--single)
|
||
- 用户新需求:① 默认就是交互模式不用手输;② 新增单文件模式(不按语言,所有人发同一个通用模板,模板单独存放便于以后复用)。
|
||
- 默认交互:`--interactive` 改 default=None,parse 后判定——未给 --send/--to/--yes/--dry-run 之一即自动进入交互;`--no-interactive` 强制关闭(脚本化用)。
|
||
- 单文件模式:templates/single.html + single.subject.txt(保留名,load_templates 排除不混入语言列表;load_single_template 单独加载,plain 自动抽取)。`--single` CLI 或交互「模板模式」选 2。发送时 grouping 固化为 single 组(不 resolve_lang),ctx 仍含每人 box_link/name/site/year;报告加 template_mode。
|
||
- single.html 样式对齐原程序邮件,正文内容区用 HTML 注释标注「编辑这里」,含可复制的强调框示例。
|
||
- 15 项检查 ALL_PASS(single 不混语言/加载/渲染/构建 + 4 交互组合场景)。
|
||
|
||
## 交互只问缺失项(不再每次重复确认战役参数)
|
||
- 用户反馈:site_url/box_prefix/old_domain/expire_date 这类一次性参数每次交互都问很烦,直接写死到这次 html 即可。
|
||
- run_interactive 重构:开头打印「[参数确认]」摘要(沿用 params.ini/CLI/app.ini 的值),**有值一律不问**;只补问 ① 完全没值的 site_url(main 里先做 external_url 兜底)② 模板用到但 extra_vars 里还没有值的自定义占位符。box_prefix 不再询问(默认 /_/,CLI 可覆盖)。
|
||
- 配置文件路径同理:CLI/params.ini 明确给了就打印沿用不问,仅默认猜测时才 ask。
|
||
- 想改值:编辑 params.ini,或把一次性内容写死进模板 html 并删掉对应 {{占位符}}(占位符删了就不会被问)。
|
||
- 测试踩坑:mock 回答数按实际提问数对齐(占位符 2 个非 3 个;摘要走 print 非 input 需 redirect_stdout 捕获)。7 项检查 ALL_PASS。
|
||
|
||
## 文案修正:模板问句人称
|
||
- 用户指出邮件模板「我该做什么?」应为「你该做什么?」(站方对收件人的口吻)。
|
||
- 已修 templates/zh-CN.html 与 zh-TW.html 对应问句;渲染验证通过、无残留。en(What should I do?)/ ja(何をすればよいですか?)保持读者视角的惯用表达未改。
|
||
|
||
## --limit 与断点续发顺序 bug 修复
|
||
- 用户反馈:第一次发送部分成功,第二次 --limit 10 时「已跳过 10、剩余 0、一条没发」。
|
||
- 根因:main 中 --limit 截取在断点续发过滤之前执行,limit 永远取名单头部那批已发用户,跳过后剩 0。
|
||
- 修复:调整顺序为「先剔除已发 → 再取前 N」,--limit 提示语带上已剔除数量;补 had_state 标志控制「剩余待发送」打印。
|
||
- 6 项回归测试 ALL_PASS(跳过+limit 组合、无重叠、limit>剩余、全已发、状态读写往返)。
|
||
|
||
## 退信核查功能(bouncecheck.v2)
|
||
- 背景:SMTP 250≠送达,阿里云对限流/无效地址先收下再异步退信到发件箱,用户要求发送后核查真实送达。
|
||
- 新增零依赖 IMAP 退信扫描:--check-bounces 登录发件箱(imaplib 993,IMAP 域名由 smtp.→imap. 推导),两段式拉取(先头部命中退信特征再取全文),--since-days 默认 3。
|
||
- 退信解析三层:标准 DSN(message/delivery-status, Action:failed)→ 中文「收信地址/收件人」标签 → 正文地址兜底(剔除发件人与 daemon/postmaster);主题关键词 + multipart/report 双判定。
|
||
- --prune-state 把退信用户从 broadcast_state.json 剔除 → 重跑自动补发;已核查 Message-ID 存 broadcast_bounces_seen.json 防重复报告。交互运行方式新增「4) 退信核查」(并移到模板模式之前,该模式跳过模板提问)。
|
||
- 12 项单元测试 ALL_PASS(含用户真实退信格式、标准 DSN、限流退信全角冒号、prune 往返)。防御修正:正则加 (?<![A-Za-z0-9]) 防乱码前缀吞地址;测试须用 message_from_bytes 贴近 IMAP 真实路径(message_from_string 双编码会污染解析)。
|
||
|
||
## 退信核查拆分三档(仅核查/仅剔除/剔除并补发)
|
||
- 用户要求「仅剔除、不一定剔除后直接发送」。交互运行方式 4 下新增子选项:1 仅核查(默认,不改清单)/ 2 仅剔除(更新清单后结束,本次不发送)/ 3 剔除并补发(剔除后继续正常发送流程,发送前仍有统计确认)。
|
||
- CLI 对应:--check-bounces 仅核查;--check-bounces --prune-state 仅剔除;--prune-state --send 剔除并补发(main 分支在 args.send 时不 sys.exit,落回正常发送流程)。
|
||
- 4 项交互 mock 测试 ALL_PASS;README 同步。
|
||
|
||
## 退信扫描起点改为群发首条记录
|
||
- 用户反馈:固定近 3 天会把群发无关的退信/邮件扫进来。
|
||
- 新增 resolve_scan_start():auto(默认)取 broadcast_state.json 最早一条 ts - 1 天余量;--since YYYY-MM-DD 显式指定;无记录/无 ts 回退 --since-days(默认3)。IMAP search 用 SINCE 日期,控制台打印 [扫描起点] 说明。
|
||
- 6 项测试 ALL_PASS(最早记录-1天/显式日期/非法日期回退/无清单回退/无 ts 回退)。
|
||
|
||
## 移除扫描起点 -1 天余量
|
||
- 用户反馈:提前 1 天余量导致扫描范围仍偏大。改为 auto 模式精确从群发首条记录的 ts 开始(不再 -1 天);--since 显式日期与空清单回退 --since-days 逻辑不变。
|
||
- 3 项测试 ALL_PASS(精确首条 ts/显式日期/空清单回退);README 同步。
|