const api = require('./api'); let polling = false; const pollIntervalMs = Number(process.env.TWITCH_POLL_INTERVAL_MS || 5000); // 5s default // Keep track of which streams we've already announced per guild:user -> { started_at } const announced = new Map(); // key: `${guildId}:${user}` -> { started_at } async function checkGuild(client, guild) { try { // Intentionally quiet: per-guild checking logs are suppressed to avoid spam const settings = await api.getServerSettings(guild.id) || {}; const liveSettings = settings.liveNotifications || {}; if (!liveSettings.enabled) return; const channelId = liveSettings.channelId; const users = (liveSettings.users || []).map(u => u.toLowerCase()).filter(Boolean); if (!channelId || users.length === 0) return; // ask backend for current live streams const query = users.join(','); const streams = await api._rawGetTwitchStreams ? api._rawGetTwitchStreams(query) : null; // If the helper isn't available, try backend proxy let live = []; if (streams) live = streams.filter(s => s.is_live); else { try { const resp = await api.tryFetchTwitchStreams(query); live = (resp || []).filter(s => s.is_live); } catch (e) { live = []; } } if (!live || live.length === 0) { // No live streams: ensure any announced keys for these users are cleared so they can be re-announced later for (const u of users) { const key = `${guild.id}:${u}`; if (announced.has(key)) { announced.delete(key); } } return; } // fetch channel using client to ensure we can reach it let channel = null; try { channel = await client.channels.fetch(channelId); } catch (e) { console.error(`TwitchWatcher: failed to fetch channel ${channelId}:`, e && e.message ? e.message : e); channel = null; } if (!channel) { // Channel not found or inaccessible; skip return; } // Build a map of live logins for quick lookup const liveLogins = new Set(live.map(s => (s.user_login || '').toLowerCase())); // Clear announced entries for users that are no longer live for (const u of users) { const key = `${guild.id}:${u}`; if (!liveLogins.has(u) && announced.has(key)) { announced.delete(key); } } // Announce each live once per live session for (const s of live) { const login = (s.user_login || '').toLowerCase(); const key = `${guild.id}:${login}`; if (announced.has(key)) continue; // already announced for this live session // mark announced for this session announced.set(key, { started_at: s.started_at || new Date().toISOString() }); // Build and send embed try { // Announce without per-guild log spam const { EmbedBuilder } = require('discord.js'); const embed = new EmbedBuilder() .setColor(0x9146FF) .setTitle(s.title || `${s.user_name} is live`) .setURL(s.url) .setAuthor({ name: s.user_name, iconURL: s.profile_image_url || undefined, url: s.url }) .setThumbnail(s.thumbnail_url || s.profile_image_url || undefined) .addFields( { name: 'Category', value: s.game_name || 'Unknown', inline: true }, { name: 'Viewers', value: String(s.viewer_count || 0), inline: true } ) .setDescription(s.description || '') .setFooter({ text: `ehchadservices • Started: ${s.started_at ? new Date(s.started_at).toLocaleString() : 'unknown'}` }); await channel.send({ embeds: [embed] }); console.log(`🔔 Announced live: ${login} - ${(s.title || '').slice(0, 80)}`); } catch (e) { console.error(`TwitchWatcher: failed to send announcement for ${login}:`, e && e.message ? e.message : e); // fallback const msg = `🔴 ${s.user_name} is live: **${s.title}**\nWatch: ${s.url}`; try { await channel.send({ content: msg }); console.log('TwitchWatcher: fallback message sent'); } catch (err) { console.error('TwitchWatcher: fallback send failed:', err && err.message ? err.message : err); } } } } catch (e) { console.error('Error checking guild for live streams:', e && e.message ? e.message : e); } } async function poll(client) { if (polling) return; polling = true; console.log(`🔁 TwitchWatcher started, polling every ${Math.round(pollIntervalMs/1000)}s`); while (polling) { try { const guilds = Array.from(client.guilds.cache.values()); for (const g of guilds) { await checkGuild(client, g).catch(err => { console.error('TwitchWatcher: checkGuild error', err && err.message ? err.message : err); }); } } catch (e) { console.error('Error during twitch poll loop:', e && e.message ? e.message : e); } await new Promise(r => setTimeout(r, pollIntervalMs)); } } function stop() { polling = false; } module.exports = { poll, stop };