#!/usr/bin/env python3 """ daily-feed-summary.py — Collect RSS feeds, summarize top articles, deliver digest. Feeds: - XDA Developers: https://www.xda-developers.com/feed/ - MakeUseOf: https://www.makeuseof.com/feed/ - VirtualizationHowTo: https://www.virtualizationhowto.com/feed/ - Top Gear: No RSS — scraped from front page - 9to5Mac: https://9to5mac.com/feed/ Output: Markdown summary with top 10-15 articles across all feeds. """ import feedparser, json, sys, os, re, subprocess from datetime import datetime, timezone, timedelta from email.mime.text import MIMEText import smtplib import urllib.request from html.parser import HTMLParser FEEDS = { "XDA Developers": "https://www.xda-developers.com/feed/", "MakeUseOf": "https://www.makeuseof.com/feed/", "Virtualization HowTo": "https://www.virtualizationhowto.com/feed/", "9to5Mac": "https://9to5mac.com/feed/", "Gizmodo": "https://gizmodo.com/feed/", "How-To Geek": "https://www.howtogeek.com/feed/", } # Top Gear — no RSS, try scraping car-news section TOPGEAR_URL = "https://www.topgear.com/car-news" class HHLinkParser(HTMLParser): """Parse HotHardware /news page for article links.""" def __init__(self): super().__init__() self.links = [] self._in_link = False self._href = None self._text = "" def handle_starttag(self, tag, attrs): attrs = dict(attrs) if tag == 'a' and 'href' in attrs: href = attrs['href'] if href.startswith('/news/') and len(href) > 7: self._in_link = True self._href = href self._text = "" def handle_data(self, data): if self._in_link: self._text += data.strip() def handle_endtag(self, tag): if self._in_link and tag == 'a': self._in_link = False txt = self._text.strip() if txt and len(txt) > 10: self.links.append((txt, self._href)) def fetch_hothardware(): """Scrape HotHardware news page for article links.""" try: req = urllib.request.Request( "https://hothardware.com/news", headers={'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'} ) with urllib.request.urlopen(req, timeout=15) as resp: html = resp.read().decode('utf-8', errors='replace') parser = HHLinkParser() parser.feed(html) seen = set() articles = [] for title, href in parser.links: if href not in seen: seen.add(href) url = f"https://hothardware.com{href}" if href.startswith('/') else href articles.append((title, url)) return articles[:10] except Exception as e: return [("HotHardware scrape failed", f"Error: {e}")] class TGLinkParser(HTMLParser): def __init__(self): super().__init__() self.links = [] self._capture = False self._href = None self._depth = 0 def handle_starttag(self, tag, attrs): attrs = dict(attrs) if tag == 'a' and 'href' in attrs: href = attrs['href'] if href.startswith('/car-news/') and not href.startswith('/car-news/?'): title = attrs.get('title', '') or '' text = '' if title: self.links.append((href, title)) self._href = href def fetch_topgear(): """Scrape Top Gear front page for article links.""" try: req = urllib.request.Request( TOPGEAR_URL, headers={'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'} ) with urllib.request.urlopen(req, timeout=15) as resp: html = resp.read().decode('utf-8', errors='replace') parser = TGLinkParser() parser.feed(html) # Deduplicate by URL seen = set() articles = [] for href, title in parser.links: if href not in seen: seen.add(href) url = f"https://www.topgear.com{href}" if href.startswith('/') else href articles.append((title, url)) return articles[:15] except Exception as e: return [("Top Gear scrape failed", f"Error: {e}")] def fetch_feed(url, limit=10): """Parse RSS feed, return list of (title, link, summary).""" try: feed = feedparser.parse(url) entries = [] for entry in feed.entries[:limit]: title = entry.get('title', 'No title') link = entry.get('link', '') # Get summary/description summary = '' if 'summary' in entry: summary = entry.summary elif 'description' in entry: summary = entry.description # Strip HTML summary = re.sub(r'<[^>]+>', '', summary)[:300] entries.append((title, link, summary)) return entries except Exception as e: return [(f"Feed error: {e}", "", "")] def build_digest(): """Collect all feeds and build markdown digest.""" lines = [] now = datetime.now(timezone.utc) eastern = now - timedelta(hours=4) # approximate ET lines.append(f"# 📰 Daily Tech Digest — {eastern.strftime('%A, %B %d, %Y')}") lines.append("") all_articles = [] # RSS feeds for name, url in FEEDS.items(): entries = fetch_feed(url, 10) lines.append(f"## {name}") lines.append("") for title, link, summary in entries: if link: lines.append(f"- **[{title}]({link})**") else: lines.append(f"- **{title}**") if summary: lines.append(f" _{summary}_") lines.append("") all_articles.extend(entries) # Top Gear lines.append("## Top Gear") lines.append("") tg = fetch_topgear() for title, url in tg: if url.startswith("https://"): lines.append(f"- **[{title}]({url})**") else: lines.append(f"- *{title}*") lines.append("") all_articles.extend([(t, u, "") for t, u in tg]) # HotHardware (scraped — no RSS) lines.append("## HotHardware") lines.append("") hh = fetch_hothardware() for title, url in hh: if url.startswith("https://"): lines.append(f"- **[{title}]({url})**") else: lines.append(f"- *{title}*") lines.append("") all_articles.extend([(t, u, "") for t, u in hh]) lines.append("---") lines.append(f"*{len(all_articles)} articles from {len(FEEDS) + 2} sources*") return "\n".join(lines) def send_email(body): """Send digest via SMTP to g@germainebrown.com as HTML.""" sender = "g@germainebrown.com" pwd_file = "/root/.config/himalaya/g-germainebrown.pass" with open(pwd_file) as f: password = f.read().strip() # Convert markdown links [text](url) to HTML text import re html_body = re.sub( r'\[([^\]]+)\]\(([^)]+)\)', r'\1', body ) # Convert markdown bold **text** to text html_body = re.sub(r'\*\*([^*]+)\*\*', r'\1', html_body) # Convert markdown italic _text_ to text html_body = re.sub(r'_([^_]+)_', r'\1', html_body) # Convert ## headers to

html_body = re.sub(r'^## (.+)$', r'

\1

', html_body, flags=re.MULTILINE) # Convert # header to

html_body = re.sub(r'^# (.+)$', r'

\1

', html_body, flags=re.MULTILINE) # Convert --- to
html_body = re.sub(r'^---+$', r'
', html_body, flags=re.MULTILINE) # Wrap in proper HTML html = f'''
{body}
''' from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart msg = MIMEMultipart('alternative') msg['Subject'] = f"Daily Tech Digest — {datetime.now().strftime('%b %d')}" msg['From'] = sender msg['To'] = sender msg.attach(MIMEText(body, 'plain', 'utf-8')) msg.attach(MIMEText(html, 'html', 'utf-8')) with smtplib.SMTP('mail.germainebrown.com', 2525) as s: s.starttls() s.login(sender, password) s.send_message(msg) print(f"Digest sent to {sender}") if __name__ == "__main__": digest = build_digest() # Output to stdout (captured by cron) and also email print(digest) # If run as standalone, also email if len(sys.argv) > 1 and sys.argv[1] == '--email': send_email(digest)