#!/usr/bin/env python3 # -*- coding: utf-8 -*- """从钉邮周报 eml 文件批量解析并归档到 weekly-reports 目录与 SQLite 数据库""" import glob import os import re import sqlite3 import sys from datetime import datetime, date from email import policy from email.parser import BytesParser BASE = "/home/yangxuan/.openclaw/workspace-resume/weekly-reports" INBOUND = "/home/yangxuan/.openclaw/media/inbound" DB = os.path.join(BASE, "weekly_reports.db") ARCHIVE_DIR = os.path.join(BASE, "2026") # 问题标题之后的签名词,用于截断 SIGN_TERMS = ("best regards", "杨轩", "地址", "手机", "邮箱", "yangxuan@") def decode_b64_part(part): payload = part.get_payload(decode=True) if payload is None: return "" charset = part.get_content_charset() or "utf-8" try: return payload.decode(charset) except Exception: return payload.decode("utf-8", errors="replace") def parse_eml(path): with open(path, "rb") as f: msg = BytesParser(policy=policy.default).parse(f) plain = "" for part in msg.walk(): if part.get_content_type() == "text/plain": plain = decode_b64_part(part) break return plain, str(msg["Subject"] or "") def extract_weekly_data(plain): """返回 (project, main_task, days, problems)""" days = {} # key: (yyyy,mm,dd) -> list[item]; 按 ymd 排序 project = "" main_task = "" problems = [] cur_ymd = None in_problem = False for raw in plain.splitlines(): line = raw.strip() if not line: continue # 项目名称 if line.startswith("项目名称"): project = re.sub(r"^[📅]*\s*项目名称[::]?\s*", "", line).strip() continue if line.startswith("主要任务"): main_task = re.sub(r"^[📅]*\s*主要任务[::]?\s*", "", line).strip() continue # 存在问题 标题 if "存在问题" in line or re.match(r"^\s*问题", line): in_problem = True cur_ymd = None continue # 其他小标题(下周计划 等)退出问题区(避免误匹配正文,如"本周...") if re.match(r"^(下周计划|下周计划:|备注[::]|说明[::]|下月计划)", line): in_problem = False cur_ymd = None continue # 签名开始,停止一切处理 low = line.lower() if any(s in low for s in SIGN_TERMS): break # 日期行:可带 emoji、可带/不带年份 m = re.match(r"^[📅🔹•]?\s*(周一|周二|周三|周四|周五|周六|周日)[((]\s*(\d{4})-(\d{2})-(\d{2})\s*[))]", line) if not m: m = re.match(r"^[📅🔹•]?\s*(周一|周二|周三|周四|周五|周六|周日)[((]\s*(\d{2})-(\d{2})\s*[))]", line) if m: wd = m.group(1) if len(m.groups()) == 4: y, mm, dd = int(m.group(2)), m.group(3), m.group(4) else: # 需要年份,用文件主题中的年份,这里用 2026 占位,调用方按主题校正 y = 2026; mm, dd = m.group(2), m.group(3) cur_ymd = (y, mm, dd, wd) days.setdefault(cur_ymd, []) in_problem = False continue # 每日明细内容:* 与文字可能被拆成两行,故 cur_ymd 有效时任何非签名行都归入 if in_problem: # 问题区(仅"存在问题"标题之后) if line not in ("无", "暂无", "None", ""): problems.append(re.sub(r"^[-*·●▪◦]\s*", "", line).strip()) elif cur_ymd: item = re.sub(r"^[-*·●▪◦]\s*", "", line).strip() if item: days[cur_ymd].append(item) return project, main_task, days, problems def main(): files = sorted(glob.glob(os.path.join(INBOUND, "*.eml"))) if not files: print("未找到 eml 文件") return results = [] for path in files: fname = os.path.basename(path) m = re.search(r"(\d{4}-\d{2}-\d{2})_(\d{4}-\d{2}-\d{2})", fname) if not m: print(f"⏭ {fname}: 文件名不含日期,跳过") continue start, end = m.group(1), m.group(2) plain, subject = parse_eml(path) project, main_task, days, problems = extract_weekly_data(plain) y0 = int(start[:4]) from datetime import date as _d iso = _d.fromisoformat(start).isocalendar() wnum = iso[1] fn = os.path.join(ARCHIVE_DIR, f"2026-W{wnum:02d}-周报.md") # 根据项目名确定标题项目代号 proj_code = "G5" if "G6" in project or "G6" in fname: proj_code = "G6" # 构建 Markdown lines = [f"# {proj_code} 开发周报 (2026-W{wnum:02d})", "", f"**日期范围:** {start} ~ {end}", "", "## 基本信息", f"项目名称: {project or '维云智造G5'}", f"主要任务: {main_task}", "", "## 本周工作内容", ""] for (y, mm, dd, wd) in sorted(days.keys(), key=lambda x: (x[0], int(x[1]), int(x[2]))): ymd = f"{y}-{mm}-{dd}" lines.append(f"{wd}({ymd})") for item in days[(y, mm, dd, wd)]: lines.append(f" - {item}") lines.append("") lines += ["## 存在问题"] if problems: for p in problems: lines.append(f" - {p}") else: lines.append("无") lines += ["", "---", f"*归档时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}*", "*来源:解析周报邮件 eml 文件*", ""] md = "\n".join(lines) os.makedirs(ARCHIVE_DIR, exist_ok=True) with open(fn, "w") as f: f.write(md) # 清理空问题条目(* ) print(f"✅ 2026-W{wnum:02d} | {fname[:38]}...") print(f" 项目={project} | 任务={main_task[:30]} | 天={len(days)} | 问题={problems}") results.append((wnum, start, end, project, main_task, len(days), len(problems))) print("\n===== 汇总 =====") for r in results: print(f" W{r[0]:02d} ({r[1]}~{r[2]}) | {r[3]} | 天{r[5]} 问题{r[6]}") if __name__ == "__main__": main()