/** * Twitch provider using Helix API */ const handler = { id: 'tw', label: 'Twitch', /** * @param {string} q * @param {{ limit: number, page?: number }} opts * @returns {Promise>} */ async search(q, opts) { const { limit = 10, page = 1 } = opts || {}; try { // First, get OAuth token const authResponse = await fetch('https://id.twitch.tv/oauth2/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ client_id: process.env.TWITCH_CLIENT_ID, client_secret: process.env.TWITCH_CLIENT_SECRET, grant_type: 'client_credentials' }) }); if (!authResponse.ok) { throw new Error(`Twitch auth error: ${authResponse.status}`); } const { access_token } = await authResponse.json(); // Then search channels with cursor-based pagination const perPage = Math.min(Math.max(1, Number(limit || 10)), 100); const targetPage = Math.max(1, Number(page || 1)); let after = ''; let currentPage = 1; let lastData = []; while (currentPage <= targetPage) { const params = new URLSearchParams({ query: q, first: String(perPage) }); if (after) params.set('after', after); const response = await fetch( `https://api.twitch.tv/helix/search/channels?` + params.toString(), { headers: { 'Client-ID': process.env.TWITCH_CLIENT_ID, 'Authorization': `Bearer ${access_token}` } } ); if (!response.ok) { throw new Error(`Twitch API error: ${response.status}`); } const data = await response.json(); if (currentPage === targetPage) { lastData = Array.isArray(data.data) ? data.data : []; break; } const nextCursor = data?.pagination?.cursor; if (!nextCursor) { lastData = []; break; } after = String(nextCursor); currentPage++; } return lastData.map(item => ({ title: item.title || item.display_name, id: item.id, url: `https://www.twitch.tv/${item.broadcaster_login}`, thumbnail: item.thumbnail_url?.replace('{width}x{height}', '440x248'), uploaderName: item.display_name, type: 'stream', isLive: item.is_live || item.started_at })); } catch (error) { console.error('Twitch search error:', error); return []; } } }; export default handler;