feat: 新增国际新闻获取和附言生成功能

- 新增国际新闻 RSS 源(BBC World、Sky News、NHK 国际)
- 新增附言生成功能(AI 生成的"舔狗"风格小故事)
- 优化邮件 HTML 模板(信纸风格、主题配色)
- 新增天气/新闻缓存机制,避免重复获取
- 新增 NEWS_PROXY_URL 环境变量配置国际新闻代理
- 完善状态管理(monitor_state.json 替代 state.json)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-11 23:47:08 +08:00
co-authored by Claude Opus 4.6
parent 37ad6116b2
commit 12bbe0058b
+391 -40
View File
@@ -16,6 +16,7 @@ import json
import re import re
import urllib.parse import urllib.parse
import urllib.request import urllib.request
import xml.etree.ElementTree as ET
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.header import Header from email.header import Header
@@ -37,6 +38,9 @@ TARGET_IPS = [
# 天气位置配置 # 天气位置配置
WEATHER_LOCATION = "成都" WEATHER_LOCATION = "成都"
# 国外新闻代理配置
NEWS_PROXY_URL = os.getenv("NEWS_PROXY_URL", "http://127.0.0.1:7890").strip() or None
# 运行时间配置 # 运行时间配置
START_HOUR = 2 # 凌晨2点开始 START_HOUR = 2 # 凌晨2点开始
END_HOUR = 22 # 晚上10点结束 END_HOUR = 22 # 晚上10点结束
@@ -57,6 +61,27 @@ DEFAULT_HTTP_HEADERS = {
"User-Agent": "Mozilla/5.0", "User-Agent": "Mozilla/5.0",
} }
NEWS_CACHE_VERSION = "mixed-news-v1"
DOMESTIC_NEWS_LIMIT = 20
FOREIGN_NEWS_LIMIT = 40
FOREIGN_NEWS_SOURCES = [
{
"name": "BBC World",
"url": "https://feeds.bbci.co.uk/news/world/rss.xml",
"proxy": True,
},
{
"name": "Sky News World",
"url": "https://feeds.skynews.com/feeds/rss/world.xml",
"proxy": True,
},
{
"name": "NHK 国际",
"url": "https://www3.nhk.or.jp/rss/news/cat6.xml",
"proxy": False,
},
]
# ============== 邮件发送函数 ============== # ============== 邮件发送函数 ==============
def send_email(subject, html_body): def send_email(subject, html_body):
@@ -124,6 +149,22 @@ def generate_morning_message(status, arrived_time=None, weather_summary=None, ne
# 第三段直接用news_summary(已经是串联好的内容) # 第三段直接用news_summary(已经是串联好的内容)
return f"{first_paragraph}\n\n{second_paragraph}\n\n{news_summary}" return f"{first_paragraph}\n\n{second_paragraph}\n\n{news_summary}"
def generate_dog_section():
"""生成附言:问题式副标题 + 故事正文 + 原创舔狗收尾"""
dog_section = get_dog_intro()
if dog_section:
return dog_section
return {
"subtitle": "喜欢为什么总慢半拍?",
"story": (
f"以前我也总觉得,只要自己再往前走一步,很多话就会有回应。后来才明白,"
f"有些人不是听不懂,只是不想接住。那天路灯已经暗下来了,他低头笑了笑,"
f"我站在原地,忽然觉得很多没说出口的话,其实早就被看穿了。"
),
"quote": "她说:\"我没有在等你回我,我只是想看看,我还能为你失眠到几点。\"",
}
def build_email_html(status, arrived_time=None, weather_summary=None, news_summary=None): def build_email_html(status, arrived_time=None, weather_summary=None, news_summary=None):
"""构建邮件HTML内容""" """构建邮件HTML内容"""
if status == "arrived": if status == "arrived":
@@ -134,14 +175,107 @@ def build_email_html(status, arrived_time=None, weather_summary=None, news_summa
# 获取AI生成的文案 # 获取AI生成的文案
morning_message = generate_morning_message(status, arrived_time, weather_summary, news_summary) morning_message = generate_morning_message(status, arrived_time, weather_summary, news_summary)
# 只切前两段,避免AI多换行时把新闻正文截断
msg_parts = morning_message.split('\n\n', 2)
sun_part = msg_parts[0] if len(msg_parts) > 0 else ""
weather_part = msg_parts[1] if len(msg_parts) > 1 else ""
news_part = msg_parts[2] if len(msg_parts) > 2 else ""
# 获取附言部分
dog_section = generate_dog_section()
theme = {
"seal": "太阳照常升起" if status == "arrived" else "吗喽也要休息",
"accent": "#A66A4C" if status == "arrived" else "#6B7485",
"ink": "#3B3028",
"sub_ink": "#6A5A4D",
"paper": "#FFF9F0",
"paper_edge": "#E8DCCB",
"line": "#E9DDCF",
"seal_bg": "#F3E1D2" if status == "arrived" else "#E2E8EF",
"seal_text": "#9C5F43" if status == "arrived" else "#5E7387",
}
html = f""" html = f"""
<html> <html>
<body> <head>
<div style="font-size: 16px; line-height: 1.8; color: #333;"> <meta charset="utf-8">
{morning_message.replace('\n', '<br>')} <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin: 0; padding: 28px 14px; background: #EEE4D6; font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: {theme['ink']};">
<div style="max-width: 640px; margin: 0 auto;">
<div style="background: {theme['paper']}; border: 1px solid {theme['paper_edge']}; border-radius: 18px; overflow: hidden; box-shadow: 0 16px 40px rgba(110, 87, 65, 0.12);">
<div style="padding: 24px 26px 12px; border-bottom: 1px solid {theme['line']};">
<div style="display: table; width: 100%; margin-bottom: 18px;">
<div style="display: table-cell; vertical-align: middle; font-size: 16px; color: {theme['sub_ink']};">
致:{YOUR_NAME}
</div>
<div style="display: table-cell; vertical-align: middle; text-align: right;">
<span style="display: inline-block; padding: 6px 12px; border-radius: 999px; background: {theme['seal_bg']}; color: {theme['seal_text']}; font-size: 12px; letter-spacing: 1px;">{theme['seal']}</span>
</div>
</div>
<div style="font-family: 'STKaiti', 'KaiTi', 'Kaiti SC', 'Songti SC', serif; font-size: 32px; line-height: 1.4; color: {theme['ink']}; margin-bottom: 14px;">
今天给你写一封晨间小信
</div>
<div style="font-size: 15px; line-height: 1.9; color: {theme['sub_ink']};">
把天气、热点和一点碎碎念都收进信里。打开它的时候,就当是有人替你先把今天轻轻铺开。
</div>
</div>
<div style="padding: 10px 26px 4px;">
<div style="padding: 18px 0 16px; border-bottom: 1px dashed {theme['line']};">
<div style="font-size: 12px; letter-spacing: 2px; color: {theme['accent']}; margin-bottom: 10px;">先和你说</div>
<div style="font-family: 'STKaiti', 'KaiTi', 'Kaiti SC', 'Songti SC', serif; font-size: 20px; line-height: 1.95; color: {theme['ink']};">
{sun_part.replace('\n', '<br>')}
</div>
</div>
<div style="padding: 18px 0 16px; border-bottom: 1px dashed {theme['line']};">
<div style="font-size: 12px; letter-spacing: 2px; color: #7187A0; margin-bottom: 10px;">今天天气</div>
<div style="font-family: 'STKaiti', 'KaiTi', 'Kaiti SC', 'Songti SC', serif; font-size: 19px; line-height: 1.95; color: {theme['ink']};">
{weather_part.replace('\n', '<br>')}
</div>
</div>
<div style="padding: 18px 0 12px;">
<div style="font-size: 12px; letter-spacing: 2px; color: #7D8D63; margin-bottom: 10px;">今天的事</div>
<div style="font-family: 'STKaiti', 'KaiTi', 'Kaiti SC', 'Songti SC', serif; font-size: 19px; line-height: 1.95; color: {theme['ink']};">
{news_part.replace('\n', '<br>')}
</div>
</div>
</div>
"""
# 如果有附言,添加附言区
if dog_section:
html += f"""
<div style="margin: 6px 26px 18px; padding: 16px 0 0; border-top: 1px dashed {theme['line']};">
<div style="font-size: 12px; letter-spacing: 2px; color: #B37064; margin-bottom: 10px;">附言</div>
<div style="font-family: 'STKaiti', 'KaiTi', 'Kaiti SC', 'Songti SC', serif; font-size: 24px; line-height: 1.6; color: #4E4038; margin-bottom: 10px;">
{dog_section['subtitle']}
</div>
<div style="font-family: 'STKaiti', 'KaiTi', 'Kaiti SC', 'Songti SC', serif; font-size: 18px; line-height: 1.95; color: #5D4840;">
{dog_section['story'].replace('\n', '<br>')}
</div>
<div style="margin-top: 12px; padding-left: 14px; border-left: 2px solid #D7C2B0; color: #7A6458; font-family: 'STKaiti', 'KaiTi', 'Kaiti SC', 'Songti SC', serif; font-size: 17px; line-height: 1.9;">
{dog_section['quote'].replace('\n', '<br>')}
</div>
</div>
"""
html += f"""
<div style="padding: 6px 26px 26px; color: #897765; border-top: 1px solid {theme['line']};">
<div style="font-family: 'STKaiti', 'KaiTi', 'Kaiti SC', 'Songti SC', serif; font-size: 20px; line-height: 1.8; margin: 12px 0 4px;">
愿今天也顺顺当当。
</div>
<div style="font-size: 12px; line-height: 1.8; color: #9B8A79;">
{datetime.datetime.now().strftime('%Y.%m.%d')} · 自动送达<br>
写信人:{FROM_NAME}
</div>
</div>
</div>
</div> </div>
<hr>
<p style="color: #888; font-size: 12px;">此邮件由系统自动发送</p>
</body> </body>
</html> </html>
""" """
@@ -242,16 +376,89 @@ def is_failed_content(content):
"""判断内容是否属于失败结果""" """判断内容是否属于失败结果"""
return not content or "获取失败" in content return not content or "获取失败" in content
def fetch_text(url, headers=None, timeout=10): def fetch_text(url, headers=None, timeout=10, proxy_url=None):
"""发起HTTP请求并返回文本内容""" """发起HTTP请求并返回文本内容"""
request_headers = dict(DEFAULT_HTTP_HEADERS) request_headers = dict(DEFAULT_HTTP_HEADERS)
if headers: if headers:
request_headers.update(headers) request_headers.update(headers)
req = urllib.request.Request(url, headers=request_headers) req = urllib.request.Request(url, headers=request_headers)
with urllib.request.urlopen(req, timeout=timeout) as response: if proxy_url:
proxy_handler = urllib.request.ProxyHandler({
"http": proxy_url,
"https": proxy_url,
})
opener = urllib.request.build_opener(proxy_handler)
response = opener.open(req, timeout=timeout)
else:
response = urllib.request.urlopen(req, timeout=timeout)
with response:
return response.read().decode('utf-8', errors='ignore') return response.read().decode('utf-8', errors='ignore')
def get_domestic_news_titles(limit=DOMESTIC_NEWS_LIMIT):
"""获取国内热点标题"""
html = fetch_text("https://top.baidu.com/board?tab=realtime")
raw_titles = re.findall(r'"word":"(.*?)"', html)
news_items = []
seen_titles = set()
for raw_title in raw_titles:
title = raw_title.strip()
if not title or title in seen_titles:
continue
seen_titles.add(title)
news_items.append(title)
if len(news_items) >= limit:
break
return news_items
def parse_rss_titles(rss_text, limit):
"""从RSS内容中提取标题"""
root = ET.fromstring(rss_text)
titles = []
seen_titles = set()
for item in root.findall('.//item'):
title_element = item.find('title')
if title_element is None or not title_element.text:
continue
title = re.sub(r'\s+', ' ', title_element.text).strip()
if not title or title in seen_titles:
continue
seen_titles.add(title)
titles.append(title)
if len(titles) >= limit:
break
return titles
def get_foreign_news_titles(limit=FOREIGN_NEWS_LIMIT):
"""获取国外新闻平台标题,必要时走代理"""
foreign_items = []
seen_titles = set()
for source in FOREIGN_NEWS_SOURCES:
proxy_url = NEWS_PROXY_URL if source.get("proxy") else None
try:
rss_text = fetch_text(source["url"], timeout=15, proxy_url=proxy_url)
titles = parse_rss_titles(rss_text, limit)
for title in titles:
if title in seen_titles:
continue
seen_titles.add(title)
foreign_items.append((source["name"], title))
if len(foreign_items) >= limit:
break
except Exception as e:
print(f"[{datetime.datetime.now()}] 获取国际新闻失败 ({source['name']}): {e}")
if len(foreign_items) >= limit:
break
return foreign_items
def get_weather(): def get_weather():
"""获取当天天气信息(带缓存)""" """获取当天天气信息(带缓存)"""
today = get_today_str() today = get_today_str()
@@ -274,16 +481,21 @@ def get_weather():
# 天气信息来源 - 使用wttr.in(免费无需API Key # 天气信息来源 - 使用wttr.in(免费无需API Key
weather_info = "" weather_info = ""
weather_ok = False
try: try:
encoded_location = urllib.parse.quote(WEATHER_LOCATION) encoded_location = urllib.parse.quote(WEATHER_LOCATION)
url = f"https://wttr.in/{encoded_location}?format=%l:+%C,+%t,+%h,+feeling:+%f,+%p" url = f"https://wttr.in/{encoded_location}?format=%l:+%C,+%t,+%h,+feeling:+%f,+%p"
weather_info = fetch_text(url) weather_info = fetch_text(url)
if not weather_info.strip(): if not weather_info.strip():
weather_info = "天气信息获取失败" weather_info = "天气信息获取失败"
else:
weather_ok = True
except Exception as e: except Exception as e:
print(f"[{datetime.datetime.now()}] 获取天气失败: {e}") print(f"[{datetime.datetime.now()}] 获取天气失败: {e}")
weather_info = "天气信息获取失败" weather_info = "天气信息获取失败"
print(f"[{datetime.datetime.now()}] - wttr.in ({WEATHER_LOCATION}): {'✓ 获取成功' if weather_ok else '✗ 获取失败'}")
# 保存缓存 # 保存缓存
os.makedirs(STATE_DIR, exist_ok=True) os.makedirs(STATE_DIR, exist_ok=True)
cache_data = { cache_data = {
@@ -302,9 +514,10 @@ def get_weather():
# ============== 新闻获取函数 ============== # ============== 新闻获取函数 ==============
def get_hot_news(): def get_hot_news_with_status():
"""获取当天热搜新闻(带缓存)""" """获取当天热搜新闻(带缓存),同时返回各源状态"""
today = get_today_str() today = get_today_str()
source_status = []
# 检查缓存 # 检查缓存
if os.path.exists(NEWS_CACHE_FILE): if os.path.exists(NEWS_CACHE_FILE):
@@ -314,36 +527,49 @@ def get_hot_news():
cached_news = cache.get("news", "") cached_news = cache.get("news", "")
if ( if (
cache.get("date") == today cache.get("date") == today
and cache.get("version") == NEWS_CACHE_VERSION
and time.time() - cache.get("timestamp", 0) < CACHE_EXPIRE and time.time() - cache.get("timestamp", 0) < CACHE_EXPIRE
and not is_failed_content(cached_news) and not is_failed_content(cached_news)
): ):
return cached_news return cached_news, []
except Exception: except Exception:
pass pass
# 从百度热搜页提取热榜关键词
news_info = "" news_info = ""
try: try:
html = fetch_text("https://top.baidu.com/board?tab=realtime") domestic_titles = get_domestic_news_titles()
raw_titles = re.findall(r'"word":"(.*?)"', html) domestic_ok = bool(domestic_titles)
news_items = [] source_status.append(("国内热点 (百度)", domestic_ok, len(domestic_titles)))
seen_titles = set()
for raw_title in raw_titles: foreign_titles = get_foreign_news_titles()
title = raw_title.strip() foreign_sources = {}
if not title or title in seen_titles: for source_name, _ in foreign_titles:
continue foreign_sources[source_name] = foreign_sources.get(source_name, 0) + 1
seen_titles.add(title) for src in FOREIGN_NEWS_SOURCES:
news_items.append(title) src_name = src["name"]
if len(news_items) >= 10: count = foreign_sources.get(src_name, 0)
break source_status.append((src_name, count > 0, count))
if not news_items: sections = []
raise ValueError("未能从百度热搜页面提取标题") if domestic_titles:
sections.append(
"国内热点:\n" + "\n".join(
f"{index}. {title}" for index, title in enumerate(domestic_titles, 1)
)
)
news_info = "\n".join( if foreign_titles:
f"{index}. {title}" for index, title in enumerate(news_items, 1) sections.append(
) "国际热点:\n" + "\n".join(
f"{index}. {source} | {title}"
for index, (source, title) in enumerate(foreign_titles, 1)
)
)
if not sections:
raise ValueError("未能获取国内或国际新闻")
news_info = "\n\n".join(sections)
except Exception as e: except Exception as e:
print(f"[{datetime.datetime.now()}] 获取新闻失败: {e}") print(f"[{datetime.datetime.now()}] 获取新闻失败: {e}")
@@ -354,6 +580,7 @@ def get_hot_news():
os.makedirs(STATE_DIR, exist_ok=True) os.makedirs(STATE_DIR, exist_ok=True)
cache_data = { cache_data = {
"date": today, "date": today,
"version": NEWS_CACHE_VERSION,
"timestamp": time.time(), "timestamp": time.time(),
"news": news_info "news": news_info
} }
@@ -363,8 +590,86 @@ def get_hot_news():
except Exception: except Exception:
pass pass
return news_info, source_status
def get_hot_news():
"""获取当天热搜新闻(带缓存)"""
news_info, source_status = get_hot_news_with_status()
if source_status:
# source_status[0] = 国内热点, source_status[1:] = 国外各源
domestic_name, domestic_ok, domestic_count = source_status[0]
print(f"[{datetime.datetime.now()}] - {domestic_name}: {'✓ 获取成功 ' + str(domestic_count) + '' if domestic_ok else '✗ 获取失败'}")
for src_info in source_status[1:]:
src_name, ok, count = src_info
print(f"[{datetime.datetime.now()}] - {src_name}: {'✓ 获取成功 ' + str(count) + '' if ok else '✗ 获取失败'}")
return news_info return news_info
# ============== 附言生成函数 ==============
def get_dog_intro():
"""让AI直接生成附言的副标题、故事和收尾台词"""
prompt = """请直接原创一段适合放进早安邮件附言里的小故事,主题是单方面喜欢、过度投入、自我感动、错位理解这类情绪,但不要写得太油腻。
请严格按下面格式输出:
第一行:一个问题式副标题,像“为什么越卑微越容易自我感动?”、“喜欢为什么总在错误频道里发生?”,长度控制在10到22字,以问号结尾。
第二部分:写一段100到170字的小故事,像一个过来人在回忆自己以前经历过的一件小事。你要自己判断叙述者更像男生还是女生、喜欢的人是谁,并保持整段故事的人称、语气、细节一致。要自然、具体、克制,带一点自嘲和电影感,但不要像鸡汤,也不要空泛说理。正文结尾要停在一个情绪悬着的位置,像还差一句话没有落下来。
第三部分:另起一段,只写一句收尾台词。这里可以是女神/男神的冷淡回应,也可以是舔狗本人特别卑微、特别会自我感动的一句话。关键要求是:如果删掉这最后一句,前面只是一个普通的暗恋小故事;加上这最后一句,整段才会一下子变得很舔、很心酸、甚至有点好笑。这里要自然写成“我说:\"...\"”、“他说:\"...\"”或者“她说:\"...\"”。引号里的内容由你原创,但必须像真实聊天里会出现的话,不要故作矫情。
要求:
- 副标题不要加书名号、引号、编号或解释
- 正文不要分段,不要列表,不要使用 markdown
- 正文和最后那句台词要有情绪上的衔接,像回忆走到那一刻时自然停下来
- 最后一段单独成段,像信里被轻轻划出来的一句原话,不要额外解释
- 台词要短一些,尽量控制在20到50字
- 最后一句必须明显提升“舔狗感”,不能只是普通对白,不能和前文松散脱节
- 只输出这三部分,每部分之间空一行"""
intro = ask_deepseek(
prompt,
system_prompt="你是一个很会讲感情小故事的人,文风克制、真诚,带一点自嘲和电影感。你尤其擅长写那种最后一句一出来,整段故事突然显得很舔、很可怜、又有点好笑的附言。",
)
if not intro:
return None
parts = intro.split("\n\n", 2)
subtitle = parts[0].strip() if parts else ""
story = parts[1].strip() if len(parts) > 1 else ""
quote = parts[2].strip() if len(parts) > 2 else ""
if not subtitle or not story or not quote:
lines = [line.strip() for line in intro.splitlines() if line.strip()]
if len(lines) >= 3:
subtitle = lines[0]
quote_index = None
for index, line in enumerate(lines[1:], start=1):
if line.startswith("我说:") or line.startswith("他说:") or line.startswith("她说:"):
quote_index = index
break
if quote_index is not None:
story = "".join(lines[1:quote_index])
quote = "".join(lines[quote_index:])
else:
story = "".join(lines[1:-1])
quote = lines[-1]
if not subtitle or not story or not quote:
return None
if not subtitle.endswith("") and not subtitle.endswith("?"):
subtitle = subtitle.rstrip("。!!,:") + ""
if not (quote.startswith("我说:") or quote.startswith("他说:") or quote.startswith("她说:")):
quote = "我说:\"你回不回都没关系,我只是想让你的对话框里也有一点我的痕迹。\""
return {
"subtitle": subtitle,
"story": story,
"quote": quote,
}
# ============== DeepSeek AI 处理函数 ============== # ============== DeepSeek AI 处理函数 ==============
def ask_deepseek(prompt, system_prompt="你是一个有用的助手,用简洁温暖的语言回复。"): def ask_deepseek(prompt, system_prompt="你是一个有用的助手,用简洁温暖的语言回复。"):
@@ -413,23 +718,35 @@ def get_weather_summary(weather_info):
return summary if summary else weather_info return summary if summary else weather_info
def get_news_summary(news_info): def get_news_summary(news_info):
"""让DeepSeek总结新闻要点,找出关联,串联成自然的段落""" """让DeepSeek把国内外新闻统一整理成中文段落"""
if not news_info or "获取失败" in news_info: if not news_info or "获取失败" in news_info:
return news_info return news_info
prompt = f"""请阅读以下今日热搜新闻,试着找出它们之间的关联或共同话题,然后写成一段200字左右的自然段落,像朋友聊天一样分享这些新闻: prompt = f"""请阅读以下今日新闻,其中包含国内热点和国外新闻平台标题。请先理解并翻译其中的外文标题,再从中筛选出最值得聊的内容,写成适合早安邮件的三段式中文短评。
{news_info} {news_info}
要求: 要求:
- 找出新闻之间的联系,用自己的话串起来讲,不要念稿 - 固定写成三段,每段之间空一行,不要加小标题
- 讲的时候像跟朋友吐槽聊天那样,说到哪个新闻就直接说,别用引号包着 - 第一段:挑 2 到 3 条更偏宏观的新闻开场,可以是国际局势、科技、公共事件,负责把今天的大背景立起来
- 语气像在说相声或脱口秀,自然调侃,不要端着 - 第二段:转到更具体、更有人味的新闻,挑 2 到 3 条民生、人物、生活感更强的内容来写,让叙述落到具体的人和细节上
- 不要用列表或条目,要连贯的段落 - 第三段:不要再继续堆新新闻,改成基于前两段做一个自然的观察、总结或轻一点的评价,像看完今天新闻后的一个收束
- 字数200字左右 - 三段之间要有内在联系,像同一个人顺着一个念头慢慢讲下来,而不是三段独立的小作文
- 只返回这段新闻解读,不要多余的话""" - 只选真正值得聊的内容,不要为了凑数把每条新闻都塞进去
- 把外文标题自然翻成中文,不要保留英文原句,也不要写“某媒体说”“我给你翻译几条”这种转述腔
- 叙述顺序要自然,避免一会儿国外一会儿国内来回跳
- 语气像一个信息敏感、表达克制的朋友,在早餐时顺手聊几件今天值得知道的事
- 可以有一点感慨和观察,但不要故作幽默,不要玩梗过多,不要像相声稿
- 不要用列表、序号、小标题,不要分成“国外新闻这边”“国内新闻这边”这种板块汇报
- 不要出现“早上好啊”“对了”“先给你翻译几条”“把这些串起来看”这类口水开场或总结套话
- 总字数控制在 260 到 360 字,第三段宁可短一点,也要起到收束作用
- ⚠️重要:不要使用任何 markdown 符号,如**加粗**、*斜体*、- 列表等,只用纯文本
- 只返回最终正文,不要解释你的写作思路"""
summary = ask_deepseek(prompt, system_prompt="你是一个贴心的早安助手,用温暖自然的语言回复。") summary = ask_deepseek(
prompt,
system_prompt="你是一个擅长写晨间新闻短评的中文编辑,知道如何筛选重点、组织主线,并把信息写得自然、克制、有人味。你尤其擅长三段式写法:前两段给信息,最后一段做收束和观察。",
)
return summary if summary else news_info return summary if summary else news_info
# ============== 时间判断 ============== # ============== 时间判断 ==============
@@ -466,13 +783,47 @@ def get_cached_content():
print(f"[{datetime.datetime.now()}] 正在获取天气信息...") print(f"[{datetime.datetime.now()}] 正在获取天气信息...")
weather_info = get_weather() weather_info = get_weather()
_cached_weather_summary = get_weather_summary(weather_info) _cached_weather_summary = get_weather_summary(weather_info)
print(f"[{datetime.datetime.now()}] 天气摘要: {_cached_weather_summary[:50]}...") weather_ok = weather_info and "获取失败" not in weather_info
print(f"[{datetime.datetime.now()}] 天气 {'✓ 获取成功' if weather_ok else '✗ 获取失败'}: {_cached_weather_summary[:50] if _cached_weather_summary else '(无内容)'}...")
print(f"[{datetime.datetime.now()}] - wttr.in (成都): {'✓ 获取成功' if weather_ok else '✗ 获取失败'}")
if _cached_news_summary is None: if _cached_news_summary is None:
print(f"[{datetime.datetime.now()}] 正在获取新闻信息...") print(f"[{datetime.datetime.now()}] 正在获取新闻信息...")
news_info = get_hot_news() news_info, source_status = get_hot_news_with_status()
_cached_news_summary = get_news_summary(news_info) _cached_news_summary = get_news_summary(news_info)
print(f"[{datetime.datetime.now()}] 新闻摘要: {_cached_news_summary[:50]}...") news_ok = news_info and "获取失败" not in news_info
print(f"[{datetime.datetime.now()}] 新闻 {'✓ 获取成功' if news_ok else '✗ 获取失败'}")
# 如果有源状态(非缓存命中),打印各源状态
if source_status:
for src_name, ok, count in source_status:
status = f"✓ 获取成功 {count}" if ok else "✗ 获取失败"
print(f"[{datetime.datetime.now()}] - {src_name}: {status}")
else:
# 缓存命中,单独检查各源
domestic_titles = []
try:
domestic_titles = get_domestic_news_titles()
except Exception:
pass
print(f"[{datetime.datetime.now()}] - 国内热点 (百度): {'✓ 获取成功 ' + str(len(domestic_titles)) + '' if domestic_titles else '✗ 获取失败 (缓存命中)'}")
foreign_items = []
try:
foreign_items = get_foreign_news_titles()
except Exception:
pass
foreign_sources = {}
for source_name, _ in foreign_items:
foreign_sources[source_name] = foreign_sources.get(source_name, 0) + 1
for src in FOREIGN_NEWS_SOURCES:
src_name = src["name"]
count = foreign_sources.get(src_name, 0)
print(f"[{datetime.datetime.now()}] - {src_name}: {'✓ 获取成功 ' + str(count) + '' if count > 0 else '✗ 获取失败 (缓存命中)'}")
print(f"[{datetime.datetime.now()}] 新闻摘要: {_cached_news_summary[:50] if _cached_news_summary else '(无内容)'}...")
print(f"[{datetime.datetime.now()}] - 附言: 发送邮件时由 AI 即时生成")
return _cached_weather_summary, _cached_news_summary return _cached_weather_summary, _cached_news_summary