diff --git a/src/commands/utility/timer.ts b/src/commands/utility/timer.ts index 3be2f95..09a1258 100644 --- a/src/commands/utility/timer.ts +++ b/src/commands/utility/timer.ts @@ -293,7 +293,7 @@ const command: Command = { } const reminders: any[] = DB.all( - 'SELECT r.*, c.name as channel_name FROM reminders r LEFT JOIN channels c ON r.channel_id = c.id WHERE r.guild_id = ? ORDER BY r.target_time ASC', + 'SELECT * FROM reminders WHERE guild_id = ? ORDER BY target_time ASC', guildId ); diff --git a/src/commands/utility/twitch.ts b/src/commands/utility/twitch.ts index 2619641..bdd8474 100644 --- a/src/commands/utility/twitch.ts +++ b/src/commands/utility/twitch.ts @@ -1,4 +1,4 @@ -import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, MessageFlags } from 'discord.js'; +import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, MessageFlags, ChannelType } from 'discord.js'; import { Command } from '../../structures/Command.js'; import { TwitchManager } from '../../structures/TwitchManager.js'; @@ -18,9 +18,13 @@ const command: Command = { subcommand .setName('add') .setDescription('Kanal überwachen (Admin).') - .addStringOption(option => + .addChannelOption(option => option.setName('channel') - .setDescription('Der Name des Twitch-Kanals') + .setDescription('Discord-Kanal für Benachrichtigungen (Standard: aktueller Kanal)') + .setRequired(false)) + .addStringOption(option => + option.setName('streamers') + .setDescription('Twitch-Kanäle (kommagetrennt, z.B. shroud, xQc, ninja)') .setRequired(true)) .addStringOption(option => option.setName('message') @@ -28,15 +32,19 @@ const command: Command = { .addSubcommand(subcommand => subcommand .setName('remove') - .setDescription('Kanal aus Überwachung entfernen (Admin).') + .setDescription('Streamer aus Überwachung entfernen (Admin).') .addStringOption(option => - option.setName('channel') - .setDescription('Der Name des Twitch-Kanals') + 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') @@ -55,10 +63,11 @@ const command: Command = { .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 [nachricht]`', value: 'Überwacht einen Kanal in diesem Kanal (Mod).\nWenn der Streamer live geht, wird benachrichtigt.', inline: false }, - { name: '`/twitch remove `', value: 'Entfernt einen Kanal aus der Überwachung (Mod).', inline: false }, - { name: '`/twitch list`', value: 'Zeigt alle überwachten Kanäle in diesem Kanal an.', inline: false }, - { name: 'Beispiel', value: '`/twitch add shroud Hallo! Shroud ist live!`', inline: false } + { name: '`/twitch add [kanal] streamer1, streamer2, ...`', value: 'Überwacht mehrere Twitch-Kanäle (Mod).\nOptional: Discord-Kanal oder aktueller Kanal.', 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(); @@ -100,7 +109,7 @@ const command: Command = { 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('Keine Twitch-Kanäle überwacht.'); + await interaction.reply({ content: 'Keine Twitch-Kanäle überwacht.', flags: [MessageFlags.Ephemeral] }); return; } @@ -110,32 +119,143 @@ const command: Command = { .setColor(0x6441a5) .setDescription(list); - await interaction.reply({ embeds: [embed] }); + await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] }); return; } - // Admin only + 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 channelName = interaction.options.getString('channel')!.toLowerCase(); + const discordChannel = interaction.options.getChannel('channel'); + const streamersInput = interaction.options.getString('streamers')!; const message = interaction.options.getString('message'); - const discordChannelId = interaction.channelId; + const discordChannelId = discordChannel?.id || interaction.channelId; - 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, channelName, discordChannelId, message, discordChannelId, message - ); - await interaction.reply(`✅ Kanal **${channelName}** wird überwacht.`); + // 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 channelName = interaction.options.getString('channel')!.toLowerCase(); - DB.run('DELETE FROM twitch_monitors WHERE guild_id = ? AND channel_name = ?', guildId, channelName); - await interaction.reply(`✅ Kanal **${channelName}** entfernt.`); + 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] }); } }, };