mirror of
https://gitee.com/houhuan/TrendRadar.git
synced 2025-12-21 16:07:15 +08:00
v2.4.0
This commit is contained in:
parent
fc927933c7
commit
8914e15d71
@ -1,15 +1,21 @@
|
|||||||
# Webhook 配置
|
# 推送配置
|
||||||
FEISHU_WEBHOOK_URL=
|
FEISHU_WEBHOOK_URL=
|
||||||
TELEGRAM_BOT_TOKEN=
|
TELEGRAM_BOT_TOKEN=
|
||||||
TELEGRAM_CHAT_ID=
|
TELEGRAM_CHAT_ID=
|
||||||
DINGTALK_WEBHOOK_URL=
|
DINGTALK_WEBHOOK_URL=
|
||||||
WEWORK_WEBHOOK_URL=
|
WEWORK_WEBHOOK_URL=
|
||||||
|
|
||||||
EMAIL_FROM=
|
EMAIL_FROM=
|
||||||
EMAIL_PASSWORD=
|
EMAIL_PASSWORD=
|
||||||
EMAIL_TO=
|
EMAIL_TO=
|
||||||
EMAIL_SMTP_SERVER=
|
EMAIL_SMTP_SERVER=
|
||||||
EMAIL_SMTP_PORT=
|
EMAIL_SMTP_PORT=
|
||||||
|
|
||||||
|
# ntfy 推送配置
|
||||||
|
NTFY_SERVER_URL=https://ntfy.sh # 默认使用公共服务,可改为自托管地址
|
||||||
|
NTFY_TOPIC= # ntfy主题名称
|
||||||
|
NTFY_TOKEN= # 可选:访问令牌(用于私有主题)
|
||||||
|
|
||||||
# 运行配置
|
# 运行配置
|
||||||
CRON_SCHEDULE=*/30 * * * * # 定时任务表达式,每 30 分钟执行一次(比如 8点,8点半,9点,9点半这种时间规律执行)
|
CRON_SCHEDULE=*/30 * * * * # 定时任务表达式,每 30 分钟执行一次(比如 8点,8点半,9点,9点半这种时间规律执行)
|
||||||
RUN_MODE=cron # 运行模式:cron/once
|
RUN_MODE=cron # 运行模式:cron/once
|
||||||
|
|||||||
@ -22,6 +22,9 @@ services:
|
|||||||
- EMAIL_TO=${EMAIL_TO:-}
|
- EMAIL_TO=${EMAIL_TO:-}
|
||||||
- EMAIL_SMTP_SERVER=${EMAIL_SMTP_SERVER:-}
|
- EMAIL_SMTP_SERVER=${EMAIL_SMTP_SERVER:-}
|
||||||
- EMAIL_SMTP_PORT=${EMAIL_SMTP_PORT:-}
|
- EMAIL_SMTP_PORT=${EMAIL_SMTP_PORT:-}
|
||||||
|
- NTFY_SERVER_URL=${NTFY_SERVER_URL:-https://ntfy.sh}
|
||||||
|
- NTFY_TOPIC=${NTFY_TOPIC:-}
|
||||||
|
- NTFY_TOKEN=${NTFY_TOKEN:-}
|
||||||
- CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
|
- CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
|
||||||
- RUN_MODE=${RUN_MODE:-cron}
|
- RUN_MODE=${RUN_MODE:-cron}
|
||||||
- IMMEDIATE_RUN=${IMMEDIATE_RUN:-true}
|
- IMMEDIATE_RUN=${IMMEDIATE_RUN:-true}
|
||||||
|
|||||||
@ -20,6 +20,9 @@ services:
|
|||||||
- EMAIL_TO=${EMAIL_TO:-}
|
- EMAIL_TO=${EMAIL_TO:-}
|
||||||
- EMAIL_SMTP_SERVER=${EMAIL_SMTP_SERVER:-}
|
- EMAIL_SMTP_SERVER=${EMAIL_SMTP_SERVER:-}
|
||||||
- EMAIL_SMTP_PORT=${EMAIL_SMTP_PORT:-}
|
- EMAIL_SMTP_PORT=${EMAIL_SMTP_PORT:-}
|
||||||
|
- NTFY_SERVER_URL=${NTFY_SERVER_URL:-https://ntfy.sh}
|
||||||
|
- NTFY_TOPIC=${NTFY_TOPIC:-}
|
||||||
|
- NTFY_TOKEN=${NTFY_TOKEN:-}
|
||||||
- CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
|
- CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
|
||||||
- RUN_MODE=${RUN_MODE:-cron}
|
- RUN_MODE=${RUN_MODE:-cron}
|
||||||
- IMMEDIATE_RUN=${IMMEDIATE_RUN:-true}
|
- IMMEDIATE_RUN=${IMMEDIATE_RUN:-true}
|
||||||
|
|||||||
355
main.py
355
main.py
@ -26,63 +26,31 @@ VERSION = "2.3.2"
|
|||||||
# === SMTP邮件配置 ===
|
# === SMTP邮件配置 ===
|
||||||
SMTP_CONFIGS = {
|
SMTP_CONFIGS = {
|
||||||
# Gmail
|
# Gmail
|
||||||
'gmail.com': {
|
"gmail.com": {"server": "smtp.gmail.com", "port": 587, "encryption": "TLS"},
|
||||||
'server': 'smtp.gmail.com',
|
|
||||||
'port': 587,
|
|
||||||
'encryption': 'TLS'
|
|
||||||
},
|
|
||||||
|
|
||||||
# QQ邮箱
|
# QQ邮箱
|
||||||
'qq.com': {
|
"qq.com": {"server": "smtp.qq.com", "port": 587, "encryption": "TLS"},
|
||||||
'server': 'smtp.qq.com',
|
|
||||||
'port': 587,
|
|
||||||
'encryption': 'TLS'
|
|
||||||
},
|
|
||||||
|
|
||||||
# Outlook
|
# Outlook
|
||||||
'outlook.com': {
|
"outlook.com": {
|
||||||
'server': 'smtp-mail.outlook.com',
|
"server": "smtp-mail.outlook.com",
|
||||||
'port': 587,
|
"port": 587,
|
||||||
'encryption': 'TLS'
|
"encryption": "TLS",
|
||||||
},
|
},
|
||||||
'hotmail.com': {
|
"hotmail.com": {
|
||||||
'server': 'smtp-mail.outlook.com',
|
"server": "smtp-mail.outlook.com",
|
||||||
'port': 587,
|
"port": 587,
|
||||||
'encryption': 'TLS'
|
"encryption": "TLS",
|
||||||
},
|
},
|
||||||
'live.com': {
|
"live.com": {"server": "smtp-mail.outlook.com", "port": 587, "encryption": "TLS"},
|
||||||
'server': 'smtp-mail.outlook.com',
|
|
||||||
'port': 587,
|
|
||||||
'encryption': 'TLS'
|
|
||||||
},
|
|
||||||
|
|
||||||
# 网易邮箱
|
# 网易邮箱
|
||||||
'163.com': {
|
"163.com": {"server": "smtp.163.com", "port": 587, "encryption": "TLS"},
|
||||||
'server': 'smtp.163.com',
|
"126.com": {"server": "smtp.126.com", "port": 587, "encryption": "TLS"},
|
||||||
'port': 587,
|
|
||||||
'encryption': 'TLS'
|
|
||||||
},
|
|
||||||
'126.com': {
|
|
||||||
'server': 'smtp.126.com',
|
|
||||||
'port': 587,
|
|
||||||
'encryption': 'TLS'
|
|
||||||
},
|
|
||||||
|
|
||||||
# 新浪邮箱
|
# 新浪邮箱
|
||||||
'sina.com': {
|
"sina.com": {"server": "smtp.sina.com", "port": 587, "encryption": "TLS"},
|
||||||
'server': 'smtp.sina.com',
|
|
||||||
'port': 587,
|
|
||||||
'encryption': 'TLS'
|
|
||||||
},
|
|
||||||
|
|
||||||
# 搜狐邮箱
|
# 搜狐邮箱
|
||||||
'sohu.com': {
|
"sohu.com": {"server": "smtp.sohu.com", "port": 587, "encryption": "TLS"},
|
||||||
'server': 'smtp.sohu.com',
|
|
||||||
'port': 587,
|
|
||||||
'encryption': 'TLS'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# === 配置管理 ===
|
# === 配置管理 ===
|
||||||
def load_config():
|
def load_config():
|
||||||
"""加载配置文件"""
|
"""加载配置文件"""
|
||||||
@ -108,7 +76,9 @@ def load_config():
|
|||||||
"ENABLE_CRAWLER": config_data["crawler"]["enable_crawler"],
|
"ENABLE_CRAWLER": config_data["crawler"]["enable_crawler"],
|
||||||
"ENABLE_NOTIFICATION": config_data["notification"]["enable_notification"],
|
"ENABLE_NOTIFICATION": config_data["notification"]["enable_notification"],
|
||||||
"MESSAGE_BATCH_SIZE": config_data["notification"]["message_batch_size"],
|
"MESSAGE_BATCH_SIZE": config_data["notification"]["message_batch_size"],
|
||||||
"DINGTALK_BATCH_SIZE": config_data["notification"].get("dingtalk_batch_size", 20000),
|
"DINGTALK_BATCH_SIZE": config_data["notification"].get(
|
||||||
|
"dingtalk_batch_size", 20000
|
||||||
|
),
|
||||||
"BATCH_SEND_INTERVAL": config_data["notification"]["batch_send_interval"],
|
"BATCH_SEND_INTERVAL": config_data["notification"]["batch_send_interval"],
|
||||||
"FEISHU_MESSAGE_SEPARATOR": config_data["notification"][
|
"FEISHU_MESSAGE_SEPARATOR": config_data["notification"][
|
||||||
"feishu_message_separator"
|
"feishu_message_separator"
|
||||||
@ -163,15 +133,15 @@ def load_config():
|
|||||||
).strip() or webhooks.get("telegram_chat_id", "")
|
).strip() or webhooks.get("telegram_chat_id", "")
|
||||||
|
|
||||||
# 邮件配置
|
# 邮件配置
|
||||||
config["EMAIL_FROM"] = os.environ.get(
|
config["EMAIL_FROM"] = os.environ.get("EMAIL_FROM", "").strip() or webhooks.get(
|
||||||
"EMAIL_FROM", ""
|
"email_from", ""
|
||||||
).strip() or webhooks.get("email_from", "")
|
)
|
||||||
config["EMAIL_PASSWORD"] = os.environ.get(
|
config["EMAIL_PASSWORD"] = os.environ.get(
|
||||||
"EMAIL_PASSWORD", ""
|
"EMAIL_PASSWORD", ""
|
||||||
).strip() or webhooks.get("email_password", "")
|
).strip() or webhooks.get("email_password", "")
|
||||||
config["EMAIL_TO"] = os.environ.get(
|
config["EMAIL_TO"] = os.environ.get("EMAIL_TO", "").strip() or webhooks.get(
|
||||||
"EMAIL_TO", ""
|
"email_to", ""
|
||||||
).strip() or webhooks.get("email_to", "")
|
)
|
||||||
config["EMAIL_SMTP_SERVER"] = os.environ.get(
|
config["EMAIL_SMTP_SERVER"] = os.environ.get(
|
||||||
"EMAIL_SMTP_SERVER", ""
|
"EMAIL_SMTP_SERVER", ""
|
||||||
).strip() or webhooks.get("email_smtp_server", "")
|
).strip() or webhooks.get("email_smtp_server", "")
|
||||||
@ -179,6 +149,17 @@ def load_config():
|
|||||||
"EMAIL_SMTP_PORT", ""
|
"EMAIL_SMTP_PORT", ""
|
||||||
).strip() or webhooks.get("email_smtp_port", "")
|
).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_TOPIC"] = os.environ.get("NTFY_TOPIC", "").strip() or webhooks.get(
|
||||||
|
"ntfy_topic", ""
|
||||||
|
)
|
||||||
|
config["NTFY_TOKEN"] = os.environ.get("NTFY_TOKEN", "").strip() or webhooks.get(
|
||||||
|
"ntfy_token", ""
|
||||||
|
)
|
||||||
|
|
||||||
# 输出配置来源信息
|
# 输出配置来源信息
|
||||||
notification_sources = []
|
notification_sources = []
|
||||||
if config["FEISHU_WEBHOOK_URL"]:
|
if config["FEISHU_WEBHOOK_URL"]:
|
||||||
@ -200,6 +181,10 @@ def load_config():
|
|||||||
from_source = "环境变量" if os.environ.get("EMAIL_FROM") else "配置文件"
|
from_source = "环境变量" if os.environ.get("EMAIL_FROM") else "配置文件"
|
||||||
notification_sources.append(f"邮件({from_source})")
|
notification_sources.append(f"邮件({from_source})")
|
||||||
|
|
||||||
|
if config["NTFY_SERVER_URL"] and config["NTFY_TOPIC"]:
|
||||||
|
server_source = "环境变量" if os.environ.get("NTFY_SERVER_URL") else "配置文件"
|
||||||
|
notification_sources.append(f"ntfy({server_source})")
|
||||||
|
|
||||||
if notification_sources:
|
if notification_sources:
|
||||||
print(f"通知渠道配置来源: {', '.join(notification_sources)}")
|
print(f"通知渠道配置来源: {', '.join(notification_sources)}")
|
||||||
else:
|
else:
|
||||||
@ -1506,6 +1491,28 @@ def format_title_for_platform(
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
elif platform == "ntfy":
|
||||||
|
if link_url:
|
||||||
|
formatted_title = f"[{cleaned_title}]({link_url})"
|
||||||
|
else:
|
||||||
|
formatted_title = cleaned_title
|
||||||
|
|
||||||
|
title_prefix = "🆕 " if title_data.get("is_new") else ""
|
||||||
|
|
||||||
|
if show_source:
|
||||||
|
result = f"[{title_data['source_name']}] {title_prefix}{formatted_title}"
|
||||||
|
else:
|
||||||
|
result = f"{title_prefix}{formatted_title}"
|
||||||
|
|
||||||
|
if rank_display:
|
||||||
|
result += f" {rank_display}"
|
||||||
|
if title_data["time_display"]:
|
||||||
|
result += f" `- {title_data['time_display']}`"
|
||||||
|
if title_data["count"] > 1:
|
||||||
|
result += f" `({title_data['count']}次)`"
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
elif platform == "html":
|
elif platform == "html":
|
||||||
rank_display = format_rank_display(
|
rank_display = format_rank_display(
|
||||||
title_data["ranks"], title_data["rank_threshold"], "html"
|
title_data["ranks"], title_data["rank_threshold"], "html"
|
||||||
@ -2534,6 +2541,8 @@ def split_content_into_batches(
|
|||||||
if max_bytes is None:
|
if max_bytes is None:
|
||||||
if format_type == "dingtalk":
|
if format_type == "dingtalk":
|
||||||
max_bytes = CONFIG.get("DINGTALK_BATCH_SIZE", 20000)
|
max_bytes = CONFIG.get("DINGTALK_BATCH_SIZE", 20000)
|
||||||
|
elif format_type == "ntfy":
|
||||||
|
max_bytes = 3800
|
||||||
else:
|
else:
|
||||||
max_bytes = CONFIG.get("MESSAGE_BATCH_SIZE", 4000)
|
max_bytes = CONFIG.get("MESSAGE_BATCH_SIZE", 4000)
|
||||||
|
|
||||||
@ -2549,6 +2558,8 @@ def split_content_into_batches(
|
|||||||
base_header = f"**总新闻数:** {total_titles}\n\n\n\n"
|
base_header = f"**总新闻数:** {total_titles}\n\n\n\n"
|
||||||
elif format_type == "telegram":
|
elif format_type == "telegram":
|
||||||
base_header = f"总新闻数: {total_titles}\n\n"
|
base_header = f"总新闻数: {total_titles}\n\n"
|
||||||
|
elif format_type == "ntfy":
|
||||||
|
base_header = f"**总新闻数:** {total_titles}\n\n"
|
||||||
elif format_type == "dingtalk":
|
elif format_type == "dingtalk":
|
||||||
base_header = f"**总新闻数:** {total_titles}\n\n"
|
base_header = f"**总新闻数:** {total_titles}\n\n"
|
||||||
base_header += f"**时间:** {now.strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
base_header += f"**时间:** {now.strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||||
@ -2564,6 +2575,10 @@ def split_content_into_batches(
|
|||||||
base_footer = f"\n\n更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}"
|
base_footer = f"\n\n更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
if update_info:
|
if update_info:
|
||||||
base_footer += f"\nTrendRadar 发现新版本 {update_info['remote_version']},当前 {update_info['current_version']}"
|
base_footer += f"\nTrendRadar 发现新版本 {update_info['remote_version']},当前 {update_info['current_version']}"
|
||||||
|
elif format_type == "ntfy":
|
||||||
|
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 == "dingtalk":
|
elif format_type == "dingtalk":
|
||||||
base_footer = f"\n\n> 更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}"
|
base_footer = f"\n\n> 更新时间:{now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
if update_info:
|
if update_info:
|
||||||
@ -2575,6 +2590,8 @@ def split_content_into_batches(
|
|||||||
stats_header = f"📊 **热点词汇统计**\n\n"
|
stats_header = f"📊 **热点词汇统计**\n\n"
|
||||||
elif format_type == "telegram":
|
elif format_type == "telegram":
|
||||||
stats_header = f"📊 热点词汇统计\n\n"
|
stats_header = f"📊 热点词汇统计\n\n"
|
||||||
|
elif format_type == "ntfy":
|
||||||
|
stats_header = f"📊 **热点词汇统计**\n\n"
|
||||||
elif format_type == "dingtalk":
|
elif format_type == "dingtalk":
|
||||||
stats_header = f"📊 **热点词汇统计**\n\n"
|
stats_header = f"📊 **热点词汇统计**\n\n"
|
||||||
|
|
||||||
@ -2641,6 +2658,17 @@ def split_content_into_batches(
|
|||||||
word_header = f"📈 {sequence_display} {word} : {count} 条\n\n"
|
word_header = f"📈 {sequence_display} {word} : {count} 条\n\n"
|
||||||
else:
|
else:
|
||||||
word_header = f"📌 {sequence_display} {word} : {count} 条\n\n"
|
word_header = f"📌 {sequence_display} {word} : {count} 条\n\n"
|
||||||
|
elif format_type == "ntfy":
|
||||||
|
if count >= 10:
|
||||||
|
word_header = (
|
||||||
|
f"🔥 {sequence_display} **{word}** : **{count}** 条\n\n"
|
||||||
|
)
|
||||||
|
elif count >= 5:
|
||||||
|
word_header = (
|
||||||
|
f"📈 {sequence_display} **{word}** : **{count}** 条\n\n"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
word_header = f"📌 {sequence_display} **{word}** : {count} 条\n\n"
|
||||||
elif format_type == "dingtalk":
|
elif format_type == "dingtalk":
|
||||||
if count >= 10:
|
if count >= 10:
|
||||||
word_header = (
|
word_header = (
|
||||||
@ -2665,6 +2693,10 @@ def split_content_into_batches(
|
|||||||
formatted_title = format_title_for_platform(
|
formatted_title = format_title_for_platform(
|
||||||
"telegram", first_title_data, show_source=True
|
"telegram", first_title_data, show_source=True
|
||||||
)
|
)
|
||||||
|
elif format_type == "ntfy":
|
||||||
|
formatted_title = format_title_for_platform(
|
||||||
|
"ntfy", first_title_data, show_source=True
|
||||||
|
)
|
||||||
elif format_type == "dingtalk":
|
elif format_type == "dingtalk":
|
||||||
formatted_title = format_title_for_platform(
|
formatted_title = format_title_for_platform(
|
||||||
"dingtalk", first_title_data, show_source=True
|
"dingtalk", first_title_data, show_source=True
|
||||||
@ -2706,6 +2738,10 @@ def split_content_into_batches(
|
|||||||
formatted_title = format_title_for_platform(
|
formatted_title = format_title_for_platform(
|
||||||
"telegram", title_data, show_source=True
|
"telegram", title_data, show_source=True
|
||||||
)
|
)
|
||||||
|
elif format_type == "ntfy":
|
||||||
|
formatted_title = format_title_for_platform(
|
||||||
|
"ntfy", title_data, show_source=True
|
||||||
|
)
|
||||||
elif format_type == "dingtalk":
|
elif format_type == "dingtalk":
|
||||||
formatted_title = format_title_for_platform(
|
formatted_title = format_title_for_platform(
|
||||||
"dingtalk", title_data, show_source=True
|
"dingtalk", title_data, show_source=True
|
||||||
@ -2737,6 +2773,8 @@ def split_content_into_batches(
|
|||||||
separator = f"\n\n\n\n"
|
separator = f"\n\n\n\n"
|
||||||
elif format_type == "telegram":
|
elif format_type == "telegram":
|
||||||
separator = f"\n\n"
|
separator = f"\n\n"
|
||||||
|
elif format_type == "ntfy":
|
||||||
|
separator = f"\n\n"
|
||||||
elif format_type == "dingtalk":
|
elif format_type == "dingtalk":
|
||||||
separator = f"\n---\n\n"
|
separator = f"\n---\n\n"
|
||||||
|
|
||||||
@ -2756,6 +2794,8 @@ def split_content_into_batches(
|
|||||||
new_header = (
|
new_header = (
|
||||||
f"\n\n🆕 本次新增热点新闻 (共 {report_data['total_new_count']} 条)\n\n"
|
f"\n\n🆕 本次新增热点新闻 (共 {report_data['total_new_count']} 条)\n\n"
|
||||||
)
|
)
|
||||||
|
elif format_type == "ntfy":
|
||||||
|
new_header = f"\n\n🆕 **本次新增热点新闻** (共 {report_data['total_new_count']} 条)\n\n"
|
||||||
elif format_type == "dingtalk":
|
elif format_type == "dingtalk":
|
||||||
new_header = f"\n---\n\n🆕 **本次新增热点新闻** (共 {report_data['total_new_count']} 条)\n\n"
|
new_header = f"\n---\n\n🆕 **本次新增热点新闻** (共 {report_data['total_new_count']} 条)\n\n"
|
||||||
|
|
||||||
@ -2779,6 +2819,8 @@ def split_content_into_batches(
|
|||||||
source_header = f"**{source_data['source_name']}** ({len(source_data['titles'])} 条):\n\n"
|
source_header = f"**{source_data['source_name']}** ({len(source_data['titles'])} 条):\n\n"
|
||||||
elif format_type == "telegram":
|
elif format_type == "telegram":
|
||||||
source_header = f"{source_data['source_name']} ({len(source_data['titles'])} 条):\n\n"
|
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 == "dingtalk":
|
elif format_type == "dingtalk":
|
||||||
source_header = f"**{source_data['source_name']}** ({len(source_data['titles'])} 条):\n\n"
|
source_header = f"**{source_data['source_name']}** ({len(source_data['titles'])} 条):\n\n"
|
||||||
|
|
||||||
@ -2868,6 +2910,8 @@ def split_content_into_batches(
|
|||||||
failed_header = f"\n\n\n\n⚠️ **数据获取失败的平台:**\n\n"
|
failed_header = f"\n\n\n\n⚠️ **数据获取失败的平台:**\n\n"
|
||||||
elif format_type == "telegram":
|
elif format_type == "telegram":
|
||||||
failed_header = f"\n\n⚠️ 数据获取失败的平台:\n\n"
|
failed_header = f"\n\n⚠️ 数据获取失败的平台:\n\n"
|
||||||
|
elif format_type == "ntfy":
|
||||||
|
failed_header = f"\n\n⚠️ **数据获取失败的平台:**\n\n"
|
||||||
elif format_type == "dingtalk":
|
elif format_type == "dingtalk":
|
||||||
failed_header = f"\n---\n\n⚠️ **数据获取失败的平台:**\n\n"
|
failed_header = f"\n---\n\n⚠️ **数据获取失败的平台:**\n\n"
|
||||||
|
|
||||||
@ -2931,7 +2975,9 @@ def send_to_notifications(
|
|||||||
|
|
||||||
if not push_manager.is_in_time_range(time_range_start, time_range_end):
|
if not push_manager.is_in_time_range(time_range_start, time_range_end):
|
||||||
now = get_beijing_time()
|
now = get_beijing_time()
|
||||||
print(f"静默模式:当前时间 {now.strftime('%H:%M')} 不在推送时间范围 {time_range_start}-{time_range_end} 内,跳过推送")
|
print(
|
||||||
|
f"静默模式:当前时间 {now.strftime('%H:%M')} 不在推送时间范围 {time_range_start}-{time_range_end} 内,跳过推送"
|
||||||
|
)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
if CONFIG["SILENT_PUSH"]["ONCE_PER_DAY"]:
|
if CONFIG["SILENT_PUSH"]["ONCE_PER_DAY"]:
|
||||||
@ -2953,6 +2999,9 @@ def send_to_notifications(
|
|||||||
email_to = CONFIG["EMAIL_TO"]
|
email_to = CONFIG["EMAIL_TO"]
|
||||||
email_smtp_server = CONFIG.get("EMAIL_SMTP_SERVER", "")
|
email_smtp_server = CONFIG.get("EMAIL_SMTP_SERVER", "")
|
||||||
email_smtp_port = CONFIG.get("EMAIL_SMTP_PORT", "")
|
email_smtp_port = CONFIG.get("EMAIL_SMTP_PORT", "")
|
||||||
|
ntfy_server_url = CONFIG["NTFY_SERVER_URL"]
|
||||||
|
ntfy_topic = CONFIG["NTFY_TOPIC"]
|
||||||
|
ntfy_token = CONFIG.get("NTFY_TOKEN", "")
|
||||||
|
|
||||||
update_info_to_send = update_info if CONFIG["SHOW_VERSION_UPDATE"] else None
|
update_info_to_send = update_info if CONFIG["SHOW_VERSION_UPDATE"] else None
|
||||||
|
|
||||||
@ -2986,6 +3035,19 @@ def send_to_notifications(
|
|||||||
mode,
|
mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 发送到 ntfy
|
||||||
|
if ntfy_server_url and ntfy_topic:
|
||||||
|
results["ntfy"] = send_to_ntfy(
|
||||||
|
ntfy_server_url,
|
||||||
|
ntfy_topic,
|
||||||
|
ntfy_token,
|
||||||
|
report_data,
|
||||||
|
report_type,
|
||||||
|
update_info_to_send,
|
||||||
|
proxy_url,
|
||||||
|
mode,
|
||||||
|
)
|
||||||
|
|
||||||
# 发送邮件
|
# 发送邮件
|
||||||
if email_from and email_password and email_to:
|
if email_from and email_password and email_to:
|
||||||
results["email"] = send_to_email(
|
results["email"] = send_to_email(
|
||||||
@ -3002,7 +3064,11 @@ def send_to_notifications(
|
|||||||
print("未配置任何通知渠道,跳过通知发送")
|
print("未配置任何通知渠道,跳过通知发送")
|
||||||
|
|
||||||
# 如果成功发送了任何通知,且启用了每天只推一次,则记录推送
|
# 如果成功发送了任何通知,且启用了每天只推一次,则记录推送
|
||||||
if CONFIG["SILENT_PUSH"]["ENABLED"] and CONFIG["SILENT_PUSH"]["ONCE_PER_DAY"] and any(results.values()):
|
if (
|
||||||
|
CONFIG["SILENT_PUSH"]["ENABLED"]
|
||||||
|
and CONFIG["SILENT_PUSH"]["ONCE_PER_DAY"]
|
||||||
|
and any(results.values())
|
||||||
|
):
|
||||||
push_manager = PushRecordManager()
|
push_manager = PushRecordManager()
|
||||||
push_manager.record_push(report_type)
|
push_manager.record_push(report_type)
|
||||||
|
|
||||||
@ -3075,7 +3141,7 @@ def send_to_dingtalk(
|
|||||||
"dingtalk",
|
"dingtalk",
|
||||||
update_info,
|
update_info,
|
||||||
max_bytes=CONFIG.get("DINGTALK_BATCH_SIZE", 20000),
|
max_bytes=CONFIG.get("DINGTALK_BATCH_SIZE", 20000),
|
||||||
mode=mode
|
mode=mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"钉钉消息分为 {len(batches)} 批次发送 [{report_type}]")
|
print(f"钉钉消息分为 {len(batches)} 批次发送 [{report_type}]")
|
||||||
@ -3093,8 +3159,7 @@ def send_to_dingtalk(
|
|||||||
# 将批次标识插入到适当位置(在标题之后)
|
# 将批次标识插入到适当位置(在标题之后)
|
||||||
if "📊 **热点词汇统计**" in batch_content:
|
if "📊 **热点词汇统计**" in batch_content:
|
||||||
batch_content = batch_content.replace(
|
batch_content = batch_content.replace(
|
||||||
"📊 **热点词汇统计**\n\n",
|
"📊 **热点词汇统计**\n\n", f"📊 **热点词汇统计** {batch_header}\n\n"
|
||||||
f"📊 **热点词汇统计** {batch_header}\n\n"
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# 如果没有统计标题,直接在开头添加
|
# 如果没有统计标题,直接在开头添加
|
||||||
@ -3270,6 +3335,7 @@ def send_to_telegram(
|
|||||||
print(f"Telegram所有 {len(batches)} 批次发送完成 [{report_type}]")
|
print(f"Telegram所有 {len(batches)} 批次发送完成 [{report_type}]")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def send_to_email(
|
def send_to_email(
|
||||||
from_email: str,
|
from_email: str,
|
||||||
password: str,
|
password: str,
|
||||||
@ -3289,7 +3355,7 @@ def send_to_email(
|
|||||||
with open(html_file_path, "r", encoding="utf-8") as f:
|
with open(html_file_path, "r", encoding="utf-8") as f:
|
||||||
html_content = f.read()
|
html_content = f.read()
|
||||||
|
|
||||||
domain = from_email.split('@')[-1].lower()
|
domain = from_email.split("@")[-1].lower()
|
||||||
|
|
||||||
if custom_smtp_server and custom_smtp_port:
|
if custom_smtp_server and custom_smtp_port:
|
||||||
# 使用自定义 SMTP 配置
|
# 使用自定义 SMTP 配置
|
||||||
@ -3299,37 +3365,37 @@ def send_to_email(
|
|||||||
elif domain in SMTP_CONFIGS:
|
elif domain in SMTP_CONFIGS:
|
||||||
# 使用预设配置
|
# 使用预设配置
|
||||||
config = SMTP_CONFIGS[domain]
|
config = SMTP_CONFIGS[domain]
|
||||||
smtp_server = config['server']
|
smtp_server = config["server"]
|
||||||
smtp_port = config['port']
|
smtp_port = config["port"]
|
||||||
use_tls = config['encryption'] == 'TLS'
|
use_tls = config["encryption"] == "TLS"
|
||||||
else:
|
else:
|
||||||
print(f"未识别的邮箱服务商: {domain},使用通用 SMTP 配置")
|
print(f"未识别的邮箱服务商: {domain},使用通用 SMTP 配置")
|
||||||
smtp_server = f"smtp.{domain}"
|
smtp_server = f"smtp.{domain}"
|
||||||
smtp_port = 587
|
smtp_port = 587
|
||||||
use_tls = True
|
use_tls = True
|
||||||
|
|
||||||
msg = MIMEMultipart('alternative')
|
msg = MIMEMultipart("alternative")
|
||||||
|
|
||||||
# 严格按照 RFC 标准设置 From header
|
# 严格按照 RFC 标准设置 From header
|
||||||
sender_name = "TrendRadar"
|
sender_name = "TrendRadar"
|
||||||
msg['From'] = formataddr((sender_name, from_email))
|
msg["From"] = formataddr((sender_name, from_email))
|
||||||
|
|
||||||
# 设置收件人
|
# 设置收件人
|
||||||
recipients = [addr.strip() for addr in to_email.split(',')]
|
recipients = [addr.strip() for addr in to_email.split(",")]
|
||||||
if len(recipients) == 1:
|
if len(recipients) == 1:
|
||||||
msg['To'] = recipients[0]
|
msg["To"] = recipients[0]
|
||||||
else:
|
else:
|
||||||
msg['To'] = ', '.join(recipients)
|
msg["To"] = ", ".join(recipients)
|
||||||
|
|
||||||
# 设置邮件主题
|
# 设置邮件主题
|
||||||
now = get_beijing_time()
|
now = get_beijing_time()
|
||||||
subject = f"TrendRadar 热点分析报告 - {report_type} - {now.strftime('%m月%d日 %H:%M')}"
|
subject = f"TrendRadar 热点分析报告 - {report_type} - {now.strftime('%m月%d日 %H:%M')}"
|
||||||
msg['Subject'] = Header(subject, 'utf-8')
|
msg["Subject"] = Header(subject, "utf-8")
|
||||||
|
|
||||||
# 设置其他标准 header
|
# 设置其他标准 header
|
||||||
msg['MIME-Version'] = '1.0'
|
msg["MIME-Version"] = "1.0"
|
||||||
msg['Date'] = formatdate(localtime=True)
|
msg["Date"] = formatdate(localtime=True)
|
||||||
msg['Message-ID'] = make_msgid()
|
msg["Message-ID"] = make_msgid()
|
||||||
|
|
||||||
# 添加纯文本部分(作为备选)
|
# 添加纯文本部分(作为备选)
|
||||||
text_content = f"""
|
text_content = f"""
|
||||||
@ -3340,10 +3406,10 @@ TrendRadar 热点分析报告
|
|||||||
|
|
||||||
请使用支持HTML的邮件客户端查看完整报告内容。
|
请使用支持HTML的邮件客户端查看完整报告内容。
|
||||||
"""
|
"""
|
||||||
text_part = MIMEText(text_content, 'plain', 'utf-8')
|
text_part = MIMEText(text_content, "plain", "utf-8")
|
||||||
msg.attach(text_part)
|
msg.attach(text_part)
|
||||||
|
|
||||||
html_part = MIMEText(html_content, 'html', 'utf-8')
|
html_part = MIMEText(html_content, "html", "utf-8")
|
||||||
msg.attach(html_part)
|
msg.attach(html_part)
|
||||||
|
|
||||||
print(f"正在发送邮件到 {to_email}...")
|
print(f"正在发送邮件到 {to_email}...")
|
||||||
@ -3398,10 +3464,142 @@ TrendRadar 热点分析报告
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"邮件发送失败 [{report_type}]:{e}")
|
print(f"邮件发送失败 [{report_type}]:{e}")
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def send_to_ntfy(
|
||||||
|
server_url: str,
|
||||||
|
topic: str,
|
||||||
|
token: Optional[str],
|
||||||
|
report_data: Dict,
|
||||||
|
report_type: str,
|
||||||
|
update_info: Optional[Dict] = None,
|
||||||
|
proxy_url: Optional[str] = None,
|
||||||
|
mode: str = "daily",
|
||||||
|
) -> bool:
|
||||||
|
"""发送到ntfy(支持分批发送,严格遵守4KB限制)"""
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "text/plain; charset=utf-8",
|
||||||
|
"Markdown": "yes",
|
||||||
|
"Title": f"TrendRadar 热点分析报告 - {report_type}",
|
||||||
|
"Priority": "default",
|
||||||
|
"Tags": "newspaper,📰",
|
||||||
|
}
|
||||||
|
|
||||||
|
if token:
|
||||||
|
headers["Authorization"] = f"Bearer {token}"
|
||||||
|
|
||||||
|
# 构建完整URL,确保格式正确
|
||||||
|
base_url = server_url.rstrip("/")
|
||||||
|
if not base_url.startswith(("http://", "https://")):
|
||||||
|
base_url = f"https://{base_url}"
|
||||||
|
url = f"{base_url}/{topic}"
|
||||||
|
|
||||||
|
proxies = None
|
||||||
|
if proxy_url:
|
||||||
|
proxies = {"http": proxy_url, "https": proxy_url}
|
||||||
|
|
||||||
|
# 获取分批内容,使用ntfy专用的4KB限制
|
||||||
|
batches = split_content_into_batches(
|
||||||
|
report_data, "ntfy", update_info, max_bytes=3800, mode=mode
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"ntfy消息分为 {len(batches)} 批次发送 [{report_type}]")
|
||||||
|
|
||||||
|
# 逐批发送
|
||||||
|
success_count = 0
|
||||||
|
for i, batch_content in enumerate(batches, 1):
|
||||||
|
batch_size = len(batch_content.encode("utf-8"))
|
||||||
|
print(
|
||||||
|
f"发送ntfy第 {i}/{len(batches)} 批次,大小:{batch_size} 字节 [{report_type}]"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查消息大小,确保不超过4KB
|
||||||
|
if batch_size > 4096:
|
||||||
|
print(f"警告:ntfy第 {i} 批次消息过大({batch_size} 字节),可能被拒绝")
|
||||||
|
|
||||||
|
# 添加批次标识
|
||||||
|
current_headers = headers.copy()
|
||||||
|
if len(batches) > 1:
|
||||||
|
batch_header = f"**[第 {i}/{len(batches)} 批次]**\n\n"
|
||||||
|
batch_content = batch_header + batch_content
|
||||||
|
current_headers["Title"] = (
|
||||||
|
f"TrendRadar 热点分析报告 - {report_type} ({i}/{len(batches)})"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
url,
|
||||||
|
headers=current_headers,
|
||||||
|
data=batch_content.encode("utf-8"),
|
||||||
|
proxies=proxies,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f"ntfy第 {i}/{len(batches)} 批次发送成功 [{report_type}]")
|
||||||
|
success_count += 1
|
||||||
|
if i < len(batches):
|
||||||
|
# 公共服务器建议 2-3 秒,自托管可以更短
|
||||||
|
interval = 2 if "ntfy.sh" in server_url else 1
|
||||||
|
time.sleep(interval)
|
||||||
|
elif response.status_code == 429:
|
||||||
|
print(
|
||||||
|
f"ntfy第 {i}/{len(batches)} 批次速率限制 [{report_type}],等待后重试"
|
||||||
|
)
|
||||||
|
time.sleep(10) # 等待10秒后重试
|
||||||
|
# 重试一次
|
||||||
|
retry_response = requests.post(
|
||||||
|
url,
|
||||||
|
headers=current_headers,
|
||||||
|
data=batch_content.encode("utf-8"),
|
||||||
|
proxies=proxies,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
if retry_response.status_code == 200:
|
||||||
|
print(f"ntfy第 {i}/{len(batches)} 批次重试成功 [{report_type}]")
|
||||||
|
success_count += 1
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"ntfy第 {i}/{len(batches)} 批次重试失败,状态码:{retry_response.status_code}"
|
||||||
|
)
|
||||||
|
elif response.status_code == 413:
|
||||||
|
print(
|
||||||
|
f"ntfy第 {i}/{len(batches)} 批次消息过大被拒绝 [{report_type}],消息大小:{batch_size} 字节"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"ntfy第 {i}/{len(batches)} 批次发送失败 [{report_type}],状态码:{response.status_code}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
error_detail = response.text[:200] # 只显示前200字符的错误信息
|
||||||
|
print(f"错误详情:{error_detail}")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectTimeout:
|
||||||
|
print(f"ntfy第 {i}/{len(batches)} 批次连接超时 [{report_type}]")
|
||||||
|
except requests.exceptions.ReadTimeout:
|
||||||
|
print(f"ntfy第 {i}/{len(batches)} 批次读取超时 [{report_type}]")
|
||||||
|
except requests.exceptions.ConnectionError as e:
|
||||||
|
print(f"ntfy第 {i}/{len(batches)} 批次连接错误 [{report_type}]:{e}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ntfy第 {i}/{len(batches)} 批次发送异常 [{report_type}]:{e}")
|
||||||
|
|
||||||
|
# 判断整体发送是否成功
|
||||||
|
if success_count == len(batches):
|
||||||
|
print(f"ntfy所有 {len(batches)} 批次发送完成 [{report_type}]")
|
||||||
|
return True
|
||||||
|
elif success_count > 0:
|
||||||
|
print(f"ntfy部分发送成功:{success_count}/{len(batches)} 批次 [{report_type}]")
|
||||||
|
return True # 部分成功也视为成功
|
||||||
|
else:
|
||||||
|
print(f"ntfy发送完全失败 [{report_type}]")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# === 主分析器 ===
|
# === 主分析器 ===
|
||||||
class NewsAnalyzer:
|
class NewsAnalyzer:
|
||||||
"""新闻分析器"""
|
"""新闻分析器"""
|
||||||
@ -3508,7 +3706,12 @@ class NewsAnalyzer:
|
|||||||
CONFIG["DINGTALK_WEBHOOK_URL"],
|
CONFIG["DINGTALK_WEBHOOK_URL"],
|
||||||
CONFIG["WEWORK_WEBHOOK_URL"],
|
CONFIG["WEWORK_WEBHOOK_URL"],
|
||||||
(CONFIG["TELEGRAM_BOT_TOKEN"] and CONFIG["TELEGRAM_CHAT_ID"]),
|
(CONFIG["TELEGRAM_BOT_TOKEN"] and CONFIG["TELEGRAM_CHAT_ID"]),
|
||||||
(CONFIG["EMAIL_FROM"] and CONFIG["EMAIL_PASSWORD"] and CONFIG["EMAIL_TO"]),
|
(
|
||||||
|
CONFIG["EMAIL_FROM"]
|
||||||
|
and CONFIG["EMAIL_PASSWORD"]
|
||||||
|
and CONFIG["EMAIL_TO"]
|
||||||
|
),
|
||||||
|
(CONFIG["NTFY_SERVER_URL"] and CONFIG["NTFY_TOPIC"]),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
147
readme.md
147
readme.md
@ -11,13 +11,15 @@
|
|||||||
[](https://github.com/sansan0/TrendRadar/stargazers)
|
[](https://github.com/sansan0/TrendRadar/stargazers)
|
||||||
[](https://github.com/sansan0/TrendRadar/network/members)
|
[](https://github.com/sansan0/TrendRadar/network/members)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://github.com/sansan0/TrendRadar)
|
[](https://github.com/sansan0/TrendRadar)
|
||||||
|
|
||||||
[](https://work.weixin.qq.com/)
|
[](https://work.weixin.qq.com/)
|
||||||
[](https://telegram.org/)
|
[](https://telegram.org/)
|
||||||
[](#)
|
[](#)
|
||||||
[](https://www.feishu.cn/)
|
[](https://www.feishu.cn/)
|
||||||
[](mailto:)
|
[](#)
|
||||||
|
[](https://github.com/binwiederhier/ntfy)
|
||||||
|
|
||||||
[](https://github.com/sansan0/TrendRadar)
|
[](https://github.com/sansan0/TrendRadar)
|
||||||
[](https://sansan0.github.io/TrendRadar)
|
[](https://sansan0.github.io/TrendRadar)
|
||||||
[](https://hub.docker.com/)
|
[](https://hub.docker.com/)
|
||||||
@ -34,7 +36,7 @@
|
|||||||
- **给予资金点赞支持** 的朋友们,你们的慷慨已化身为键盘旁的零食饮料,陪伴着项目的每一次迭代
|
- **给予资金点赞支持** 的朋友们,你们的慷慨已化身为键盘旁的零食饮料,陪伴着项目的每一次迭代
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>👉 点击查看<strong>致谢名单</strong> (当前 <strong>🔥24🔥</strong> 位)</summary>
|
<summary>👉 点击查看<strong>致谢名单</strong> (当前 <strong>🔥25🔥</strong> 位)</summary>
|
||||||
|
|
||||||
### 数据支持
|
### 数据支持
|
||||||
|
|
||||||
@ -54,6 +56,7 @@
|
|||||||
|
|
||||||
| 点赞人 | 金额 | 日期 | 备注 |
|
| 点赞人 | 金额 | 日期 | 备注 |
|
||||||
| :-------------------------: | :----: | :----: | :-----------------------: |
|
| :-------------------------: | :----: | :----: | :-----------------------: |
|
||||||
|
| **培 | 5.2 | 2025.10.2 | github-yzyf1312:开源万岁 |
|
||||||
| *椿 | 3 | 2025.9.23 | 加油,很不错 |
|
| *椿 | 3 | 2025.9.23 | 加油,很不错 |
|
||||||
| *🍍 | 10 | 2025.9.21 | |
|
| *🍍 | 10 | 2025.9.21 | |
|
||||||
| E*f | 1 | 2025.9.20 | |
|
| E*f | 1 | 2025.9.20 | |
|
||||||
@ -421,7 +424,7 @@ weight:
|
|||||||
|
|
||||||
### **多渠道实时推送**
|
### **多渠道实时推送**
|
||||||
|
|
||||||
支持**企业微信**(+ 微信推送方案)、**飞书**、**钉钉**、**Telegram**、**邮件**,消息直达手机和邮箱
|
支持**企业微信**(+ 微信推送方案)、**飞书**、**钉钉**、**Telegram**、**邮件**、**ntfy**,消息直达手机和邮箱
|
||||||
|
|
||||||
### **多端适配**
|
### **多端适配**
|
||||||
- **GitHub Pages**:自动生成精美网页报告,PC/移动端适配
|
- **GitHub Pages**:自动生成精美网页报告,PC/移动端适配
|
||||||
@ -459,6 +462,25 @@ GitHub 一键 Fork 即可使用,无需编程基础。
|
|||||||
- **小版本更新**:从 v2.x 升级到 v2.y, 用本项目的 `main.py` 代码替换你 fork 仓库中的对应文件
|
- **小版本更新**:从 v2.x 升级到 v2.y, 用本项目的 `main.py` 代码替换你 fork 仓库中的对应文件
|
||||||
- **大版本升级**:从 v1.x 升级到 v2.y, 建议删除现有 fork 后重新 fork,这样更省力且避免配置冲突
|
- **大版本升级**:从 v1.x 升级到 v2.y, 建议删除现有 fork 后重新 fork,这样更省力且避免配置冲突
|
||||||
|
|
||||||
|
### 2025/10/2 - v2.4.0
|
||||||
|
|
||||||
|
**新增 ntfy 推送通知**
|
||||||
|
|
||||||
|
- **核心功能**:
|
||||||
|
- 支持 ntfy.sh 公共服务和自托管服务器
|
||||||
|
|
||||||
|
- **使用场景**:
|
||||||
|
- 适合追求隐私的用户(支持自托管)
|
||||||
|
- 跨平台推送(iOS、Android、Desktop、Web)
|
||||||
|
- 无需注册账号(公共服务器)
|
||||||
|
- 开源免费(MIT 协议)
|
||||||
|
|
||||||
|
- **更新提示**:
|
||||||
|
- 建议使用【大版本更新】
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>👉 历史更新</strong></summary>
|
||||||
|
|
||||||
### 2025/09/26 - v2.3.2
|
### 2025/09/26 - v2.3.2
|
||||||
|
|
||||||
- 修正了邮件通知配置检查被遗漏的问题([#88](https://github.com/sansan0/TrendRadar/issues/88))
|
- 修正了邮件通知配置检查被遗漏的问题([#88](https://github.com/sansan0/TrendRadar/issues/88))
|
||||||
@ -466,9 +488,6 @@ GitHub 一键 Fork 即可使用,无需编程基础。
|
|||||||
**修复说明**:
|
**修复说明**:
|
||||||
- 解决了即使正确配置邮件通知,系统仍提示"未配置任何webhook"的问题
|
- 解决了即使正确配置邮件通知,系统仍提示"未配置任何webhook"的问题
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><strong>👉 历史更新</strong></summary>
|
|
||||||
|
|
||||||
### 2025/09/22 - v2.3.1
|
### 2025/09/22 - v2.3.1
|
||||||
|
|
||||||
- **新增邮件推送功能**,支持将热点新闻报告发送到邮箱
|
- **新增邮件推送功能**,支持将热点新闻报告发送到邮箱
|
||||||
@ -816,6 +835,117 @@ frequency_words.txt 文件增加了一个【必须词】功能,使用 + 号
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> <strong>👉 ntfy 推送</strong>(开源免费,支持自托管)</summary>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
**两种使用方式:**
|
||||||
|
|
||||||
|
### 方式一:免费使用(推荐新手) 🆓
|
||||||
|
|
||||||
|
**特点**:
|
||||||
|
- ✅ 无需注册账号,立即使用
|
||||||
|
- ✅ 每天 250 条消息(足够 90% 用户)
|
||||||
|
- ✅ Topic 名称即"密码"(需选择不易猜测的名称)
|
||||||
|
- ⚠️ 消息未加密,不适合敏感信息, 但适合我们这个项目的不敏感信息
|
||||||
|
|
||||||
|
**快速开始:**
|
||||||
|
|
||||||
|
1. **下载 ntfy 应用**:
|
||||||
|
- Android:[Google Play](https://play.google.com/store/apps/details?id=io.heckel.ntfy) / [F-Droid](https://f-droid.org/en/packages/io.heckel.ntfy/)
|
||||||
|
- iOS:[App Store](https://apps.apple.com/us/app/ntfy/id1625396347)
|
||||||
|
- 桌面:访问 [ntfy.sh](https://ntfy.sh)
|
||||||
|
|
||||||
|
2. **订阅主题**(选择一个难猜的名称):
|
||||||
|
```
|
||||||
|
建议格式:trendradar-{你的名字缩写}-{随机数字}
|
||||||
|
|
||||||
|
✅ 好例子:trendradar-zs-8492
|
||||||
|
❌ 坏例子:news、alerts(太容易被猜到)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **配置 GitHub Secret**:
|
||||||
|
- `NTFY_TOPIC`:填写你刚才订阅的主题名称
|
||||||
|
- `NTFY_SERVER_URL`:留空(默认使用 ntfy.sh)
|
||||||
|
- `NTFY_TOKEN`:留空
|
||||||
|
|
||||||
|
4. **测试**:
|
||||||
|
```bash
|
||||||
|
curl -d "测试消息" ntfy.sh/你的主题名称
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 方式二:自托管(完全隐私控制) 🔒
|
||||||
|
|
||||||
|
**适合人群**:有服务器、追求完全隐私、技术能力强
|
||||||
|
|
||||||
|
**优势**:
|
||||||
|
- ✅ 完全开源(Apache 2.0 + GPLv2)
|
||||||
|
- ✅ 数据完全自主控制
|
||||||
|
- ✅ 无任何限制
|
||||||
|
- ✅ 零费用
|
||||||
|
|
||||||
|
**Docker 一键部署**:
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name ntfy \
|
||||||
|
-p 80:80 \
|
||||||
|
-v /var/cache/ntfy:/var/cache/ntfy \
|
||||||
|
binwiederhier/ntfy \
|
||||||
|
serve --cache-file /var/cache/ntfy/cache.db
|
||||||
|
```
|
||||||
|
|
||||||
|
**配置 TrendRadar**:
|
||||||
|
```yaml
|
||||||
|
NTFY_SERVER_URL: https://ntfy.yourdomain.com
|
||||||
|
NTFY_TOPIC: trendradar-alerts # 自托管可用简单名称
|
||||||
|
NTFY_TOKEN: tk_your_token # 可选:启用访问控制
|
||||||
|
```
|
||||||
|
|
||||||
|
**在应用中订阅**:
|
||||||
|
- 点击"Use another server"
|
||||||
|
- 输入你的服务器地址
|
||||||
|
- 输入主题名称
|
||||||
|
- (可选)输入登录凭据
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**常见问题:**
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Q1: 免费版够用吗?</strong></summary>
|
||||||
|
|
||||||
|
每天 250 条消息对大多数用户足够。按 30 分钟抓取一次计算,每天约 48 次推送,完全够用。
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Q2: Topic 名称真的安全吗?</strong></summary>
|
||||||
|
|
||||||
|
如果你选择随机的、足够长的名称(如 `trendradar-zs-8492-news`),暴力破解几乎不可能:
|
||||||
|
- ntfy 有严格的速率限制(1 秒 1 次请求)
|
||||||
|
- 64 个字符选择(A-Z, a-z, 0-9, _, -)
|
||||||
|
- 10 位随机字符串有 64^10 种可能性(需要数年才能破解)
|
||||||
|
</details>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**推荐选择:**
|
||||||
|
|
||||||
|
| 用户类型 | 推荐方案 | 理由 |
|
||||||
|
|---------|---------|------|
|
||||||
|
| 普通用户 | 方式一(免费) | 简单快速,够用 |
|
||||||
|
| 技术用户 | 方式二(自托管) | 完全控制,无限制 |
|
||||||
|
| 高频用户 | 方式三(付费) | 这个自己去官网看吧 |
|
||||||
|
|
||||||
|
**相关链接:**
|
||||||
|
- [ntfy 官方文档](https://docs.ntfy.sh/)
|
||||||
|
- [自托管教程](https://docs.ntfy.sh/install/)
|
||||||
|
- [GitHub 仓库](https://github.com/binwiederhier/ntfy)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
3. **配置说明:**:
|
3. **配置说明:**:
|
||||||
|
|
||||||
- **推送设置**:在 [config/config.yaml](config/config.yaml) 中配置推送模式和通知选项
|
- **推送设置**:在 [config/config.yaml](config/config.yaml) 中配置推送模式和通知选项
|
||||||
@ -1020,10 +1150,11 @@ docker exec -it trend-radar ls -la /app/config/
|
|||||||
|
|
||||||
### 项目相关
|
### 项目相关
|
||||||
|
|
||||||
> **3 篇文章**:
|
> **4 篇文章**:
|
||||||
|
|
||||||
- [可在该文章下方留言,方便项目作者用手机答疑](https://mp.weixin.qq.com/s/KYEPfTPVzZNWFclZh4am_g)
|
- [可在该文章下方留言,方便项目作者用手机答疑](https://mp.weixin.qq.com/s/KYEPfTPVzZNWFclZh4am_g)
|
||||||
- [2个月破 1000 star,我的GitHub项目推广实战经验](https://mp.weixin.qq.com/s/jzn0vLiQFX408opcfpPPxQ)
|
- [2个月破 1000 star,我的GitHub项目推广实战经验](https://mp.weixin.qq.com/s/jzn0vLiQFX408opcfpPPxQ)
|
||||||
|
- [github fork 运行本项目的注意事项 ](https://mp.weixin.qq.com/s/C8evK-U7onG1sTTdwdW2zg)
|
||||||
- [基于本项目,如何开展公众号或者新闻资讯类文章写作](https://mp.weixin.qq.com/s/8ghyfDAtQZjLrnWTQabYOQ)
|
- [基于本项目,如何开展公众号或者新闻资讯类文章写作](https://mp.weixin.qq.com/s/8ghyfDAtQZjLrnWTQabYOQ)
|
||||||
|
|
||||||
>**AI 开发**:
|
>**AI 开发**:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user