"""favorites 命令 — 查看微信收藏""" import os import sqlite3 import xml.etree.ElementTree as ET from contextlib import closing from datetime import datetime import click from ..core.contacts import get_contact_names from ..output.formatter import output _FAV_TYPE_MAP = { 1: '文本', 2: '图片', 5: '文章', 19: '名片', 20: '视频号', } _FAV_TYPE_FILTERS = { 'text': 1, 'image': 2, 'article': 5, 'card': 19, 'video': 20, } def _parse_fav_content(content, fav_type): """从 XML content 提取摘要信息。""" if not content: return '' try: root = ET.fromstring(content) except ET.ParseError: return '' item = root if root.tag == 'favitem' else root.find('.//favitem') if item is None: return '' if fav_type == 1: return (item.findtext('desc') or '').strip() if fav_type == 2: return '[图片收藏]' if fav_type == 5: title = (item.findtext('.//pagetitle') or '').strip() desc = (item.findtext('.//pagedesc') or '').strip() return f"{title} - {desc}" if desc else title if fav_type == 19: return (item.findtext('desc') or '').strip() if fav_type == 20: nickname = (item.findtext('.//nickname') or '').strip() desc = (item.findtext('.//desc') or '').strip() parts = [p for p in [nickname, desc] if p] return ' '.join(parts) if parts else '[视频号]' desc = (item.findtext('desc') or '').strip() return desc if desc else '[收藏]' @click.command("favorites") @click.option("--limit", default=20, help="返回数量") @click.option("--type", "fav_type", default=None, type=click.Choice(list(_FAV_TYPE_FILTERS.keys())), help="按类型过滤: text/image/article/card/video") @click.option("--query", default=None, help="关键词搜索") @click.option("--format", "fmt", default="json", type=click.Choice(["json", "text"]), help="输出格式") @click.pass_context def favorites(ctx, limit, fav_type, query, fmt): """查看微信收藏 \b 示例: wechat-cli favorites # 最近收藏 wechat-cli favorites --type article # 只看文章 wechat-cli favorites --query "计算机网络" # 搜索收藏 wechat-cli favorites --limit 5 --format text """ app = ctx.obj # 查找 favorite.db fav_path = None pre_decrypted = os.path.join(app.decrypted_dir, "favorite", "favorite.db") if os.path.exists(pre_decrypted): fav_path = pre_decrypted else: fav_path = app.cache.get(os.path.join("favorite", "favorite.db")) if not fav_path: click.echo("错误: 无法访问 favorite.db", err=True) ctx.exit(3) names = get_contact_names(app.cache, app.decrypted_dir) with closing(sqlite3.connect(fav_path)) as conn: where_parts = [] params = [] if fav_type: where_parts.append('type = ?') params.append(_FAV_TYPE_FILTERS[fav_type]) if query: where_parts.append('content LIKE ?') params.append(f'%{query}%') where_sql = f"WHERE {' AND '.join(where_parts)}" if where_parts else '' rows = conn.execute(f""" SELECT local_id, type, update_time, content, fromusr, realchatname FROM fav_db_item {where_sql} ORDER BY update_time DESC LIMIT ? """, (*params, limit)).fetchall() results = [] for local_id, typ, ts, content, fromusr, realchat in rows: from_display = names.get(fromusr, fromusr) if fromusr else '' chat_display = names.get(realchat, realchat) if realchat else '' summary = _parse_fav_content(content, typ) results.append({ 'id': local_id, 'type': _FAV_TYPE_MAP.get(typ, f'type={typ}'), 'time': datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M'), 'summary': summary, 'from': from_display, 'source_chat': chat_display, }) if fmt == 'json': output({ 'count': len(results), 'favorites': results, }, 'json') else: if not results: output("没有找到收藏", 'text') return lines = [] for r in results: entry = f"[{r['time']}] [{r['type']}] {r['summary']}" if r['from']: entry += f"\n 来自: {r['from']}" if r['source_chat']: entry += f" 聊天: {r['source_chat']}" lines.append(entry) output(f"收藏列表({len(results)} 条):\n\n" + "\n\n".join(lines), 'text')