| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 |
- # coding=utf-8
- """
- 通知内容渲染模块
- 提供多平台通知内容渲染功能,生成格式化的推送消息
- """
- from datetime import datetime
- from typing import Dict, Optional, Callable
- from trendradar.report.formatter import format_title_for_platform
- def render_feishu_content(
- report_data: Dict,
- update_info: Optional[Dict] = None,
- mode: str = "daily",
- separator: str = "---",
- reverse_content_order: bool = False,
- get_time_func: Optional[Callable[[], datetime]] = None,
- rss_items: Optional[list] = None,
- ) -> str:
- """渲染飞书通知内容(支持热榜+RSS合并)
- Args:
- report_data: 报告数据字典,包含 stats, new_titles, failed_ids, total_new_count
- update_info: 版本更新信息(可选)
- mode: 报告模式 ("daily", "incremental", "current")
- separator: 内容分隔符
- reverse_content_order: 是否反转内容顺序(新增在前)
- get_time_func: 获取当前时间的函数(可选,默认使用 datetime.now())
- rss_items: RSS 条目列表(可选,用于合并推送)
- Returns:
- 格式化的飞书消息内容
- """
- # 生成热点词汇统计部分
- stats_content = ""
- if report_data["stats"]:
- stats_content += "📊 **热点词汇统计**\n\n"
- total_count = len(report_data["stats"])
- for i, stat in enumerate(report_data["stats"]):
- word = stat["word"]
- count = stat["count"]
- sequence_display = f"<font color='grey'>[{i + 1}/{total_count}]</font>"
- if count >= 10:
- stats_content += f"🔥 {sequence_display} **{word}** : <font color='red'>{count}</font> 条\n\n"
- elif count >= 5:
- stats_content += f"📈 {sequence_display} **{word}** : <font color='orange'>{count}</font> 条\n\n"
- else:
- stats_content += f"📌 {sequence_display} **{word}** : {count} 条\n\n"
- for j, title_data in enumerate(stat["titles"], 1):
- formatted_title = format_title_for_platform(
- "feishu", title_data, show_source=True
- )
- stats_content += f" {j}. {formatted_title}\n"
- if j < len(stat["titles"]):
- stats_content += "\n"
- if i < len(report_data["stats"]) - 1:
- stats_content += f"\n{separator}\n\n"
- # 生成新增新闻部分
- new_titles_content = ""
- if report_data["new_titles"]:
- new_titles_content += (
- f"🆕 **本次新增热点新闻** (共 {report_data['total_new_count']} 条)\n\n"
- )
- for source_data in report_data["new_titles"]:
- new_titles_content += (
- f"**{source_data['source_name']}** ({len(source_data['titles'])} 条):\n"
- )
- for j, title_data in enumerate(source_data["titles"], 1):
- title_data_copy = title_data.copy()
- title_data_copy["is_new"] = False
- formatted_title = format_title_for_platform(
- "feishu", title_data_copy, show_source=False
- )
- new_titles_content += f" {j}. {formatted_title}\n"
- new_titles_content += "\n"
- # 根据配置决定内容顺序
- text_content = ""
- if reverse_content_order:
- # 新增热点在前,热点词汇统计在后
- if new_titles_content:
- text_content += new_titles_content
- if stats_content:
- text_content += f"\n{separator}\n\n"
- if stats_content:
- text_content += stats_content
- else:
- # 默认:热点词汇统计在前,新增热点在后
- if stats_content:
- text_content += stats_content
- if new_titles_content:
- text_content += f"\n{separator}\n\n"
- if new_titles_content:
- text_content += new_titles_content
- # 添加 RSS 内容(如果有)
- if rss_items:
- rss_content = _render_rss_section_feishu(rss_items, separator)
- if text_content:
- text_content += f"\n{separator}\n\n"
- text_content += rss_content
- if not text_content:
- if mode == "incremental":
- mode_text = "增量模式下暂无新增匹配的热点词汇"
- elif mode == "current":
- mode_text = "当前榜单模式下暂无匹配的热点词汇"
- else:
- mode_text = "暂无匹配的热点词汇"
- text_content = f"📭 {mode_text}\n\n"
- if report_data["failed_ids"]:
- if text_content and "暂无匹配" not in text_content:
- text_content += f"\n{separator}\n\n"
- text_content += "⚠️ **数据获取失败的平台:**\n\n"
- for i, id_value in enumerate(report_data["failed_ids"], 1):
- text_content += f" • <font color='red'>{id_value}</font>\n"
- # 获取当前时间
- now = get_time_func() if get_time_func else datetime.now()
- text_content += (
- f"\n\n<font color='grey'>更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}</font>"
- )
- if update_info:
- text_content += f"\n<font color='grey'>TrendRadar 发现新版本 {update_info['remote_version']},当前 {update_info['current_version']}</font>"
- return text_content
- def render_dingtalk_content(
- report_data: Dict,
- update_info: Optional[Dict] = None,
- mode: str = "daily",
- reverse_content_order: bool = False,
- get_time_func: Optional[Callable[[], datetime]] = None,
- rss_items: Optional[list] = None,
- ) -> str:
- """渲染钉钉通知内容(支持热榜+RSS合并)
- Args:
- report_data: 报告数据字典,包含 stats, new_titles, failed_ids, total_new_count
- update_info: 版本更新信息(可选)
- mode: 报告模式 ("daily", "incremental", "current")
- reverse_content_order: 是否反转内容顺序(新增在前)
- get_time_func: 获取当前时间的函数(可选,默认使用 datetime.now())
- rss_items: RSS 条目列表(可选,用于合并推送)
- Returns:
- 格式化的钉钉消息内容
- """
- total_titles = sum(
- len(stat["titles"]) for stat in report_data["stats"] if stat["count"] > 0
- )
- now = get_time_func() if get_time_func else datetime.now()
- # 头部信息
- header_content = f"**总新闻数:** {total_titles}\n\n"
- header_content += f"**时间:** {now.strftime('%Y-%m-%d %H:%M:%S')}\n\n"
- header_content += "**类型:** 热点分析报告\n\n"
- header_content += "---\n\n"
- # 生成热点词汇统计部分
- stats_content = ""
- if report_data["stats"]:
- stats_content += "📊 **热点词汇统计**\n\n"
- total_count = len(report_data["stats"])
- for i, stat in enumerate(report_data["stats"]):
- word = stat["word"]
- count = stat["count"]
- sequence_display = f"[{i + 1}/{total_count}]"
- if count >= 10:
- stats_content += f"🔥 {sequence_display} **{word}** : **{count}** 条\n\n"
- elif count >= 5:
- stats_content += f"📈 {sequence_display} **{word}** : **{count}** 条\n\n"
- else:
- stats_content += f"📌 {sequence_display} **{word}** : {count} 条\n\n"
- for j, title_data in enumerate(stat["titles"], 1):
- formatted_title = format_title_for_platform(
- "dingtalk", title_data, show_source=True
- )
- stats_content += f" {j}. {formatted_title}\n"
- if j < len(stat["titles"]):
- stats_content += "\n"
- if i < len(report_data["stats"]) - 1:
- stats_content += "\n---\n\n"
- # 生成新增新闻部分
- new_titles_content = ""
- if report_data["new_titles"]:
- new_titles_content += (
- f"🆕 **本次新增热点新闻** (共 {report_data['total_new_count']} 条)\n\n"
- )
- for source_data in report_data["new_titles"]:
- new_titles_content += f"**{source_data['source_name']}** ({len(source_data['titles'])} 条):\n\n"
- for j, title_data in enumerate(source_data["titles"], 1):
- title_data_copy = title_data.copy()
- title_data_copy["is_new"] = False
- formatted_title = format_title_for_platform(
- "dingtalk", title_data_copy, show_source=False
- )
- new_titles_content += f" {j}. {formatted_title}\n"
- new_titles_content += "\n"
- # 根据配置决定内容顺序
- text_content = header_content
- if reverse_content_order:
- # 新增热点在前,热点词汇统计在后
- if new_titles_content:
- text_content += new_titles_content
- if stats_content:
- text_content += "\n---\n\n"
- if stats_content:
- text_content += stats_content
- else:
- # 默认:热点词汇统计在前,新增热点在后
- if stats_content:
- text_content += stats_content
- if new_titles_content:
- text_content += "\n---\n\n"
- if new_titles_content:
- text_content += new_titles_content
- # 添加 RSS 内容(如果有)
- if rss_items:
- rss_content = _render_rss_section_markdown(rss_items)
- if stats_content or new_titles_content:
- text_content += "\n---\n\n"
- text_content += rss_content
- if not stats_content and not new_titles_content and not rss_items:
- if mode == "incremental":
- mode_text = "增量模式下暂无新增匹配的热点词汇"
- elif mode == "current":
- mode_text = "当前榜单模式下暂无匹配的热点词汇"
- else:
- mode_text = "暂无匹配的热点词汇"
- text_content += f"📭 {mode_text}\n\n"
- if report_data["failed_ids"]:
- if "暂无匹配" not in text_content:
- text_content += "\n---\n\n"
- text_content += "⚠️ **数据获取失败的平台:**\n\n"
- for i, id_value in enumerate(report_data["failed_ids"], 1):
- text_content += f" • **{id_value}**\n"
- text_content += f"\n\n> 更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}"
- if update_info:
- text_content += f"\n> TrendRadar 发现新版本 **{update_info['remote_version']}**,当前 **{update_info['current_version']}**"
- return text_content
- def render_rss_feishu_content(
- rss_items: list,
- feeds_info: Optional[Dict] = None,
- separator: str = "---",
- get_time_func: Optional[Callable[[], datetime]] = None,
- ) -> str:
- """渲染 RSS 飞书通知内容
- Args:
- rss_items: RSS 条目列表,每个条目包含:
- - title: 标题
- - feed_id: RSS 源 ID
- - feed_name: RSS 源名称
- - url: 链接
- - published_at: 发布时间
- - summary: 摘要(可选)
- - author: 作者(可选)
- feeds_info: RSS 源 ID 到名称的映射
- separator: 内容分隔符
- get_time_func: 获取当前时间的函数(可选)
- Returns:
- 格式化的飞书消息内容
- """
- if not rss_items:
- now = get_time_func() if get_time_func else datetime.now()
- return f"📭 暂无新的 RSS 订阅内容\n\n<font color='grey'>更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}</font>"
- # 按 feed_id 分组
- feeds_map: Dict[str, list] = {}
- for item in rss_items:
- feed_id = item.get("feed_id", "unknown")
- if feed_id not in feeds_map:
- feeds_map[feed_id] = []
- feeds_map[feed_id].append(item)
- text_content = f"📰 **RSS 订阅更新** (共 {len(rss_items)} 条)\n\n"
- text_content += f"{separator}\n\n"
- for feed_id, items in feeds_map.items():
- feed_name = items[0].get("feed_name", feed_id) if items else feed_id
- if feeds_info and feed_id in feeds_info:
- feed_name = feeds_info[feed_id]
- text_content += f"**{feed_name}** ({len(items)} 条)\n\n"
- for i, item in enumerate(items, 1):
- title = item.get("title", "")
- url = item.get("url", "")
- published_at = item.get("published_at", "")
- if url:
- text_content += f" {i}. [{title}]({url})"
- else:
- text_content += f" {i}. {title}"
- if published_at:
- text_content += f" <font color='grey'>- {published_at}</font>"
- text_content += "\n"
- if i < len(items):
- text_content += "\n"
- text_content += f"\n{separator}\n\n"
- now = get_time_func() if get_time_func else datetime.now()
- text_content += f"<font color='grey'>更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}</font>"
- return text_content
- def render_rss_dingtalk_content(
- rss_items: list,
- feeds_info: Optional[Dict] = None,
- get_time_func: Optional[Callable[[], datetime]] = None,
- ) -> str:
- """渲染 RSS 钉钉通知内容
- Args:
- rss_items: RSS 条目列表
- feeds_info: RSS 源 ID 到名称的映射
- get_time_func: 获取当前时间的函数(可选)
- Returns:
- 格式化的钉钉消息内容
- """
- now = get_time_func() if get_time_func else datetime.now()
- if not rss_items:
- return f"📭 暂无新的 RSS 订阅内容\n\n> 更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}"
- # 按 feed_id 分组
- feeds_map: Dict[str, list] = {}
- for item in rss_items:
- feed_id = item.get("feed_id", "unknown")
- if feed_id not in feeds_map:
- feeds_map[feed_id] = []
- feeds_map[feed_id].append(item)
- # 头部信息
- text_content = f"**总条目数:** {len(rss_items)}\n\n"
- text_content += f"**时间:** {now.strftime('%Y-%m-%d %H:%M:%S')}\n\n"
- text_content += "**类型:** RSS 订阅更新\n\n"
- text_content += "---\n\n"
- for feed_id, items in feeds_map.items():
- feed_name = items[0].get("feed_name", feed_id) if items else feed_id
- if feeds_info and feed_id in feeds_info:
- feed_name = feeds_info[feed_id]
- text_content += f"📰 **{feed_name}** ({len(items)} 条)\n\n"
- for i, item in enumerate(items, 1):
- title = item.get("title", "")
- url = item.get("url", "")
- published_at = item.get("published_at", "")
- if url:
- text_content += f" {i}. [{title}]({url})"
- else:
- text_content += f" {i}. {title}"
- if published_at:
- text_content += f" - {published_at}"
- text_content += "\n"
- if i < len(items):
- text_content += "\n"
- text_content += "\n---\n\n"
- text_content += f"> 更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}"
- return text_content
- def render_rss_markdown_content(
- rss_items: list,
- feeds_info: Optional[Dict] = None,
- get_time_func: Optional[Callable[[], datetime]] = None,
- ) -> str:
- """渲染 RSS 通用 Markdown 格式内容(企业微信、Bark、ntfy、Slack)
- Args:
- rss_items: RSS 条目列表
- feeds_info: RSS 源 ID 到名称的映射
- get_time_func: 获取当前时间的函数(可选)
- Returns:
- 格式化的 Markdown 消息内容
- """
- now = get_time_func() if get_time_func else datetime.now()
- if not rss_items:
- return f"📭 暂无新的 RSS 订阅内容\n\n更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}"
- # 按 feed_id 分组
- feeds_map: Dict[str, list] = {}
- for item in rss_items:
- feed_id = item.get("feed_id", "unknown")
- if feed_id not in feeds_map:
- feeds_map[feed_id] = []
- feeds_map[feed_id].append(item)
- text_content = f"📰 **RSS 订阅更新** (共 {len(rss_items)} 条)\n\n"
- for feed_id, items in feeds_map.items():
- feed_name = items[0].get("feed_name", feed_id) if items else feed_id
- if feeds_info and feed_id in feeds_info:
- feed_name = feeds_info[feed_id]
- text_content += f"**{feed_name}** ({len(items)} 条)\n"
- for i, item in enumerate(items, 1):
- title = item.get("title", "")
- url = item.get("url", "")
- published_at = item.get("published_at", "")
- if url:
- text_content += f" {i}. [{title}]({url})"
- else:
- text_content += f" {i}. {title}"
- if published_at:
- text_content += f" `{published_at}`"
- text_content += "\n"
- text_content += "\n"
- text_content += f"更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}"
- return text_content
- # === RSS 内容渲染辅助函数(用于合并推送) ===
- def _render_rss_section_feishu(rss_items: list, separator: str = "---") -> str:
- """渲染 RSS 内容区块(飞书格式,用于合并推送)"""
- if not rss_items:
- return ""
- # 按 feed_id 分组
- feeds_map: Dict[str, list] = {}
- for item in rss_items:
- feed_id = item.get("feed_id", "unknown")
- if feed_id not in feeds_map:
- feeds_map[feed_id] = []
- feeds_map[feed_id].append(item)
- text_content = f"📰 **RSS 订阅更新** (共 {len(rss_items)} 条)\n\n"
- for feed_id, items in feeds_map.items():
- feed_name = items[0].get("feed_name", feed_id) if items else feed_id
- text_content += f"**{feed_name}** ({len(items)} 条)\n\n"
- for i, item in enumerate(items, 1):
- title = item.get("title", "")
- url = item.get("url", "")
- published_at = item.get("published_at", "")
- if url:
- text_content += f" {i}. [{title}]({url})"
- else:
- text_content += f" {i}. {title}"
- if published_at:
- text_content += f" <font color='grey'>- {published_at}</font>"
- text_content += "\n"
- if i < len(items):
- text_content += "\n"
- text_content += "\n"
- return text_content.rstrip("\n")
- def _render_rss_section_markdown(rss_items: list) -> str:
- """渲染 RSS 内容区块(通用 Markdown 格式,用于合并推送)"""
- if not rss_items:
- return ""
- # 按 feed_id 分组
- feeds_map: Dict[str, list] = {}
- for item in rss_items:
- feed_id = item.get("feed_id", "unknown")
- if feed_id not in feeds_map:
- feeds_map[feed_id] = []
- feeds_map[feed_id].append(item)
- text_content = f"📰 **RSS 订阅更新** (共 {len(rss_items)} 条)\n\n"
- for feed_id, items in feeds_map.items():
- feed_name = items[0].get("feed_name", feed_id) if items else feed_id
- text_content += f"**{feed_name}** ({len(items)} 条)\n"
- for i, item in enumerate(items, 1):
- title = item.get("title", "")
- url = item.get("url", "")
- published_at = item.get("published_at", "")
- if url:
- text_content += f" {i}. [{title}]({url})"
- else:
- text_content += f" {i}. {title}"
- if published_at:
- text_content += f" `{published_at}`"
- text_content += "\n"
- text_content += "\n"
- return text_content.rstrip("\n")
|