fix bandcamp login

This commit is contained in:
2026-05-17 19:15:03 +02:00
parent 8393271ee3
commit 560d95cfc0
3 changed files with 258 additions and 63 deletions
+65 -19
View File
@@ -251,13 +251,15 @@ function SpotifySection() {
// ── Bandcamp credentials ──────────────────────────────────────────────────────
function BandcampSection() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [pwdSet, setPwdSet] = useState(false);
const [loaded, setLoaded] = useState(false);
const [saveState, setSaveState] = useState('idle');
const [testState, setTestState] = useState('idle');
const [testMsg, setTestMsg] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [cookies, setCookies] = useState('');
const [pwdSet, setPwdSet] = useState(false);
const [cookiesSet, setCookiesSet] = useState(false);
const [loaded, setLoaded] = useState(false);
const [saveState, setSaveState] = useState('idle');
const [testState, setTestState] = useState('idle');
const [testMsg, setTestMsg] = useState('');
useEffect(() => {
fetch('/api/bandcamp/credentials')
@@ -265,6 +267,7 @@ function BandcampSection() {
.then(data => {
setEmail(data.email || '');
setPwdSet(!!data.password_set);
setCookiesSet(!!data.cookies_set);
setLoaded(true);
})
.catch(() => setLoaded(true));
@@ -275,6 +278,7 @@ function BandcampSection() {
try {
const body = { email };
if (password) body.password = password;
if (cookies) body.cookies = cookies;
const r = await fetch('/api/bandcamp/credentials', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -282,16 +286,26 @@ function BandcampSection() {
});
if (!r.ok) throw new Error();
setSaveState('ok');
if (password) setPwdSet(true);
setPassword('');
if (password) { setPwdSet(true); setPassword(''); }
if (cookies) { setCookiesSet(true); setCookies(''); }
} catch {
setSaveState('error');
}
setTimeout(() => setSaveState('idle'), 4000);
};
const testLogin = async () => {
if (email || password) await save();
const clearCookies = async () => {
try {
const r = await fetch('/api/bandcamp/credentials', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cookies: '' }),
});
if (r.ok) { setCookiesSet(false); setCookies(''); }
} catch {}
};
const testConnection = async () => {
setTestState('testing');
setTestMsg('');
try {
@@ -299,8 +313,8 @@ function BandcampSection() {
const data = await r.json();
setTestState(data.ok ? 'ok' : 'error');
setTestMsg(data.ok
? `Logged in${data.username ? ` as ${data.username}` : ''}`
: (data.error || 'Login failed'));
? `Connected${data.username ? ` as ${data.username}` : ''}`
: (data.error || 'Connection failed'));
} catch {
setTestState('error');
setTestMsg('Could not reach bandcamp-api service');
@@ -310,10 +324,42 @@ function BandcampSection() {
return (
<>
<div className="settings-row-hint" style={{ marginBottom: 10 }}>
Session cookies bypass Bandcamp's login captcha and are used first when saved.
Log in at{' '}
<a href="https://bandcamp.com" target="_blank" rel="noreferrer" style={{ color: 'var(--accent)' }}>
bandcamp.com
</a>
{' '}in your browser, open DevTools (F12) → Network tab → click any bandcamp.com
request → Request Headers → copy the <code>Cookie</code> value and paste it below.
</div>
<div className="settings-row" style={{ alignItems: 'flex-start' }}>
<div className="settings-row-left" style={{ paddingTop: 4 }}>
<div className="settings-row-label">Session Cookies</div>
<div className="settings-row-hint">
{!loaded ? '' : cookiesSet
? <span>Saved — <button onClick={clearCookies} style={{ background: 'none', border: 'none', color: 'var(--accent)', cursor: 'pointer', padding: 0, fontSize: 'inherit' }}>clear</button></span>
: 'Not saved'}
</div>
</div>
<textarea
className="settings-input"
style={{ height: 60, resize: 'vertical', fontFamily: 'monospace', fontSize: 11 }}
value={cookies}
onChange={e => setCookies(e.target.value)}
placeholder={cookiesSet ? '(saved paste new value to replace)' : 'client_id=; csrf_token=; js_account_details='}
spellCheck={false}
/>
</div>
<div className="settings-row-hint" style={{ margin: '10px 0 6px', opacity: 0.55 }}>
Email and password are used as fallback when no cookies are saved.
</div>
<div className="settings-row">
<div className="settings-row-left">
<div className="settings-row-label">Email</div>
<div className="settings-row-hint">Bandcamp account email</div>
</div>
<input
className="settings-input"
@@ -329,7 +375,7 @@ function BandcampSection() {
<div className="settings-row-left">
<div className="settings-row-label">Password</div>
<div className="settings-row-hint">
{loaded && !pwdSet ? 'Not saved yet' : loaded ? 'Saved (leave blank to keep)' : '…'}
{loaded && !pwdSet ? 'Not saved' : loaded ? 'Saved (leave blank to keep)' : ''}
</div>
</div>
<input
@@ -345,13 +391,13 @@ function BandcampSection() {
<button className="settings-action-btn" onClick={save} disabled={saveState === 'saving'}>
{saveState === 'saving' ? 'Saving' : 'Save'}
</button>
<button className="settings-action-btn" onClick={testLogin} disabled={testState === 'testing'}>
{testState === 'testing' ? 'Testing…' : 'Test Login'}
<button className="settings-action-btn" onClick={testConnection} disabled={testState === 'testing'}>
{testState === 'testing' ? 'Testing' : 'Test Connection'}
</button>
</div>
{saveState === 'ok' && <p className="settings-feedback settings-feedback--ok"> Credentials saved</p>}
{saveState === 'error' && <p className="settings-feedback settings-feedback--error"> Failed to save credentials</p>}
{saveState === 'ok' && <p className="settings-feedback settings-feedback--ok">✓ Saved</p>}
{saveState === 'error' && <p className="settings-feedback settings-feedback--error">✗ Failed to save</p>}
{testState === 'ok' && <p className="settings-feedback settings-feedback--ok">✓ {testMsg}</p>}
{testState === 'error' && <p className="settings-feedback settings-feedback--error">✗ {testMsg}</p>}
</>