Podcast: resume where you left off, synced between devices (shared progress store with audiobooks)
build-container / build (push) Successful in 9m26s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-22 15:37:51 -04:00
1 parent aa2133ed9a
commit b3494b9720
5 files changed
+61 -16

No files matched your search

+7 -3
View File
@@ -1,5 +1,6 @@
// Podcast proxy: only the allowed Podgrab calls get through, the Podgrab password is added server-side, audio ranges and redirects work.
const http = require('http'), assert = require('assert');
const http = require('http'), assert = require('assert'), fs = require('fs'), os = require('os'), path = require('path');
process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'nehub-pod-'));
const seen = [];
const fake = http.createServer((req, res) => {
let b = ''; req.on('data', c => b += c); req.on('end', () => {
@@ -11,8 +12,8 @@ const fake = http.createServer((req, res) => {
});
}).listen(0, async () => {
process.env.PODCAST_URL = 'http://127.0.0.1:' + fake.address().port + '/'; process.env.PODGRAB_PASSWORD = 'secret';
const podcast = require('../podcast');
const hub = http.createServer((req, res) => podcast.handle(req, res)).listen(0, async () => {
const podcast = require('../podcast'), users = require('../users');
const hub = http.createServer((req, res) => users.runAs({ id: 'pod-user', name: 'P' }, () => podcast.handle(req, res))).listen(0, async () => {
const base = 'http://127.0.0.1:' + hub.address().port, get = (p, o = {}) => fetch(base + '/api/podcast' + p, { redirect: 'manual', ...o });
try {
let r = await get('/podcasts'); assert.equal(r.status, 200); assert.equal((await r.json())[0].Title, 'Show');
@@ -20,6 +21,9 @@ const fake = http.createServer((req, res) => {
r = await get('/podcastitems/e1/file', { headers: { range: 'bytes=0-3' } }); assert.equal(r.status, 206); assert.equal(await r.text(), 'abcd'); assert.equal(seen.at(-1).range, 'bytes=0-3');
r = await get('/podcastitems/e2/file'); assert.equal(r.status, 302); assert.equal(r.headers.get('location'), 'https://cdn.example.com/a.mp3');
r = await get('/podcasts', { method: 'POST', body: '{"url":"https://x.test/feed"}' }); assert.equal(r.status, 200); assert.equal(seen.at(-1).b, '{"url":"https://x.test/feed"}');
const post = b => get('/progress', { method: 'POST', body: JSON.stringify(b) }).then(x => x.json());
await post({ e1: { pos: 90, dur: 600, t: 10 } }); const pr = await post({ e1: { pos: 5, dur: 600, t: 3 }, e2: { pos: 7, dur: 300, t: 4 } });
assert.equal(pr.e1.pos, 90, 'the newest position wins'); assert.equal((await (await get('/progress')).json()).e2.pos, 7);
const n = seen.length;
for (const [m, p] of [['GET', '/settings'], ['POST', '/settings'], ['GET', '/opml'], ['DELETE', '/tags/1'], ['GET', '/backups'], ['GET', '/podcasts/../settings'], ['DELETE', '/podcastitems/e1']]) {
r = await get(p, { method: m }); assert.equal(r.status, 404, m + ' ' + p);