/** * 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 } = 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 streams const response = await fetch( `https://api.twitch.tv/helix/search/channels?` + new URLSearchParams({ query: q, first: Math.min(limit, 100).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(); return data.data?.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;