Finishing up the trio of scripts for the various post types.

This commit is contained in:
mnw
2025-12-24 21:31:24 -06:00
parent 2fdff679f5
commit 15e32c49d2
8 changed files with 957 additions and 0 deletions
+39
View File
@@ -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.
+1
View File
@@ -0,0 +1 @@
requests
+101
View File
@@ -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/<movie-slug>.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/<event-slug>.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"
================================================================================
+347
View File
@@ -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()
+89
View File
@@ -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()
+215
View File
@@ -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()
+43
View File
@@ -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 ""
+122
View File
@@ -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": ""
}
}