diff --git a/README.md b/README.md new file mode 100644 index 0000000..62afa28 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# marcus-web + +Personal blog powered by Hugo. Many references to I Saw The TV Glow its a fantastic movie. + +## Clone + +```bash +git clone ssh://git@git.sdf.org/mnw/marcus-web.git +cd marcus-web +``` + +## Setup Scripts + +```bash +./scripts/setup.sh +source .venv/bin/activate +``` + +## Usage + +See `scripts/execution-notes.txt` for full details, but the short version: + +```bash +# Movies (Frank's Couch) +python scripts/import_letterboxd.py + +# Beer Calls (Luna Juice) +python scripts/new_beercall.py + +# Beer Events +python scripts/new_lunajuice.py + +# Tech Posts (Fun Center) +python scripts/new_techpost.py +``` + +## Build + +Hugo builds happen on SDF, but using this repo and the scripts I can work on it locally where I am. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests diff --git a/scripts/execution-notes.txt b/scripts/execution-notes.txt new file mode 100644 index 0000000..11256f1 --- /dev/null +++ b/scripts/execution-notes.txt @@ -0,0 +1,101 @@ +================================================================================ + MARCUS-WEB SCRIPTS CHEAT SHEET +================================================================================ + +FIRST TIME SETUP +---------------- +After cloning on a new machine: + + cd marcus-web + ./scripts/setup.sh + +This creates a .venv and installs dependencies (just 'requests'). + +Then either activate the venv: + + source .venv/bin/activate + python scripts/import_letterboxd.py + +Or run directly: + + .venv/bin/python scripts/import_letterboxd.py + + + +MOVIES (Frank's Couch) +---------------------- +Import a movie from Letterboxd: + + python scripts/import_letterboxd.py + + --latest Import most recent without picking + --theater Skip straight to theater questions + --home Skip straight to home video questions + --list Just show recent movies, don't import + +Workflow: Log movie on Letterboxd → Run script → Pick movie → Answer prompts +Creates: content/posts/.md + downloads poster + + +BEER CALLS (Luna Juice - Weekly Thursday Meetups) +------------------------------------------------- +Add a beer call to the yearly log: + + python scripts/new_beercall.py + + --date 2024-12-30 Specific date (for Beer Crawl, holidays, etc) + --list Just show recent Untappd checkins + +Workflow: Run script → It checks Untappd for where you were → Pick venue → Done +Appends to: content/posts/beercall/2024.md (or 2025.md, etc - auto-created) + + +LUNA JUICE EVENTS (Festivals, Special Occasions) +------------------------------------------------ +Create a standalone beer event post: + + python scripts/new_lunajuice.py + python scripts/new_lunajuice.py "Beer Festival 2025" + +Creates: content/posts/beercall/.md + + +VENUE DATABASE +-------------- +Known venues are stored in: scripts/venues.json +New venues are added automatically when you enter them. + + +TECH POSTS (Fun Center) +----------------------- +Create a new technology blog post: + + python scripts/new_techpost.py + python scripts/new_techpost.py "My Post Title" + +Prompts for: + - Type: How I Did It / Grinds My Gears / Quick Tip + - Tags: suggests common ones, you add more + - Summary: one-liner + +Creates a skeleton outline based on post type so you just fill in the blanks. + + +MASTODON COMMENTS +----------------- +After publishing a post and tooting about it: + +1. Get the Mastodon post ID from the URL (the number at the end) +2. Add to your post's front matter: + + mastodon_id: "123456789" + +3. Rebuild site - comments will show with count + +To block a reply: + + mastodon_blocked: + - "https://tilde.zone/@someone/123456789" + + +================================================================================ diff --git a/scripts/new_beercall.py b/scripts/new_beercall.py new file mode 100755 index 0000000..8f870b2 --- /dev/null +++ b/scripts/new_beercall.py @@ -0,0 +1,347 @@ +#!/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() diff --git a/scripts/new_lunajuice.py b/scripts/new_lunajuice.py new file mode 100755 index 0000000..b54c1c0 --- /dev/null +++ b/scripts/new_lunajuice.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +""" +Create a new standalone Luna Juice post (festivals, events, special occasions). + +Usage: + python scripts/new_lunajuice.py # Interactive + python scripts/new_lunajuice.py "Beer Festival 2025" # With title + +For regular weekly beer calls, use new_beercall.py instead. +This script is for special events that deserve their own post. +""" + +import argparse +import re +import sys +from datetime import datetime, timezone +from pathlib import Path + +# Paths +SCRIPT_DIR = Path(__file__).parent +PROJECT_ROOT = SCRIPT_DIR.parent +CONTENT_DIR = PROJECT_ROOT / "content" / "posts" / "beercall" + + +def slugify(title): + """Convert title to URL-friendly slug.""" + slug = title.lower() + slug = re.sub(r"[^a-z0-9\s-]", "", slug) + slug = re.sub(r"[\s_]+", "-", slug) + slug = re.sub(r"-+", "-", slug) + return slug.strip("-") + + +def create_post(title, summary=""): + """Create a new Luna Juice post.""" + CONTENT_DIR.mkdir(parents=True, exist_ok=True) + + slug = slugify(title) + filename = f"{slug}.md" + filepath = CONTENT_DIR / filename + + if filepath.exists(): + print(f"Post already exists: {filepath.relative_to(PROJECT_ROOT)}") + overwrite = input("Overwrite? (y/N): ").strip().lower() + if overwrite != "y": + return None + + now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + + content = f"""+++ +title = '{title}' +date = {now} +draft = true +summary = '{summary}' +series = "Luna Juice" ++++ + +Write about your beer adventure here... + +""" + + filepath.write_text(content) + return filepath + + +def main(): + parser = argparse.ArgumentParser(description="Create a new Luna Juice event post") + parser.add_argument("title", nargs="?", help="Post title") + args = parser.parse_args() + + if args.title: + title = args.title + else: + title = input("Event title: ").strip() + if not title: + print("Title is required") + sys.exit(1) + + summary = input("Summary (one line): ").strip() + + filepath = create_post(title, summary) + + if filepath: + print(f"\nCreated: {filepath.relative_to(PROJECT_ROOT)}") + print("Edit the file to add your content!") + + +if __name__ == "__main__": + main() diff --git a/scripts/new_techpost.py b/scripts/new_techpost.py new file mode 100755 index 0000000..765211d --- /dev/null +++ b/scripts/new_techpost.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +""" +Create a new Fun Center (technology) blog post. + +Usage: + python scripts/new_techpost.py + python scripts/new_techpost.py "My Post Title" +""" + +import argparse +import re +import sys +from datetime import datetime, timezone +from pathlib import Path + +# Paths +SCRIPT_DIR = Path(__file__).parent +PROJECT_ROOT = SCRIPT_DIR.parent +CONTENT_DIR = PROJECT_ROOT / "content" / "posts" + +# Common tags for tech posts +COMMON_TAGS = [ + "linux", + "ubuntu", + "self-hosted", + "open-source", + "privacy", + "automation", + "devops", + "python", + "homelab", + "sdf", + "hugo", +] + + +def slugify(title): + """Convert title to URL-friendly slug.""" + slug = title.lower() + slug = re.sub(r"[^a-z0-9\s-]", "", slug) + slug = re.sub(r"[\s_]+", "-", slug) + slug = re.sub(r"-+", "-", slug) + return slug.strip("-") + + +def prompt_post_type(): + """Ask user what type of post this is.""" + print("\nWhat kind of post is this?") + print(" 1. How I Did It - Problem/solution, troubleshooting, tutorials") + print(" 2. Grinds My Gears - Opinion, rant, commentary") + print(" 3. Quick Tip - Short discovery, TIL, neat trick") + + while True: + choice = input("Enter 1, 2, or 3: ").strip() + if choice == "1": + return "how-to" + elif choice == "2": + return "opinion" + elif choice == "3": + return "quick-tip" + else: + print("Please enter 1, 2, or 3") + + +def prompt_tags(post_type): + """Suggest and collect tags.""" + # Suggest tags based on post type + type_tags = { + "how-to": ["how-i-did-it", "technology"], + "opinion": ["opinion", "technology"], + "quick-tip": ["til", "technology"], + } + + suggested = type_tags.get(post_type, ["technology"]) + + print(f"\nSuggested tags: {', '.join(suggested)}") + print(f"Common tags: {', '.join(COMMON_TAGS)}") + + additional = input("Additional tags (comma-separated, or Enter to skip): ").strip() + + tags = suggested.copy() + if additional: + for tag in additional.split(","): + tag = tag.strip().lower().replace(" ", "-") + if tag and tag not in tags: + tags.append(tag) + + return tags + + +def get_skeleton(post_type): + """Get the content skeleton based on post type.""" + if post_type == "how-to": + return """ +## The Problem + +What broke? What were you trying to do? + + +## What I Tried + +The journey - commands, dead ends, frustration... + + +## What Worked + +The fix! Include the commands/steps. + + +## Hindsight + +What would you do differently? Any gotchas for future-you? + +""" + elif post_type == "opinion": + return """ +## The Thing + +What's on your mind? + + +## Why It Matters + +Some context... + + +## My Take + +Your opinion here... + + +## Links + +- [Reference 1](https://example.com) + +""" + else: # quick-tip + return """ +Quick tip or discovery here. Keep it short! + +```bash +# command or code if relevant +``` + +""" + + +def create_post(title, post_type, tags, summary): + """Create the post file.""" + CONTENT_DIR.mkdir(parents=True, exist_ok=True) + + slug = slugify(title) + filename = f"{slug}.md" + filepath = CONTENT_DIR / filename + + if filepath.exists(): + print(f"\nPost already exists: {filepath.relative_to(PROJECT_ROOT)}") + overwrite = input("Overwrite? (y/N): ").strip().lower() + if overwrite != "y": + return None + + now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + tags_yaml = "\n".join(f" - {tag}" for tag in tags) + skeleton = get_skeleton(post_type) + + content = f'''--- +title: '{title}' +date: {now} +draft: true +series: "Fun Center" +summary: "{summary}" +tags: +{tags_yaml} +--- +{skeleton}''' + + filepath.write_text(content) + return filepath + + +def main(): + parser = argparse.ArgumentParser(description="Create a new Fun Center tech post") + parser.add_argument("title", nargs="?", help="Post title") + args = parser.parse_args() + + # Get title + if args.title: + title = args.title + else: + title = input("Post title: ").strip() + if not title: + print("Title is required") + sys.exit(1) + + # Get post type + post_type = prompt_post_type() + + # Get tags + tags = prompt_tags(post_type) + + # Get summary + summary = input("\nOne-line summary: ").strip() + + # Create the post + filepath = create_post(title, post_type, tags, summary) + + if filepath: + print(f"\nCreated: {filepath.relative_to(PROJECT_ROOT)}") + print(f"Type: {post_type}") + print(f"Tags: {', '.join(tags)}") + print("\nEdit the file to fill in the skeleton!") + + +if __name__ == "__main__": + main() diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100755 index 0000000..9d86984 --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# +# Setup script for marcus-web blog scripts +# Run this once after cloning the repo on a new machine +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +echo "Setting up marcus-web scripts..." +echo "Project root: $PROJECT_ROOT" + +# Check for Python 3 +if ! command -v python3 &> /dev/null; then + echo "ERROR: python3 not found. Please install Python 3." + exit 1 +fi + +# Create virtual environment if it doesn't exist +VENV_DIR="$PROJECT_ROOT/.venv" +if [ ! -d "$VENV_DIR" ]; then + echo "Creating virtual environment..." + python3 -m venv "$VENV_DIR" +fi + +# Activate and install dependencies +echo "Installing dependencies..." +source "$VENV_DIR/bin/activate" +pip install --quiet --upgrade pip +pip install --quiet -r "$PROJECT_ROOT/requirements.txt" + +echo "" +echo "Done! To use the scripts, either:" +echo "" +echo " 1. Activate the venv first:" +echo " source .venv/bin/activate" +echo " python scripts/import_letterboxd.py" +echo "" +echo " 2. Or run directly with the venv python:" +echo " .venv/bin/python scripts/import_letterboxd.py" +echo "" diff --git a/scripts/venues.json b/scripts/venues.json new file mode 100644 index 0000000..5ca0321 --- /dev/null +++ b/scripts/venues.json @@ -0,0 +1,122 @@ +{ + "draught-house": { + "name": "Draught House", + "aliases": ["DH", "Draught House"], + "address": "4112 Medical Pkwy, Austin, TX 78756", + "beerlist": "https://www.draughthouse.com/drinks" + }, + "abgb": { + "name": "The ABGB", + "aliases": ["ABGB", "The ABGB"], + "address": "1305 W Oltorf St, Austin, TX 78704", + "beerlist": "https://theabgb.com/beers/" + }, + "lazarus": { + "name": "Lazarus Brewing", + "aliases": ["Lazarus", "Lazarus 2"], + "address": "4803 Airport Blvd, Austin, TX 78751", + "beerlist": "https://lazarusbrewing.com/our-beer/" + }, + "abw": { + "name": "Austin Beer Works", + "aliases": ["ABW", "Austin Beer Works", "Austin Beerworks"], + "address": "3001 Industrial Terrace, Austin, TX 78758", + "beerlist": "https://austinbeerworks.com/tap-room" + }, + "abw-sprinkle": { + "name": "Austin Beer Works (Sprinkle Valley)", + "aliases": ["Sprinkle Valley", "ABW Sprinkle"], + "address": "10300 Springdale Rd, Austin, TX 78754", + "beerlist": "https://austinbeerworks.com/page/welcome-to-sprinkle-valley" + }, + "pint-house-burnet": { + "name": "Pint House Burnet", + "aliases": ["PHP Burnet", "Pint House Pizza Burnet", "Pinthouse Burnet"], + "address": "4729 Burnet Rd, Austin, TX 78756", + "beerlist": "https://pinthouse.com/burnet/beer/beer-on-tap" + }, + "pint-house-south-lamar": { + "name": "Pint House South Lamar", + "aliases": ["PHP South Lamar", "Pint House Pizza South Lamar", "Pinthouse South Lamar"], + "address": "4236 S Lamar Blvd, Austin, TX 78704", + "beerlist": "https://pinthouse.com/ben-white/beer/beer-on-tap" + }, + "pint-house-ben-white": { + "name": "Pint House Ben White", + "aliases": ["PHP Ben White", "Pint House Pizza Ben White"], + "address": "2201 E Ben White Blvd, Austin, TX 78744", + "beerlist": "https://pinthouse.com/ben-white/beer/beer-on-tap" + }, + "burnet-go-to": { + "name": "Burnet Go-To", + "aliases": ["Burnet Go To", "Go-To", "Go To"], + "address": "6800 Burnet Rd #2, Austin, TX 78757", + "beerlist": "https://burnetgoto.com/draught-beer.html" + }, + "black-star": { + "name": "Black Star Coop", + "aliases": ["Black Star", "Blackstar"], + "address": "7020 Easy Wind Dr, Austin, TX 78752", + "beerlist": "https://www.blackstar.coop/beer" + }, + "celis": { + "name": "Celis Brewery", + "aliases": ["Celis"], + "address": "10001 Metric Blvd, Austin, TX 78758", + "beerlist": "https://www.celisbeers.com/copy-of-core-beers-1" + }, + "batch": { + "name": "Batch Kolache", + "aliases": ["Batch", "Batch Kolache and Brewing"], + "address": "3220 Manor Rd, Austin, TX 78723", + "beerlist": "https://batchatx.com/beer/" + }, + "easy-tiger-linc": { + "name": "Easy Tiger Linc", + "aliases": ["Easy Tiger", "Easy Tiger LINC"], + "address": "6406 N Interstate Hwy 35 Ste 1100, Austin, TX 78752", + "beerlist": "https://www.easytigerusa.com/location/easy-tiger-linc/" + }, + "live-oak": { + "name": "Live Oak Brewing Company", + "aliases": ["Live Oak", "Live Oak Brewing"], + "address": "1615 Crozier Ln, Del Valle, TX 78617", + "beerlist": "https://liveoakbrewing.com/beer/" + }, + "front-page": { + "name": "Front Page", + "aliases": ["Front Page Austin"], + "address": "1023 Springdale Rd #1b, Austin, TX 78721", + "beerlist": "https://www.frontpageaustin.com/beers" + }, + "brewtorium": { + "name": "Brewtorium", + "aliases": ["The Brewtorium"], + "address": "6015 Dillard Cir A, Austin, TX 78752", + "beerlist": "https://www.thebrewtorium.com/beer" + }, + "meanwhile": { + "name": "Meanwhile Brewing", + "aliases": ["Meanwhile"], + "address": "3901 Promontory Point Dr, Austin, TX 78744", + "beerlist": "https://www.meanwhilebeer.com/" + }, + "oskar-blues": { + "name": "Oskar Blues", + "aliases": ["Oskar Blues Austin"], + "address": "10420 Metric BLVD #150, Austin, TX 78758", + "beerlist": "https://oskarblues.com/location/austin/#brewery-menu" + }, + "austin-craft": { + "name": "Austin Craft Brewing", + "aliases": ["Austin Craft Brew"], + "address": "4700 Burleson, Austin, TX", + "beerlist": "https://www.austincraftbrew.com/" + }, + "poodies": { + "name": "Poodie's", + "aliases": ["Poodies", "Poodie's Hilltop"], + "address": "22308 TX-71, Spicewood, TX 78669", + "beerlist": "" + } +}