/** * Minimal PeerTube provider handler */ const handler = { id: 'pt', label: 'PeerTube', /** * @param {string} q * @param {{ limit: number, page?: number }} opts * @returns {Promise>} */ async search(q, opts) { const { limit = 10, page = 1 } = opts || {}; try { // Use PeerTube API search const response = await fetch( `https://sepiasearch.org/api/v1/search/videos?` + new URLSearchParams({ search: q, count: Math.min(limit, 50).toString(), start: Math.max(0, (Math.max(1, Number(page || 1)) - 1) * Math.min(limit, 50)).toString() }) ); if (!response.ok) { throw new Error(`PeerTube API error: ${response.status}`); } const data = await response.json(); return (data.data || []).map(item => { const rawThumb = item.thumbnailUrl || item.thumbnailPath || ''; const thumbnail = rawThumb ? rawThumb.startsWith('http') ? rawThumb : rawThumb.startsWith('//') ? `https:${rawThumb}` : new URL(rawThumb, item.url || 'https://sepiasearch.org').toString() : undefined; // Dimensions max des fichiers (signal d'orientation pour isShortItem). // Défensif : sepiasearch n'expose pas toujours `files`. let fw = 0; let fh = 0; try { for (const f of (Array.isArray(item.files) ? item.files : [])) { const w = Number(f?.width); const h = Number(f?.height); if (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0 && w * h >= fw * fh) { fw = w; fh = h; } } } catch {} return { title: item.name, id: item.uuid, url: item.url, thumbnail, uploaderName: (item.account && (item.account.displayName || item.account.name)) || undefined, channelId: (item.channel && (item.channel.name || item.channel.uuid)) || (item.account && item.account.name) || undefined, type: 'video', duration: typeof item.duration === 'number' && item.duration > 0 ? Math.round(item.duration) : undefined, ...(fw > 0 ? { width: fw } : {}), ...(fh > 0 ? { height: fh } : {}), }; }); } catch (error) { console.error('PeerTube search error:', error); return []; } } }; export default handler;