#!/usr/bin/env python3 """ Add a new Beer Call entry to the yearly log. Usage: python scripts/new_beercall.py # Interactive, defaults to last Thursday python scripts/new_beercall.py --date 2024-12-19 # Specific date python scripts/new_beercall.py --list # Just show recent Untappd checkins Fetches recent checkins from Untappd RSS to help identify venue. Beer calls are typically on Thursdays, except for special events like Beer Crawl (around New Year's) or when holidays fall on Thursday. """ import argparse import json import re import sys import xml.etree.ElementTree as ET from datetime import datetime, timedelta from pathlib import Path import requests # Configuration UNTAPPD_RSS = "https://untappd.com/rss/user/Craniumslows?key=e8110a1087c289fdb992448e75adf35c" # Paths SCRIPT_DIR = Path(__file__).parent PROJECT_ROOT = SCRIPT_DIR.parent BEERCALL_DIR = PROJECT_ROOT / "content" / "posts" / "beercall" VENUES_FILE = SCRIPT_DIR / "venues.json" def load_venues(): """Load venue database from JSON file.""" if VENUES_FILE.exists(): with open(VENUES_FILE) as f: return json.load(f) return {} def save_venues(venues): """Save updated venue database.""" with open(VENUES_FILE, "w") as f: json.dump(venues, f, indent=2) def fetch_untappd_rss(): """Fetch and parse Untappd RSS feed.""" try: resp = requests.get(UNTAPPD_RSS, timeout=10) resp.raise_for_status() return ET.fromstring(resp.content) except Exception as e: print(f"Warning: Could not fetch Untappd RSS: {e}") return None def parse_checkins(root): """Extract checkin data from RSS.""" checkins = [] if root is None: return checkins for item in root.findall(".//item"): title = item.find("title") pub_date = item.find("pubDate") description = item.find("description") if title is not None and pub_date is not None: # Parse title: "Cranium S. is drinking a Beer by Brewery at Venue" title_text = title.text or "" # Extract venue (after " at ") venue_match = re.search(r" at (.+)$", title_text) venue = venue_match.group(1) if venue_match else "" # Extract beer and brewery beer_match = re.search(r"is drinking an? (.+) by (.+?) at", title_text) if beer_match: beer = beer_match.group(1) brewery = beer_match.group(2) else: beer = "" brewery = "" # Parse date try: dt = datetime.strptime(pub_date.text, "%a, %d %b %Y %H:%M:%S %z") except ValueError: continue checkins.append({ "date": dt, "venue": venue, "beer": beer, "brewery": brewery, "notes": description.text if description is not None else "", }) return checkins def get_last_thursday(): """Get the date of the most recent Thursday (including today if Thursday).""" today = datetime.now() days_since_thursday = (today.weekday() - 3) % 7 if days_since_thursday == 0 and today.hour < 12: # If it's Thursday morning, probably mean last Thursday days_since_thursday = 7 return today - timedelta(days=days_since_thursday) def find_venue_by_name(venues, name): """Try to match a venue name to our database.""" name_lower = name.lower() for key, venue in venues.items(): if name_lower == venue["name"].lower(): return key, venue for alias in venue.get("aliases", []): if name_lower == alias.lower() or alias.lower() in name_lower: return key, venue return None, None def display_venues(venues): """Display numbered list of venues.""" print("\nKnown venues:") sorted_venues = sorted(venues.items(), key=lambda x: x[1]["name"]) for i, (key, venue) in enumerate(sorted_venues, 1): print(f" {i:2}. {venue['name']}") return sorted_venues def get_or_create_year_file(year): """Get the path to the year's beer call log, creating if needed.""" BEERCALL_DIR.mkdir(parents=True, exist_ok=True) filepath = BEERCALL_DIR / f"{year}.md" if not filepath.exists(): # Create new year file with frontmatter content = f"""+++ title = 'Beer Call Log for {year}' date = {datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')} draft = false summary = 'A listing of the beer calls that I have remembered to write down in {year}' series = "Luna Juice" +++ """ filepath.write_text(content) print(f"Created new log file: {filepath.relative_to(PROJECT_ROOT)}") return filepath def format_date_header(date): """Format date for the entry header.""" return date.strftime("%B %-d, %Y") # e.g., "December 19, 2024" def add_entry(filepath, venue_name, address, beerlist, date, attendees, notes): """Add a new beer call entry to the log file.""" # Read current content content = filepath.read_text() # Find where to insert (after frontmatter, before first entry or at end) # We want newest entries at the top lines = content.split("\n") # Find end of frontmatter frontmatter_end = 0 in_frontmatter = False for i, line in enumerate(lines): if line.strip() == "+++": if in_frontmatter: frontmatter_end = i + 1 break else: in_frontmatter = True # Build the new entry date_str = format_date_header(date) entry = f""" # {venue_name} - {date_str} | | | | :------------------- | :---------------- | | Location | {address} | | Beerlist | {beerlist} | | Attendees | {attendees} | | Notes | {notes} | """ # Insert after frontmatter (and any blank lines) insert_pos = frontmatter_end while insert_pos < len(lines) and lines[insert_pos].strip() == "": insert_pos += 1 # Insert the new entry new_lines = lines[:insert_pos] + entry.split("\n") + lines[insert_pos:] filepath.write_text("\n".join(new_lines)) return True def main(): parser = argparse.ArgumentParser(description="Add a new Beer Call entry") parser.add_argument("--date", help="Date of beer call (YYYY-MM-DD), default is last Thursday") parser.add_argument("--list", action="store_true", help="Just list recent Untappd checkins") args = parser.parse_args() # Load venues venues = load_venues() # Determine target date if args.date: try: target_date = datetime.strptime(args.date, "%Y-%m-%d") except ValueError: print("Invalid date format. Use YYYY-MM-DD") sys.exit(1) else: target_date = get_last_thursday() print(f"Beer Call date: {target_date.strftime('%A, %B %-d, %Y')}") # Fetch Untappd checkins print("\nFetching Untappd checkins...") root = fetch_untappd_rss() checkins = parse_checkins(root) # Filter to target date target_date_str = target_date.strftime("%Y-%m-%d") day_checkins = [c for c in checkins if c["date"].strftime("%Y-%m-%d") == target_date_str] if args.list: print(f"\nRecent Untappd checkins:") for c in checkins[:15]: print(f" {c['date'].strftime('%Y-%m-%d %H:%M')} - {c['beer']} at {c['venue']}") sys.exit(0) # Show checkins for the target date if day_checkins: print(f"\nUntappd checkins on {target_date_str}:") venues_seen = set() for c in day_checkins: if c["venue"] not in venues_seen: print(f" - {c['venue']}: {c['beer']} by {c['brewery']}") venues_seen.add(c["venue"]) # Try to suggest a venue suggested_venue = None for c in day_checkins: key, venue = find_venue_by_name(venues, c["venue"]) if venue: suggested_venue = (key, venue, c["venue"]) break else: print(f"\nNo Untappd checkins found for {target_date_str}") suggested_venue = None # Venue selection print("\n" + "=" * 50) if suggested_venue: key, venue, untappd_name = suggested_venue print(f"Suggested venue from Untappd: {venue['name']}") use_suggested = input("Use this venue? (Y/n): ").strip().lower() if use_suggested != "n": selected_venue = venue selected_key = key else: selected_venue = None else: selected_venue = None if not selected_venue: sorted_venues = display_venues(venues) print(f" {len(sorted_venues) + 1}. [New venue]") print(f" {len(sorted_venues) + 2}. [Skip/Out of Town]") choice = input("\nSelect venue number: ").strip() try: idx = int(choice) - 1 if idx == len(sorted_venues): # New venue venue_name = input("Venue name: ").strip() address = input("Address: ").strip() beerlist = input("Beer list URL: ").strip() # Add to database key = venue_name.lower().replace(" ", "-").replace("'", "") venues[key] = { "name": venue_name, "aliases": [venue_name], "address": address, "beerlist": beerlist, } save_venues(venues) print(f"Added {venue_name} to venue database!") selected_venue = venues[key] selected_key = key elif idx == len(sorted_venues) + 1: # Out of town / skip venue_name = input("Title (e.g., 'Out of Town', 'Holiday'): ").strip() or "Out of Town" notes = input("Notes: ").strip() filepath = get_or_create_year_file(target_date.year) add_entry(filepath, venue_name, "NA", "NA", target_date, "DNR", notes) print(f"\nEntry added to {filepath.relative_to(PROJECT_ROOT)}") sys.exit(0) elif 0 <= idx < len(sorted_venues): selected_key, selected_venue = sorted_venues[idx] else: print("Invalid selection") sys.exit(1) except ValueError: print("Invalid selection") sys.exit(1) # Collect attendees and notes print(f"\nVenue: {selected_venue['name']}") print(f"Address: {selected_venue['address']}") print(f"Beerlist: {selected_venue['beerlist']}") attendees = input("\nAttendees (comma-separated, or 'DNR'): ").strip() or "DNR" notes = input("Notes: ").strip() # Add to year file filepath = get_or_create_year_file(target_date.year) add_entry( filepath, selected_venue["name"], selected_venue["address"], selected_venue["beerlist"], target_date, attendees, notes, ) print(f"\nEntry added to {filepath.relative_to(PROJECT_ROOT)}") print("Done!") if __name__ == "__main__": main()