fix bandcamp login
This commit is contained in:
@@ -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>}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user