Files
ECS-FullStack/discord-bot/twitch-watcher.js

127 lines
4.9 KiB
JavaScript

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 };