|
|
@@ -20,7 +20,7 @@ import requests
|
|
|
import yaml
|
|
|
|
|
|
|
|
|
-VERSION = "3.2.0"
|
|
|
+VERSION = "3.3.0"
|
|
|
|
|
|
|
|
|
# === SMTP邮件配置 ===
|
|
|
@@ -50,6 +50,8 @@ SMTP_CONFIGS = {
|
|
|
"sohu.com": {"server": "smtp.sohu.com", "port": 465, "encryption": "SSL"},
|
|
|
# 天翼邮箱(使用 SSL)
|
|
|
"189.cn": {"server": "smtp.189.cn", "port": 465, "encryption": "SSL"},
|
|
|
+ # 阿里云邮箱(使用 TLS)
|
|
|
+ "aliyun.com": {"server": "smtp.aliyun.com", "port": 465, "encryption": "TLS"},
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -97,6 +99,7 @@ def load_config():
|
|
|
"dingtalk_batch_size", 20000
|
|
|
),
|
|
|
"FEISHU_BATCH_SIZE": config_data["notification"].get("feishu_batch_size", 29000),
|
|
|
+ "BARK_BATCH_SIZE": config_data["notification"].get("bark_batch_size", 3600),
|
|
|
"BATCH_SEND_INTERVAL": config_data["notification"]["batch_send_interval"],
|
|
|
"FEISHU_MESSAGE_SEPARATOR": config_data["notification"][
|
|
|
"feishu_message_separator"
|
|
|
@@ -182,9 +185,11 @@ def load_config():
|
|
|
).strip() or webhooks.get("email_smtp_port", "")
|
|
|
|
|
|
# ntfy配置
|
|
|
- config["NTFY_SERVER_URL"] = os.environ.get(
|
|
|
- "NTFY_SERVER_URL", "https://ntfy.sh"
|
|
|
- ).strip() or webhooks.get("ntfy_server_url", "https://ntfy.sh")
|
|
|
+ config["NTFY_SERVER_URL"] = (
|
|
|
+ os.environ.get("NTFY_SERVER_URL", "").strip()
|
|
|
+ or webhooks.get("ntfy_server_url")
|
|
|
+ or "https://ntfy.sh"
|
|
|
+ )
|
|
|
config["NTFY_TOPIC"] = os.environ.get("NTFY_TOPIC", "").strip() or webhooks.get(
|
|
|
"ntfy_topic", ""
|
|
|
)
|
|
|
@@ -192,6 +197,11 @@ def load_config():
|
|
|
"ntfy_token", ""
|
|
|
)
|
|
|
|
|
|
+ # Bark配置
|
|
|
+ config["BARK_URL"] = os.environ.get("BARK_URL", "").strip() or webhooks.get(
|
|
|
+ "bark_url", ""
|
|
|
+ )
|
|
|
+
|
|
|
# 输出配置来源信息
|
|
|
notification_sources = []
|
|
|
if config["FEISHU_WEBHOOK_URL"]:
|
|
|
@@ -217,6 +227,10 @@ def load_config():
|
|
|
server_source = "环境变量" if os.environ.get("NTFY_SERVER_URL") else "配置文件"
|
|
|
notification_sources.append(f"ntfy({server_source})")
|
|
|
|
|
|
+ if config["BARK_URL"]:
|
|
|
+ bark_source = "环境变量" if os.environ.get("BARK_URL") else "配置文件"
|
|
|
+ notification_sources.append(f"Bark({bark_source})")
|
|
|
+
|
|
|
if notification_sources:
|
|
|
print(f"通知渠道配置来源: {', '.join(notification_sources)}")
|
|
|
else:
|
|
|
@@ -3397,6 +3411,7 @@ def send_to_notifications(
|
|
|
ntfy_server_url = CONFIG["NTFY_SERVER_URL"]
|
|
|
ntfy_topic = CONFIG["NTFY_TOPIC"]
|
|
|
ntfy_token = CONFIG.get("NTFY_TOKEN", "")
|
|
|
+ bark_url = CONFIG["BARK_URL"]
|
|
|
|
|
|
update_info_to_send = update_info if CONFIG["SHOW_VERSION_UPDATE"] else None
|
|
|
|
|
|
@@ -3443,6 +3458,17 @@ def send_to_notifications(
|
|
|
mode,
|
|
|
)
|
|
|
|
|
|
+ # 发送到 Bark
|
|
|
+ if bark_url:
|
|
|
+ results["bark"] = send_to_bark(
|
|
|
+ bark_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(
|
|
|
@@ -4129,6 +4155,116 @@ def send_to_ntfy(
|
|
|
return False
|
|
|
|
|
|
|
|
|
+def send_to_bark(
|
|
|
+ bark_url: str,
|
|
|
+ report_data: Dict,
|
|
|
+ report_type: str,
|
|
|
+ update_info: Optional[Dict] = None,
|
|
|
+ proxy_url: Optional[str] = None,
|
|
|
+ mode: str = "daily",
|
|
|
+) -> bool:
|
|
|
+ """发送到Bark(支持分批发送,使用纯文本格式)"""
|
|
|
+ proxies = None
|
|
|
+ if proxy_url:
|
|
|
+ proxies = {"http": proxy_url, "https": proxy_url}
|
|
|
+
|
|
|
+ # 获取分批内容(Bark 限制为 3600 字节以避免 413 错误)
|
|
|
+ batches = split_content_into_batches(
|
|
|
+ report_data, "wework", update_info, max_bytes=CONFIG["BARK_BATCH_SIZE"], mode=mode
|
|
|
+ )
|
|
|
+
|
|
|
+ total_batches = len(batches)
|
|
|
+ print(f"Bark消息分为 {total_batches} 批次发送 [{report_type}]")
|
|
|
+
|
|
|
+ # 反转批次顺序,使得在Bark客户端显示时顺序正确
|
|
|
+ # Bark显示最新消息在上面,所以我们从最后一批开始推送
|
|
|
+ reversed_batches = list(reversed(batches))
|
|
|
+
|
|
|
+ print(f"Bark将按反向顺序推送(最后批次先推送),确保客户端显示顺序正确")
|
|
|
+
|
|
|
+ # 逐批发送(反向顺序)
|
|
|
+ success_count = 0
|
|
|
+ for idx, batch_content in enumerate(reversed_batches, 1):
|
|
|
+ # 计算正确的批次编号(用户视角的编号)
|
|
|
+ actual_batch_num = total_batches - idx + 1
|
|
|
+
|
|
|
+ # 添加批次标识(使用正确的批次编号)
|
|
|
+ if total_batches > 1:
|
|
|
+ batch_header = f"[第 {actual_batch_num}/{total_batches} 批次]\n\n"
|
|
|
+ batch_content = batch_header + batch_content
|
|
|
+
|
|
|
+ # 清理 markdown 语法(Bark 不支持 markdown)
|
|
|
+ plain_content = strip_markdown(batch_content)
|
|
|
+
|
|
|
+ batch_size = len(plain_content.encode("utf-8"))
|
|
|
+ print(
|
|
|
+ f"发送Bark第 {actual_batch_num}/{total_batches} 批次(推送顺序: {idx}/{total_batches}),大小:{batch_size} 字节 [{report_type}]"
|
|
|
+ )
|
|
|
+
|
|
|
+ # 检查消息大小(Bark使用APNs,限制4KB)
|
|
|
+ if batch_size > 4096:
|
|
|
+ print(
|
|
|
+ f"警告:Bark第 {actual_batch_num}/{total_batches} 批次消息过大({batch_size} 字节),可能被拒绝"
|
|
|
+ )
|
|
|
+
|
|
|
+ # 构建JSON payload
|
|
|
+ payload = {
|
|
|
+ "title": report_type,
|
|
|
+ "body": plain_content,
|
|
|
+ "sound": "default",
|
|
|
+ "group": "TrendRadar",
|
|
|
+ }
|
|
|
+
|
|
|
+ try:
|
|
|
+ response = requests.post(
|
|
|
+ bark_url,
|
|
|
+ json=payload,
|
|
|
+ proxies=proxies,
|
|
|
+ timeout=30,
|
|
|
+ )
|
|
|
+
|
|
|
+ if response.status_code == 200:
|
|
|
+ result = response.json()
|
|
|
+ if result.get("code") == 200:
|
|
|
+ print(f"Bark第 {actual_batch_num}/{total_batches} 批次发送成功 [{report_type}]")
|
|
|
+ success_count += 1
|
|
|
+ # 批次间间隔
|
|
|
+ if idx < total_batches:
|
|
|
+ time.sleep(CONFIG["BATCH_SEND_INTERVAL"])
|
|
|
+ else:
|
|
|
+ print(
|
|
|
+ f"Bark第 {actual_batch_num}/{total_batches} 批次发送失败 [{report_type}],错误:{result.get('message', '未知错误')}"
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ print(
|
|
|
+ f"Bark第 {actual_batch_num}/{total_batches} 批次发送失败 [{report_type}],状态码:{response.status_code}"
|
|
|
+ )
|
|
|
+ try:
|
|
|
+ print(f"错误详情:{response.text}")
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+
|
|
|
+ except requests.exceptions.ConnectTimeout:
|
|
|
+ print(f"Bark第 {actual_batch_num}/{total_batches} 批次连接超时 [{report_type}]")
|
|
|
+ except requests.exceptions.ReadTimeout:
|
|
|
+ print(f"Bark第 {actual_batch_num}/{total_batches} 批次读取超时 [{report_type}]")
|
|
|
+ except requests.exceptions.ConnectionError as e:
|
|
|
+ print(f"Bark第 {actual_batch_num}/{total_batches} 批次连接错误 [{report_type}]:{e}")
|
|
|
+ except Exception as e:
|
|
|
+ print(f"Bark第 {actual_batch_num}/{total_batches} 批次发送异常 [{report_type}]:{e}")
|
|
|
+
|
|
|
+ # 判断整体发送是否成功
|
|
|
+ if success_count == total_batches:
|
|
|
+ print(f"Bark所有 {total_batches} 批次发送完成 [{report_type}]")
|
|
|
+ return True
|
|
|
+ elif success_count > 0:
|
|
|
+ print(f"Bark部分发送成功:{success_count}/{total_batches} 批次 [{report_type}]")
|
|
|
+ return True # 部分成功也视为成功
|
|
|
+ else:
|
|
|
+ print(f"Bark发送完全失败 [{report_type}]")
|
|
|
+ return False
|
|
|
+
|
|
|
+
|
|
|
# === 主分析器 ===
|
|
|
class NewsAnalyzer:
|
|
|
"""新闻分析器"""
|
|
|
@@ -4241,6 +4377,7 @@ class NewsAnalyzer:
|
|
|
and CONFIG["EMAIL_TO"]
|
|
|
),
|
|
|
(CONFIG["NTFY_SERVER_URL"] and CONFIG["NTFY_TOPIC"]),
|
|
|
+ CONFIG["BARK_URL"],
|
|
|
]
|
|
|
)
|
|
|
|