Spotify integration added
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user