wechat_digest_generator.py 11.9 KB
#!/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()