send_digest.py
This commit is contained in:
177
scripts/news_digest/send_digest.py
Executable file
177
scripts/news_digest/send_digest.py
Executable file
@@ -0,0 +1,177 @@
|
|||||||
|
#!/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.home() / ".openclaw/workspace/scripts/news_digest"
|
||||||
|
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"),
|
||||||
|
"-v"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Run fetch (this saves articles to DB and generates summaries)
|
||||||
|
result = subprocess.run(fetch_cmd, capture_output=True, text=True, cwd=SCRIPT_DIR)
|
||||||
|
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"
|
||||||
|
]
|
||||||
|
|
||||||
|
result = subprocess.run(query_cmd, capture_output=True, text=True, cwd=SCRIPT_DIR)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"Query failed: {result.stderr}", file=sys.stderr)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Parse JSON - find the JSON array in stdout
|
||||||
|
stdout = result.stdout.strip()
|
||||||
|
|
||||||
|
# Find where JSON array starts and ends
|
||||||
|
start_idx = stdout.find('[')
|
||||||
|
end_idx = stdout.rfind(']')
|
||||||
|
|
||||||
|
if start_idx == -1 or end_idx == -1 or start_idx >= end_idx:
|
||||||
|
print("No valid JSON array found", file=sys.stderr)
|
||||||
|
print(f"Debug output: {stdout[:500]}", file=sys.stderr)
|
||||||
|
return []
|
||||||
|
|
||||||
|
json_output = stdout[start_idx:end_idx+1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
articles = json.loads(json_output)
|
||||||
|
return articles
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"Failed to parse JSON: {e}", file=sys.stderr)
|
||||||
|
print(f"Raw output: {json_output[: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']
|
||||||
|
|
||||||
|
for cat in category_order:
|
||||||
|
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 = [
|
||||||
|
str(Path.home() / ".local/bin/himalaya"),
|
||||||
|
"message", "send",
|
||||||
|
"-a", "Youlu" # Account name from config.toml
|
||||||
|
]
|
||||||
|
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, input=message)
|
||||||
|
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()
|
||||||
Reference in New Issue
Block a user