'use strict'; // ---- Podcast: your shows and episodes from Podgrab, through the hub (the server holds the Podgrab sign-in). Episodes play in the same player as Music. const pdApi = (p, opts) => jfetch('/api/podcast' + p, opts); const pdOpt = (m, o) => ({ method: m, headers: { 'content-type': 'application/json' }, body: o ? JSON.stringify(o) : undefined }); // Podgrab sends its field names with a capital letter (ID, Title, ...); accept either spelling. const pdF = (o, k) => o == null ? undefined : o[k] !== undefined ? o[k] : o[k[0].toUpperCase() + k.slice(1)] !== undefined ? o[k[0].toUpperCase() + k.slice(1)] : o[k.toUpperCase()]; const pdId = o => pdF(o, 'id'); const pdImg = (kind, o, w) => `/api/podcast/${kind}/${encodeURIComponent(pdId(o))}/image`; const pdDur = s => { s = Math.round(+s || 0); if (!s) return ''; const h = Math.floor(s / 3600), m = Math.floor(s % 3600 / 60); return h ? h + ' h ' + m + ' min' : Math.max(1, m) + ' min'; }; const pdDate = d => { const t = Date.parse(d); return t > 0 ? new Date(t).toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' }) : ''; }; const pdTime = d => Math.max(0, Date.parse(d) || 0); const pdPlain = h => { const d = document.createElement('div'); d.innerHTML = String(h || ''); return (d.textContent || '').trim(); }; const pdTrack = (e, show) => ({ key: 'pod:' + pdId(e), title: pdF(e, 'title') || 'Episode', artist: pdF(show, 'title') || '', album: '', thumb: pdImg('podcasts', show), src: '/api/podcast/podcastitems/' + encodeURIComponent(pdId(e)) + '/file', dur: (+pdF(e, 'duration') || 0) * 1000 }); async function viewPodcast(args = []) { const v = $('#view'); v.innerHTML = '
Loading…
${esc(e.message)}
No podcasts yet. Add one with its feed address.
'}Searching uses Apple's public podcast directory, so the words you type are sent to Apple.
`); const q = el.querySelector('#pdq'), res = el.querySelector('#pdres'), err = el.querySelector('#pde'); let timer, seq = 0, found = []; const add = async (url, btn) => { err.textContent = ''; if (btn) btn.disabled = true; try { await pdApi('/podcasts', pdOpt('POST', { url })); closeSheet(); toast('Added; episodes will appear in a moment'); route(); } catch (e) { err.textContent = e.status === 502 || e.status === 409 || e.status === 400 ? 'Podgrab could not add that (it may already be there)' : e.message; if (btn) btn.disabled = false; } }; const run = async () => { const term = q.value.trim(), my = ++seq; err.textContent = ''; if (/^https?:\/\/\S+$/i.test(term)) { res.innerHTML = ``; res.querySelector('#pdfeed').onclick = e => add(term, e.currentTarget); return; } if (term.length < 2) { res.innerHTML = ''; return; } res.innerHTML = 'Searching…
'; try { found = await pdApi('/discover?q=' + encodeURIComponent(term)); if (my !== seq) return; res.innerHTML = found.length ? found.map((f, i) => `Nothing found.
'; res.querySelectorAll('button[data-i]').forEach(b => b.onclick = () => add(found[b.dataset.i].feedUrl, b)); } catch (e) { if (my === seq) res.innerHTML = `${esc(e.message)}
`; } }; q.oninput = () => { clearTimeout(timer); timer = setTimeout(run, 350); }; q.onkeydown = e => { if (e.key === 'Enter') { clearTimeout(timer); run(); } }; q.focus(); }; } async function pdShow(id) { const [show, items0] = await Promise.all([pdApi('/podcasts/' + encodeURIComponent(id)), pdApi('/podcasts/' + encodeURIComponent(id) + '/items')]); if (!location.hash.startsWith('#/podcast')) return; const items = (Array.isArray(items0) ? items0 : []).sort((a, b) => pdTime(pdF(b, 'pubDate')) - pdTime(pdF(a, 'pubDate'))); const paused = !!pdF(show, 'isPaused'), ep = e => `${esc(pdF(show, 'author') || '')}
No episodes yet. Podgrab may still be fetching the feed.
'}