feat: add YouTube cookies upload via web UI

Adds a Settings panel to upload a cookies.txt file directly from the
browser, persisted in a named Docker volume. yt-dlp uses the file
when present to bypass YouTube bot detection.
This commit is contained in:
Julien Calixte
2026-03-23 19:32:51 +01:00
parent c49ecab33f
commit 57910462e4
5 changed files with 91 additions and 3 deletions

View File

@@ -255,9 +255,23 @@
<div class="container">
<header>
<h1><span>apoena</span> transcript</h1>
<span id="device-badge" class="badge badge-loading">Loading...</span>
<div style="display:flex;align-items:center;gap:0.6rem">
<span id="device-badge" class="badge badge-loading">Loading...</span>
<button id="settings-toggle" style="background:transparent;border:1px solid #2a2a38;color:#64748b;padding:0.3rem 0.6rem;font-size:0.8rem;font-weight:400">⚙ Settings</button>
</div>
</header>
<!-- Settings panel -->
<div id="settings-panel" class="card" style="display:none;margin-bottom:1rem">
<h2>YouTube Cookies</h2>
<p style="font-size:0.82rem;color:#64748b;margin-bottom:0.8rem">Upload a <code>cookies.txt</code> (Netscape format) to bypass YouTube bot detection. Export it from Chrome using the <em>Get cookies.txt LOCALLY</em> extension while logged in to YouTube.</p>
<div style="display:flex;gap:0.6rem;align-items:center;flex-wrap:wrap">
<input type="file" id="cookies-input" accept=".txt,text/plain" style="font-size:0.85rem;color:#94a3b8" />
<button id="cookies-btn" disabled>Upload</button>
</div>
<div id="cookies-status" style="font-size:0.82rem;color:#64748b;margin-top:0.6rem"></div>
</div>
<!-- Model status -->
<div id="model-status" class="model-status">
Loading model — first visit downloads ~100 MB, then it's cached locally.
@@ -684,6 +698,49 @@ function pad(n, len = 2) { return String(Math.floor(n)).padStart(len, '0'); }
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// ── Settings (cookies upload) ───────────────────────────────────────────────
const settingsToggle = document.getElementById('settings-toggle');
const settingsPanel = document.getElementById('settings-panel');
const cookiesInput = document.getElementById('cookies-input');
const cookiesBtn = document.getElementById('cookies-btn');
const cookiesStatus = document.getElementById('cookies-status');
settingsToggle.addEventListener('click', async () => {
const open = settingsPanel.style.display !== 'none';
settingsPanel.style.display = open ? 'none' : 'block';
if (!open) {
const res = await fetch('/admin/cookies/status').then(r => r.json()).catch(() => null);
if (res) {
cookiesStatus.textContent = res.present
? `✓ Cookies file present (${(res.size / 1024).toFixed(1)} KB)`
: 'No cookies file uploaded yet.';
}
}
});
cookiesInput.addEventListener('change', () => {
cookiesBtn.disabled = !cookiesInput.files.length;
});
cookiesBtn.addEventListener('click', async () => {
const file = cookiesInput.files[0];
if (!file) return;
const form = new FormData();
form.append('file', file);
cookiesBtn.disabled = true;
cookiesStatus.textContent = 'Uploading…';
try {
const res = await fetch('/admin/cookies', { method: 'POST', body: form });
if (!res.ok) throw new Error((await res.json()).detail || res.statusText);
cookiesStatus.textContent = '✓ Cookies uploaded successfully.';
cookiesInput.value = '';
} catch (err) {
cookiesStatus.textContent = 'Error: ' + err.message;
} finally {
cookiesBtn.disabled = false;
}
});
</script>
</body>
</html>