#!/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

πŸ€– News Minimalist

AI-curated news Β· {cat} Β· Score {score_range} Β· Cache: {cache_date}
{len(news)} articles
πŸ“‘ 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()