/** * Minimal Odysee provider handler */ const handler = { id: 'od', label: 'Odysee', /** * @param {string} q * @param {{ limit: number, page?: number }} opts * @returns {Promise>} */ async search(q, opts) { const { limit = 10, page = 1 } = opts || {}; try { const perPage = Math.min(Math.max(1, Number(limit || 10)), 50); const pageNum = Math.max(1, Number(page || 1)); const params = new URLSearchParams({ s: q, size: perPage.toString(), from: ((pageNum - 1) * perPage).toString(), include: 'channel,thumbnail_url,title,description,duration,release_time,claimId,name', mediaType: 'video' }); const response = await fetch(`https://lighthouse.odysee.tv/search?${params.toString()}`); if (!response.ok) { throw new Error(`Odysee API error: ${response.status}`); } const data = await response.json(); return (Array.isArray(data) ? data : []).map(item => { const rawThumb = item.thumbnail_url || ''; const thumbnail = rawThumb ? rawThumb.startsWith('http') ? rawThumb : `https://thumbnails.odycdn.com/optimize/s:390:0/quality:85/plain/${rawThumb.replace(/^\//, '')}` : undefined; const name = item.name || ''; const claimId = item.claimId || item.claim_id || ''; const urlSegment = claimId ? `${encodeURIComponent(name)}:${claimId}` : encodeURIComponent(name); return { title: item.title || name, id: claimId || name, url: claimId ? `https://odysee.com/${urlSegment}` : `https://odysee.com/${encodeURIComponent(name)}`, thumbnail, uploaderName: item.channel || undefined, type: 'video', duration: typeof item.duration === 'number' && item.duration > 0 ? Math.round(item.duration) : (typeof item.video?.duration === 'number' && item.video.duration > 0 ? Math.round(item.video.duration) : undefined) }; }); } catch (error) { console.error('Odysee search error:', error); return []; } } }; export default handler;