Chores, Family page, genre search, child accounts, wider Search and Notifications center
build-container / build (push) Successful in 4m6s
build-container / build (push) Successful in 4m6s
- Chores tab with repeats, points and a week/month board - Family page: calendar, chores, shopping, notes, chat and tasks on one start page - Genre search in the Store, Free Books and LibriVox - Child accounts: safe content, daily limit and bedtime, ask a parent - Search now covers notes, chat, contacts, chores, recipes and your books - Notifications center: bell with a per-person list of chat, reminders and announcements - Roadmap, README and tests updated Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf292e3bc7
commit
0383da87f5
38 files changed
+720
-79
No files matched your search
+4
-3
@@ -30,14 +30,14 @@ async function piGet(path) {
|
||||
const r = await fetch(PI_URL + path, { headers: { 'user-agent': 'NE-Hub/1.0', 'x-auth-key': process.env.PODCASTINDEX_KEY, 'x-auth-date': date, authorization: sig }, signal: AbortSignal.timeout(10000) });
|
||||
if (!r.ok) throw new Error('status ' + r.status); return r.json();
|
||||
}
|
||||
const piShape = f => ({ title: f.title, author: f.author || f.ownerName || '', feedUrl: f.url, image: f.image || f.artwork || '' });
|
||||
const piShape = f => ({ title: f.title, author: f.author || f.ownerName || '', feedUrl: f.url, image: f.image || f.artwork || '', explicit: !!f.explicit });
|
||||
const piKeep = f => f && /^https?:\/\//i.test(f.url || '') && !f.dead;
|
||||
const piSearch = async q => ((await piGet('/search/byterm?max=20&q=' + encodeURIComponent(q))).feeds || []).filter(piKeep).map(piShape);
|
||||
const piTrending = async cat => ((await piGet('/podcasts/trending?max=20&lang=en&cat=' + encodeURIComponent(cat))).feeds || []).filter(piKeep).map(piShape);
|
||||
async function appleSearch(q) {
|
||||
const r = await fetch((process.env.APPLE_PODCASTS_URL || 'https://itunes.apple.com/search') + '?' + new URLSearchParams({ media: 'podcast', limit: '15', term: q }), { signal: AbortSignal.timeout(10000) });
|
||||
if (!r.ok) throw new Error('status ' + r.status);
|
||||
return ((await r.json()).results || []).filter(x => /^https?:\/\//i.test(x.feedUrl || '')).map(x => ({ title: x.collectionName, author: x.artistName, feedUrl: x.feedUrl, image: x.artworkUrl100 }));
|
||||
return ((await r.json()).results || []).filter(x => /^https?:\/\//i.test(x.feedUrl || '')).map(x => ({ title: x.collectionName, author: x.artistName, feedUrl: x.feedUrl, image: x.artworkUrl100, explicit: x.collectionExplicitness === 'explicit' || x.trackExplicitness === 'explicit' }));
|
||||
}
|
||||
const idOf = o => o && (o.ID !== undefined ? o.ID : o.id);
|
||||
async function podgrab(p, init = {}) {
|
||||
@@ -63,7 +63,8 @@ async function handle(req, res) {
|
||||
if (pi.status === 'rejected' && ap.status === 'rejected') return json(res, 502, { error: 'The podcast directory did not answer' });
|
||||
const seen = new Set(), out = [];
|
||||
for (const x of [...(pi.value || []), ...(ap.value || [])]) { const k = x.feedUrl.replace(/\/+$/, '').toLowerCase(); if (!seen.has(k)) { seen.add(k); out.push(x); } }
|
||||
return json(res, 200, out.slice(0, 30));
|
||||
const me0 = users.current(), safe = !!(me0 && me0.kid && me0.kid.on && me0.kid.safe); // (a child account does not see podcasts marked explicit)
|
||||
return json(res, 200, out.filter(x => !(safe && x.explicit)).slice(0, 30));
|
||||
}
|
||||
if (p === '/discover/topics') return json(res, 200, piOn() ? PI_TOPICS : []);
|
||||
if (p === '/progress') {
|
||||
|
||||
Reference in new issue
Block a user