Fixed Twitch Live notis
This commit is contained in:
@@ -60,41 +60,86 @@ async function fetchUserInfo(login) {
|
||||
|
||||
let polling = false;
|
||||
const pollIntervalMs = Number(process.env.TWITCH_POLL_INTERVAL_MS || 5000); // 5s default
|
||||
const debugMode = false; // Debug logging disabled
|
||||
|
||||
// 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) {
|
||||
const guildId = guild.id;
|
||||
const guildName = guild.name;
|
||||
|
||||
try {
|
||||
// Intentionally quiet: per-guild checking logs are suppressed to avoid spam
|
||||
const settings = await api.getServerSettings(guild.id) || {};
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Checking guild ${guildName} (${guildId})`);
|
||||
|
||||
const settings = await api.getServerSettings(guildId) || {};
|
||||
const liveSettings = settings.liveNotifications || {};
|
||||
if (!liveSettings.enabled) return;
|
||||
|
||||
if (debugMode) {
|
||||
console.log(`🔍 [DEBUG] TwitchWatcher: Guild ${guildName} settings:`, {
|
||||
enabled: liveSettings.enabled,
|
||||
channelId: liveSettings.channelId,
|
||||
usersCount: (liveSettings.users || []).length,
|
||||
hasCustomMessage: !!liveSettings.customMessage,
|
||||
hasDefaultMessage: !!liveSettings.message
|
||||
});
|
||||
}
|
||||
|
||||
if (!liveSettings.enabled) {
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Live notifications disabled for ${guildName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const channelId = liveSettings.channelId;
|
||||
const users = (liveSettings.users || []).map(u => u.toLowerCase()).filter(Boolean);
|
||||
if (!channelId || users.length === 0) return;
|
||||
|
||||
if (debugMode) {
|
||||
console.log(`🔍 [DEBUG] TwitchWatcher: Guild ${guildName} - Channel: ${channelId}, Users: [${users.join(', ')}]`);
|
||||
}
|
||||
|
||||
if (!channelId || users.length === 0) {
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Skipping ${guildName} - ${!channelId ? 'No channel configured' : 'No users configured'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// ask backend for current live streams
|
||||
const query = users.join(',');
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Fetching streams for query: ${query}`);
|
||||
|
||||
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 {
|
||||
if (streams && Array.isArray(streams)) {
|
||||
live = streams.filter(s => s.is_live);
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Found ${live.length} live streams via _rawGetTwitchStreams`);
|
||||
} else {
|
||||
if (debugMode && streams) {
|
||||
console.log(`🔍 [DEBUG] TwitchWatcher: _rawGetTwitchStreams returned non-array:`, typeof streams, streams);
|
||||
}
|
||||
try {
|
||||
const resp = await api.tryFetchTwitchStreams(query);
|
||||
live = (resp || []).filter(s => s.is_live);
|
||||
live = (Array.isArray(resp) ? resp : []).filter(s => s.is_live);
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Found ${live.length} live streams via tryFetchTwitchStreams`);
|
||||
} catch (e) {
|
||||
console.error(`❌ TwitchWatcher: Failed to fetch streams for ${guildName}:`, e && e.message ? e.message : e);
|
||||
live = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (debugMode && live.length > 0) {
|
||||
console.log(`🔍 [DEBUG] TwitchWatcher: Live streams:`, live.map(s => `${s.user_login} (${s.viewer_count} viewers)`));
|
||||
}
|
||||
|
||||
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}`;
|
||||
const key = `${guildId}:${u}`;
|
||||
if (announced.has(key)) {
|
||||
announced.delete(key);
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Cleared announcement for ${u} in ${guildName} (no longer live)`);
|
||||
}
|
||||
}
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: No live streams found for ${guildName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,16 +148,28 @@ async function checkGuild(client, guild) {
|
||||
let channel = null;
|
||||
try {
|
||||
channel = await client.channels.fetch(channelId);
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Successfully fetched channel ${channel.name} (${channelId}) in ${guildName}`);
|
||||
|
||||
if (channel.type !== 0) { // 0 is text channel
|
||||
console.error(`TwitchWatcher: channel ${channelId} is not a text channel (type: ${channel.type})`);
|
||||
console.error(`❌ TwitchWatcher: Channel ${channelId} in ${guildName} is not a text channel (type: ${channel.type})`);
|
||||
channel = null;
|
||||
} else {
|
||||
// Check if bot has permission to send messages
|
||||
const permissions = channel.permissionsFor(client.user);
|
||||
if (!permissions || !permissions.has('SendMessages')) {
|
||||
console.error(`❌ TwitchWatcher: Bot lacks SendMessages permission in channel ${channel.name} (${channelId}) for ${guildName}`);
|
||||
channel = null;
|
||||
} else if (debugMode) {
|
||||
console.log(`🔍 [DEBUG] TwitchWatcher: Bot has SendMessages permission in ${channel.name}`);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`TwitchWatcher: failed to fetch channel ${channelId}:`, e && e.message ? e.message : e);
|
||||
console.error(`❌ TwitchWatcher: Failed to fetch channel ${channelId} for ${guildName}:`, e && e.message ? e.message : e);
|
||||
channel = null;
|
||||
}
|
||||
if (!channel) {
|
||||
// Channel not found or inaccessible; skip
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Skipping announcements for ${guildName} - channel unavailable`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -121,40 +178,51 @@ async function checkGuild(client, guild) {
|
||||
|
||||
// Clear announced entries for users that are no longer live
|
||||
for (const u of users) {
|
||||
const key = `${guild.id}:${u}`;
|
||||
const key = `${guildId}:${u}`;
|
||||
if (!liveLogins.has(u) && announced.has(key)) {
|
||||
announced.delete(key);
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Cleared announcement for ${u} in ${guildName} (stream ended)`);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
const key = `${guildId}:${login}`;
|
||||
if (announced.has(key)) {
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Skipping ${login} in ${guildName} - already announced`);
|
||||
continue; // already announced for this live session
|
||||
}
|
||||
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Preparing announcement for ${login} in ${guildName}`);
|
||||
|
||||
// mark announced for this session
|
||||
announced.set(key, { started_at: s.started_at || new Date().toISOString() });
|
||||
|
||||
// Build and send embed (standardized layout)
|
||||
try {
|
||||
// Announce without per-guild log spam
|
||||
const { EmbedBuilder } = require('discord.js');
|
||||
// Attempt to enrich with user bio (description) if available
|
||||
let bio = '';
|
||||
try {
|
||||
const info = await fetchUserInfo(login);
|
||||
if (info && info.description) bio = info.description.slice(0, 200);
|
||||
} catch (_) {}
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Fetched user info for ${login} - bio length: ${bio.length}`);
|
||||
} catch (e) {
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Failed to fetch user info for ${login}:`, e && e.message ? e.message : e);
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x9146FF)
|
||||
.setColor('#6441A5') // Twitch purple
|
||||
.setAuthor({ name: s.user_name, iconURL: s.profile_image_url || undefined, url: s.url })
|
||||
.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)
|
||||
.setThumbnail(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 }
|
||||
)
|
||||
.setImage(s.thumbnail_url ? s.thumbnail_url.replace('{width}', '640').replace('{height}', '360') + `?t=${Date.now()}` : null)
|
||||
.setDescription(bio || (s.description || '').slice(0, 200))
|
||||
.setFooter({ text: `ehchadservices • Started: ${s.started_at ? new Date(s.started_at).toLocaleString() : 'unknown'}` });
|
||||
|
||||
@@ -167,43 +235,75 @@ async function checkGuild(client, guild) {
|
||||
} else {
|
||||
prefixMsg = `🔴 ${s.user_name} is now live!`;
|
||||
}
|
||||
|
||||
// Replace template variables in custom messages
|
||||
prefixMsg = prefixMsg
|
||||
.replace(/\{user\}/g, s.user_name || login)
|
||||
.replace(/\{title\}/g, s.title || 'Untitled Stream')
|
||||
.replace(/\{category\}/g, s.game_name || 'Unknown')
|
||||
.replace(/\{viewers\}/g, String(s.viewer_count || 0));
|
||||
|
||||
if (debugMode) {
|
||||
console.log(`🔍 [DEBUG] TwitchWatcher: Sending announcement for ${login} in ${guildName} to #${channel.name}`);
|
||||
console.log(`🔍 [DEBUG] TwitchWatcher: Message content: "${prefixMsg}"`);
|
||||
}
|
||||
|
||||
// Ensure we always hyperlink the title via embed; prefix is optional add above embed
|
||||
const payload = prefixMsg ? { content: prefixMsg, embeds: [embed] } : { embeds: [embed] };
|
||||
await channel.send(payload);
|
||||
console.log(`🔔 Announced live: ${login} - ${(s.title || '').slice(0, 80)}`);
|
||||
console.log(`🔔 TwitchWatcher: Successfully announced ${login} in ${guildName} - "${(s.title || '').slice(0, 80)}"`);
|
||||
} catch (e) {
|
||||
console.error(`TwitchWatcher: failed to send announcement for ${login}:`, e && e.message ? e.message : e);
|
||||
console.error(`❌ TwitchWatcher: Failed to send announcement for ${login} in ${guildName}:`, 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); }
|
||||
try {
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Attempting fallback message for ${login} in ${guildName}`);
|
||||
await channel.send({ content: msg });
|
||||
console.log(`🔔 TwitchWatcher: Fallback message sent for ${login} in ${guildName}`);
|
||||
} catch (err) {
|
||||
console.error(`❌ TwitchWatcher: Fallback send failed for ${login} in ${guildName}:`, err && err.message ? err.message : err);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error checking guild for live streams:', e && e.message ? e.message : e);
|
||||
console.error(`❌ TwitchWatcher: Error checking guild ${guildName} (${guildId}) 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`);
|
||||
console.log(`🔁 TwitchWatcher started, polling every ${Math.round(pollIntervalMs/1000)}s${debugMode ? ' (DEBUG MODE ENABLED)' : ''}`);
|
||||
|
||||
// Initial check on restart: send messages for currently live users
|
||||
try {
|
||||
const guilds = Array.from(client.guilds.cache.values());
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Initial check for ${guilds.length} guilds`);
|
||||
|
||||
for (const g of guilds) {
|
||||
await checkGuild(client, g).catch(err => { console.error('TwitchWatcher: initial checkGuild error', err && err.message ? err.message : err); });
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Initial check for guild ${g.name} (${g.id})`);
|
||||
await checkGuild(client, g).catch(err => {
|
||||
console.error(`❌ TwitchWatcher: Initial checkGuild error for ${g.name}:`, err && err.message ? err.message : err);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error during initial twitch check:', e && e.message ? e.message : e);
|
||||
console.error('❌ TwitchWatcher: Error during initial twitch check:', e && e.message ? e.message : e);
|
||||
}
|
||||
|
||||
while (polling) {
|
||||
try {
|
||||
const guilds = Array.from(client.guilds.cache.values());
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Polling cycle starting for ${guilds.length} guilds`);
|
||||
|
||||
for (const g of guilds) {
|
||||
await checkGuild(client, g).catch(err => { console.error('TwitchWatcher: checkGuild error', err && err.message ? err.message : err); });
|
||||
await checkGuild(client, g).catch(err => {
|
||||
console.error(`❌ TwitchWatcher: checkGuild error for ${g.name}:`, err && err.message ? err.message : err);
|
||||
});
|
||||
}
|
||||
|
||||
if (debugMode) console.log(`🔍 [DEBUG] TwitchWatcher: Polling cycle completed, waiting ${Math.round(pollIntervalMs/1000)}s`);
|
||||
} catch (e) {
|
||||
console.error('Error during twitch poll loop:', e && e.message ? e.message : e);
|
||||
console.error('❌ TwitchWatcher: Error during twitch poll loop:', e && e.message ? e.message : e);
|
||||
}
|
||||
await new Promise(r => setTimeout(r, pollIntervalMs));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user