32 lines
2.0 KiB
JavaScript
32 lines
2.0 KiB
JavaScript
// Device hints only fill gaps; extractor metadata stays authoritative.
|
|
export function mergeMissing(existing = {}, incoming = {}) {
|
|
const out = { ...existing };
|
|
for (const [key, value] of Object.entries(incoming)) {
|
|
const old = out[key];
|
|
if (old == null || old === '' || old === 0 || (key === 'title' && (old === '(untitled)' || old === out.id))) out[key] = value;
|
|
}
|
|
if (!existing.channel && (existing.uploader || existing.artist)) out.channel = existing.uploader || existing.artist;
|
|
return out;
|
|
}
|
|
export function validateMeta(id, body) {
|
|
if (!/^[A-Za-z0-9_-]{11}$/.test(id) || !body || typeof body !== 'object' || Array.isArray(body) || (body.id != null && body.id !== id) || body.custom || body.upload) throw new Error('Invalid YouTube video');
|
|
const clean = (value, required = false) => {
|
|
if (value == null && !required) return '';
|
|
if (typeof value !== 'string' || value.length > 300 || /[\x00-\x08\x0b\x0c\x0e-\x1f]/.test(value)) throw new Error('Invalid title or artist');
|
|
const text = value.trim(); if (required && (!text || text === '(untitled)' || text === id)) throw new Error('A video title is required'); return text;
|
|
};
|
|
const title = clean(body.title, true), channel = clean(body.channel || body.artist);
|
|
const duration = body.duration ?? 0;
|
|
if (typeof duration !== 'number' || !Number.isFinite(duration) || duration < 0 || duration > 86400) throw new Error('Invalid duration');
|
|
let thumbnail = `https://i.ytimg.com/vi/${id}/hqdefault.jpg`;
|
|
if (body.thumbnail) {
|
|
if (typeof body.thumbnail !== 'string' || body.thumbnail.length > 2048) throw new Error('Invalid thumbnail');
|
|
if (body.thumbnail !== `/api/catalog/${id}/thumbnail`) {
|
|
let url; try { url = new URL(body.thumbnail); } catch { throw new Error('Invalid thumbnail'); }
|
|
if (url.protocol !== 'https:' || !/^(?:i|i\d)\.ytimg\.com$/.test(url.hostname) || url.port || url.username || url.password) throw new Error('Invalid thumbnail');
|
|
thumbnail = url.href;
|
|
}
|
|
}
|
|
return { id, title, channel, duration, thumbnail };
|
|
}
|