fix bandcamp login
This commit is contained in:
+87
-31
@@ -45,22 +45,21 @@ SESSION.headers["User-Agent"] = (
|
||||
|
||||
# ── Credentials ───────────────────────────────────────────────────────────────
|
||||
|
||||
def load_credentials() -> tuple[str, str]:
|
||||
def load_credentials() -> tuple[str, str, str]:
|
||||
"""Returns (email, password, cookies). Cookies take priority when present."""
|
||||
email = os.environ.get("BANDCAMP_EMAIL", "")
|
||||
password = os.environ.get("BANDCAMP_PASSWORD", "")
|
||||
cookies = ""
|
||||
|
||||
if email and password:
|
||||
return email, password
|
||||
return email, password, cookies
|
||||
|
||||
try:
|
||||
r = requests.get("http://localhost:8091/credentials", timeout=3)
|
||||
if r.ok:
|
||||
data = r.json()
|
||||
api_email = data.get("email", "")
|
||||
# password_set=True but we can't retrieve the plaintext from GET;
|
||||
# the service exposes /test-login but not the raw password.
|
||||
# Fall through to the JSON file which stores plaintext.
|
||||
if api_email:
|
||||
email = api_email
|
||||
if data.get("email"):
|
||||
email = data["email"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -70,19 +69,20 @@ def load_credentials() -> tuple[str, str]:
|
||||
cfg = json.load(f)
|
||||
email = cfg.get("email", email)
|
||||
password = cfg.get("password", password)
|
||||
cookies = cfg.get("cookies", cookies)
|
||||
|
||||
# Also try the Docker volume path (running on host alongside Docker)
|
||||
docker_cfg = Path("/var/lib/docker/volumes/audioserver_bandcamp_config/_data/bandcamp.json")
|
||||
if not password and docker_cfg.exists():
|
||||
if not password and not cookies and docker_cfg.exists():
|
||||
try:
|
||||
with open(docker_cfg) as f:
|
||||
cfg = json.load(f)
|
||||
email = cfg.get("email", email)
|
||||
password = cfg.get("password", password)
|
||||
cookies = cfg.get("cookies", cookies)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return email, password
|
||||
return email, password, cookies
|
||||
|
||||
|
||||
# ── Bandcamp auth ─────────────────────────────────────────────────────────────
|
||||
@@ -90,20 +90,26 @@ def load_credentials() -> tuple[str, str]:
|
||||
def bc_login(email: str, password: str) -> dict:
|
||||
"""Login to Bandcamp. Returns account dict with fan_id."""
|
||||
r = SESSION.get("https://bandcamp.com/login", timeout=15)
|
||||
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:
|
||||
m = re.search(r'<meta\s+name=["\']csrf-token["\']\s+content=["\']([^"\']+)["\']', r.text, re.IGNORECASE)
|
||||
if not m:
|
||||
m = re.search(r'<meta\s+content=["\']([^"\']+)["\']\s+name=["\']csrf-token["\']', r.text, re.IGNORECASE)
|
||||
if not m:
|
||||
raise RuntimeError("Could not get CSRF token from Bandcamp login page")
|
||||
csrf = m.group(1)
|
||||
|
||||
time.sleep(REQUEST_DELAY)
|
||||
r = SESSION.post(
|
||||
"https://bandcamp.com/login_cb",
|
||||
data={"username": email, "password": password, "csrf_token": csrf},
|
||||
data={
|
||||
"user.name": email,
|
||||
"login.password": password,
|
||||
"login.twofactor": "",
|
||||
"login.twofactor_remember": "",
|
||||
"login.from": "",
|
||||
"to_band_path": "",
|
||||
},
|
||||
headers={
|
||||
"X-CSRFToken": csrf,
|
||||
"X-CSRF-TOKEN": csrf,
|
||||
"Referer": "https://bandcamp.com/login",
|
||||
"Accept": "application/json, text/javascript, */*",
|
||||
"Origin": "https://bandcamp.com",
|
||||
@@ -112,12 +118,53 @@ def bc_login(email: str, password: str) -> dict:
|
||||
)
|
||||
payload = r.json()
|
||||
if payload.get("ok") != 1:
|
||||
err = payload.get("error") or payload.get("error_message") or json.dumps(payload)
|
||||
errors = payload.get("errors")
|
||||
if errors and isinstance(errors, list):
|
||||
err = "; ".join(f"{e.get('field','')}: {e.get('reason','')}" for e in errors if isinstance(e, dict))
|
||||
else:
|
||||
err = payload.get("error") or payload.get("error_message") or json.dumps(payload)
|
||||
raise RuntimeError(f"Bandcamp login failed: {err}")
|
||||
|
||||
return payload.get("account", {})
|
||||
|
||||
|
||||
def apply_cookies(session: requests.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 get_session_info(session: requests.Session) -> tuple[int, str]:
|
||||
"""Returns (fan_id, username) from session cookies or homepage."""
|
||||
js_raw = session.cookies.get('js_account_details', '')
|
||||
fan_id = None
|
||||
username = ''
|
||||
if js_raw:
|
||||
try:
|
||||
data = json.loads(unquote(js_raw))
|
||||
fan_id = data.get('fan_id') or data.get('id')
|
||||
username = data.get('username', '')
|
||||
if fan_id:
|
||||
return int(fan_id), username
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
time.sleep(REQUEST_DELAY)
|
||||
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)
|
||||
if not fan_id:
|
||||
raise RuntimeError("Could not determine fan_id from session cookies — they may have expired")
|
||||
return fan_id, username
|
||||
|
||||
|
||||
def get_fan_id(account: dict) -> int:
|
||||
fan_id = account.get("fan_id") or account.get("id")
|
||||
if fan_id:
|
||||
@@ -317,18 +364,27 @@ def main():
|
||||
|
||||
music_dir = args.music_dir
|
||||
|
||||
# Load credentials
|
||||
email, password = load_credentials()
|
||||
if not email or not password:
|
||||
print("✗ No credentials found. Save them in the Settings → Bandcamp section, or set")
|
||||
print(" BANDCAMP_EMAIL / BANDCAMP_PASSWORD environment variables.")
|
||||
sys.exit(1)
|
||||
email, password, cookies = load_credentials()
|
||||
|
||||
print(f"Logging in as {email}…")
|
||||
account = bc_login(email, password)
|
||||
fan_id = get_fan_id(account)
|
||||
username = account.get("username", str(fan_id))
|
||||
print(f"✓ Logged in — {username} (fan_id={fan_id})")
|
||||
if cookies:
|
||||
apply_cookies(SESSION, cookies)
|
||||
print("Using saved session cookies…")
|
||||
try:
|
||||
fan_id, username = get_session_info(SESSION)
|
||||
print(f"✓ Session active — {username or 'unknown'} (fan_id={fan_id})")
|
||||
except RuntimeError as exc:
|
||||
print(f"✗ {exc}")
|
||||
sys.exit(1)
|
||||
elif email and password:
|
||||
print(f"Logging in as {email}…")
|
||||
account = bc_login(email, password)
|
||||
fan_id = get_fan_id(account)
|
||||
username = account.get("username", str(fan_id))
|
||||
print(f"✓ Logged in — {username} (fan_id={fan_id})")
|
||||
else:
|
||||
print("✗ No credentials found. Save session cookies or email/password in Settings → Bandcamp,")
|
||||
print(" or set BANDCAMP_EMAIL / BANDCAMP_PASSWORD environment variables.")
|
||||
sys.exit(1)
|
||||
|
||||
print("Fetching collection…")
|
||||
items = fetch_collection(fan_id)
|
||||
|
||||
Reference in New Issue
Block a user