import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, MessageFlags } from 'discord.js'; import { Command } from '../../structures/Command.js'; import { TwitchManager } from '../../structures/TwitchManager.js'; const command: Command = { data: new SlashCommandBuilder() .setName('twitch') .setDescription('Twitch-Befehle.') .addSubcommand(subcommand => subcommand .setName('online') .setDescription('Prüft, ob ein Kanal online ist.') .addStringOption(option => option.setName('channel') .setDescription('Der Name des Twitch-Kanals') .setRequired(true))) .addSubcommand(subcommand => subcommand .setName('add') .setDescription('Kanal überwachen (Admin).') .addStringOption(option => option.setName('streamers') .setDescription('Twitch-Kanäle (kommagetrennt, z.B. shroud, xQc, ninja)') .setRequired(true)) .addStringOption(option => option.setName('message') .setDescription('Optionale Nachricht'))) .addSubcommand(subcommand => subcommand .setName('remove') .setDescription('Streamer aus Überwachung entfernen (Admin).') .addStringOption(option => option.setName('streamers') .setDescription('Twitch-Kanäle (kommagetrennt, z.B. shroud, xQc, ninja)') .setRequired(true))) .addSubcommand(subcommand => subcommand .setName('list') .setDescription('Listet überwachte Kanäle.')) .addSubcommand(subcommand => subcommand .setName('listall') .setDescription('Listet alle überwachten Kanäle des Servers (Admin).')) .addSubcommand(subcommand => subcommand .setName('help') .setDescription('Zeigt Hilfe zu den Twitch-Befehlen an.')), category: 'Public', async execute(interaction: any) { const subcommand = interaction.options.getSubcommand(); const guildId = interaction.guildId; const isAdmin = interaction.memberPermissions?.has(PermissionFlagsBits.ModerateMembers); const DB = interaction.client.DB; if (subcommand === 'help') { const embed = new EmbedBuilder() .setTitle('📺 Twitch Hilfe') .setColor(0x6441a5) .setDescription('Überwache Twitch-Streams und bekomme Benachrichtigungen.') .addFields( { name: '`/twitch online `', value: 'Prüft, ob ein Kanal gerade online ist.', inline: false }, { name: '`/twitch add streamer1, streamer2, ...`', value: 'Überwacht Twitch-Kanäle in diesem Kanal (Mod).', inline: false }, { name: '`/twitch remove streamer1, streamer2, ...`', value: 'Entfernt Twitch-Kanäle (Mod).', inline: false }, { name: '`/twitch list`', value: 'Zeigt überwachte Kanäle in diesem Kanal.', inline: false }, { name: '`/twitch listall`', value: 'Zeigt alle überwachten Kanäle des Servers (Mod).', inline: false }, { name: 'Beispiele', value: '`/twitch add shroud, ninja`\n`/twitch add #general shroud, ninja "Live!"`\n`/twitch remove shroud, ninja`', inline: false } ) .setTimestamp(); await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] }); return; } if (subcommand === 'online') { const channelName = interaction.options.getString('channel')!.toLowerCase(); await interaction.deferReply(); const stream = await TwitchManager.fetchStreamData(channelName); if (!stream) { await interaction.editReply(`🔴 Der Kanal **${channelName}** ist offline.`); return; } const thumbnailUrl = stream.thumbnail_url .replace('{width}', '1280') .replace('{height}', '720') + `?t=${Date.now()}`; const embed = new EmbedBuilder() .setTitle(`🟢 ${stream.user_name} ist ONLINE!`) .setURL(`https://twitch.tv/${stream.user_login}`) .setColor(0x00FF00) .setImage(thumbnailUrl) .addFields( { name: 'Titel', value: stream.title || 'Kein Titel', inline: false }, { name: 'Kategorie', value: stream.game_name || 'Unbekannt', inline: true }, { name: 'Zuschauer', value: stream.viewer_count.toString(), inline: true } ) .setTimestamp(); await interaction.editReply({ embeds: [embed] }); return; } if (subcommand === 'list') { const monitors: any[] = DB.all('SELECT * FROM twitch_monitors WHERE guild_id = ? AND discord_channel_id = ?', guildId, interaction.channelId); if (monitors.length === 0) { await interaction.reply({ content: 'Keine Twitch-Kanäle überwacht.', flags: [MessageFlags.Ephemeral] }); return; } const list = monitors.map((m: any) => `• **${m.channel_name}**`).join('\n'); const embed = new EmbedBuilder() .setTitle('📺 Überwachte Twitch-Kanäle') .setColor(0x6441a5) .setDescription(list); await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] }); return; } if (subcommand === 'listall') { if (!isAdmin) { await interaction.reply({ content: '❌ Keine Berechtigung.', flags: [MessageFlags.Ephemeral] }); return; } const monitors: any[] = DB.all('SELECT * FROM twitch_monitors WHERE guild_id = ? ORDER BY channel_name ASC', guildId); if (monitors.length === 0) { await interaction.reply({ content: 'Keine Twitch-Kanäle überwacht.', flags: [MessageFlags.Ephemeral] }); return; } const embed = new EmbedBuilder() .setTitle('📺 Alle überwachten Twitch-Kanäle') .setColor(0x6441a5) .setTimestamp(); const chunks: string[] = []; const chunkSize = 15; for (let i = 0; i < monitors.length; i += chunkSize) { const chunk = monitors.slice(i, i + chunkSize).map((m: any) => { const channel = interaction.guild.channels.cache.get(m.discord_channel_id); const channelName = channel ? channel.name : m.discord_channel_id; return `• **${m.channel_name}** → #${channelName}`; }).join('\n'); chunks.push(chunk); } embed.setDescription(chunks[0]); await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] }); for (let i = 1; i < chunks.length; i++) { const nextEmbed = new EmbedBuilder() .setColor(0x6441a5) .setDescription(chunks[i]); await interaction.followUp({ embeds: [nextEmbed], flags: [MessageFlags.Ephemeral] }); } return; } // Admin only for add/remove if (!isAdmin) { await interaction.reply({ content: '❌ Keine Berechtigung.', flags: [MessageFlags.Ephemeral] }); return; } if (subcommand === 'add') { const discordChannel = interaction.options.getChannel('channel'); const streamersInput = interaction.options.getString('streamers')!; const message = interaction.options.getString('message'); const discordChannelId = interaction.channelId; // Parse comma-separated streamers const streamers = streamersInput .split(',') .map((s: string) => s.trim().toLowerCase()) .filter((s: string) => s.length > 0); if (streamers.length === 0) { await interaction.reply({ content: '❌ Keine gültigen Streamer angegeben.', flags: [MessageFlags.Ephemeral] }); return; } const results = { added: 0, updated: 0, errors: 0 }; const errors: string[] = []; for (const streamer of streamers) { try { // Check if stream exists const stream = await TwitchManager.fetchStreamData(streamer); if (!stream) { // Streamer might be valid but offline - still add them } // Check if already exists const existing = DB.get('SELECT id FROM twitch_monitors WHERE guild_id = ? AND channel_name = ?', guildId, streamer); DB.run( 'INSERT INTO twitch_monitors (guild_id, channel_name, discord_channel_id, custom_message) VALUES (?, ?, ?, ?) ON CONFLICT(guild_id, channel_name) DO UPDATE SET discord_channel_id = ?, custom_message = ?', guildId, streamer, discordChannelId, message, discordChannelId, message ); if (existing) { results.updated++; } else { results.added++; } } catch (error) { errors.push(streamer); results.errors++; } } let reply = `✅ **${streamers.length}** Twitch-Kanal/Kanäle für <#${discordChannelId}> konfiguriert.`; if (results.added > 0) reply += `\n🆕 Neu: ${results.added}`; if (results.updated > 0) reply += `\n🔄 Aktualisiert: ${results.updated}`; if (results.errors > 0) reply += `\n❌ Fehler: ${results.errors} (${errors.join(', ')})`; await interaction.reply({ content: reply, flags: [MessageFlags.Ephemeral] }); return; } if (subcommand === 'remove') { const streamersInput = interaction.options.getString('streamers')!; const streamers = streamersInput .split(',') .map((s: string) => s.trim().toLowerCase()) .filter((s: string) => s.length > 0); if (streamers.length === 0) { await interaction.reply({ content: '❌ Keine gültigen Streamer angegeben.', flags: [MessageFlags.Ephemeral] }); return; } const results = { removed: 0, notFound: 0 }; const notFound: string[] = []; for (const streamer of streamers) { const result = DB.run('DELETE FROM twitch_monitors WHERE guild_id = ? AND channel_name = ?', guildId, streamer); if (result.changes === 0) { notFound.push(streamer); results.notFound++; } else { results.removed++; } } let reply = ''; if (results.removed > 0) reply += `✅ ${results.removed} Streamer entfernt.`; if (results.notFound > 0) reply += `\n❌ Nicht gefunden: ${notFound.join(', ')}`; await interaction.reply({ content: reply, flags: [MessageFlags.Ephemeral] }); } }, }; export default command;