"""Opt-in daily digest — a finite, calm morning email of today's brief. Ritual, not capture: no streaks, no "you missed", no urgency, no unread counts. One send per opted-in user per day, gated to a morning window in the site timezone and deduped via digest_sends. On a thin day it skips quietly rather than padding. Reuses the existing SMTP/email pipeline. """ from __future__ import annotations import os import secrets import sqlite3 from datetime import datetime from html import escape from . import email_send from .localtime import local_now, local_today from .paywall import is_paywalled DIGEST_HOUR = int(os.environ.get("GOODNEWS_DIGEST_HOUR", "7")) DIGEST_WINDOW_HOURS = 4 # send between DIGEST_HOUR and +4h, site-local MIN_ITEMS = 4 # below this, skip the day rather than pad def _base_url() -> str: return os.environ.get("GOODNEWS_PUBLIC_BASE_URL", "https://upbeatbytes.com").rstrip("/") def digest_items(conn: sqlite3.Connection, brief_date: str, limit: int = 7) -> list[dict]: """The brief's items with the bits a calm email needs (visible sources only).""" rows = conn.execute( """ SELECT a.id, a.title, a.canonical_url, s.name AS source, sc.reason_text, (SELECT summary FROM article_summaries WHERE article_id = a.id) AS summary FROM daily_briefs b JOIN daily_brief_items bi ON bi.brief_id = b.id JOIN articles a ON a.id = bi.article_id JOIN sources s ON s.id = a.source_id LEFT JOIN article_scores sc ON sc.article_id = a.id WHERE b.brief_date = ? AND s.content_visible = 1 ORDER BY bi.rank LIMIT ? """, (brief_date, limit), ).fetchall() items = [] for r in rows: d = dict(r) d["paywalled"] = is_paywalled(d["canonical_url"]) items.append(d) return items def _weekday(brief_date: str) -> str: try: return datetime.strptime(brief_date, "%Y-%m-%d").strftime("%A") except (ValueError, TypeError): return "today" def build_digest(items: list[dict], brief_date: str, unsub_url: str, base: str | None = None) -> tuple[str, str, str]: """Return (subject, text, html) for the digest — calm and dated, no urgency.""" base = base or _base_url() n = len(items) weekday = _weekday(brief_date) subject = f"{weekday}'s Upbeat Bytes · {n} calm read{'' if n == 1 else 's'}" if weekday == "today": subject = f"Today's Upbeat Bytes · {n} calm reads" text_lines = [f"Today's good news — {n} calm reads.\n"] for it in items: text_lines.append(f"• {it['title']} ({it['source']})") if it.get("summary"): text_lines.append(f" {it['summary']}") text_lines.append(f" Read: {base}/a/{it['id']}") text_lines.append(f" Source: {it['canonical_url']}\n") text_lines.append("That's today's good news. You're caught up — see you tomorrow.") text_lines.append(f"\nTo stop these emails: {unsub_url}") text = "\n".join(text_lines) blocks = [] for it in items: summary = f'
{n} calm reads for {escape(weekday)}
' + "".join(blocks) + 'That’s today’s good news. ' 'You’re caught up — see you tomorrow.
' f'You’re getting this because you turned on ' f'the daily digest. Unsubscribe.
' '