Spotify integration added

This commit is contained in:
2026-05-17 13:35:34 +02:00
parent 9a9275dae5
commit 4d53efc9e4
16 changed files with 1890 additions and 82 deletions
+7
View File
@@ -0,0 +1,7 @@
FROM python:3.12-slim
RUN pip install --no-cache-dir requests
WORKDIR /app
COPY app.py .
HEALTHCHECK --interval=20s --timeout=5s \
CMD python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:8091/health')" || exit 1
CMD ["python3", "app.py"]
+172
View File
@@ -0,0 +1,172 @@
#!/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 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
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}"}
csrf = unquote(r.cookies.get("csrf_token", ""))
if not csrf:
m = re.search(r'"csrf_token"\s*:\s*"([^"]+)"', r.text)
if m:
csrf = unquote(m.group(1))
if not csrf:
return {"ok": False, "error": "Could not obtain CSRF token from Bandcamp"}
# Step 2 — POST login
try:
r = session.post(
"https://bandcamp.com/login_cb",
data={"username": email, "password": password, "csrf_token": csrf},
headers={
"X-CSRFToken": csrf,
"Referer": "https://bandcamp.com/login",
"Accept": "application/json, text/javascript, */*",
"Origin": "https://bandcamp.com",
},
timeout=15,
)
payload = r.json()
except Exception as exc:
return {"ok": False, "error": str(exc)}
if payload.get("ok") == 1 or payload.get("account"):
username = (payload.get("account") or {}).get("username", "")
return {"ok": True, "username": username}
err = payload.get("error") or payload.get("error_message") or "Login failed"
return {"ok": False, "error": err}
# ── HTTP handler ──────────────────────────────────────────────────────────────
class _Handler(BaseHTTPRequestHandler):
def log_message(self, *_):
pass
def _json(self, status: int, data: dict):
body = json.dumps(data).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_OPTIONS(self):
self.send_response(204)
self.send_header("Allow", "GET, POST, OPTIONS")
self.end_headers()
def do_GET(self):
if self.path in ("/credentials", "/credentials/"):
cfg = _read()
self._json(200, {"email": cfg.get("email", ""), "password_set": bool(cfg.get("password"))})
elif self.path in ("/spotify/credentials", "/spotify/credentials/"):
cfg = _spotify_read()
self._json(200, {
"client_id": cfg.get("client_id", ""),
"client_secret_set": bool(cfg.get("client_secret")),
})
elif self.path in ("/health", "/health/"):
self._json(200, {"ok": True})
else:
self._json(404, {"error": "not found"})
def do_POST(self):
length = int(self.headers.get("Content-Length", 0))
try:
body = json.loads(self.rfile.read(length) or b"{}")
except json.JSONDecodeError:
self._json(400, {"error": "invalid JSON"})
return
if self.path in ("/credentials", "/credentials/"):
cfg = _read()
if "email" in body:
cfg["email"] = body["email"].strip()
if "password" in body and body["password"]:
cfg["password"] = body["password"]
_write(cfg)
self._json(200, {"ok": True})
elif self.path in ("/test-login", "/test-login/"):
cfg = _read()
email = cfg.get("email", "")
password = cfg.get("password", "")
if not email or not password:
self._json(400, {"ok": False, "error": "No credentials saved — save them first"})
return
result = _bc_login(email, password)
self._json(200, result)
elif self.path in ("/spotify/credentials", "/spotify/credentials/"):
cfg = _spotify_read()
if "client_id" in body:
cfg["client_id"] = body["client_id"].strip()
if "client_secret" in body and body["client_secret"]:
cfg["client_secret"] = body["client_secret"].strip()
_spotify_write(cfg)
self._json(200, {"ok": True})
else:
self._json(404, {"error": "not found"})
if __name__ == "__main__":
port = int(os.environ.get("PORT", 8091))
server = HTTPServer(("0.0.0.0", port), _Handler)
print(f"bandcamp-api listening on :{port}", flush=True)
server.serve_forever()