176 lines
5.0 KiB
Python
Executable File
176 lines
5.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Daily News Digest - Fetch news and send email summary
|
|
Runs news_digest, formats output, sends via himalaya
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
# Config
|
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
DB_PATH = SCRIPT_DIR / "news_digest.db"
|
|
CONFIG_PATH = SCRIPT_DIR / "config.json"
|
|
|
|
# Email config
|
|
EMAIL_TO = "lu@luyx.org"
|
|
EMAIL_FROM = "youlu@luyanxin.com"
|
|
|
|
def run_news_digest():
|
|
"""Run news_digest and get articles with summaries"""
|
|
# Fetch new articles using run.sh (handles uv dependencies)
|
|
fetch_cmd = [
|
|
str(SCRIPT_DIR / "run.sh"),
|
|
]
|
|
|
|
# Run fetch (this saves articles to DB and generates summaries)
|
|
try:
|
|
result = subprocess.run(fetch_cmd, capture_output=True, text=True, cwd=SCRIPT_DIR, timeout=300)
|
|
except subprocess.TimeoutExpired:
|
|
print("Fetch timed out after 300s", file=sys.stderr)
|
|
sys.exit(1)
|
|
if result.returncode != 0:
|
|
print(f"Fetch failed: {result.stderr}", file=sys.stderr)
|
|
return None
|
|
|
|
# Now query with summary field
|
|
query_cmd = [
|
|
str(SCRIPT_DIR / "run.sh"),
|
|
"--no-fetch",
|
|
"-f", "id,title,url,summary,feed_name,category"
|
|
]
|
|
|
|
try:
|
|
result = subprocess.run(query_cmd, capture_output=True, text=True, cwd=SCRIPT_DIR, timeout=30)
|
|
except subprocess.TimeoutExpired:
|
|
print("Query timed out after 30s", file=sys.stderr)
|
|
sys.exit(1)
|
|
if result.returncode != 0:
|
|
print(f"Query failed: {result.stderr}", file=sys.stderr)
|
|
return None
|
|
|
|
# Parse JSON output
|
|
try:
|
|
articles = json.loads(result.stdout)
|
|
return articles
|
|
except json.JSONDecodeError as e:
|
|
print(f"Failed to parse JSON: {e}", file=sys.stderr)
|
|
print(f"Raw output: {result.stdout[:500]}", file=sys.stderr)
|
|
return []
|
|
|
|
def format_email(articles):
|
|
"""Format articles into email body"""
|
|
if not articles:
|
|
return None
|
|
|
|
today = datetime.now().strftime('%Y-%m-%d')
|
|
|
|
lines = [
|
|
f"📰 每日新闻摘要 ({today})",
|
|
"=" * 50,
|
|
""
|
|
]
|
|
|
|
# Group by category
|
|
by_category = {}
|
|
for a in articles:
|
|
cat = a.get('category', 'Other')
|
|
if cat not in by_category:
|
|
by_category[cat] = []
|
|
by_category[cat].append(a)
|
|
|
|
# Sort categories
|
|
category_order = ['Tech', 'China', 'World', 'Other']
|
|
ordered_cats = category_order + [c for c in by_category if c not in category_order]
|
|
|
|
for cat in ordered_cats:
|
|
if cat not in by_category:
|
|
continue
|
|
|
|
cat_name = {
|
|
'Tech': '💻 科技',
|
|
'China': '🇨🇳 国内',
|
|
'World': '🌍 国际',
|
|
'Other': '📄 其他'
|
|
}.get(cat, cat)
|
|
|
|
lines.append(f"\n{cat_name}")
|
|
lines.append("-" * 30)
|
|
|
|
for article in by_category[cat]:
|
|
title = article.get('title', '无标题')
|
|
url = article.get('url', '')
|
|
summary = article.get('summary', '')
|
|
source = article.get('feed_name', '未知来源')
|
|
|
|
lines.append(f"\n• {title}")
|
|
if summary:
|
|
# Clean up summary (remove excessive newlines, preserve content)
|
|
summary_clean = summary.replace('\n', ' ').strip()
|
|
lines.append(f" {summary_clean}")
|
|
lines.append(f" 来源: {source} | {url}")
|
|
|
|
lines.append("\n" + "=" * 50)
|
|
lines.append("由 RSS News Digest 自动生成")
|
|
|
|
return '\n'.join(lines)
|
|
|
|
def send_email(subject, body):
|
|
"""Send email via himalaya"""
|
|
# Construct raw email message with headers
|
|
message = f"""From: {EMAIL_FROM}
|
|
To: {EMAIL_TO}
|
|
Subject: {subject}
|
|
Content-Type: text/plain; charset=utf-8
|
|
|
|
{body}"""
|
|
|
|
cmd = [
|
|
"himalaya",
|
|
"message", "send",
|
|
"-a", "Youlu" # Account name from config.toml
|
|
]
|
|
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, input=message, timeout=30)
|
|
except subprocess.TimeoutExpired:
|
|
return False, "himalaya timed out after 30s"
|
|
return result.returncode == 0, result.stderr
|
|
|
|
def main():
|
|
# Fetch and get articles
|
|
articles = run_news_digest()
|
|
|
|
if articles is None:
|
|
# Fetch failed, don't send email
|
|
sys.exit(1)
|
|
|
|
if not articles:
|
|
# No new articles, send a "no news" email or skip
|
|
# Let's skip sending email if no articles
|
|
print("No articles to send")
|
|
return
|
|
|
|
# Format email
|
|
body = format_email(articles)
|
|
if not body:
|
|
print("Failed to format email")
|
|
sys.exit(1)
|
|
|
|
# Send email
|
|
today = datetime.now().strftime('%Y-%m-%d')
|
|
subject = f"📰 每日新闻摘要 - {today}"
|
|
|
|
success, error = send_email(subject, body)
|
|
if success:
|
|
print(f"Email sent successfully: {len(articles)} articles")
|
|
else:
|
|
print(f"Failed to send email: {error}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|