diff --git a/main.py b/main.py index 62d12dd..942c181 100644 --- a/main.py +++ b/main.py @@ -20,7 +20,7 @@ import requests import yaml -VERSION = "3.3.0" +VERSION = "3.4.0" # === SMTP邮件配置 === @@ -100,6 +100,7 @@ def load_config(): ), "FEISHU_BATCH_SIZE": config_data["notification"].get("feishu_batch_size", 29000), "BARK_BATCH_SIZE": config_data["notification"].get("bark_batch_size", 3600), + "SLACK_BATCH_SIZE": config_data["notification"].get("slack_batch_size", 4000), "BATCH_SEND_INTERVAL": config_data["notification"]["batch_send_interval"], "FEISHU_MESSAGE_SEPARATOR": config_data["notification"][ "feishu_message_separator" @@ -202,6 +203,11 @@ def load_config(): "bark_url", "" ) + # Slack配置 + config["SLACK_WEBHOOK_URL"] = os.environ.get("SLACK_WEBHOOK_URL", "").strip() or webhooks.get( + "slack_webhook_url", "" + ) + # 输出配置来源信息 notification_sources = [] if config["FEISHU_WEBHOOK_URL"]: @@ -231,6 +237,10 @@ def load_config(): bark_source = "环境变量" if os.environ.get("BARK_URL") else "配置文件" notification_sources.append(f"Bark({bark_source})") + if config["SLACK_WEBHOOK_URL"]: + slack_source = "环境变量" if os.environ.get("SLACK_WEBHOOK_URL") else "配置文件" + notification_sources.append(f"Slack({slack_source})") + if notification_sources: print(f"通知渠道配置来源: {', '.join(notification_sources)}") else: @@ -3412,6 +3422,7 @@ def send_to_notifications( ntfy_topic = CONFIG["NTFY_TOPIC"] ntfy_token = CONFIG.get("NTFY_TOKEN", "") bark_url = CONFIG["BARK_URL"] + slack_webhook_url = CONFIG["SLACK_WEBHOOK_URL"] update_info_to_send = update_info if CONFIG["SHOW_VERSION_UPDATE"] else None @@ -3469,6 +3480,17 @@ def send_to_notifications( mode, ) + # 发送到 Slack + if slack_webhook_url: + results["slack"] = send_to_slack( + slack_webhook_url, + report_data, + report_type, + update_info_to_send, + proxy_url, + mode, + ) + # 发送邮件 if email_from and email_password and email_to: results["email"] = send_to_email( @@ -4265,6 +4287,90 @@ def send_to_bark( return False +def convert_markdown_to_mrkdwn(content: str) -> str: + """ + 将标准 Markdown 转换为 Slack 的 mrkdwn 格式 + + 转换规则: + - **粗体** → *粗体* + - [文本](url) → + - 保留其他格式(代码块、列表等) + """ + # 1. 转换链接格式: [文本](url) → + content = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<\2|\1>', content) + + # 2. 转换粗体: **文本** → *文本* + content = re.sub(r'\*\*([^*]+)\*\*', r'*\1*', content) + + return content + + +def send_to_slack( + webhook_url: str, + report_data: Dict, + report_type: str, + update_info: Optional[Dict] = None, + proxy_url: Optional[str] = None, + mode: str = "daily", +) -> bool: + """发送到Slack(支持分批发送,使用 mrkdwn 格式)""" + headers = {"Content-Type": "application/json"} + proxies = None + if proxy_url: + proxies = {"http": proxy_url, "https": proxy_url} + + # 获取分批内容(使用 Slack 批次大小) + batches = split_content_into_batches( + report_data, "wework", update_info, max_bytes=CONFIG["SLACK_BATCH_SIZE"], mode=mode + ) + + print(f"Slack消息分为 {len(batches)} 批次发送 [{report_type}]") + + # 逐批发送 + for i, batch_content in enumerate(batches, 1): + # 添加批次标识 + if len(batches) > 1: + batch_header = f"*[第 {i}/{len(batches)} 批次]*\n\n" + batch_content = batch_header + batch_content + + # 转换 Markdown 到 mrkdwn 格式 + mrkdwn_content = convert_markdown_to_mrkdwn(batch_content) + + batch_size = len(mrkdwn_content.encode("utf-8")) + print( + f"发送Slack第 {i}/{len(batches)} 批次,大小:{batch_size} 字节 [{report_type}]" + ) + + # 构建 Slack payload(使用简单的 text 字段,支持 mrkdwn) + payload = { + "text": mrkdwn_content + } + + try: + response = requests.post( + webhook_url, headers=headers, json=payload, proxies=proxies, timeout=30 + ) + + # Slack Incoming Webhooks 成功时返回 "ok" 文本 + if response.status_code == 200 and response.text == "ok": + print(f"Slack第 {i}/{len(batches)} 批次发送成功 [{report_type}]") + # 批次间间隔 + if i < len(batches): + time.sleep(CONFIG["BATCH_SEND_INTERVAL"]) + else: + error_msg = response.text if response.text else f"状态码:{response.status_code}" + print( + f"Slack第 {i}/{len(batches)} 批次发送失败 [{report_type}],错误:{error_msg}" + ) + return False + except Exception as e: + print(f"Slack第 {i}/{len(batches)} 批次发送出错 [{report_type}]:{e}") + return False + + print(f"Slack所有 {len(batches)} 批次发送完成 [{report_type}]") + return True + + # === 主分析器 === class NewsAnalyzer: """新闻分析器""" @@ -4378,6 +4484,7 @@ class NewsAnalyzer: ), (CONFIG["NTFY_SERVER_URL"] and CONFIG["NTFY_TOPIC"]), CONFIG["BARK_URL"], + CONFIG["SLACK_WEBHOOK_URL"], ] )