auto: sync OpenClaw config 2026-08-27 11:17

This commit is contained in:
2026-08-27 11:17:10 +08:00
parent 0269b99815
commit 7a5ca9f1a7
94 changed files with 4497 additions and 2088 deletions
+247 -12
View File
@@ -1,7 +1,12 @@
#!/usr/bin/env python3
"""
G6 周报发送脚本
用法:构造多段邮件(纯文本 + HTML),存入钉邮草稿箱
周报发送脚本(支持 G5/G6
功能:
1. 构造多段邮件(纯文本 + HTML),存入钉邮草稿箱
2. 自动归档到 weekly-reports/YYYY/YYYY-Www.md
用法:
python3 send_weekly_report.py --project G5 --plain "..." --html "..." --date-range "2026-07-27 ~ 2026-07-31" --archive
"""
import email
from email.mime.multipart import MIMEMultipart
@@ -11,6 +16,10 @@ import imaplib
import os
import time
import sys
import re
import argparse
from datetime import datetime
from pathlib import Path
def load_env(path):
@@ -36,15 +45,214 @@ IMAP_PORT = 993
IMAP_USER = _ENV.get('WITSOFT_IMAP_USER', 'yangxuan@witsoft.cn')
IMAP_PASSWORD = _ENV.get('WITSOFT_IMAP_PASSWORD', '')
def send_to_drafts(plain_body: str, html_body: str, date_range: str, project: str = "G6"):
"""构造多段邮件并存入钉邮草稿箱
# MySQL 同步配置(简历 resume 库,用于月度/年度总结)
MYSQL_HOST = '127.0.0.1'
MYSQL_PORT = 3306
MYSQL_USER = _ENV.get('MYSQL_USER', 'root')
MYSQL_PASSWORD = _ENV.get('MYSQL_PWD', '')
MYSQL_DB = 'resume'
MYSQL_ENABLED = bool(MYSQL_PASSWORD)
def archive_report(plain_body: str, date_range: str, project: str = "G5"):
"""将周报归档到 weekly-reports/YYYY/YYYY-Www.md
Args:
plain_body: 纯文本正文
date_range: 日期范围字符串,如 "2026-07-27 ~ 2026-07-31"
project: 项目名称
Returns:
归档文件路径
"""
# 解析日期范围
match = re.match(r'(\d{4}-\d{2}-\d{2})\s*~\s*(\d{4}-\d{2}-\d{2})', date_range)
if not match:
print(f"⚠️ 日期格式错误,跳过归档:{date_range}")
return None
start_date = datetime.strptime(match.group(1), '%Y-%m-%d')
end_date = datetime.strptime(match.group(2), '%Y-%m-%d')
# 计算周数
week_number = start_date.isocalendar()[1]
year = start_date.year
# 创建年份目录
reports_dir = Path(__file__).parent / 'weekly-reports' / str(year)
reports_dir.mkdir(parents=True, exist_ok=True)
# 生成文件名
filename = f"{year}-W{week_number:02d}-周报.md"
filepath = reports_dir / filename
# 生成归档内容
archive_content = f"""# {project}开发周报 ({year}-W{week_number:02d})
**日期范围:** {date_range}
## 基本信息
{plain_body.split('本周工作内容')[0].strip()}
## 本周工作内容
{plain_body.split('本周工作内容')[1].split('存在问题')[0].strip()}
## 存在问题
{plain_body.split('存在问题')[1].strip() if '存在问题' in plain_body else ''}
---
*归档时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}*
*原始邮件主题:{project}开发周报 ({date_range})*
"""
# 写入文件
with open(filepath, 'w', encoding='utf-8') as f:
f.write(archive_content)
print(f"✅ 周报已归档:{filepath}")
return str(filepath)
def _parse_plain_for_db(plain_body: str):
"""从纯文本正文解析出项目、主要任务、每日明细、问题,供 MySQL 写入。
返回 dict{project, main_task, days:[(date, weekday, items)], problems:[]}
"""
project = "G5"
main_task = ""
days = []
problems = []
cur = None
in_prob = False
weekday_map = {"周一": 1, "周二": 2, "周三": 3, "周四": 4, "周五": 5, "周六": 6, "周日": 7}
for raw in plain_body.splitlines():
line = raw.strip()
if not line:
continue
if line.startswith("项目名称"):
pm = re.sub(r"^[\U0001F4C5]?\s*项目名称[:]?\s*", "", line).strip()
if "G6" in pm:
project = "G6"
elif "G5" in pm:
project = "G5"
continue
if line.startswith("主要任务"):
main_task = re.sub(r"^[\U0001F4C5]?\s*主要任务[:]?\s*", "", line).strip()
continue
if "存在问题" in line:
in_prob = True
cur = None
continue
if re.match(r"^(下周计划|下周计划:|备注[::]|说明[::])", line):
in_prob = False
cur = None
continue
low = line.lower()
if any(s in low for s in ("best regards", "杨轩", "地址", "手机", "邮箱", "yangxuan@")):
break
dm = re.match(r"^[\U0001F4C5\U0001F539\u2022]?\s*(周一|周二|周三|周四|周五|周六|周日)[((]\s*(\d{4})-(\d{2})-(\d{2})\s*[)]", line)
if not dm:
dm = re.match(r"^[\U0001F4C5\U0001F539\u2022]?\s*(周一|周二|周三|周四|周五|周六|周日)[((]\s*(\d{2})-(\d{2})\s*[)]", line)
if dm:
wd = dm.group(1)
if len(dm.groups()) == 4:
y, mm, dd = dm.group(2), dm.group(3), dm.group(4)
else:
y, mm, dd = "2026", dm.group(2), dm.group(3)
cur = {"date": f"{y}-{mm}-{dd}", "weekday": weekday_map.get(wd, 0), "items": []}
days.append(cur)
in_prob = False
continue
if in_prob:
if line not in ("", "暂无", "None", ""):
problems.append(re.sub(r"^[-*\u00B7\u25CF\u25C6]\s*", "", line).strip())
elif cur:
item = re.sub(r"^[-*\u00B7\u25CF\u25C6]\s*", "", line).strip()
if item:
cur["items"].append(item)
return {"project": project, "main_task": main_task, "days": days, "problems": problems}
def sync_to_mysql(plain_body: str, date_range: str, project: str = "G5"):
"""将周报数据同步到 MySQL resume 库(用于月度/年度总结、OKR 分析)。
无密码或连接失败时静默跳过,不影响发信与本地归档。
"""
if not MYSQL_ENABLED:
print("⚠️ MYSQL_PWD 未配置,跳过 MySQL 同步")
return
try:
import pymysql
except ImportError:
print("⚠️ 未安装 pymysql,跳过 MySQL 同步(pip install pymysql")
return
data = _parse_plain_for_db(plain_body)
real_proj = data["project"] or project
m = re.match(r"(\d{4}-\d{2}-\d{2})\s*~\s*(\d{4}-\d{2}-\d{2})", date_range)
if not m:
print("⚠️ 日期格式错误,跳过 MySQL 同步")
return
start_s, end_s = m.group(1), m.group(2)
start_d = datetime.strptime(start_s, "%Y-%m-%d")
week_number = start_d.isocalendar()[1]
year = start_d.year
try:
conn = pymysql.connect(host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER,
password=MYSQL_PASSWORD, database=MYSQL_DB, charset="utf8mb4")
cur = conn.cursor()
# 清同键旧数据(含级联)
cur.execute("SELECT id FROM weekly_reports WHERE year=%s AND week_number=%s AND project=%s",
(year, week_number, real_proj))
rows = cur.fetchall()
for (rid,) in rows:
cur.execute("DELETE FROM weekly_report_daily WHERE report_id=%s", (rid,))
cur.execute("DELETE FROM weekly_report_problems WHERE report_id=%s", (rid,))
cur.execute("DELETE FROM weekly_reports WHERE id=%s", (rid,))
# 插入主表
cur.execute(
"INSERT INTO weekly_reports (year, week_number, start_date, end_date, project, main_task, has_problems) "
"VALUES (%s,%s,%s,%s,%s,%s,%s)",
(year, week_number, start_s, end_s, real_proj, data["main_task"], 1 if data["problems"] else 0))
new_id = cur.lastrowid
for d in data["days"]:
cur.execute(
"INSERT INTO weekly_report_daily (report_id, day_of_week, work_date, work_items) VALUES (%s,%s,%s,%s)",
(new_id, d["weekday"], d["date"], "\n".join(d["items"])))
for p in data["problems"]:
cur.execute(
"INSERT INTO weekly_report_problems (report_id, problem_description) VALUES (%s,%s)",
(new_id, p))
conn.commit()
cur.close()
conn.close()
print(f"✅ 周报已同步 MySQL resume 库:{real_proj} W{week_number:02d}{data['main_task'][:20] or '无任务'}")
except Exception as e:
print(f"⚠️ MySQL 同步失败(不影响发信):{e}")
def send_to_drafts(plain_body: str, html_body: str, date_range: str, project: str = "G5", archive: bool = True, sync_mysql: bool = True):
"""构造多段邮件并存入钉邮草稿箱,同时可选归档到本地
Args:
plain_body: 纯文本正文
html_body: HTML 正文
date_range: 日期范围字符串
project: 项目名称,默认"G6",用于邮件主题
project: 项目名称,默认"G5"
archive: 是否同时归档到本地,默认 True
sync_mysql: 是否同步到 MySQL resume 库,默认 True
Returns:
IMAP append 结果
"""
# 归档到本地
if archive:
archive_report(plain_body, date_range, project)
# 同步 MySQL(月度/年度总结数据源)
if sync_mysql:
sync_to_mysql(plain_body, date_range, project)
msg = MIMEMultipart('related')
msg['From'] = Header('杨轩', 'utf-8').encode() + ' <yangxuan@witsoft.cn>'
msg['To'] = Header('刘强', 'utf-8').encode() + ' <liuqiang@witsoft.cn>'
@@ -70,17 +278,44 @@ def send_to_drafts(plain_body: str, html_body: str, date_range: str, project: st
conn.logout()
return result
def main():
parser = argparse.ArgumentParser(description='周报发送脚本(支持 G5/G6')
parser.add_argument('--project', type=str, default='G5', help='项目名称,默认 G5')
parser.add_argument('--plain', type=str, required=True, help='纯文本正文')
parser.add_argument('--html', type=str, required=True, help='HTML 正文')
parser.add_argument('--date-range', type=str, required=True, help='日期范围,如 "2026-07-27 ~ 2026-07-31"')
parser.add_argument('--no-archive', action='store_true', help='不归档到本地')
parser.add_argument('--no-mysql', action='store_true', help='不同步到 MySQL resume 库')
args = parser.parse_args()
result = send_to_drafts(
plain_body=args.plain,
html_body=args.html,
date_range=args.date_range,
project=args.project,
archive=not args.no_archive,
sync_mysql=not args.no_mysql
)
print(f"邮件存入草稿箱结果:{result}")
if __name__ == '__main__':
# 测试
plain = """项目名称: 维云智造 G6
# 命令行模式
if len(sys.argv) > 1:
main()
else:
# 测试模式
plain = """项目名称: 维云智造 G5
主要任务: 测试
本周工作内容
周一(2026-07-13
- 测试
周一(2026-07-27
- 测试内容 1
- 测试内容 2
存在问题
"""
html = "<p>test</p>"
result = send_to_drafts(plain, html, "2026-07-13 ~ 2026-07-17", "G6")
print(result)
html = "<p>test</p>"
result = send_to_drafts(plain, html, "2026-07-27 ~ 2026-07-31", "G5")
print(result)