Skip to content

Commit 87e89ba

Browse files
committed
fix(notification): 修复 RSS 翻译未应用到网页版 + Telegram 格式不匹配问题
1 parent d1fe2b7 commit 87e89ba

3 files changed

Lines changed: 78 additions & 13 deletions

File tree

trendradar/__main__.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,22 @@ def _run_analysis_pipeline(
867867
standalone_data=standalone_data
868868
)
869869

870-
# HTML生成(如果启用)
870+
# 翻译 RSS 内容(如果启用)— 在 HTML 生成前执行,确保网页版也能展示翻译内容
871+
# 注意:仅翻译 rss_items 和 rss_new_items,不翻译 standalone_data(通知前会重新生成)
872+
# 热榜翻译在推送时由 dispatch_all 处理 report_data
873+
trans_config = self.ctx.config.get("AI_TRANSLATION", {})
874+
if trans_config.get("ENABLED", False):
875+
dispatcher = self.ctx.create_notification_dispatcher()
876+
display_regions = self.ctx.config.get("DISPLAY", {}).get("REGIONS", {})
877+
_, rss_items, rss_new_items, _ = \
878+
dispatcher.translate_content(
879+
report_data={"stats": [], "new_titles": []},
880+
rss_items=rss_items,
881+
rss_new_items=rss_new_items,
882+
display_regions=display_regions,
883+
)
884+
885+
# HTML生成(如果启用)— 使用翻译后的数据
871886
html_file = None
872887
if self.ctx.config["STORAGE"]["FORMATS"]["HTML"]:
873888
html_file = self.ctx.generate_html(
@@ -960,6 +975,7 @@ def _send_notification_if_needed(
960975
update_info_to_send = self.update_info if cfg["SHOW_VERSION_UPDATE"] else None
961976

962977
# 使用 NotificationDispatcher 发送到所有渠道
978+
# RSS/独立展示区数据已在分析流水线中翻译过,跳过重复翻译(仅翻译热榜 report_data)
963979
dispatcher = self.ctx.create_notification_dispatcher()
964980
results = dispatcher.dispatch_all(
965981
report_data=report_data,
@@ -972,6 +988,7 @@ def _send_notification_if_needed(
972988
rss_new_items=rss_new_items,
973989
ai_analysis=ai_result,
974990
standalone_data=standalone_data,
991+
skip_translation=True,
975992
)
976993

977994
if not results:

trendradar/ai/formatter.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,13 +308,48 @@ def render_ai_analysis_plain(result: AIAnalysisResult) -> str:
308308
return "\n".join(lines)
309309

310310

311+
def render_ai_analysis_telegram(result: AIAnalysisResult) -> str:
312+
"""渲染为 Telegram HTML 格式(配合 parse_mode: HTML)
313+
314+
Telegram Bot API 的 HTML 模式仅支持有限标签:
315+
<b>, <i>, <u>, <s>, <code>, <pre>, <a href="">, <blockquote>
316+
换行直接使用 \\n,不支持 <br>, <div>, <h1>-<h6> 等标签。
317+
"""
318+
if not result.success:
319+
return f"⚠️ AI 分析失败: {_escape_html(result.error)}"
320+
321+
lines = ["<b>✨ AI 热点分析</b>", ""]
322+
323+
if result.core_trends:
324+
lines.extend(["<b>核心热点态势</b>", _escape_html(_format_list_content(result.core_trends)), ""])
325+
326+
if result.sentiment_controversy:
327+
lines.extend(["<b>舆论风向争议</b>", _escape_html(_format_list_content(result.sentiment_controversy)), ""])
328+
329+
if result.signals:
330+
lines.extend(["<b>异动与弱信号</b>", _escape_html(_format_list_content(result.signals)), ""])
331+
332+
if result.rss_insights:
333+
lines.extend(["<b>RSS 深度洞察</b>", _escape_html(_format_list_content(result.rss_insights)), ""])
334+
335+
if result.outlook_strategy:
336+
lines.extend(["<b>研判策略建议</b>", _escape_html(_format_list_content(result.outlook_strategy)), ""])
337+
338+
if result.standalone_summaries:
339+
summaries_text = _format_standalone_summaries(result.standalone_summaries)
340+
if summaries_text:
341+
lines.extend(["<b>独立源点速览</b>", _escape_html(summaries_text)])
342+
343+
return "\n".join(lines)
344+
345+
311346
def get_ai_analysis_renderer(channel: str):
312347
"""根据渠道获取对应的渲染函数"""
313348
renderers = {
314349
"feishu": render_ai_analysis_feishu,
315350
"dingtalk": render_ai_analysis_dingtalk,
316351
"wework": render_ai_analysis_markdown,
317-
"telegram": render_ai_analysis_markdown,
352+
"telegram": render_ai_analysis_telegram,
318353
"email": render_ai_analysis_html_rich, # 邮件使用丰富样式,配合 HTML 报告的 CSS
319354
"ntfy": render_ai_analysis_markdown,
320355
"bark": render_ai_analysis_plain,

trendradar/notification/dispatcher.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,14 @@ def __init__(
7373
self.max_accounts = config.get("MAX_ACCOUNTS_PER_CHANNEL", 3)
7474
self.translator = translator
7575

76-
def _translate_content(
76+
def translate_content(
7777
self,
7878
report_data: Dict,
7979
rss_items: Optional[List[Dict]] = None,
8080
rss_new_items: Optional[List[Dict]] = None,
8181
standalone_data: Optional[Dict] = None,
8282
display_regions: Optional[Dict] = None,
83+
skip_rss: bool = False,
8384
) -> tuple:
8485
"""
8586
翻译推送内容
@@ -90,6 +91,7 @@ def _translate_content(
9091
rss_new_items: RSS 新增条目
9192
standalone_data: 独立展示区数据
9293
display_regions: 区域显示配置(不展示的区域跳过翻译)
94+
skip_rss: 跳过 RSS 和独立展示区翻译(当数据已在上游翻译过时使用)
9395
9496
Returns:
9597
tuple: (翻译后的 report_data, rss_items, rss_new_items, standalone_data)
@@ -127,14 +129,14 @@ def _translate_content(
127129
title_locations.append(("new_titles", source_idx, title_idx))
128130

129131
# 3. RSS 统计标题(结构与 stats 一致:[{word, count, titles: [{title, ...}]}])
130-
if rss_items and scope.get("RSS", True) and display_regions.get("RSS", True):
132+
if not skip_rss and rss_items and scope.get("RSS", True) and display_regions.get("RSS", True):
131133
for stat_idx, stat in enumerate(rss_items):
132134
for title_idx, title_data in enumerate(stat.get("titles", [])):
133135
titles_to_translate.append(title_data.get("title", ""))
134136
title_locations.append(("rss_items", stat_idx, title_idx))
135137

136138
# 4. RSS 新增标题(结构与 stats 一致)
137-
if rss_new_items and scope.get("RSS", True) and display_regions.get("RSS", True) and display_regions.get("NEW_ITEMS", True):
139+
if not skip_rss and rss_new_items and scope.get("RSS", True) and display_regions.get("RSS", True) and display_regions.get("NEW_ITEMS", True):
138140
for stat_idx, stat in enumerate(rss_new_items):
139141
for title_idx, title_data in enumerate(stat.get("titles", [])):
140142
titles_to_translate.append(title_data.get("title", ""))
@@ -147,11 +149,12 @@ def _translate_content(
147149
titles_to_translate.append(item.get("title", ""))
148150
title_locations.append(("standalone_platforms", plat_idx, item_idx))
149151

150-
# 6. 独立展示区 - RSS 源
151-
for feed_idx, feed in enumerate(standalone_data.get("rss_feeds", [])):
152-
for item_idx, item in enumerate(feed.get("items", [])):
153-
titles_to_translate.append(item.get("title", ""))
154-
title_locations.append(("standalone_rss", feed_idx, item_idx))
152+
# 6. 独立展示区 - RSS 源(跳过已翻译的)
153+
if not skip_rss:
154+
for feed_idx, feed in enumerate(standalone_data.get("rss_feeds", [])):
155+
for item_idx, item in enumerate(feed.get("items", [])):
156+
titles_to_translate.append(item.get("title", ""))
157+
title_locations.append(("standalone_rss", feed_idx, item_idx))
155158

156159
if not titles_to_translate:
157160
print("[翻译] 没有需要翻译的内容")
@@ -225,6 +228,7 @@ def dispatch_all(
225228
rss_new_items: Optional[List[Dict]] = None,
226229
ai_analysis: Optional[AIAnalysisResult] = None,
227230
standalone_data: Optional[Dict] = None,
231+
skip_translation: bool = False,
228232
) -> Dict[str, bool]:
229233
"""
230234
分发通知到所有已配置的渠道(支持热榜+RSS合并推送+AI分析+独立展示区)
@@ -240,6 +244,7 @@ def dispatch_all(
240244
rss_new_items: RSS 新增条目列表(用于 RSS 新增区块)
241245
ai_analysis: AI 分析结果(可选)
242246
standalone_data: 独立展示区数据(可选)
247+
skip_translation: 跳过翻译(当数据已在上游翻译过时使用)
243248
244249
Returns:
245250
Dict[str, bool]: 每个渠道的发送结果,key 为渠道名,value 为是否成功
@@ -250,9 +255,17 @@ def dispatch_all(
250255
display_regions = self.config.get("DISPLAY", {}).get("REGIONS", {})
251256

252257
# 执行翻译(如果启用,根据 display_regions 跳过不展示的区域)
253-
report_data, rss_items, rss_new_items, standalone_data = self._translate_content(
254-
report_data, rss_items, rss_new_items, standalone_data, display_regions
255-
)
258+
# skip_translation=True 时,RSS 已在上游翻译过,跳过 RSS 重复翻译
259+
if not skip_translation:
260+
report_data, rss_items, rss_new_items, standalone_data = self.translate_content(
261+
report_data, rss_items, rss_new_items, standalone_data, display_regions
262+
)
263+
else:
264+
# RSS 已翻译,仅翻译热榜 report_data 和独立展示区热榜部分
265+
report_data, _, _, standalone_data = self.translate_content(
266+
report_data, standalone_data=standalone_data, display_regions=display_regions,
267+
skip_rss=True,
268+
)
256269

257270
# 飞书
258271
if self.config.get("FEISHU_WEBHOOK_URL"):

0 commit comments

Comments
 (0)