Add bulk import of a YouTube playlist URL
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
* GET /api/user/data?fp=<fp> retrieve stored playlists + history
|
||||
* POST /api/playlist/share share a single playlist → { ok, code }
|
||||
* GET /api/playlist/shared?code=<code> retrieve shared playlist → { ok, code, playlist, createdAt }
|
||||
* GET /api/playlist/expand?url=<url> expand YouTube playlist → { ok, title, entries, truncated? }
|
||||
* GET /* serve frontend/public static files
|
||||
*
|
||||
* JSON shapes mirror the Tauri (Rust) bridge exactly so the existing app.js
|
||||
@@ -730,6 +731,62 @@ app.get('/api/playlist/shared', async (c) => {
|
||||
}
|
||||
});
|
||||
|
||||
function isYoutubeHost(hostname) {
|
||||
const h = (hostname || '').toLowerCase();
|
||||
return h === 'youtube.com' || h.endsWith('.youtube.com') ||
|
||||
h === 'youtu.be' || h.endsWith('.youtu.be');
|
||||
}
|
||||
|
||||
function isValidYoutubeUrl(rawUrl) {
|
||||
if (typeof rawUrl !== 'string' || !rawUrl.trim()) return false;
|
||||
try {
|
||||
const u = new URL(rawUrl.trim());
|
||||
return (u.protocol === 'http:' || u.protocol === 'https:') && isYoutubeHost(u.hostname);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/playlist/expand?url=<youtube playlist url>
|
||||
// Expands a YouTube playlist URL into slim video cards without resolving streams.
|
||||
app.get('/api/playlist/expand', async (c) => {
|
||||
const url = (c.req.query('url') || '').trim();
|
||||
if (!url || !isValidYoutubeUrl(url)) {
|
||||
return c.json({ ok: false, error: 'invalid YouTube URL' }, 400);
|
||||
}
|
||||
|
||||
try {
|
||||
const out = await runYtdlp([
|
||||
url,
|
||||
'--dump-json', '--flat-playlist',
|
||||
'--no-warnings', '--ignore-errors',
|
||||
'--playlist-end', '201',
|
||||
]);
|
||||
const parsed = parseCards(out);
|
||||
let title = '';
|
||||
for (const line of out.split('\n')) {
|
||||
const t = line.trim();
|
||||
if (!t) continue;
|
||||
try {
|
||||
const j = JSON.parse(t);
|
||||
title = title || pick(j, 'playlist_title', 'playlist');
|
||||
if (title) break;
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
const truncated = parsed.length > 200;
|
||||
const entries = parsed.slice(0, 200);
|
||||
const resp = {
|
||||
ok: true,
|
||||
title: title || '',
|
||||
entries,
|
||||
};
|
||||
if (truncated) resp.truncated = true;
|
||||
return c.json(resp);
|
||||
} catch (err) {
|
||||
return c.json({ ok: false, error: err.message }, 502);
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/user/sync
|
||||
// Body: { fingerprint, playlists?, recentVideo?, appVersion? }
|
||||
app.post('/api/user/sync', async (c) => {
|
||||
|
||||
Reference in New Issue
Block a user