348 lines
11 KiB
Python
Executable File
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()
|