forked from Ikalus1988/MisakaNet
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdraft_reminder.py
More file actions
187 lines (155 loc) · 5.92 KB
/
Copy pathdraft_reminder.py
File metadata and controls
187 lines (155 loc) · 5.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env python3
"""
MisakaNet Lesson Draft Reminder (v2)
======================================
扫描本地草稿和仓库中 status=draft 的 lesson,超 48h 未审则推飞书提醒。
用法:
python3 misakanet/scripts/draft_reminder.py
Cron: 0 9,14 * * * python3 /path/to/MisakaNet/misakanet/scripts/draft_reminder.py
(每天 9:00 和 14:00 各跑一次)
"""
import json
import os
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
PROJECT_ROOT = Path(__file__).parent / ".." / ".."
LESSONS_DIR = PROJECT_ROOT / "lessons"
DRAFTS_DIR = Path.home() / ".hermes" / "lessons" / "drafts"
STALE_HOURS = 48
def _get_webhook_url() -> str:
"""从环境变量或 config.yaml 读取飞书 webhook URL"""
url = os.environ.get("FEISHU_WEBHOOK_URL", "")
if url:
return url
config_path = PROJECT_ROOT / "config.yaml"
if config_path.exists():
import yaml
try:
cfg = yaml.safe_load(config_path.read_text(encoding="utf-8"))
url = cfg.get("feishu", {}).get("webhook_url", "")
if url and "${" not in url:
return url
except Exception:
pass
return ""
def _get_file_age_hours(path: Path) -> float:
"""文件最后修改距今的小时数"""
now = datetime.now().timestamp()
age = now - path.stat().st_mtime
return age / 3600
def _get_lesson_age_from_git(filepath: Path) -> float | None:
"""通过 git log 获取 lesson 文件最后提交距今的小时数"""
try:
rel = filepath.relative_to(PROJECT_ROOT)
result = subprocess.run(
["git", "log", "-1", "--format=%ct", "--", str(rel)],
capture_output=True, text=True, timeout=10,
cwd=str(PROJECT_ROOT),
)
if result.returncode == 0 and result.stdout.strip():
commit_ts = int(result.stdout.strip())
return (datetime.now().timestamp() - commit_ts) / 3600
except Exception:
pass
return None
def _scan_drafts() -> list[dict]:
"""扫描 ~/.hermes/lessons/drafts/ 中超 48h 的草稿"""
stale = []
if not DRAFTS_DIR.exists():
return stale
for f in sorted(DRAFTS_DIR.glob("*.md")):
age = _get_file_age_hours(f)
if age > STALE_HOURS:
title = f.name.replace(".md", "").replace("_auto_", " | ")
stale.append({
"source": "drafts",
"title": title,
"age_h": round(age),
"path": str(f),
})
return stale
def _scan_repo_drafts() -> list[dict]:
"""扫描 lessons/*.md 中 status: draft 且超 48h 未审的"""
stale = []
if not LESSONS_DIR.exists():
return stale
for f in sorted(LESSONS_DIR.glob("**/*.md")):
if f.name == "index.md":
continue
content = f.read_text(encoding="utf-8")
if "status: draft" not in content:
continue
# 提取 title
title = ""
for line in content.split("\n"):
if line.startswith("title:"):
title = line.split(":", 1)[1].strip().strip('"').strip("'")
break
if not title:
title = f.name.replace(".md", "")
# 用 git log 判断年龄
age = _get_lesson_age_from_git(f)
if age is not None and age > STALE_HOURS:
stale.append({
"source": "repo",
"title": title,
"age_h": round(age),
"path": str(f),
})
return stale
def _push_feishu(draft_items: list[dict], repo_items: list[dict]):
"""推送到飞书"""
webhook = _get_webhook_url()
if not webhook:
print("[draft_reminder] 未配置飞书 webhook,跳过通知")
return
lines = ["📝 MisakaNet Lesson 草稿提醒"]
now = datetime.now().strftime("%Y-%m-%d %H:%M")
if draft_items:
lines.append(f"\n⏳ 本地草稿待审 ({len(draft_items)} 条):")
for item in draft_items:
lines.append(f" • {item['title']} ({item['age_h']}h)")
lines.append(" 审核: queue_hook_stats.py promote <filename>")
if repo_items:
lines.append(f"\n📄 lessons/ 中 draft 待审 ({len(repo_items)} 条):")
for item in repo_items:
lines.append(f" • {item['title']} ({item['age_h']}h)")
lines.append(" 审核: 编辑文件将 status: draft → status: active")
lines.append(" 或: queue_lesson.py promote <filename>")
if not draft_items and not repo_items:
lines.append("\n✅ 无待审草稿")
lines.append(f"\n---\n巡检时间: {now}")
text = "\n".join(lines)
payload = {"msg_type": "text", "content": {"text": text}}
import requests
try:
resp = requests.post(webhook, json=payload, timeout=10)
if resp.status_code == 200:
print(f" ✅ Feishu 推送成功 ({len(draft_items) + len(repo_items)} 条提醒)")
else:
print(f" ⚠ Feishu 推送返回 {resp.status_code}: {resp.text[:100]}")
except Exception as e:
print(f" ⚠ Feishu 推送失败: {e}")
def main():
print(f"=== MisakaNet Draft Reminder ===")
print(f" 草稿目录: {DRAFTS_DIR}")
print(f" lessons 目录: {LESSONS_DIR}")
print(f" 超时阈值: {STALE_HOURS}h")
draft_items = _scan_drafts()
repo_items = _scan_repo_drafts()
print(f" 本地草稿超 {STALE_HOURS}h: {len(draft_items)} 条")
print(f" lessons/ draft 超 {STALE_HOURS}h: {len(repo_items)} 条")
if draft_items:
for item in draft_items:
print(f" ⏳ {item['title']} ({item['age_h']}h)")
if repo_items:
for item in repo_items[:10]: # 防止刷屏
print(f" 📄 {item['title']} ({item['age_h']}h)")
if len(repo_items) > 10:
print(f" ... 还有 {len(repo_items) - 10} 条")
_push_feishu(draft_items, repo_items)
print("=== done ===")
if __name__ == "__main__":
main()