#!/usr/bin/env python3
"""News Minimalist RSS Server β serves RSS/HTML from scraped cache"""
import http.server
import socketserver
import json
import os
import time
from datetime import datetime, timezone
from collections import defaultdict
PORT = 1202
CACHE_FILE = '/root/news_cache.json'
def load_cache():
try:
with open(CACHE_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
return {'news': [], 'date': 'unknown', 'count': 0}
def generate_rss(data):
"""Generate RSS 2.0 XML with Atom self-link."""
news = data.get('news', [])
cat = data.get('category', 'all')
score_range = data.get('score_range', '0-10')
updated = data.get('updated', '')
cat_label = 'All Categories' if cat == 'all' else cat.title()
items_xml = ''
for n in news[:50]:
title = n.get('title', '')
title_zh = n.get('title_zh', '')
link = n.get('link', BASE_URL)
score = n.get('score')
source = n.get('source', '')
summary = n.get('summary', '')
prefix = f'[{score}] ' if score is not None else ''
desc = f'
Significance: {score}/10
' if score is not None else ''
if title_zh:
desc += f'π¨π³ δΈζ: {title_zh}
'
if summary:
desc += f'AI Analysis: {summary}
'
if source:
desc += f'Source: {source}
'
items_xml += f''' -
{prefix}{title}
{link}
{link}
{source or 'News Minimalist'}
'''
return f'''
News Minimalist β {cat_label} [{score_range}]
{BASE_URL}
AI-curated significant news. Category: {cat_label}, Score: {score_range}. Scored 0-10 by Gemini.
en
{updated}
{items_xml}
'''
def generate_html(data):
"""Generate beautiful HTML page."""
news = data.get('news', [])
cache_date = data.get('date', 'unknown')
updated = data.get('updated', '')
cat = data.get('category', 'all')
score_range = data.get('score_range', '0-10')
if not news:
return '''
π No articles cached yet
Cache will be populated on next scrape cycle.
'''
# Group by score tiers
hot = [n for n in news if n.get('score') and n['score'] >= 6.5]
notable = [n for n in news if n.get('score') and 6.0 <= n['score'] < 6.5]
rest = [n for n in news if n.get('score') and n['score'] < 6.0] + [n for n in news if n.get('score') is None]
def render_items(items, color, badge):
html = ''
for n in items:
score = n.get('score')
title = n.get('title', '')
title_zh = n.get('title_zh', '')
link = n.get('link', '')
source = n.get('source', '')
summary = n.get('summary', '')
display_title = title_zh or title
subtitle = title if title_zh else ''
html += f'''
{badge} {score}
{display_title}
{f'
{title}
' if subtitle else ''}
{f'
{summary}
' if summary else ''}
{source or 'newsminimalist.com'}
'''
return html
body = ''
if hot:
body += 'π₯ Trending (6.5+) ' + render_items(hot, '#ef4444', 'π₯')
if notable:
body += 'β Notable (6.0-6.4) ' + render_items(notable, '#3b82f6', 'β')
if rest:
body += 'π° All Articles ' + render_items(rest, '#22c55e', 'π°')
return f'''
News Minimalist β RSS Feed
{body}
'''
BASE_URL = 'https://www.newsminimalist.com'
class Handler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if self.path in ['/health', '/ping']:
data = load_cache()
age = time.time() - os.path.getmtime(CACHE_FILE) if os.path.exists(CACHE_FILE) else 99999
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({
'status': 'ok',
'cache_age_hours': round(age / 3600, 1),
'article_count': data.get('count', 0),
'date': data.get('date', 'unknown'),
}).encode())
return
if self.path.startswith('/rss') or self.path == '/feed':
data = load_cache()
rss = generate_rss(data)
self.send_response(200)
self.send_header('Content-Type', 'application/rss+xml; charset=utf-8')
self.send_header('Cache-Control', 'public, max-age=14400')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(rss.encode('utf-8'))
return
if self.path in ['/', '/home', '/index.html']:
data = load_cache()
html = generate_html(data)
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(html.encode('utf-8'))
return
self.send_response(404)
self.end_headers()
self.wfile.write(b'Not Found')
if __name__ == '__main__':
print(f'News Minimalist RSS on :{PORT} β scraping newsminimalist.com')
with socketserver.TCPServer(('', PORT), Handler) as httpd:
httpd.serve_forever()