Initial infrastructure documentation - comprehensive homelab reference

This commit is contained in:
Funky (OpenClaw)
2026-02-23 03:42:22 +00:00
commit 0682c79580
169 changed files with 63913 additions and 0 deletions

View File

@@ -0,0 +1,286 @@
#!/usr/bin/env python3
"""
Import all cocktails from TheCocktailDB into Bar Assistant
Requirements:
pip install requests
Usage:
python import-cocktaildb-to-bar-assistant-v2.py
"""
import requests
import time
import json
import re
from typing import Dict, List, Optional, Tuple
# Configuration
BAR_ASSISTANT_URL = "https://cocktails.nianticbooks.com"
BAR_ASSISTANT_API = f"{BAR_ASSISTANT_URL}/bar/api"
COCKTAILDB_API = "https://www.thecocktaildb.com/api/json/v1/1"
BAR_ASSISTANT_TOKEN = "3|dqtZPWZoKdU59fFRLkGrL83I9HKm9EqmLCgO4kkI96199a82"
BAR_ID = 1
# Cache for ingredients
ingredient_cache = {}
def parse_amount(measure_str: str) -> Tuple[Optional[float], str]:
"""Parse amount string like '2 oz' into (2.0, 'oz')"""
if not measure_str or not measure_str.strip():
return (None, "")
measure_str = measure_str.strip()
# Try to extract number and unit
# Handles: "2 oz", "1/2 oz", "1.5 oz", "2", etc.
match = re.match(r'^([\d./\s]+)\s*(.*)$', measure_str)
if match:
amount_str = match.group(1).strip()
unit = match.group(2).strip()
# Handle fractions like "1/2" or "1 1/2"
try:
# Split on spaces to handle mixed fractions
parts = amount_str.split()
total = 0.0
for part in parts:
if '/' in part:
# Handle fraction
num, denom = part.split('/')
total += float(num) / float(denom)
else:
# Handle whole number or decimal
total += float(part)
return (total, unit if unit else "oz")
except:
pass
# If we can't parse, return None
return (None, "")
def get_or_create_ingredient(name: str, headers: Dict) -> Optional[int]:
"""Get ingredient ID by name, or create if it doesn't exist"""
# Check cache first
name_lower = name.lower().strip()
if name_lower in ingredient_cache:
return ingredient_cache[name_lower]
# Search for ingredient
url = f"{BAR_ASSISTANT_API}/ingredients"
params = {"filter[name]": name}
try:
response = requests.get(url, params=params, headers=headers, timeout=10)
response.raise_for_status()
data = response.json()
# Check if we found a match
if data.get('data') and len(data['data']) > 0:
for ingredient in data['data']:
if ingredient['name'].lower() == name_lower:
ingredient_id = ingredient['id']
ingredient_cache[name_lower] = ingredient_id
return ingredient_id
# Ingredient not found, create it
print(f" Creating new ingredient: {name}")
create_data = {
"name": name,
"strength": 0,
"description": f"Imported from TheCocktailDB"
}
response = requests.post(url, json=create_data, headers=headers, timeout=10)
response.raise_for_status()
ingredient_id = response.json()['data']['id']
ingredient_cache[name_lower] = ingredient_id
return ingredient_id
except Exception as e:
print(f" [!] Error with ingredient '{name}': {e}")
return None
def get_all_cocktails_from_cocktaildb() -> List[Dict]:
"""Fetch all cocktails from TheCocktailDB"""
print("Fetching all cocktails from TheCocktailDB...")
all_cocktails = []
letters = 'abcdefghijklmnopqrstuvwxyz0123456789'
for letter in letters:
print(f" Fetching cocktails starting with '{letter}'...")
url = f"{COCKTAILDB_API}/search.php?f={letter}"
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
if data.get('drinks'):
cocktails = data['drinks']
all_cocktails.extend(cocktails)
print(f" Found {len(cocktails)} cocktails")
time.sleep(0.5)
except Exception as e:
print(f" Error fetching '{letter}': {e}")
continue
print(f"\n[OK] Total cocktails found: {len(all_cocktails)}")
return all_cocktails
def convert_cocktail_ingredients(cocktail: Dict, headers: Dict) -> List[Dict]:
"""Extract and convert ingredients from TheCocktailDB format"""
ingredients = []
for i in range(1, 16):
ingredient_key = f"strIngredient{i}"
measure_key = f"strMeasure{i}"
ingredient_name = cocktail.get(ingredient_key)
measure = cocktail.get(measure_key, "")
if ingredient_name and ingredient_name.strip():
# Get or create the ingredient
ingredient_id = get_or_create_ingredient(ingredient_name.strip(), headers)
if ingredient_id:
# Parse the amount
amount, units = parse_amount(measure)
ingredients.append({
"sort": i, # Add sort field for ingredient order
"ingredient_id": ingredient_id,
"amount": amount if amount is not None else 0,
"units": units if units else "oz", # Default to "oz" if no unit specified
"optional": False
})
return ingredients
def import_cocktail(cocktail: Dict, headers: Dict) -> bool:
"""Import a single cocktail into Bar Assistant"""
# Convert ingredients first
ingredients = convert_cocktail_ingredients(cocktail, headers)
if not ingredients:
print(f" [!] No valid ingredients found")
return False
# Build cocktail data
cocktail_data = {
"name": cocktail.get('strDrink', 'Unknown'),
"instructions": cocktail.get('strInstructions', ''),
"description": f"Imported from TheCocktailDB. Category: {cocktail.get('strCategory', 'N/A')}. {cocktail.get('strAlcoholic', '')}",
"source": f"TheCocktailDB (ID: {cocktail.get('idDrink')})",
"garnish": cocktail.get('strGarnish', ''),
"glass": cocktail.get('strGlass', ''),
"ingredients": ingredients
# Note: Images are not imported - Bar Assistant expects image IDs, not URLs
}
# Add tags
tags = []
if cocktail.get('strCategory'):
tags.append(cocktail['strCategory'])
if cocktail.get('strAlcoholic'):
tags.append(cocktail['strAlcoholic'])
if cocktail.get('strIBA'):
tags.append(cocktail['strIBA'])
if tags:
cocktail_data['tags'] = tags
# Import
url = f"{BAR_ASSISTANT_API}/cocktails"
try:
response = requests.post(url, json=cocktail_data, headers=headers, timeout=30)
response.raise_for_status()
return True
except requests.exceptions.HTTPError as e:
if e.response.status_code == 409:
print(f" [!] Already exists")
return False
elif e.response.status_code == 422:
print(f" [X] Validation error: {e.response.text[:200]}")
return False
else:
print(f" [X] HTTP Error {e.response.status_code}")
return False
except Exception as e:
print(f" [X] Error: {e}")
return False
def main():
"""Main execution"""
print("="*60)
print("TheCocktailDB -> Bar Assistant Bulk Import")
print("="*60)
# Set up headers
headers = {
"Authorization": f"Bearer {BAR_ASSISTANT_TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json",
"Bar-Assistant-Bar-Id": str(BAR_ID)
}
# Step 1: Fetch all cocktails from TheCocktailDB
cocktails = get_all_cocktails_from_cocktaildb()
if not cocktails:
print("\n[ERROR] No cocktails found from TheCocktailDB")
return
# Step 2: Import
print(f"\nImporting {len(cocktails)} cocktails to Bar Assistant...")
print("This may take a while...\n")
success_count = 0
skip_count = 0
error_count = 0
for idx, cocktail in enumerate(cocktails, 1):
cocktail_name = cocktail.get('strDrink', 'Unknown')
print(f"[{idx}/{len(cocktails)}] {cocktail_name}...")
if import_cocktail(cocktail, headers):
success_count += 1
print(f" [OK] Imported successfully")
else:
skip_count += 1
time.sleep(0.5)
# Summary
print("\n" + "="*60)
print("Import Complete!")
print("="*60)
print(f"[OK] Successfully imported: {success_count}")
print(f"[!] Skipped (duplicates/errors): {skip_count}")
print(f"\nTotal processed: {len(cocktails)}")
print(f"\nView your cocktails at: {BAR_ASSISTANT_URL}")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n[!] Import cancelled by user")
except Exception as e:
print(f"\n\n[ERROR] Error: {e}")