|
@@ -40,7 +40,7 @@ from .renderer import (
|
|
|
|
|
|
|
|
# 类型检查时导入,运行时不导入(避免循环导入)
|
|
# 类型检查时导入,运行时不导入(避免循环导入)
|
|
|
if TYPE_CHECKING:
|
|
if TYPE_CHECKING:
|
|
|
- from trendradar.ai import AIAnalysisResult
|
|
|
|
|
|
|
+ from trendradar.ai import AIAnalysisResult, AITranslator
|
|
|
|
|
|
|
|
|
|
|
|
|
class NotificationDispatcher:
|
|
class NotificationDispatcher:
|
|
@@ -56,6 +56,7 @@ class NotificationDispatcher:
|
|
|
config: Dict[str, Any],
|
|
config: Dict[str, Any],
|
|
|
get_time_func: Callable,
|
|
get_time_func: Callable,
|
|
|
split_content_func: Callable,
|
|
split_content_func: Callable,
|
|
|
|
|
+ translator: Optional["AITranslator"] = None,
|
|
|
):
|
|
):
|
|
|
"""
|
|
"""
|
|
|
初始化通知调度器
|
|
初始化通知调度器
|
|
@@ -64,11 +65,99 @@ class NotificationDispatcher:
|
|
|
config: 完整的配置字典,包含所有通知渠道的配置
|
|
config: 完整的配置字典,包含所有通知渠道的配置
|
|
|
get_time_func: 获取当前时间的函数
|
|
get_time_func: 获取当前时间的函数
|
|
|
split_content_func: 内容分批函数
|
|
split_content_func: 内容分批函数
|
|
|
|
|
+ translator: AI 翻译器实例(可选)
|
|
|
"""
|
|
"""
|
|
|
self.config = config
|
|
self.config = config
|
|
|
self.get_time_func = get_time_func
|
|
self.get_time_func = get_time_func
|
|
|
self.split_content_func = split_content_func
|
|
self.split_content_func = split_content_func
|
|
|
self.max_accounts = config.get("MAX_ACCOUNTS_PER_CHANNEL", 3)
|
|
self.max_accounts = config.get("MAX_ACCOUNTS_PER_CHANNEL", 3)
|
|
|
|
|
+ self.translator = translator
|
|
|
|
|
+
|
|
|
|
|
+ def _translate_content(
|
|
|
|
|
+ self,
|
|
|
|
|
+ report_data: Dict,
|
|
|
|
|
+ rss_items: Optional[List[Dict]] = None,
|
|
|
|
|
+ rss_new_items: Optional[List[Dict]] = None,
|
|
|
|
|
+ ) -> tuple:
|
|
|
|
|
+ """
|
|
|
|
|
+ 翻译推送内容
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ report_data: 报告数据
|
|
|
|
|
+ rss_items: RSS 统计条目
|
|
|
|
|
+ rss_new_items: RSS 新增条目
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ tuple: (翻译后的 report_data, rss_items, rss_new_items)
|
|
|
|
|
+ """
|
|
|
|
|
+ if not self.translator or not self.translator.enabled:
|
|
|
|
|
+ return report_data, rss_items, rss_new_items
|
|
|
|
|
+
|
|
|
|
|
+ import copy
|
|
|
|
|
+ print(f"[翻译] 开始翻译内容到 {self.translator.target_language}...")
|
|
|
|
|
+
|
|
|
|
|
+ # 深拷贝避免修改原始数据
|
|
|
|
|
+ report_data = copy.deepcopy(report_data)
|
|
|
|
|
+ rss_items = copy.deepcopy(rss_items) if rss_items else None
|
|
|
|
|
+ rss_new_items = copy.deepcopy(rss_new_items) if rss_new_items else None
|
|
|
|
|
+
|
|
|
|
|
+ # 收集所有需要翻译的标题
|
|
|
|
|
+ titles_to_translate = []
|
|
|
|
|
+ title_locations = [] # 记录标题位置,用于回填
|
|
|
|
|
+
|
|
|
|
|
+ # 1. 热榜标题
|
|
|
|
|
+ for stat_idx, stat in enumerate(report_data.get("stats", [])):
|
|
|
|
|
+ for title_idx, title_data in enumerate(stat.get("titles", [])):
|
|
|
|
|
+ titles_to_translate.append(title_data.get("title", ""))
|
|
|
|
|
+ title_locations.append(("stats", stat_idx, title_idx))
|
|
|
|
|
+
|
|
|
|
|
+ # 2. 新增热点标题
|
|
|
|
|
+ for source_idx, source in enumerate(report_data.get("new_titles", [])):
|
|
|
|
|
+ for title_idx, title_data in enumerate(source.get("titles", [])):
|
|
|
|
|
+ titles_to_translate.append(title_data.get("title", ""))
|
|
|
|
|
+ title_locations.append(("new_titles", source_idx, title_idx))
|
|
|
|
|
+
|
|
|
|
|
+ # 3. RSS 统计标题
|
|
|
|
|
+ if rss_items:
|
|
|
|
|
+ for item_idx, item in enumerate(rss_items):
|
|
|
|
|
+ titles_to_translate.append(item.get("title", ""))
|
|
|
|
|
+ title_locations.append(("rss_items", item_idx, None))
|
|
|
|
|
+
|
|
|
|
|
+ # 4. RSS 新增标题
|
|
|
|
|
+ if rss_new_items:
|
|
|
|
|
+ for item_idx, item in enumerate(rss_new_items):
|
|
|
|
|
+ titles_to_translate.append(item.get("title", ""))
|
|
|
|
|
+ title_locations.append(("rss_new_items", item_idx, None))
|
|
|
|
|
+
|
|
|
|
|
+ if not titles_to_translate:
|
|
|
|
|
+ print("[翻译] 没有需要翻译的内容")
|
|
|
|
|
+ return report_data, rss_items, rss_new_items
|
|
|
|
|
+
|
|
|
|
|
+ print(f"[翻译] 共 {len(titles_to_translate)} 条标题待翻译")
|
|
|
|
|
+
|
|
|
|
|
+ # 批量翻译
|
|
|
|
|
+ result = self.translator.translate_batch(titles_to_translate)
|
|
|
|
|
+
|
|
|
|
|
+ if result.success_count == 0:
|
|
|
|
|
+ print(f"[翻译] 翻译失败: {result.results[0].error if result.results else '未知错误'}")
|
|
|
|
|
+ return report_data, rss_items, rss_new_items
|
|
|
|
|
+
|
|
|
|
|
+ print(f"[翻译] 翻译完成: {result.success_count}/{result.total_count} 成功")
|
|
|
|
|
+
|
|
|
|
|
+ # 回填翻译结果
|
|
|
|
|
+ for i, (loc_type, idx1, idx2) in enumerate(title_locations):
|
|
|
|
|
+ if i < len(result.results) and result.results[i].success:
|
|
|
|
|
+ translated = result.results[i].translated_text
|
|
|
|
|
+ if loc_type == "stats":
|
|
|
|
|
+ report_data["stats"][idx1]["titles"][idx2]["title"] = translated
|
|
|
|
|
+ elif loc_type == "new_titles":
|
|
|
|
|
+ report_data["new_titles"][idx1]["titles"][idx2]["title"] = translated
|
|
|
|
|
+ elif loc_type == "rss_items" and rss_items:
|
|
|
|
|
+ rss_items[idx1]["title"] = translated
|
|
|
|
|
+ elif loc_type == "rss_new_items" and rss_new_items:
|
|
|
|
|
+ rss_new_items[idx1]["title"] = translated
|
|
|
|
|
+
|
|
|
|
|
+ return report_data, rss_items, rss_new_items
|
|
|
|
|
|
|
|
def dispatch_all(
|
|
def dispatch_all(
|
|
|
self,
|
|
self,
|
|
@@ -103,73 +192,77 @@ class NotificationDispatcher:
|
|
|
"""
|
|
"""
|
|
|
results = {}
|
|
results = {}
|
|
|
|
|
|
|
|
- # 获取 AI 推送模式
|
|
|
|
|
- ai_config = self.config.get("AI_ANALYSIS", {})
|
|
|
|
|
- ai_push_mode = ai_config.get("PUSH_MODE", "both")
|
|
|
|
|
|
|
+ # 获取区域显示配置
|
|
|
|
|
+ display_regions = self.config.get("DISPLAY", {}).get("REGIONS", {})
|
|
|
|
|
+
|
|
|
|
|
+ # 执行翻译(如果启用)
|
|
|
|
|
+ report_data, rss_items, rss_new_items = self._translate_content(
|
|
|
|
|
+ report_data, rss_items, rss_new_items
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
# 飞书
|
|
# 飞书
|
|
|
if self.config.get("FEISHU_WEBHOOK_URL"):
|
|
if self.config.get("FEISHU_WEBHOOK_URL"):
|
|
|
results["feishu"] = self._send_feishu(
|
|
results["feishu"] = self._send_feishu(
|
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
|
- ai_analysis, ai_push_mode, standalone_data
|
|
|
|
|
|
|
+ ai_analysis, display_regions, standalone_data
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# 钉钉
|
|
# 钉钉
|
|
|
if self.config.get("DINGTALK_WEBHOOK_URL"):
|
|
if self.config.get("DINGTALK_WEBHOOK_URL"):
|
|
|
results["dingtalk"] = self._send_dingtalk(
|
|
results["dingtalk"] = self._send_dingtalk(
|
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
|
- ai_analysis, ai_push_mode, standalone_data
|
|
|
|
|
|
|
+ ai_analysis, display_regions, standalone_data
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# 企业微信
|
|
# 企业微信
|
|
|
if self.config.get("WEWORK_WEBHOOK_URL"):
|
|
if self.config.get("WEWORK_WEBHOOK_URL"):
|
|
|
results["wework"] = self._send_wework(
|
|
results["wework"] = self._send_wework(
|
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
|
- ai_analysis, ai_push_mode, standalone_data
|
|
|
|
|
|
|
+ ai_analysis, display_regions, standalone_data
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# Telegram(需要配对验证)
|
|
# Telegram(需要配对验证)
|
|
|
if self.config.get("TELEGRAM_BOT_TOKEN") and self.config.get("TELEGRAM_CHAT_ID"):
|
|
if self.config.get("TELEGRAM_BOT_TOKEN") and self.config.get("TELEGRAM_CHAT_ID"):
|
|
|
results["telegram"] = self._send_telegram(
|
|
results["telegram"] = self._send_telegram(
|
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
|
- ai_analysis, ai_push_mode, standalone_data
|
|
|
|
|
|
|
+ ai_analysis, display_regions, standalone_data
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# ntfy(需要配对验证)
|
|
# ntfy(需要配对验证)
|
|
|
if self.config.get("NTFY_SERVER_URL") and self.config.get("NTFY_TOPIC"):
|
|
if self.config.get("NTFY_SERVER_URL") and self.config.get("NTFY_TOPIC"):
|
|
|
results["ntfy"] = self._send_ntfy(
|
|
results["ntfy"] = self._send_ntfy(
|
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
|
- ai_analysis, ai_push_mode, standalone_data
|
|
|
|
|
|
|
+ ai_analysis, display_regions, standalone_data
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# Bark
|
|
# Bark
|
|
|
if self.config.get("BARK_URL"):
|
|
if self.config.get("BARK_URL"):
|
|
|
results["bark"] = self._send_bark(
|
|
results["bark"] = self._send_bark(
|
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
|
- ai_analysis, ai_push_mode, standalone_data
|
|
|
|
|
|
|
+ ai_analysis, display_regions, standalone_data
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# Slack
|
|
# Slack
|
|
|
if self.config.get("SLACK_WEBHOOK_URL"):
|
|
if self.config.get("SLACK_WEBHOOK_URL"):
|
|
|
results["slack"] = self._send_slack(
|
|
results["slack"] = self._send_slack(
|
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
|
- ai_analysis, ai_push_mode, standalone_data
|
|
|
|
|
|
|
+ ai_analysis, display_regions, standalone_data
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# 通用 Webhook
|
|
# 通用 Webhook
|
|
|
if self.config.get("GENERIC_WEBHOOK_URL"):
|
|
if self.config.get("GENERIC_WEBHOOK_URL"):
|
|
|
results["generic_webhook"] = self._send_generic_webhook(
|
|
results["generic_webhook"] = self._send_generic_webhook(
|
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
report_data, report_type, update_info, proxy_url, mode, rss_items, rss_new_items,
|
|
|
- ai_analysis, ai_push_mode, standalone_data
|
|
|
|
|
|
|
+ ai_analysis, display_regions, standalone_data
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- # 邮件(保持原有逻辑,已支持多收件人)
|
|
|
|
|
|
|
+ # 邮件(保持原有逻辑,已支持多收件人,AI 分析已嵌入 HTML)
|
|
|
if (
|
|
if (
|
|
|
self.config.get("EMAIL_FROM")
|
|
self.config.get("EMAIL_FROM")
|
|
|
and self.config.get("EMAIL_PASSWORD")
|
|
and self.config.get("EMAIL_PASSWORD")
|
|
|
and self.config.get("EMAIL_TO")
|
|
and self.config.get("EMAIL_TO")
|
|
|
):
|
|
):
|
|
|
- results["email"] = self._send_email(report_type, html_file_path, ai_analysis, ai_push_mode)
|
|
|
|
|
|
|
+ results["email"] = self._send_email(report_type, html_file_path)
|
|
|
|
|
|
|
|
return results
|
|
return results
|
|
|
|
|
|
|
@@ -217,13 +310,14 @@ class NotificationDispatcher:
|
|
|
rss_items: Optional[List[Dict]] = None,
|
|
rss_items: Optional[List[Dict]] = None,
|
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
|
- ai_push_mode: str = "both",
|
|
|
|
|
|
|
+ display_regions: Optional[Dict] = None,
|
|
|
standalone_data: Optional[Dict] = None,
|
|
standalone_data: Optional[Dict] = None,
|
|
|
) -> bool:
|
|
) -> bool:
|
|
|
"""发送到飞书(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
"""发送到飞书(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
|
- # 根据 AI 推送模式决定是否发送原始内容
|
|
|
|
|
- if ai_push_mode == "only_analysis" and ai_analysis:
|
|
|
|
|
- report_data = {"stats": [], "failed_ids": [], "new_titles": {}, "id_to_name": {}}
|
|
|
|
|
|
|
+ display_regions = display_regions or {}
|
|
|
|
|
+ # 根据区域开关决定是否发送对应内容
|
|
|
|
|
+ if not display_regions.get("HOTLIST", True):
|
|
|
|
|
+ report_data = {"stats": [], "failed_ids": [], "new_titles": [], "id_to_name": {}}
|
|
|
|
|
|
|
|
return self._send_to_multi_accounts(
|
|
return self._send_to_multi_accounts(
|
|
|
channel_name="飞书",
|
|
channel_name="飞书",
|
|
@@ -240,11 +334,11 @@ class NotificationDispatcher:
|
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
|
split_content_func=self.split_content_func,
|
|
split_content_func=self.split_content_func,
|
|
|
get_time_func=self.get_time_func,
|
|
get_time_func=self.get_time_func,
|
|
|
- rss_items=rss_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- rss_new_items=rss_new_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- ai_analysis=ai_analysis,
|
|
|
|
|
- ai_push_mode=ai_push_mode,
|
|
|
|
|
- standalone_data=standalone_data,
|
|
|
|
|
|
|
+ rss_items=rss_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ rss_new_items=rss_new_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ ai_analysis=ai_analysis if display_regions.get("AI_ANALYSIS", True) else None,
|
|
|
|
|
+ display_regions=display_regions,
|
|
|
|
|
+ standalone_data=standalone_data if display_regions.get("STANDALONE", False) else None,
|
|
|
),
|
|
),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -258,12 +352,13 @@ class NotificationDispatcher:
|
|
|
rss_items: Optional[List[Dict]] = None,
|
|
rss_items: Optional[List[Dict]] = None,
|
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
|
- ai_push_mode: str = "both",
|
|
|
|
|
|
|
+ display_regions: Optional[Dict] = None,
|
|
|
standalone_data: Optional[Dict] = None,
|
|
standalone_data: Optional[Dict] = None,
|
|
|
) -> bool:
|
|
) -> bool:
|
|
|
"""发送到钉钉(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
"""发送到钉钉(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
|
- if ai_push_mode == "only_analysis" and ai_analysis:
|
|
|
|
|
- report_data = {"stats": [], "failed_ids": [], "new_titles": {}, "id_to_name": {}}
|
|
|
|
|
|
|
+ display_regions = display_regions or {}
|
|
|
|
|
+ if not display_regions.get("HOTLIST", True):
|
|
|
|
|
+ report_data = {"stats": [], "failed_ids": [], "new_titles": [], "id_to_name": {}}
|
|
|
|
|
|
|
|
return self._send_to_multi_accounts(
|
|
return self._send_to_multi_accounts(
|
|
|
channel_name="钉钉",
|
|
channel_name="钉钉",
|
|
@@ -279,11 +374,11 @@ class NotificationDispatcher:
|
|
|
batch_size=self.config.get("DINGTALK_BATCH_SIZE", 20000),
|
|
batch_size=self.config.get("DINGTALK_BATCH_SIZE", 20000),
|
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
|
split_content_func=self.split_content_func,
|
|
split_content_func=self.split_content_func,
|
|
|
- rss_items=rss_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- rss_new_items=rss_new_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- ai_analysis=ai_analysis,
|
|
|
|
|
- ai_push_mode=ai_push_mode,
|
|
|
|
|
- standalone_data=standalone_data,
|
|
|
|
|
|
|
+ rss_items=rss_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ rss_new_items=rss_new_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ ai_analysis=ai_analysis if display_regions.get("AI_ANALYSIS", True) else None,
|
|
|
|
|
+ display_regions=display_regions,
|
|
|
|
|
+ standalone_data=standalone_data if display_regions.get("STANDALONE", False) else None,
|
|
|
),
|
|
),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -297,12 +392,13 @@ class NotificationDispatcher:
|
|
|
rss_items: Optional[List[Dict]] = None,
|
|
rss_items: Optional[List[Dict]] = None,
|
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
|
- ai_push_mode: str = "both",
|
|
|
|
|
|
|
+ display_regions: Optional[Dict] = None,
|
|
|
standalone_data: Optional[Dict] = None,
|
|
standalone_data: Optional[Dict] = None,
|
|
|
) -> bool:
|
|
) -> bool:
|
|
|
"""发送到企业微信(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
"""发送到企业微信(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
|
- if ai_push_mode == "only_analysis" and ai_analysis:
|
|
|
|
|
- report_data = {"stats": [], "failed_ids": [], "new_titles": {}, "id_to_name": {}}
|
|
|
|
|
|
|
+ display_regions = display_regions or {}
|
|
|
|
|
+ if not display_regions.get("HOTLIST", True):
|
|
|
|
|
+ report_data = {"stats": [], "failed_ids": [], "new_titles": [], "id_to_name": {}}
|
|
|
|
|
|
|
|
return self._send_to_multi_accounts(
|
|
return self._send_to_multi_accounts(
|
|
|
channel_name="企业微信",
|
|
channel_name="企业微信",
|
|
@@ -319,11 +415,11 @@ class NotificationDispatcher:
|
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
|
msg_type=self.config.get("WEWORK_MSG_TYPE", "markdown"),
|
|
msg_type=self.config.get("WEWORK_MSG_TYPE", "markdown"),
|
|
|
split_content_func=self.split_content_func,
|
|
split_content_func=self.split_content_func,
|
|
|
- rss_items=rss_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- rss_new_items=rss_new_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- ai_analysis=ai_analysis,
|
|
|
|
|
- ai_push_mode=ai_push_mode,
|
|
|
|
|
- standalone_data=standalone_data,
|
|
|
|
|
|
|
+ rss_items=rss_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ rss_new_items=rss_new_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ ai_analysis=ai_analysis if display_regions.get("AI_ANALYSIS", True) else None,
|
|
|
|
|
+ display_regions=display_regions,
|
|
|
|
|
+ standalone_data=standalone_data if display_regions.get("STANDALONE", False) else None,
|
|
|
),
|
|
),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -337,12 +433,13 @@ class NotificationDispatcher:
|
|
|
rss_items: Optional[List[Dict]] = None,
|
|
rss_items: Optional[List[Dict]] = None,
|
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
|
- ai_push_mode: str = "both",
|
|
|
|
|
|
|
+ display_regions: Optional[Dict] = None,
|
|
|
standalone_data: Optional[Dict] = None,
|
|
standalone_data: Optional[Dict] = None,
|
|
|
) -> bool:
|
|
) -> bool:
|
|
|
"""发送到 Telegram(多账号,需验证 token 和 chat_id 配对,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
"""发送到 Telegram(多账号,需验证 token 和 chat_id 配对,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
|
- if ai_push_mode == "only_analysis" and ai_analysis:
|
|
|
|
|
- report_data = {"stats": [], "failed_ids": [], "new_titles": {}, "id_to_name": {}}
|
|
|
|
|
|
|
+ display_regions = display_regions or {}
|
|
|
|
|
+ if not display_regions.get("HOTLIST", True):
|
|
|
|
|
+ report_data = {"stats": [], "failed_ids": [], "new_titles": [], "id_to_name": {}}
|
|
|
|
|
|
|
|
telegram_tokens = parse_multi_account_config(self.config["TELEGRAM_BOT_TOKEN"])
|
|
telegram_tokens = parse_multi_account_config(self.config["TELEGRAM_BOT_TOKEN"])
|
|
|
telegram_chat_ids = parse_multi_account_config(self.config["TELEGRAM_CHAT_ID"])
|
|
telegram_chat_ids = parse_multi_account_config(self.config["TELEGRAM_CHAT_ID"])
|
|
@@ -381,11 +478,11 @@ class NotificationDispatcher:
|
|
|
batch_size=self.config.get("MESSAGE_BATCH_SIZE", 4000),
|
|
batch_size=self.config.get("MESSAGE_BATCH_SIZE", 4000),
|
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
|
split_content_func=self.split_content_func,
|
|
split_content_func=self.split_content_func,
|
|
|
- rss_items=rss_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- rss_new_items=rss_new_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- ai_analysis=ai_analysis,
|
|
|
|
|
- ai_push_mode=ai_push_mode,
|
|
|
|
|
- standalone_data=standalone_data,
|
|
|
|
|
|
|
+ rss_items=rss_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ rss_new_items=rss_new_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ ai_analysis=ai_analysis if display_regions.get("AI_ANALYSIS", True) else None,
|
|
|
|
|
+ display_regions=display_regions,
|
|
|
|
|
+ standalone_data=standalone_data if display_regions.get("STANDALONE", False) else None,
|
|
|
)
|
|
)
|
|
|
results.append(result)
|
|
results.append(result)
|
|
|
|
|
|
|
@@ -401,12 +498,13 @@ class NotificationDispatcher:
|
|
|
rss_items: Optional[List[Dict]] = None,
|
|
rss_items: Optional[List[Dict]] = None,
|
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
|
- ai_push_mode: str = "both",
|
|
|
|
|
|
|
+ display_regions: Optional[Dict] = None,
|
|
|
standalone_data: Optional[Dict] = None,
|
|
standalone_data: Optional[Dict] = None,
|
|
|
) -> bool:
|
|
) -> bool:
|
|
|
"""发送到 ntfy(多账号,需验证 topic 和 token 配对,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
"""发送到 ntfy(多账号,需验证 topic 和 token 配对,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
|
- if ai_push_mode == "only_analysis" and ai_analysis:
|
|
|
|
|
- report_data = {"stats": [], "failed_ids": [], "new_titles": {}, "id_to_name": {}}
|
|
|
|
|
|
|
+ display_regions = display_regions or {}
|
|
|
|
|
+ if not display_regions.get("HOTLIST", True):
|
|
|
|
|
+ report_data = {"stats": [], "failed_ids": [], "new_titles": [], "id_to_name": {}}
|
|
|
|
|
|
|
|
ntfy_server_url = self.config["NTFY_SERVER_URL"]
|
|
ntfy_server_url = self.config["NTFY_SERVER_URL"]
|
|
|
ntfy_topics = parse_multi_account_config(self.config["NTFY_TOPIC"])
|
|
ntfy_topics = parse_multi_account_config(self.config["NTFY_TOPIC"])
|
|
@@ -444,11 +542,11 @@ class NotificationDispatcher:
|
|
|
account_label=account_label,
|
|
account_label=account_label,
|
|
|
batch_size=3800,
|
|
batch_size=3800,
|
|
|
split_content_func=self.split_content_func,
|
|
split_content_func=self.split_content_func,
|
|
|
- rss_items=rss_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- rss_new_items=rss_new_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- ai_analysis=ai_analysis,
|
|
|
|
|
- ai_push_mode=ai_push_mode,
|
|
|
|
|
- standalone_data=standalone_data,
|
|
|
|
|
|
|
+ rss_items=rss_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ rss_new_items=rss_new_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ ai_analysis=ai_analysis if display_regions.get("AI_ANALYSIS", True) else None,
|
|
|
|
|
+ display_regions=display_regions,
|
|
|
|
|
+ standalone_data=standalone_data if display_regions.get("STANDALONE", False) else None,
|
|
|
)
|
|
)
|
|
|
results.append(result)
|
|
results.append(result)
|
|
|
|
|
|
|
@@ -464,12 +562,13 @@ class NotificationDispatcher:
|
|
|
rss_items: Optional[List[Dict]] = None,
|
|
rss_items: Optional[List[Dict]] = None,
|
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
|
- ai_push_mode: str = "both",
|
|
|
|
|
|
|
+ display_regions: Optional[Dict] = None,
|
|
|
standalone_data: Optional[Dict] = None,
|
|
standalone_data: Optional[Dict] = None,
|
|
|
) -> bool:
|
|
) -> bool:
|
|
|
"""发送到 Bark(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
"""发送到 Bark(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
|
- if ai_push_mode == "only_analysis" and ai_analysis:
|
|
|
|
|
- report_data = {"stats": [], "failed_ids": [], "new_titles": {}, "id_to_name": {}}
|
|
|
|
|
|
|
+ display_regions = display_regions or {}
|
|
|
|
|
+ if not display_regions.get("HOTLIST", True):
|
|
|
|
|
+ report_data = {"stats": [], "failed_ids": [], "new_titles": [], "id_to_name": {}}
|
|
|
|
|
|
|
|
return self._send_to_multi_accounts(
|
|
return self._send_to_multi_accounts(
|
|
|
channel_name="Bark",
|
|
channel_name="Bark",
|
|
@@ -485,11 +584,11 @@ class NotificationDispatcher:
|
|
|
batch_size=self.config.get("BARK_BATCH_SIZE", 3600),
|
|
batch_size=self.config.get("BARK_BATCH_SIZE", 3600),
|
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
|
split_content_func=self.split_content_func,
|
|
split_content_func=self.split_content_func,
|
|
|
- rss_items=rss_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- rss_new_items=rss_new_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- ai_analysis=ai_analysis,
|
|
|
|
|
- ai_push_mode=ai_push_mode,
|
|
|
|
|
- standalone_data=standalone_data,
|
|
|
|
|
|
|
+ rss_items=rss_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ rss_new_items=rss_new_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ ai_analysis=ai_analysis if display_regions.get("AI_ANALYSIS", True) else None,
|
|
|
|
|
+ display_regions=display_regions,
|
|
|
|
|
+ standalone_data=standalone_data if display_regions.get("STANDALONE", False) else None,
|
|
|
),
|
|
),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -503,12 +602,13 @@ class NotificationDispatcher:
|
|
|
rss_items: Optional[List[Dict]] = None,
|
|
rss_items: Optional[List[Dict]] = None,
|
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
|
- ai_push_mode: str = "both",
|
|
|
|
|
|
|
+ display_regions: Optional[Dict] = None,
|
|
|
standalone_data: Optional[Dict] = None,
|
|
standalone_data: Optional[Dict] = None,
|
|
|
) -> bool:
|
|
) -> bool:
|
|
|
"""发送到 Slack(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
"""发送到 Slack(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
|
- if ai_push_mode == "only_analysis" and ai_analysis:
|
|
|
|
|
- report_data = {"stats": [], "failed_ids": [], "new_titles": {}, "id_to_name": {}}
|
|
|
|
|
|
|
+ display_regions = display_regions or {}
|
|
|
|
|
+ if not display_regions.get("HOTLIST", True):
|
|
|
|
|
+ report_data = {"stats": [], "failed_ids": [], "new_titles": [], "id_to_name": {}}
|
|
|
|
|
|
|
|
return self._send_to_multi_accounts(
|
|
return self._send_to_multi_accounts(
|
|
|
channel_name="Slack",
|
|
channel_name="Slack",
|
|
@@ -524,11 +624,11 @@ class NotificationDispatcher:
|
|
|
batch_size=self.config.get("SLACK_BATCH_SIZE", 4000),
|
|
batch_size=self.config.get("SLACK_BATCH_SIZE", 4000),
|
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
|
split_content_func=self.split_content_func,
|
|
split_content_func=self.split_content_func,
|
|
|
- rss_items=rss_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- rss_new_items=rss_new_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- ai_analysis=ai_analysis,
|
|
|
|
|
- ai_push_mode=ai_push_mode,
|
|
|
|
|
- standalone_data=standalone_data,
|
|
|
|
|
|
|
+ rss_items=rss_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ rss_new_items=rss_new_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ ai_analysis=ai_analysis if display_regions.get("AI_ANALYSIS", True) else None,
|
|
|
|
|
+ display_regions=display_regions,
|
|
|
|
|
+ standalone_data=standalone_data if display_regions.get("STANDALONE", False) else None,
|
|
|
),
|
|
),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -542,12 +642,13 @@ class NotificationDispatcher:
|
|
|
rss_items: Optional[List[Dict]] = None,
|
|
rss_items: Optional[List[Dict]] = None,
|
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
rss_new_items: Optional[List[Dict]] = None,
|
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
ai_analysis: Optional[AIAnalysisResult] = None,
|
|
|
- ai_push_mode: str = "both",
|
|
|
|
|
|
|
+ display_regions: Optional[Dict] = None,
|
|
|
standalone_data: Optional[Dict] = None,
|
|
standalone_data: Optional[Dict] = None,
|
|
|
) -> bool:
|
|
) -> bool:
|
|
|
"""发送到通用 Webhook(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
"""发送到通用 Webhook(多账号,支持热榜+RSS合并+AI分析+独立展示区)"""
|
|
|
- if ai_push_mode == "only_analysis" and ai_analysis:
|
|
|
|
|
- report_data = {"stats": [], "failed_ids": [], "new_titles": {}, "id_to_name": {}}
|
|
|
|
|
|
|
+ display_regions = display_regions or {}
|
|
|
|
|
+ if not display_regions.get("HOTLIST", True):
|
|
|
|
|
+ report_data = {"stats": [], "failed_ids": [], "new_titles": [], "id_to_name": {}}
|
|
|
|
|
|
|
|
urls = parse_multi_account_config(self.config.get("GENERIC_WEBHOOK_URL", ""))
|
|
urls = parse_multi_account_config(self.config.get("GENERIC_WEBHOOK_URL", ""))
|
|
|
templates = parse_multi_account_config(self.config.get("GENERIC_WEBHOOK_TEMPLATE", ""))
|
|
templates = parse_multi_account_config(self.config.get("GENERIC_WEBHOOK_TEMPLATE", ""))
|
|
@@ -583,11 +684,11 @@ class NotificationDispatcher:
|
|
|
batch_size=self.config.get("MESSAGE_BATCH_SIZE", 4000),
|
|
batch_size=self.config.get("MESSAGE_BATCH_SIZE", 4000),
|
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
batch_interval=self.config.get("BATCH_SEND_INTERVAL", 1.0),
|
|
|
split_content_func=self.split_content_func,
|
|
split_content_func=self.split_content_func,
|
|
|
- rss_items=rss_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- rss_new_items=rss_new_items if ai_push_mode != "only_analysis" else None,
|
|
|
|
|
- ai_analysis=ai_analysis,
|
|
|
|
|
- ai_push_mode=ai_push_mode,
|
|
|
|
|
- standalone_data=standalone_data,
|
|
|
|
|
|
|
+ rss_items=rss_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ rss_new_items=rss_new_items if display_regions.get("RSS", True) else None,
|
|
|
|
|
+ ai_analysis=ai_analysis if display_regions.get("AI_ANALYSIS", True) else None,
|
|
|
|
|
+ display_regions=display_regions,
|
|
|
|
|
+ standalone_data=standalone_data if display_regions.get("STANDALONE", False) else None,
|
|
|
)
|
|
)
|
|
|
results.append(result)
|
|
results.append(result)
|
|
|
|
|
|
|
@@ -597,10 +698,12 @@ class NotificationDispatcher:
|
|
|
self,
|
|
self,
|
|
|
report_type: str,
|
|
report_type: str,
|
|
|
html_file_path: Optional[str],
|
|
html_file_path: Optional[str],
|
|
|
- ai_analysis: Optional[AIAnalysisResult] = None,
|
|
|
|
|
- ai_push_mode: str = "both",
|
|
|
|
|
) -> bool:
|
|
) -> bool:
|
|
|
- """发送邮件(保持原有逻辑,已支持多收件人,支持AI分析)"""
|
|
|
|
|
|
|
+ """发送邮件(保持原有逻辑,已支持多收件人)
|
|
|
|
|
+
|
|
|
|
|
+ Note:
|
|
|
|
|
+ AI 分析内容已在 HTML 生成时嵌入,无需在此传递
|
|
|
|
|
+ """
|
|
|
return send_to_email(
|
|
return send_to_email(
|
|
|
from_email=self.config["EMAIL_FROM"],
|
|
from_email=self.config["EMAIL_FROM"],
|
|
|
password=self.config["EMAIL_PASSWORD"],
|
|
password=self.config["EMAIL_PASSWORD"],
|
|
@@ -610,8 +713,6 @@ class NotificationDispatcher:
|
|
|
custom_smtp_server=self.config.get("EMAIL_SMTP_SERVER", ""),
|
|
custom_smtp_server=self.config.get("EMAIL_SMTP_SERVER", ""),
|
|
|
custom_smtp_port=self.config.get("EMAIL_SMTP_PORT", ""),
|
|
custom_smtp_port=self.config.get("EMAIL_SMTP_PORT", ""),
|
|
|
get_time_func=self.get_time_func,
|
|
get_time_func=self.get_time_func,
|
|
|
- ai_analysis=ai_analysis,
|
|
|
|
|
- ai_push_mode=ai_push_mode,
|
|
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# === RSS 通知方法 ===
|
|
# === RSS 通知方法 ===
|