#!/usr/bin/env python3 """ Newspaper Magazine Generator Proof of concept: generates a magazine-style PDF from RSS feeds """ import re import subprocess from concurrent.futures import ThreadPoolExecutor from pathlib import Path SCRIPT_DIR = Path(__file__).parent LLM_AGGREGATOR = SCRIPT_DIR.parent / "llm_aggregator" / "llm_aggregator" OUTPUT_DIR = SCRIPT_DIR / "output" TEX_FILE = OUTPUT_DIR / "magazine.tex" PDF_FILE = OUTPUT_DIR / "magazine.pdf" # Strip think tags STRIP_TAG_START = "" STRIP_TAG_END = "" STRIP_TAG_REPLACEMENT = "" # Base prompt ending PROMPT_ENDING = r"""Output in LaTeX format suitable for an editorial section. Use asterisk section headers (\section*{...}) to prevent numbering. Use the correct 1st January 2001 date format. Use English; not American dialect (color -> colour, formalize -> formalise, etc.). Use ``...'' for quotation marks, \emph{...} for italics and \textbf{...} for bold text. Do not attempt citations.""" CATEGORIES = [ { "name": "LINUX", "icon": r"\faLinux", "feed": SCRIPT_DIR / "feeds" / "linux.txt", "prompt": f"""Summarise the latest Linux kernel discussions and technical developments from the LKML feed. Focus on significant patches, kernel updates, and developer discussions. {PROMPT_ENDING}""", }, { "name": "FREE SOFTWARE", "icon": r"\faCogs", "feed": SCRIPT_DIR / "feeds" / "free_software.txt", "prompt": f"""Summarise the latest news from the free and open source software world. Cover new releases, project updates, and community news. {PROMPT_ENDING}""", }, { "name": r"AI \& SCIENCE", "icon": r"\faFlask", "feed": SCRIPT_DIR / "feeds" / "ai_science.txt", "prompt": f"""Summarise the latest news regarding science and AI. Pay attention to esoteric and difficult topics, rather than what is likely to be easy pop-science without substance. {PROMPT_ENDING}""", }, { "name": "POLAND", "icon": r"\faFlag", "feed": SCRIPT_DIR / "feeds" / "poland.txt", "prompt": f"""Summarise today's top news from Poland covering politics, finance, and current events. Include major stories and their significance. {PROMPT_ENDING}""", }, { "name": "ESOTERIC", "icon": r"\faUserSecret", "feed": SCRIPT_DIR / "feeds" / "esoteric.txt", "prompt": f"""Summarise news from sources that are not mainstream, regarding topics not often discussed in the public square. The readers enjoy this section, so maintain a supportive tone, rather than accusative. Seek out topics that specifically challenge popular preconceptions. Do not write disclaimers or observations. {PROMPT_ENDING}""", }, ] def strip_tags(content: str) -> str: """Strip content between (and including) two tags. Leaves placeholder.""" if STRIP_TAG_START and STRIP_TAG_END: pattern = f"{re.escape(STRIP_TAG_START)}.*?{re.escape(STRIP_TAG_END)}" return re.sub(pattern, STRIP_TAG_REPLACEMENT, content, flags=re.DOTALL) return content def run_llm_aggregator(category: dict) -> dict: """Run llm_aggregator for a category and return the result.""" output_file = OUTPUT_DIR / f"{category['name'].lower()}.txt" cmd = [ str(LLM_AGGREGATOR), "-f", str(category["feed"]), "-p", category["prompt"], "-P", "--output-file", str(output_file), "-v", ] print(f"📰 Processing: {category['name']}") result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: print(f" ❌ Error: {result.stderr}") raise RuntimeError(f"llm_aggregator failed for {category['name']}") content = output_file.read_text() content = strip_tags(content) print(f" ✓ Done: {category['name']}") return {"name": category["name"], "icon": category["icon"], "content": content} def generate_latex(content_files: list[dict]) -> None: """Generate the LaTeX document.""" latex = r"""\documentclass[12pt,a4paper]{article} \usepackage{fancyhdr, fontawesome, fontspec, graphicx, geometry, sectsty, setspace, parskip} \usepackage[UKenglish]{isodate} \setmainfont[Ligatures=TeX]{Liberation Sans} \geometry{margin=2.5cm} \sectionfont{\centering\bfseries\scshape} \subsectionfont{\large\bfseries} \pagestyle{fancy} \fancyhf{} \fancyhead[C]{\today} \fancyfoot[C]{\thepage} \begin{document} \onehalfspacing \vspace*{2cm} {\LARGE\bfseries The Daily Digest}\\[0.5em] {\Large \today}\\[2em] \hrule \vspace{2em} """ for item in content_files: name = item["name"] icon = item["icon"] content = item["content"] latex += f"{{\\centering\\fontsize{{64}}{{12}}{icon}\\par}}\n" latex += f"\\section*{{{name}}}\n" latex += content latex += "\n\n\\vspace{1.5em}\n\n" latex += r"\end{document}" with open(TEX_FILE, "w") as f: f.write(latex) print(f" Created: {TEX_FILE}") def compile_pdf() -> bool: """Compile LaTeX to PDF.""" print() print("🔄 Compiling PDF...") result = subprocess.run( ["xelatex", "-interaction=batchmode", "magazine.tex"], cwd=OUTPUT_DIR, capture_output=True, ) return PDF_FILE.exists() def main(): OUTPUT_DIR.mkdir(parents=True, exist_ok=True) print("🗞️ Newspaper Magazine Generator") print("===================================") print() # Run all feeds through llm_aggregator in parallel print("📰 Processing feeds in parallel...") print() with ThreadPoolExecutor(max_workers=8) as executor: content_files = list(executor.map(run_llm_aggregator, CATEGORIES)) print() print("📄 Generating LaTeX magazine...") print() generate_latex(content_files) print() if compile_pdf(): print("✅ Magazine generated successfully!") print() print(f" PDF: {PDF_FILE}") print() print("📖 Opening PDF viewer...") subprocess.run(["xdg-open", str(PDF_FILE)]) else: print("❌ PDF compilation failed") return 1 return 0 if __name__ == "__main__": exit(main())