Files
weread-notes/daily_report_ai.py
fxy 2508746d24 fix: 阅读日报时间窗口修复 - 日历日改为24小时滚动窗口
- parse_time_from_line: 改为捕获完整 datetime (YYY-MM-DD HH:MM)
- load_today_data: 过滤条件从 date_str 字符串匹配 改为 since_dt 时间戳比较
- 晚间22:00~23:59的阅读标记不再漏入次日日历间隙
- 经验:任何每日定时任务如果按日历日过滤,滚动窗口(过去N小时)比日历日更鲁棒
2026-06-20 23:27:56 +08:00

316 lines
11 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
daily_report_ai.py — AI 驱动的微信读书每日阅读报告
调用 DeepSeek API 对当日划线+批注进行深度分析,
生成有洞察的阅读报告,推送到 Gitea。
定时:每天 22:00配合 sync.py 同步)
依赖pip install requests
"""
import os
import re
import sys
import json
import subprocess
from datetime import datetime, timedelta
REPO_DIR = os.path.expanduser("~/weread-notes")
NOTES_DIR = os.path.join(REPO_DIR, "notes")
DAILY_DIR = os.path.join(REPO_DIR, "daily")
SYNC_SCRIPT = os.path.expanduser("~/weread-sync/sync.py")
API_KEY_FILE = os.path.expanduser("~/.keys/deepseek_api_key")
DEEPSEEK_URL = "https://api.deepseek.com/chat/completions"
DEEPSEEK_MODEL = "deepseek-chat"
# ─── 读取今日数据 ────────────────────────────────────
def parse_time_from_line(line):
m = re.search(r'🕐\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})', line)
if m:
return datetime.strptime(m.group(1), "%Y-%m-%d %H:%M")
return None
def load_today_data(date_str=None):
"""读取过去24小时所有划线+批注(滚动窗口,避免漏掉晚间阅读)"""
if not date_str:
date_str = datetime.now().strftime("%Y-%m-%d")
# 24小时滚动窗口避免晚间阅读漏入次日日历间隙
since_dt = datetime.now() - timedelta(hours=24)
today_entries = []
for fname in os.listdir(NOTES_DIR):
if not fname.endswith(".md"):
continue
filepath = os.path.join(NOTES_DIR, fname)
book_title = fname[:-3]
with open(filepath, "r", encoding="utf-8") as f:
lines = f.readlines()
# 提取书籍元信息
meta = {"author": "", "progress": "", "category": ""}
for line in lines[:35]:
m = re.match(r'\|\s*\*\*作者\*\*\s*\|\s*(.+?)\s*\|', line)
if m: meta["author"] = m.group(1).strip()
m = re.match(r'\|\s*\*\*阅读进度\*\*\s*\|\s*(\d+)%\s*\|', line)
if m: meta["progress"] = f"{m.group(1)}%"
m = re.match(r'\|\s*\*\*分类\*\*\s*\|\s*(.+?)\s*\|', line)
if m: meta["category"] = m.group(1).strip()
# 解析时间戳行
for i, line in enumerate(lines):
ts_dt = parse_time_from_line(line)
if ts_dt is None:
continue
if ts_dt < since_dt:
continue
entry = {
"book": book_title,
"author": meta["author"],
"progress": meta["progress"],
"category": meta["category"],
"time": line.strip(),
}
# 上一行是原文
if i > 0:
prev = lines[i - 1].strip()
entry["text"] = re.sub(r'^>\s*', '', prev).strip()
# 后续行找批注
for j in range(i + 1, min(i + 5, len(lines))):
t = lines[j].strip()
if t.startswith("💬"):
entry["thought"] = re.sub(r'^💬\s*\*{0,2}(.*?)\*{0,2}$', r'\1', t).strip()
break
today_entries.append(entry)
return today_entries
def format_entries_for_prompt(entries):
"""将今日条目格式化为 AI 可读的文本"""
if not entries:
return "今日暂无阅读记录。"
# 按书名分组
from collections import defaultdict
by_book = defaultdict(list)
for e in entries:
by_book[e["book"]].append(e)
parts = []
for book_name in sorted(by_book.keys()):
items = by_book[book_name]
info = []
if items[0]["author"]:
info.append(f"作者:{items[0]['author']}")
if items[0]["category"]:
info.append(f"分类:{items[0]['category']}")
if items[0]["progress"]:
info.append(f"进度:{items[0]['progress']}")
parts.append(f"## 《{book_name}")
if info:
parts.append("" + " | ".join(info) + "")
parts.append("")
for e in items:
parts.append(f"### 划线")
parts.append(f"{e['text']}")
parts.append(f"时间:{e['time']}")
if e.get("thought"):
parts.append(f"")
parts.append(f"我的批注:{e['thought']}")
parts.append("")
return "\n".join(parts)
# ─── AI 分析 ──────────────────────────────────────────
def call_deepseek(prompt, system_prompt):
"""调用 DeepSeek API"""
api_key_file = os.path.expanduser(API_KEY_FILE)
if os.path.exists(api_key_file):
with open(api_key_file) as f:
api_key = f.read().strip()
else:
print("[ERROR] DeepSeek API Key not found")
print(f" Please save your key to: {API_KEY_FILE}")
return None
import urllib.request
body = json.dumps({
"model": DEEPSEEK_MODEL,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
],
"temperature": 0.7,
"max_tokens": 4096,
}).encode()
req = urllib.request.Request(
DEEPSEEK_URL,
data=body,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
)
try:
resp = urllib.request.urlopen(req, timeout=120)
result = json.loads(resp.read())
return result["choices"][0]["message"]["content"]
except Exception as e:
print(f"[ERROR] DeepSeek API call failed: {e}")
return None
# ─── 报告生成 ────────────────────────────────────────
SYSTEM_PROMPT = """你是一位深度阅读分析助手,不是摘要工具。
你的任务是分析用户今日在微信读书上的划线和批注,生成一份高质量的阅读报告。
## 写作要求
- 每一条划线都要展开分析:补充背景知识、指出其深层含义、联系用户的实际场景
- 如果有批注,要围绕批注展开讨论,追问关键问题
- 找出当日阅读内容之间的内在联系(主题、方法、思想上的呼应)
- 像一位私人思考伙伴一样,提供有启发的见解和追问
- 报告要有深度、有观点,不要写成流水账或摘要
- 使用平实、客观的语言,不要抒情、不要煽情
- 适当引用原文,但不要大段堆砌
## 报告结构
1. 今日阅读概览(总览性描述当天阅读的特点)
2. 分书深度分析(每本书独立小节,有洞察的分析)
3. 主题串联(找出跨书的联系和启发)
4. 明日阅读建议(基于当天阅读给出具体建议)"""
def generate_report_md(entries, ai_analysis, date_str):
"""组装最终报告 Markdown含字数与阅读时间估算。"""
cn_chars = len(re.findall(r"[\u4e00-\u9fff]", ai_analysis))
en_words = len(re.findall(r"[a-zA-Z]+", ai_analysis))
total = cn_chars + en_words
read_min = max(1, round(total / 400))
header = f"> 本文共 **{total}** 字 · 预计阅读 **{read_min}** 分钟"
lines = []
lines.append(f"# 今日阅读报告 · {date_str}")
lines.append("")
lines.append(header)
lines.append("")
lines.append("---")
lines.append("")
lines.append(ai_analysis)
lines.append("")
lines.append("---")
lines.append("")
lines.append(f"*报告生成:{datetime.now().strftime('%Y-%m-%d %H:%M')} | AI 模型DeepSeek*")
return "\n".join(lines)
lines.append(f"# 今日阅读报告 · {date_str}")
lines.append("")
lines.append("---")
lines.append("")
lines.append(ai_analysis)
lines.append("")
lines.append("---")
lines.append("")
lines.append(f"*报告生成:{datetime.now().strftime('%Y-%m-%d %H:%M')} | AI 模型DeepSeek*")
return "\n".join(lines)
# ─── Git 操作 ────────────────────────────────────────
def save_and_push(content, date_str):
"""保存报告并推送到 Gitea"""
os.makedirs(DAILY_DIR, exist_ok=True)
filename = f"每日阅读_{date_str}.md"
filepath = os.path.join(DAILY_DIR, filename)
with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
print(f"[OUTPUT] {filepath}")
try:
subprocess.run(["git", "add", "daily/"], cwd=REPO_DIR,
capture_output=True, timeout=30)
result = subprocess.run(
["git", "commit", "-m", f"report: 每日阅读报告 {date_str} (AI分析)", "--allow-empty"],
cwd=REPO_DIR, capture_output=True, timeout=30
)
if result.returncode == 0:
subprocess.run(["git", "push"], cwd=REPO_DIR,
capture_output=True, timeout=60)
print(f"[GIT] Pushed: 每日阅读报告 {date_str}")
else:
print(f"[GIT] No changes")
except subprocess.CalledProcessError as e:
print(f"[GIT] Error: {e.stderr.decode()[:200]}")
# ─── 主流程 ───────────────────────────────────────────
def main():
date_str = datetime.now().strftime("%Y-%m-%d")
print(f"\n{'='*50}")
print(f"每日阅读报告 (AI) | {datetime.now().strftime('%Y-%m-%d %H:%M')}")
print(f"{'='*50}")
# 1. 同步
print("\n[1/4] 同步微信读书笔记...")
if os.path.exists(SYNC_SCRIPT):
subprocess.run(["python3", SYNC_SCRIPT], capture_output=True, timeout=300)
print(" 同步完成")
else:
print(f" [SKIP] {SYNC_SCRIPT} not found")
# 2. 解析今日数据
print(f"\n[2/4] 提取今日数据 ({date_str})...")
entries = load_today_data(date_str)
print(f" 找到 {len(entries)} 条记录")
if not entries:
md = f"# 今日阅读报告 · {date_str}\n\n> 暂无阅读记录\n"
save_and_push(md, date_str)
print("\n今日无阅读数据,已生成空报告")
return
# 3. AI 分析
print(f"\n[3/4] AI 深度分析 ({len(entries)} 条)...")
prompt = format_entries_for_prompt(entries)
full_prompt = f"""以下是用户今日在微信读书上的划线和批注数据:
{prompt}
请根据这些数据生成一份高质量的今日阅读报告。"""
ai_content = call_deepseek(full_prompt, SYSTEM_PROMPT)
if not ai_content:
print("[ERROR] AI 分析失败,使用备用模板")
ai_content = "> AI 分析暂时不可用,请稍后重试。"
# 4. 输出
print(f"\n[4/4] 生成报告并推送...")
md = generate_report_md(entries, ai_content, date_str)
save_and_push(md, date_str)
print(f"\n{'='*50}")
print(f"每日阅读报告完成 ✅")
print(f"{'='*50}")
if __name__ == "__main__":
main()