#!/usr/bin/env python3 """Bandcamp credential store + login-test micro-service.""" import json import os import re from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.parse import unquote import requests as _requests CONFIG_FILE = os.environ.get("CONFIG_FILE", "/config/bandcamp.json") SPOTIFY_CONFIG_FILE = os.environ.get("SPOTIFY_CONFIG_FILE", "/config/spotify.json") # ── Config helpers ──────────────────────────────────────────────────────────── def _read(): if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE) as f: return json.load(f) return {} def _write(data: dict): os.makedirs(os.path.dirname(CONFIG_FILE) or ".", exist_ok=True) with open(CONFIG_FILE, "w") as f: json.dump(data, f, indent=2) # ── Spotify credential helpers ──────────────────────────────────────────────── def _spotify_read() -> dict: if os.path.exists(SPOTIFY_CONFIG_FILE): with open(SPOTIFY_CONFIG_FILE) as f: return json.load(f) return {} def _spotify_write(data: dict): os.makedirs(os.path.dirname(SPOTIFY_CONFIG_FILE) or ".", exist_ok=True) with open(SPOTIFY_CONFIG_FILE, "w") as f: json.dump(data, f, indent=2) # ── Bandcamp cookie auth ───────────────────────────────────────────────────── def _apply_cookies(session, cookie_str: str): for part in cookie_str.split(';'): part = part.strip() if '=' in part: name, _, value = part.partition('=') session.cookies.set(name.strip(), value.strip(), domain='.bandcamp.com') def _bc_verify_cookies(cookie_str: str) -> dict: session = _requests.Session() session.headers["User-Agent"] = ( "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/124.0 Safari/537.36" ) _apply_cookies(session, cookie_str) # Try js_account_details cookie first (avoids an HTTP round-trip) fan_id = None username = '' js_raw = session.cookies.get('js_account_details', '') if js_raw: try: data = json.loads(unquote(js_raw)) fan_id = data.get('fan_id') or data.get('id') username = data.get('username', '') except Exception: pass # Fall back to fetching the homepage if not fan_id: try: r = session.get("https://bandcamp.com/", timeout=15) m = re.search(r'"fan_id"\s*:\s*(\d+)', r.text) if m: fan_id = int(m.group(1)) if not username: m2 = re.search(r'"username"\s*:\s*"([^"]+)"', r.text) if m2: username = m2.group(1) except Exception as exc: return {"ok": False, "error": f"Could not reach bandcamp.com: {exc}"} if not fan_id: return {"ok": False, "error": "Cookies invalid or expired — no fan account found"} # Verify with a lightweight API call try: r = session.post( "https://bandcamp.com/api/fan/2/collection_summary", json={"fan_id": fan_id}, timeout=15, ) if not r.ok: return {"ok": False, "error": "Cookies rejected by Bandcamp (may have expired)"} except Exception as exc: return {"ok": False, "error": str(exc)} return {"ok": True, "username": username} # ── Bandcamp login ──────────────────────────────────────────────────────────── def _bc_login(email: str, password: str) -> dict: session = _requests.Session() session.headers["User-Agent"] = ( "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/124.0 Safari/537.36" ) # Step 1 — obtain CSRF token from meta tag try: r = session.get("https://bandcamp.com/login", timeout=15) except Exception as exc: return {"ok": False, "error": f"Could not reach bandcamp.com: {exc}"} m = re.search(r'