auto: sync OpenClaw config 2026-08-27 11:17
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user