Files
marcus-web/scripts/new_beercall.py

348 lines
11 KiB
Python
Executable File

#!/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()