#!/usr/bin/env python3 """ Script to get the Wikipedia page for the saint of the day based on French saints days calendar. Optionally posts to Mastodon with the --post flag. """ import argparse import csv import os from datetime import datetime from pathlib import Path import urllib.parse from dotenv import load_dotenv from mastodon import Mastodon # Entries that are holidays, feasts, or events rather than actual saints/people NON_PERSON_ENTRIES = { "Epiphany", "Holy Name of Jesus", "Conversion of St. Paul", "Presentation of the Lord", "Our Lady of Lourdes", "Chair of St. Peter", "Annunciation", "Our Lady of Fatima", "Visitation of the Virgin Mary", "Nativity of John the Baptist", "First Martyrs of Rome", "Our Lady of Mount Carmel", "Dedication of St. Mary Major", "Transfiguration", "Assumption of Mary", "Queenship of Mary", "Beheading of John the Baptist", "Nativity of Mary", "Holy Name of Mary", "Exaltation of the Holy Cross", "Our Lady of Sorrows", "Guardian Angels", "Our Lady of the Rosary", "All Saints", "All Souls", "Dedication of the Lateran Basilica", "Dedication of the Basilicas of Peter and Paul", "Presentation of Mary", "Immaculate Conception", "Our Lady of Guadalupe", "Holy Innocents", } def get_standardized_name(saint_name: str) -> str: """Get the standardized name used for Wikipedia URLs and filenames.""" return saint_name.replace(" ", "_") def get_wikipedia_url(saint_name: str) -> str: """Generate Wikipedia URL for a saint name.""" wiki_name = get_standardized_name(saint_name) encoded_name = urllib.parse.quote(wiki_name, safe="_") return f"https://en.wikipedia.org/wiki/{encoded_name}" def get_saint_picture_path(saint_name: str) -> Path | None: """Find the picture file for a saint, if it exists.""" pics_dir = Path(__file__).parent / "pics" standardized_name = get_standardized_name(saint_name) # Check for common image extensions for ext in [".jpg", ".jpeg", ".png", ".gif", ".webp"]: pic_path = pics_dir / f"{standardized_name}{ext}" if pic_path.exists(): return pic_path return None def get_saints_for_today() -> list[dict]: """ Read the CSV and return saints matching today's date. Returns a list of dicts with saint info and Wikipedia URLs. """ today = datetime.now() today_str = today.strftime("%B %-d") # e.g., "January 22" csv_path = Path(__file__).parent / "saints_days_france.csv" saints = [] with open(csv_path, "r", encoding="utf-8") as f: reader = csv.DictReader(f) for row in reader: if row["Date"] == today_str: saint_name = row["Saint Name"] # Skip non-person entries (holidays, feasts, events) if saint_name in NON_PERSON_ENTRIES: continue saints.append({ "name": saint_name, "french_name": row["French Name"], "notes": row["Notes"], "wikipedia_url": get_wikipedia_url(saint_name), "picture_path": get_saint_picture_path(saint_name), }) return saints def format_mastodon_post(saint: dict) -> str: """Format a saint's information as a Mastodon post.""" today = datetime.now() date_str = today.strftime("%B %d") lines = [ f"{date_str}: the Feast of Saint {saint['name']}", ] lines.extend([ saint["wikipedia_url"], "", "#saints #hagiography", ]) return "\n".join(lines) def post_to_mastodon(saint: dict) -> dict: """Post a saint to Mastodon. Returns the status dict from the API.""" # Load environment variables load_dotenv(Path(__file__).parent / ".env") instance_url = os.getenv("MASTODON_INSTANCE_URL") access_token = os.getenv("MASTODON_ACCESS_TOKEN") if not instance_url or not access_token: raise ValueError( "Missing Mastodon credentials. Please set MASTODON_INSTANCE_URL and " "MASTODON_ACCESS_TOKEN in .env file." ) if access_token == "your_access_token_here": raise ValueError( "Please update the MASTODON_ACCESS_TOKEN in .env with your actual token." ) # Initialize Mastodon client mastodon = Mastodon( access_token=access_token, api_base_url=instance_url, ) # Format the post post_text = format_mastodon_post(saint) # Upload image if available media_ids = None if saint["picture_path"]: print(f" Uploading image: {saint['picture_path'].name}") media = mastodon.media_post( str(saint["picture_path"]), description=f"Image of {saint['name']}", ) media_ids = [media["id"]] # Post the status status = mastodon.status_post( post_text, media_ids=media_ids, visibility="public", ) return status def main(): parser = argparse.ArgumentParser( description="Get the saint of the day and optionally post to Mastodon." ) parser.add_argument( "--post", action="store_true", help="Post to Mastodon (requires credentials in .env)", ) args = parser.parse_args() saints = get_saints_for_today() if not saints: print("No saint's day today (or today is a holiday/feast day).") return for saint in saints: print(f"Saint: {saint['name']}") print(f"French: {saint['french_name']}") if saint["notes"]: print(f"Notes: {saint['notes']}") print(f"Wikipedia: {saint['wikipedia_url']}") if saint["picture_path"]: print(f"Picture: {saint['picture_path']}") else: print("Picture: (none available)") if args.post: if not saint["picture_path"]: print("\nSkipping Mastodon post (no picture available)") else: print("\nPosting to Mastodon...") try: status = post_to_mastodon(saint) print(f" Posted! URL: {status['url']}") except Exception as e: print(f" Error posting: {e}") print() if __name__ == "__main__": main()