Enhance: Twitch multi-streamer support and fix timer listall
All checks were successful
Auto Build and Push Docker Image / build (push) Successful in 7s
All checks were successful
Auto Build and Push Docker Image / build (push) Successful in 7s
- Twitch add: multiple streamers via comma separation - Twitch add: optional discord channel (defaults to current) - Twitch remove: multiple streamers via comma separation - Twitch listall: new command showing all server streamers (admin) - Timer listall: fix SQL error by removing invalid JOIN Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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 <kanal>`', value: 'Prüft, ob ein Kanal gerade online ist.', inline: false },
|
||||
{ name: '`/twitch add <kanal> [nachricht]`', value: 'Überwacht einen Kanal in diesem Kanal (Mod).\nWenn der Streamer live geht, wird benachrichtigt.', inline: false },
|
||||
{ name: '`/twitch remove <kanal>`', 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] });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user