const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, ChannelType } = require('discord.js'); module.exports = { data: new SlashCommandBuilder() .setName('streams') .setDescription('Twitch & YouTube live stream notifications') .addSubcommand(sub => sub.setName('add').setDescription('Add a streamer to track') .addStringOption(opt => opt.setName('platform').setDescription('Streaming platform') .addChoices( { name: 'Twitch', value: 'twitch' }, { name: 'YouTube', value: 'youtube' } ).setRequired(true)) .addStringOption(opt => opt.setName('username').setDescription('Streamer username or channel ID').setRequired(true)) .addChannelOption(opt => opt.setName('channel').setDescription('Channel to send notifications').setRequired(true) .addChannelTypes(ChannelType.GuildText, ChannelType.GuildAnnouncement))) .addSubcommand(sub => sub.setName('remove').setDescription('Remove a streamer from tracking') .addStringOption(opt => opt.setName('username').setDescription('Streamer username').setRequired(true))) .addSubcommand(sub => sub.setName('list').setDescription('List all tracked streamers')) .addSubcommand(sub => sub.setName('message').setDescription('Customize the notification message') .addStringOption(opt => opt.setName('username').setDescription('Streamer username').setRequired(true)) .addStringOption(opt => opt.setName('message').setDescription('Custom message ({streamer}, {title}, {game}, {url})').setRequired(true).setMaxLength(500))) .addSubcommand(sub => sub.setName('test').setDescription('Test notification for a streamer') .addStringOption(opt => opt.setName('username').setDescription('Streamer username').setRequired(true))), async execute(interaction, supabase, client) { if (!supabase) { return interaction.reply({ content: 'Database not available.', ephemeral: true }); } if (!interaction.member.permissions.has(PermissionFlagsBits.ManageGuild)) { return interaction.reply({ content: 'You need Manage Server permission to manage stream notifications.', ephemeral: true }); } const subcommand = interaction.options.getSubcommand(); if (subcommand === 'add') { const platform = interaction.options.getString('platform'); const username = interaction.options.getString('username').toLowerCase().trim(); const channel = interaction.options.getChannel('channel'); const { data: existing } = await supabase .from('stream_subscriptions') .select('id') .eq('guild_id', interaction.guildId) .eq('username', username) .eq('platform', platform) .maybeSingle(); if (existing) { return interaction.reply({ content: `${username} is already being tracked on ${platform}.`, ephemeral: true }); } const { count } = await supabase .from('stream_subscriptions') .select('*', { count: 'exact', head: true }) .eq('guild_id', interaction.guildId); if (count >= 25) { return interaction.reply({ content: 'Maximum of 25 streamers per server reached.', ephemeral: true }); } const { error } = await supabase.from('stream_subscriptions').insert({ guild_id: interaction.guildId, channel_id: channel.id, platform, username, added_by: interaction.user.id, }); if (error) { console.error('Stream subscription error:', error); return interaction.reply({ content: 'Failed to add streamer.', ephemeral: true }); } const platformEmoji = platform === 'twitch' ? '<:twitch:🟣>' : '<:youtube:🔴>'; const embed = new EmbedBuilder() .setColor(platform === 'twitch' ? 0x9146ff : 0xff0000) .setTitle('Streamer Added!') .setDescription(`Now tracking **${username}** on ${platform}`) .addFields( { name: 'Platform', value: platform.charAt(0).toUpperCase() + platform.slice(1), inline: true }, { name: 'Notification Channel', value: `${channel}`, inline: true } ) .setFooter({ text: 'You will be notified when they go live!' }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'remove') { const username = interaction.options.getString('username').toLowerCase().trim(); const { data: sub } = await supabase .from('stream_subscriptions') .select('id, platform') .eq('guild_id', interaction.guildId) .eq('username', username) .maybeSingle(); if (!sub) { return interaction.reply({ content: `${username} is not being tracked.`, ephemeral: true }); } const { error } = await supabase .from('stream_subscriptions') .delete() .eq('id', sub.id); if (error) { return interaction.reply({ content: 'Failed to remove streamer.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0xff6600) .setTitle('Streamer Removed') .setDescription(`No longer tracking **${username}** on ${sub.platform}`) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'list') { const { data: subs } = await supabase .from('stream_subscriptions') .select('*') .eq('guild_id', interaction.guildId) .order('created_at', { ascending: true }); if (!subs || subs.length === 0) { const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Stream Subscriptions') .setDescription('No streamers are being tracked.\nUse `/streams add` to add one!') .setTimestamp(); return interaction.reply({ embeds: [embed] }); } const platformEmojis = { twitch: '🟣', youtube: '🔴' }; const subList = subs.map((s, i) => { const emoji = platformEmojis[s.platform] || '📺'; const status = s.is_live ? '🔴 LIVE' : '⚫ Offline'; return `${i + 1}. ${emoji} **${s.username}** (${s.platform})\n Channel: <#${s.channel_id}> | ${status}`; }).join('\n\n'); const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('Stream Subscriptions') .setDescription(subList) .setFooter({ text: `${subs.length}/25 slots used` }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'message') { const username = interaction.options.getString('username').toLowerCase().trim(); const customMessage = interaction.options.getString('message'); const { data: sub } = await supabase .from('stream_subscriptions') .select('id') .eq('guild_id', interaction.guildId) .eq('username', username) .maybeSingle(); if (!sub) { return interaction.reply({ content: `${username} is not being tracked.`, ephemeral: true }); } const { error } = await supabase .from('stream_subscriptions') .update({ custom_message: customMessage, updated_at: new Date().toISOString() }) .eq('id', sub.id); if (error) { return interaction.reply({ content: 'Failed to update message.', ephemeral: true }); } const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('Custom Message Set') .setDescription(`Notification message updated for **${username}**`) .addFields({ name: 'New Message', value: customMessage }) .setFooter({ text: 'Variables: {streamer}, {title}, {game}, {url}' }) .setTimestamp(); await interaction.reply({ embeds: [embed] }); } if (subcommand === 'test') { const username = interaction.options.getString('username').toLowerCase().trim(); const { data: sub } = await supabase .from('stream_subscriptions') .select('*') .eq('guild_id', interaction.guildId) .eq('username', username) .maybeSingle(); if (!sub) { return interaction.reply({ content: `${username} is not being tracked.`, ephemeral: true }); } const channel = interaction.guild.channels.cache.get(sub.channel_id); if (!channel) { return interaction.reply({ content: 'Notification channel not found.', ephemeral: true }); } let message = sub.custom_message || '🔴 **{streamer}** is now live!\n{title}\n{url}'; message = message .replace('{streamer}', username) .replace('{title}', 'Test Stream Title') .replace('{game}', 'Just Chatting') .replace('{url}', sub.platform === 'twitch' ? `https://twitch.tv/${username}` : `https://youtube.com/@${username}/live`); const embed = new EmbedBuilder() .setColor(sub.platform === 'twitch' ? 0x9146ff : 0xff0000) .setTitle(`${username} is now live!`) .setDescription('Test Stream Title') .addFields( { name: 'Game/Category', value: 'Just Chatting', inline: true }, { name: 'Platform', value: sub.platform.charAt(0).toUpperCase() + sub.platform.slice(1), inline: true } ) .setURL(sub.platform === 'twitch' ? `https://twitch.tv/${username}` : `https://youtube.com/@${username}/live`) .setFooter({ text: 'TEST NOTIFICATION' }) .setTimestamp(); try { await channel.send({ content: message, embeds: [embed] }); await interaction.reply({ content: `Test notification sent to ${channel}!`, ephemeral: true }); } catch (err) { await interaction.reply({ content: 'Failed to send test notification. Check bot permissions.', ephemeral: true }); } } }, };