#!/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 '' in content: try: # 提取 XML xml_match = re.search(r'.*?', 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:\nAgent-Reach: AI Agent 互联网读取能力开源工具支持多平台信息抓取,开源社区活跃https://mp.weixin.qq.com/s/xxxxThinkInAI社区", "sender": "wxid_xxx", "time": "2026-06-23 10:00"}, {"type": 49, "content": "wxid_yyy:\nProject-LLM-Wiki: 解决 Agent 反复读代码问题通过缓存机制优化代码读取流程,节省15% tokenhttps://mp.weixin.qq.com/s/yyyy陶艺夫", "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()