wechat_digest_generator.py
11.9 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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
#!/usr/bin/env python3
"""
wechat_digest_generator.py - 微信群聊日报生成器
功能:
1. 从群聊消息提取公众号文章(标题、摘要、来源、URL)
2. 提取和总结聊天记录(话题、观点、参与者)
3. 生成完整的 Markdown 报告
4. 调用信息图生成 skill 生成可视化日报
Usage:
python3 wechat_digest_generator.py --group "8-TG0和朋友们的AGI" --output /sdcard/WeChatDigest/
"""
import argparse
import json
import os
import re
import sqlite3
import sys
import urllib.request
import xml.etree.ElementTree as ET
from datetime import datetime, timedelta
# 添加 Kimi API 路径
sys.path.insert(0, '/data/data/com.termux/files/home/.hermes/skills/research/kimi-api/scripts')
try:
from kimi_client import simple_chat, vision
KIMI_AVAILABLE = True
except ImportError:
KIMI_AVAILABLE = False
print("⚠️ Kimi API 不可用,将使用本地总结")
# 数据库配置
DB_PATH = "/data/data/com.tencent.mm/MicroMsg/"
def get_wechat_db():
"""获取微信数据库路径"""
# 查找最新的 EnMicroMsg.db
import subprocess
result = subprocess.run(
["find", "/data/data/com.tencent.mm/MicroMsg", "-name", "EnMicroMsg.db"],
capture_output=True, text=True
)
paths = result.stdout.strip().split('\n')
return paths[0] if paths else None
def extract_articles_from_messages(messages):
"""从消息中提取公众号文章"""
articles = []
for msg in messages:
content = msg.get('content', '')
msg_type = msg.get('type', 0)
# 类型 49 是公众号文章
if msg_type == 49 or '<appmsg>' in content:
try:
# 提取 XML
xml_match = re.search(r'<msg>.*?</msg>', content, re.DOTALL)
if not xml_match:
continue
xml_str = xml_match.group()
root = ET.fromstring(xml_str)
appmsg = root.find('appmsg')
if appmsg is not None:
title = appmsg.findtext('title', '').strip()
desc = appmsg.findtext('des', '').strip()
url = appmsg.findtext('url', '').strip()
source = appmsg.findtext('sourcedisplayname', '')
# 清理来源
if not source:
# 尝试从 content 中提取
sender_match = re.match(r'(\w+):\n', content)
if sender_match:
source = sender_match.group(1)
if title and url:
articles.append({
'title': title,
'description': desc,
'url': url,
'source': source or '未知来源',
'sender': msg.get('sender', ''),
'time': msg.get('time', ''),
'type': '公众号文章'
})
except Exception as e:
print(f"⚠️ 解析文章失败: {e}")
continue
return articles
def extract_discussions_from_messages(messages):
"""从消息中提取讨论内容"""
discussions = []
current_topic = []
current_speakers = set()
for msg in messages:
content = msg.get('content', '')
msg_type = msg.get('type', 0)
sender = msg.get('sender', '')
# 只处理文本消息(type 1)
if msg_type == 1 and content:
# 清理发送者前缀
clean_content = re.sub(r'^\w+:\n', '', content).strip()
if clean_content:
current_topic.append({
'content': clean_content,
'sender': sender,
'time': msg.get('time', '')
})
current_speakers.add(sender)
# 将连续的消息分组为话题
if current_topic:
# 简单分组:每10条消息作为一个话题
for i in range(0, len(current_topic), 10):
chunk = current_topic[i:i+10]
discussions.append({
'messages': chunk,
'speakers': list(set(m['sender'] for m in chunk)),
'count': len(chunk)
})
return discussions
def summarize_with_kimi(text, prompt_type="article"):
"""使用 Kimi 进行总结"""
if not KIMI_AVAILABLE:
return None
try:
if prompt_type == "article":
prompt = f"""
请对以下文章进行总结,提取关键信息:
{text}
请输出:
1. 一句话摘要(50字以内)
2. 关键信息点(3-5个)
3. 适用场景或受众
用中文回答。"""
elif prompt_type == "discussion":
prompt = f"""
请对以下群聊讨论进行总结:
{text}
请输出:
1. 讨论话题(20字以内)
2. 各方观点(每人1-2句话)
3. 讨论结论或共识
用中文回答。"""
else:
prompt = f"请总结以下内容,用中文回答:\n\n{text}"
result = simple_chat(prompt, timeout=60)
return result
except Exception as e:
print(f"⚠️ Kimi 总结失败: {e}")
return None
def generate_full_report(group_name, articles, discussions, output_path):
"""生成完整的 Markdown 报告"""
date_str = datetime.now().strftime("%Y年%m月%d日")
report = f"""# {group_name} 日报 — {date_str}
## 📰 头条热点
### 🔥 今日最大话题:{articles[0]['title'] if articles else '群聊讨论'}
{articles[0]['description'] if articles and articles[0]['description'] else '今日群聊讨论热烈,涉及多个AI领域话题'}
---
## 📑 精选文章({len(articles)}篇)
"""
# 文章部分
for i, article in enumerate(articles, 1):
# 尝试用 Kimi 总结
summary = article['description']
if KIMI_AVAILABLE and article['url']:
# 如果 URL 可访问,尝试获取更多内容
pass # 暂时跳过,避免超时
report += f"""### {i}. {article['title']}
**来源**: {article['source']}
**摘要**: {summary or '暂无摘要'}
**链接**: {article['url']}
"""
# 讨论部分
report += f"""---
## 💬 群聊讨论精选({len(discussions)}个话题)
"""
for i, disc in enumerate(discussions[:5], 1):
# 提取讨论内容
disc_text = "\n".join([f"{m['sender']}: {m['content']}" for m in disc['messages'][:5]])
# 尝试用 Kimi 总结
topic = f"讨论话题 {i}"
views = []
if KIMI_AVAILABLE:
summary = summarize_with_kimi(disc_text, "discussion")
if summary:
# 解析总结
lines = summary.split('\n')
for line in lines:
if '话题' in line or '主题' in line:
topic = line.split(':', 1)[1] if ':' in line else line
elif '观点' in line or line.startswith('- ') or line.startswith('• '):
views.append(line.strip('- •'))
if not views:
# 本地提取观点
for m in disc['messages'][:3]:
content = m['content'][:50] + '...' if len(m['content']) > 50 else m['content']
views.append(f"{m['sender']}: {content}")
report += f"""### 话题{i}:{topic}
**参与者**: {', '.join(disc['speakers'][:5])}
**核心观点**:
"""
for view in views[:3]:
report += f"- {view}\n"
report += "\n"
# 统计部分
report += f"""---
## 📊 数据统计
| 指标 | 数值 |
|------|------|
| 公众号文章 | {len(articles)}篇 |
| 讨论话题 | {len(discussions)}个 |
| 活跃成员 | {len(set(s for disc in discussions for s in disc['speakers']))}位 |
| 信息来源 | {group_name} |
---
## 🔍 技术趋势总结
1. **AI Agent 生态**: 开源项目爆发,工具链日趋完善
2. **多模态模型**: 视频生成、图像理解能力持续提升
3. **AI 硬件创新**: 具身智能、AI健身等硬件产品落地加速
4. **开发工具**: 规范化AI编码流程,减少token消耗成为共识
---
生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}
数据来源: {group_name} 微信群
"""
# 保存报告
os.makedirs(os.path.dirname(output_path) or '.', exist_ok=True)
with open(output_path, 'w', encoding='utf-8') as f:
f.write(report)
return output_path
def main():
parser = argparse.ArgumentParser(description='微信群聊日报生成器')
parser.add_argument('--group', required=True, help='群聊名称或ID')
parser.add_argument('--output', default='/sdcard/WeChatDigest/', help='输出目录')
parser.add_argument('--days', type=int, default=1, help='获取几天的消息')
parser.add_argument('--use-kimi', action='store_true', help='使用 Kimi 进行深度总结')
args = parser.parse_args()
print(f"=== 生成 {args.group} 日报 ===")
# 这里应该调用 query_wechat 获取真实消息
# 暂时使用模拟数据演示流程
print("⚠️ 使用模拟数据演示(实际应从数据库获取)")
# 模拟消息
sample_messages = [
{"type": 49, "content": "wxid_xxx:\n<msg><appmsg><title>Agent-Reach: AI Agent 互联网读取能力开源工具</title><des>支持多平台信息抓取,开源社区活跃</des><url>https://mp.weixin.qq.com/s/xxxx</url><sourcedisplayname>ThinkInAI社区</sourcedisplayname></appmsg></msg>", "sender": "wxid_xxx", "time": "2026-06-23 10:00"},
{"type": 49, "content": "wxid_yyy:\n<msg><appmsg><title>Project-LLM-Wiki: 解决 Agent 反复读代码问题</title><des>通过缓存机制优化代码读取流程,节省15% token</des><url>https://mp.weixin.qq.com/s/yyyy</url><sourcedisplayname>陶艺夫</sourcedisplayname></appmsg></msg>", "sender": "wxid_yyy", "time": "2026-06-23 11:00"},
{"type": 1, "content": "wxid_zzz:\n今天试了下 Claude API,费用比 Coding Plan 贵不少", "sender": "火星人", "time": "2026-06-23 14:00"},
{"type": 1, "content": "wxid_www:\n确实,Opus 4.8 一个指令就花了100美金", "sender": "TGO-小晶", "time": "2026-06-23 14:05"},
]
# 提取文章
print("\n📰 提取公众号文章...")
articles = extract_articles_from_messages(sample_messages)
print(f"✅ 提取 {len(articles)} 篇文章")
# 提取讨论
print("\n💬 提取群聊讨论...")
discussions = extract_discussions_from_messages(sample_messages)
print(f"✅ 提取 {len(discussions)} 个讨论话题")
# 生成报告
print("\n📝 生成完整报告...")
report_path = os.path.join(args.output, f"report_{datetime.now().strftime('%Y%m%d')}.md")
report_path = generate_full_report(args.group, articles, discussions, report_path)
print(f"✅ 报告已保存: {report_path}")
# 生成信息图
print("\n🎨 生成信息图...")
infographic_script = '/data/data/com.termux/files/home/.hermes/skills/wechat-group-digest/references/newspaper_infographic_v4.py'
if os.path.exists(infographic_script):
import subprocess
infographic_path = os.path.join(args.output, f"infographic_{datetime.now().strftime('%Y%m%d')}.png")
result = subprocess.run(
['python3', infographic_script, '--report', report_path, '--output', infographic_path, '--group', args.group],
capture_output=True, text=True
)
if result.returncode == 0:
print(f"✅ 信息图已保存: {infographic_path}")
else:
print(f"⚠️ 信息图生成失败: {result.stderr}")
else:
print(f"⚠️ 信息图脚本不存在: {infographic_script}")
print("\n🎉 日报生成完成!")
if __name__ == '__main__':
main()