Rss2Newsletter/src/rss2newsletter/__main__.py

162 lines
4.7 KiB
Python
Raw Normal View History

2025-08-04 15:56:02 -04:00
"""Main entry point for rss2newsletter."""
import sys
import argparse
import logging
from typing import List, Dict
from .config import get_config_from_env, setup_logging
from .rss_fetcher import get_todays_articles
from .ollama_client import create_ollama_client, summarize_articles
from .html_generator import generate_newsletter_html, save_newsletter_html
logger = logging.getLogger(__name__)
def create_argument_parser() -> argparse.ArgumentParser:
"""Create command line argument parser."""
parser = argparse.ArgumentParser(
description="Generate HTML newsletter from RSS feed using Ollama AI summaries",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python -m rss2newsletter https://feeds.example.com/rss
python -m rss2newsletter https://blog.example.com/feed.xml --output my_newsletter.html
python -m rss2newsletter https://news.example.com/rss --model llama3.1
""",
)
parser.add_argument("rss_url", help="RSS feed URL to process")
parser.add_argument(
"--output",
"-o",
default=None,
help="Output HTML filename (default: newsletter.html)",
)
parser.add_argument(
"--model",
"-m",
default=None,
help="Ollama model to use for summaries (default: llama3.2)",
)
parser.add_argument(
"--ollama-url",
default=None,
help="Ollama server URL (default: http://localhost:11434)",
)
parser.add_argument(
"--title", "-t", default=None, help="Newsletter title (default: RSS Newsletter)"
)
parser.add_argument(
"--verbose", "-v", action="store_true", help="Enable verbose logging"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Fetch articles but don't generate summaries or save HTML",
)
return parser
def merge_config_with_args(config: Dict, args: argparse.Namespace) -> Dict:
"""Merge configuration with command line arguments."""
if args.output:
config["output"]["filename"] = args.output
if args.model:
config["ollama"]["model"] = args.model
if args.ollama_url:
config["ollama"]["base_url"] = args.ollama_url
if args.title:
config["output"]["feed_title"] = args.title
if args.verbose:
config["logging"]["level"] = "DEBUG"
return config
def process_rss_to_newsletter(rss_url: str, config: Dict, dry_run: bool = False) -> str:
"""Main processing pipeline for RSS to newsletter conversion."""
logger.info(f"Starting RSS newsletter generation for: {rss_url}")
# Step 1: Fetch today's articles
logger.info("Fetching articles from RSS feed...")
articles = get_todays_articles(rss_url)
if not articles:
logger.warning("No articles found for today")
return generate_newsletter_html([], config["output"]["feed_title"])
logger.info(f"Found {len(articles)} articles from today")
if dry_run:
logger.info("Dry run mode - stopping before summarization")
for i, article in enumerate(articles, 1):
logger.info(f"Article {i}: {article['title']}")
return ""
# Step 2: Create Ollama client and summarize articles
logger.info("Generating AI summaries...")
ollama_client = create_ollama_client(config["ollama"]["base_url"])
summarized_articles = summarize_articles(
ollama_client, articles, config["ollama"]["model"]
)
# Step 3: Generate HTML newsletter
logger.info("Generating HTML newsletter...")
html_content = generate_newsletter_html(
summarized_articles, config["output"]["feed_title"]
)
return html_content
def main() -> int:
"""Main entry point."""
parser = create_argument_parser()
args = parser.parse_args()
# Load and merge configuration
config = get_config_from_env()
config = merge_config_with_args(config, args)
# Setup logging
setup_logging(config)
try:
# Process RSS feed to newsletter
html_content = process_rss_to_newsletter(args.rss_url, config, args.dry_run)
if not args.dry_run and html_content:
# Save HTML file
output_filename = save_newsletter_html(
html_content, config["output"]["filename"]
)
print(f"Newsletter saved to: {output_filename}")
return 0
except KeyboardInterrupt:
logger.info("Process interrupted by user")
return 1
except Exception as e:
logger.error(f"Error generating newsletter: {e}")
if config["logging"]["level"] == "DEBUG":
logger.exception("Full error details:")
return 1
if __name__ == "__main__":
sys.exit(main())