Files
pixelpoebel/src/commands/utility/twitch.ts
sarah 0435056271
All checks were successful
Auto Build and Push Docker Image / build (push) Successful in 6s
Fix: Remove optional channel option from twitch add
Discord does not support optional channel options in subcommands.
Streamers are now monitored in the channel where the command is executed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 09:56:58 +01:00

259 lines
12 KiB
TypeScript

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 <kanal>`', 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;