/** * 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,video', 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); // Dimensions vidéo (signal d'orientation pour isShortItem) quand // lighthouse les expose — lecture défensive, aucun appel en plus. const vw = Number(item.video?.width ?? item.video?.video_width ?? item.width); const vh = Number(item.video?.height ?? item.video?.video_height ?? item.height); // Phase 1.2 - `release_time` est un timestamp unix (secondes) et etait // deja demande dans `include` mais jamais mappe : `publishedAt` etait donc // TOUJOURS undefined, ce qui rendait le filtre `period=` inoperant sur Odysee. const release = Number(item.release_time); // Phase 1.1 - `video.view_count` remonte par lighthouse quand `video` // est inclus. L'`effective_amount` est un montant LBC (bid), PAS un // nombre de vues : on ne l'utilise jamais comme compteur. const views = Number(item.video?.view_count ?? item.video?.views); 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), ...(Number.isFinite(release) && release > 0 ? { publishedAt: new Date(release * 1000).toISOString() } : {}), ...(Number.isFinite(views) && views >= 0 ? { views: Math.round(views) } : {}), ...(Number.isFinite(vw) && vw > 0 ? { width: vw } : {}), ...(Number.isFinite(vh) && vh > 0 ? { height: vh } : {}), }; }); } catch (error) { console.error('Odysee search error:', error); return []; } } }; export default handler;