sansan hai 6 meses
pai
achega
5dfca546f5
Modificáronse 4 ficheiros con 128 adicións e 35 borrados
  1. 1 0
      config/config.yaml
  2. 118 30
      main.py
  3. 8 4
      readme.md
  4. 1 1
      version

+ 1 - 0
config/config.yaml

@@ -32,6 +32,7 @@ notification:
   enable_notification: true # 是否启用通知功能,如果 false,则不发送手机通知
   message_batch_size: 4000 # 消息分批大小(字节)(这个配置别动)
   dingtalk_batch_size: 20000 # 钉钉消息分批大小(字节)(这个配置也别动)
+  feishu_batch_size: 29000 # 飞书消息分批大小(字节)
   batch_send_interval: 1 # 批次发送间隔(秒)
   feishu_message_separator: "━━━━━━━━━━━━━━━━━━━" # feishu 消息分割线
 

+ 118 - 30
main.py

@@ -20,7 +20,7 @@ import requests
 import yaml
 
 
-VERSION = "3.0.3"
+VERSION = "3.0.4"
 
 
 # === SMTP邮件配置 ===
@@ -79,6 +79,7 @@ def load_config():
         "DINGTALK_BATCH_SIZE": config_data["notification"].get(
             "dingtalk_batch_size", 20000
         ),
+        "FEISHU_BATCH_SIZE": config_data["notification"].get("feishu_batch_size", 29000),
         "BATCH_SEND_INTERVAL": config_data["notification"]["batch_send_interval"],
         "FEISHU_MESSAGE_SEPARATOR": config_data["notification"][
             "feishu_message_separator"
@@ -2816,6 +2817,8 @@ def split_content_into_batches(
     if max_bytes is None:
         if format_type == "dingtalk":
             max_bytes = CONFIG.get("DINGTALK_BATCH_SIZE", 20000)
+        elif format_type == "feishu":
+            max_bytes = CONFIG.get("FEISHU_BATCH_SIZE", 29000)
         elif format_type == "ntfy":
             max_bytes = 3800
         else:
@@ -2835,6 +2838,8 @@ def split_content_into_batches(
         base_header = f"总新闻数: {total_titles}\n\n"
     elif format_type == "ntfy":
         base_header = f"**总新闻数:** {total_titles}\n\n"
+    elif format_type == "feishu":
+        base_header = ""
     elif format_type == "dingtalk":
         base_header = f"**总新闻数:** {total_titles}\n\n"
         base_header += f"**时间:** {now.strftime('%Y-%m-%d %H:%M:%S')}\n\n"
@@ -2854,6 +2859,10 @@ def split_content_into_batches(
         base_footer = f"\n\n> 更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}"
         if update_info:
             base_footer += f"\n> TrendRadar 发现新版本 **{update_info['remote_version']}**,当前 **{update_info['current_version']}**"
+    elif format_type == "feishu":
+        base_footer = f"\n\n<font color='grey'>更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}</font>"
+        if update_info:
+            base_footer += f"\n<font color='grey'>TrendRadar 发现新版本 {update_info['remote_version']},当前 {update_info['current_version']}</font>"
     elif format_type == "dingtalk":
         base_footer = f"\n\n> 更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}"
         if update_info:
@@ -2867,6 +2876,8 @@ def split_content_into_batches(
             stats_header = f"📊 热点词汇统计\n\n"
         elif format_type == "ntfy":
             stats_header = f"📊 **热点词汇统计**\n\n"
+        elif format_type == "feishu":
+            stats_header = f"📊 **热点词汇统计**\n\n"
         elif format_type == "dingtalk":
             stats_header = f"📊 **热点词汇统计**\n\n"
 
@@ -2944,6 +2955,13 @@ def split_content_into_batches(
                     )
                 else:
                     word_header = f"📌 {sequence_display} **{word}** : {count} 条\n\n"
+            elif format_type == "feishu":
+                if count >= 10:
+                    word_header = f"🔥 <font color='grey'>{sequence_display}</font> **{word}** : <font color='red'>{count}</font> 条\n\n"
+                elif count >= 5:
+                    word_header = f"📈 <font color='grey'>{sequence_display}</font> **{word}** : <font color='orange'>{count}</font> 条\n\n"
+                else:
+                    word_header = f"📌 <font color='grey'>{sequence_display}</font> **{word}** : {count} 条\n\n"
             elif format_type == "dingtalk":
                 if count >= 10:
                     word_header = (
@@ -2972,6 +2990,10 @@ def split_content_into_batches(
                     formatted_title = format_title_for_platform(
                         "ntfy", first_title_data, show_source=True
                     )
+                elif format_type == "feishu":
+                    formatted_title = format_title_for_platform(
+                        "feishu", first_title_data, show_source=True
+                    )
                 elif format_type == "dingtalk":
                     formatted_title = format_title_for_platform(
                         "dingtalk", first_title_data, show_source=True
@@ -3017,6 +3039,10 @@ def split_content_into_batches(
                     formatted_title = format_title_for_platform(
                         "ntfy", title_data, show_source=True
                     )
+                elif format_type == "feishu":
+                    formatted_title = format_title_for_platform(
+                        "feishu", title_data, show_source=True
+                    )
                 elif format_type == "dingtalk":
                     formatted_title = format_title_for_platform(
                         "dingtalk", title_data, show_source=True
@@ -3050,6 +3076,8 @@ def split_content_into_batches(
                     separator = f"\n\n"
                 elif format_type == "ntfy":
                     separator = f"\n\n"
+                elif format_type == "feishu":
+                    separator = f"\n{CONFIG['FEISHU_MESSAGE_SEPARATOR']}\n\n"
                 elif format_type == "dingtalk":
                     separator = f"\n---\n\n"
 
@@ -3071,6 +3099,8 @@ def split_content_into_batches(
             )
         elif format_type == "ntfy":
             new_header = f"\n\n🆕 **本次新增热点新闻** (共 {report_data['total_new_count']} 条)\n\n"
+        elif format_type == "feishu":
+            new_header = f"\n{CONFIG['FEISHU_MESSAGE_SEPARATOR']}\n\n🆕 **本次新增热点新闻** (共 {report_data['total_new_count']} 条)\n\n"
         elif format_type == "dingtalk":
             new_header = f"\n---\n\n🆕 **本次新增热点新闻** (共 {report_data['total_new_count']} 条)\n\n"
 
@@ -3096,6 +3126,8 @@ def split_content_into_batches(
                 source_header = f"{source_data['source_name']} ({len(source_data['titles'])} 条):\n\n"
             elif format_type == "ntfy":
                 source_header = f"**{source_data['source_name']}** ({len(source_data['titles'])} 条):\n\n"
+            elif format_type == "feishu":
+                source_header = f"**{source_data['source_name']}** ({len(source_data['titles'])} 条):\n\n"
             elif format_type == "dingtalk":
                 source_header = f"**{source_data['source_name']}** ({len(source_data['titles'])} 条):\n\n"
 
@@ -3114,6 +3146,10 @@ def split_content_into_batches(
                     formatted_title = format_title_for_platform(
                         "telegram", title_data_copy, show_source=False
                     )
+                elif format_type == "feishu":
+                    formatted_title = format_title_for_platform(
+                        "feishu", title_data_copy, show_source=False
+                    )
                 elif format_type == "dingtalk":
                     formatted_title = format_title_for_platform(
                         "dingtalk", title_data_copy, show_source=False
@@ -3155,6 +3191,10 @@ def split_content_into_batches(
                     formatted_title = format_title_for_platform(
                         "telegram", title_data_copy, show_source=False
                     )
+                elif format_type == "feishu":
+                    formatted_title = format_title_for_platform(
+                        "feishu", title_data_copy, show_source=False
+                    )
                 elif format_type == "dingtalk":
                     formatted_title = format_title_for_platform(
                         "dingtalk", title_data_copy, show_source=False
@@ -3187,6 +3227,8 @@ def split_content_into_batches(
             failed_header = f"\n\n⚠️ 数据获取失败的平台:\n\n"
         elif format_type == "ntfy":
             failed_header = f"\n\n⚠️ **数据获取失败的平台:**\n\n"
+        elif format_type == "feishu":
+            failed_header = f"\n{CONFIG['FEISHU_MESSAGE_SEPARATOR']}\n\n⚠️ **数据获取失败的平台:**\n\n"
         elif format_type == "dingtalk":
             failed_header = f"\n---\n\n⚠️ **数据获取失败的平台:**\n\n"
 
@@ -3204,7 +3246,9 @@ def split_content_into_batches(
             current_batch_has_content = True
 
         for i, id_value in enumerate(report_data["failed_ids"], 1):
-            if format_type == "dingtalk":
+            if format_type == "feishu":
+                failed_line = f"  • <font color='red'>{id_value}</font>\n"
+            elif format_type == "dingtalk":
                 failed_line = f"  • **{id_value}**\n"
             else:
                 failed_line = f"  • {id_value}\n"
@@ -3358,42 +3402,86 @@ def send_to_feishu(
     proxy_url: Optional[str] = None,
     mode: str = "daily",
 ) -> bool:
-    """发送到飞书"""
+    """发送到飞书(支持分批发送)"""
     headers = {"Content-Type": "application/json"}
+    proxies = None
+    if proxy_url:
+        proxies = {"http": proxy_url, "https": proxy_url}
 
-    text_content = render_feishu_content(report_data, update_info, mode)
-    total_titles = sum(
-        len(stat["titles"]) for stat in report_data["stats"] if stat["count"] > 0
+    # 获取分批内容,使用飞书专用的批次大小
+    batches = split_content_into_batches(
+        report_data,
+        "feishu",
+        update_info,
+        max_bytes=CONFIG.get("FEISHU_BATCH_SIZE", 29000),
+        mode=mode,
     )
 
-    now = get_beijing_time()
-    payload = {
-        "msg_type": "text",
-        "content": {
-            "total_titles": total_titles,
-            "timestamp": now.strftime("%Y-%m-%d %H:%M:%S"),
-            "report_type": report_type,
-            "text": text_content,
-        },
-    }
+    print(f"飞书消息分为 {len(batches)} 批次发送 [{report_type}]")
 
-    proxies = None
-    if proxy_url:
-        proxies = {"http": proxy_url, "https": proxy_url}
+    # 逐批发送
+    for i, batch_content in enumerate(batches, 1):
+        batch_size = len(batch_content.encode("utf-8"))
+        print(
+            f"发送飞书第 {i}/{len(batches)} 批次,大小:{batch_size} 字节 [{report_type}]"
+        )
 
-    try:
-        response = requests.post(
-            webhook_url, headers=headers, json=payload, proxies=proxies, timeout=30
+        # 添加批次标识
+        if len(batches) > 1:
+            batch_header = f"**[第 {i}/{len(batches)} 批次]**\n\n"
+            # 将批次标识插入到适当位置(在统计标题之后)
+            if "📊 **热点词汇统计**" in batch_content:
+                batch_content = batch_content.replace(
+                    "📊 **热点词汇统计**\n\n", f"📊 **热点词汇统计** {batch_header}"
+                )
+            else:
+                # 如果没有统计标题,直接在开头添加
+                batch_content = batch_header + batch_content
+
+        total_titles = sum(
+            len(stat["titles"]) for stat in report_data["stats"] if stat["count"] > 0
         )
-        if response.status_code == 200:
-            print(f"飞书通知发送成功 [{report_type}]")
-            return True
-        else:
-            print(f"飞书通知发送失败 [{report_type}],状态码:{response.status_code}")
+        now = get_beijing_time()
+
+        payload = {
+            "msg_type": "text",
+            "content": {
+                "total_titles": total_titles,
+                "timestamp": now.strftime("%Y-%m-%d %H:%M:%S"),
+                "report_type": report_type,
+                "text": batch_content,
+            },
+        }
+
+        try:
+            response = requests.post(
+                webhook_url, headers=headers, json=payload, proxies=proxies, timeout=30
+            )
+            if response.status_code == 200:
+                result = response.json()
+                # 检查飞书的响应状态
+                if result.get("StatusCode") == 0 or result.get("code") == 0:
+                    print(f"飞书第 {i}/{len(batches)} 批次发送成功 [{report_type}]")
+                    # 批次间间隔
+                    if i < len(batches):
+                        time.sleep(CONFIG["BATCH_SEND_INTERVAL"])
+                else:
+                    error_msg = result.get("msg") or result.get("StatusMessage", "未知错误")
+                    print(
+                        f"飞书第 {i}/{len(batches)} 批次发送失败 [{report_type}],错误:{error_msg}"
+                    )
+                    return False
+            else:
+                print(
+                    f"飞书第 {i}/{len(batches)} 批次发送失败 [{report_type}],状态码:{response.status_code}"
+                )
+                return False
+        except Exception as e:
+            print(f"飞书第 {i}/{len(batches)} 批次发送出错 [{report_type}]:{e}")
             return False
-    except Exception as e:
-        print(f"飞书通知发送出错 [{report_type}]:{e}")
-        return False
+
+    print(f"飞书所有 {len(batches)} 批次发送完成 [{report_type}]")
+    return True
 
 
 def send_to_dingtalk(

+ 8 - 4
readme.md

@@ -11,7 +11,7 @@
 [![GitHub Stars](https://img.shields.io/github/stars/sansan0/TrendRadar?style=flat-square&logo=github&color=yellow)](https://github.com/sansan0/TrendRadar/stargazers)
 [![GitHub Forks](https://img.shields.io/github/forks/sansan0/TrendRadar?style=flat-square&logo=github&color=blue)](https://github.com/sansan0/TrendRadar/network/members)
 [![License](https://img.shields.io/badge/license-GPL--3.0-blue.svg?style=flat-square)](LICENSE)
-[![Version](https://img.shields.io/badge/version-v3.0.3-blue.svg)](https://github.com/sansan0/TrendRadar)
+[![Version](https://img.shields.io/badge/version-v3.0.4-blue.svg)](https://github.com/sansan0/TrendRadar)
 [![MCP](https://img.shields.io/badge/MCP-v1.0.1-green.svg)](https://github.com/sansan0/TrendRadar)
 
 [![企业微信通知](https://img.shields.io/badge/企业微信-通知-00D4AA?style=flat-square)](https://work.weixin.qq.com/)
@@ -522,14 +522,18 @@ GitHub 一键 Fork 即可使用,无需编程基础。
   - 统一所有工具的时间参数格式
 
 
-### 2025/10/23 - v3.0.3
+### 2025/10/31 - v3.0.4
 
-- 扩大 ntfy 错误信息显示范围
+- 解决飞书因推送内容过长而产生的错误,实现了分批推送
 
 
 <details>
 <summary><strong>👉 历史更新</strong></summary>
 
+### 2025/10/23 - v3.0.3
+
+- 扩大 ntfy 错误信息显示范围
+
 
 ### 2025/10/21 - v3.0.2
 
@@ -785,7 +789,7 @@ frequency_words.txt 文件增加了一个【必须词】功能,使用 + 号
 
 ## 🚀 快速开始
 
-> 部署成功后,新闻数据一般一小时后才会更新,如想加快,可参照【第4步】手动测试配置效果
+> 配置完成后,新闻数据一小时后才会更新,如想加快,可参照【第4步】手动测试配置效果
 
 1. **Fork 本项目**到你的 GitHub 账户
 

+ 1 - 1
version

@@ -1 +1 @@
-3.0.3
+3.0.4