From 5ee6959662fbdbe0bf80a60d2f5fce211829e30a Mon Sep 17 00:00:00 2001 From: sarah Date: Mon, 23 Mar 2026 10:24:47 +0100 Subject: [PATCH] Add: Welcome and goodbye message module - Add /welcome command with setchannel, add, remove, list, help - Welcome messages on guildMemberAdd event - Goodbye messages on guildMemberRemove event - Auto-use Discord system channel if no custom channel set - Variables: {user}, {username}, {server}, {membercount} - Add welcome_message and goodbye_message to guild_settings Co-Authored-By: Claude Opus 4.6 --- src/commands/utility/welcome.ts | 158 ++++++++++++++++++++++++++++++++ src/events/guildMemberAdd.ts | 34 +++++++ src/events/guildMemberRemove.ts | 34 +++++++ src/structures/Database.ts | 2 + 4 files changed, 228 insertions(+) create mode 100644 src/commands/utility/welcome.ts create mode 100644 src/events/guildMemberAdd.ts create mode 100644 src/events/guildMemberRemove.ts diff --git a/src/commands/utility/welcome.ts b/src/commands/utility/welcome.ts new file mode 100644 index 0000000..c2ca64f --- /dev/null +++ b/src/commands/utility/welcome.ts @@ -0,0 +1,158 @@ +import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, MessageFlags } from 'discord.js'; +import { Command } from '../../structures/Command.js'; + +const command: Command = { + data: new SlashCommandBuilder() + .setName('welcome') + .setDescription('Willkommens- und Abschiedsnachrichten verwalten.') + .addSubcommand(subcommand => + subcommand + .setName('setchannel') + .setDescription('Kanal für Willkommen/Abschied setzen (Mod).') + .addChannelOption(option => + option.setName('channel') + .setDescription('Discord-Kanal') + .setRequired(true))) + .addSubcommand(subcommand => + subcommand + .setName('add') + .setDescription('Nachricht setzen (Mod).') + .addStringOption(option => + option.setName('type') + .setDescription('Art der Nachricht') + .setRequired(true) + .addChoices( + { name: 'Willkommen', value: 'welcome' }, + { name: 'Abschied', value: 'goodbye' } + )) + .addStringOption(option => + option.setName('message') + .setDescription('Die Nachricht. Variablen: {user}, {username}, {server}, {membercount}') + .setRequired(true))) + .addSubcommand(subcommand => + subcommand + .setName('remove') + .setDescription('Nachricht entfernen (Mod).') + .addStringOption(option => + option.setName('type') + .setDescription('Art der Nachricht') + .setRequired(true) + .addChoices( + { name: 'Willkommen', value: 'welcome' }, + { name: 'Abschied', value: 'goodbye' } + ))) + .addSubcommand(subcommand => + subcommand + .setName('list') + .setDescription('Aktuelle Einstellungen anzeigen.')) + .addSubcommand(subcommand => + subcommand + .setName('help') + .setDescription('Hilfe zu Welcome-Befehlen.')), + category: 'Admin', + 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; + + // Ensure guild settings exist + DB.run('INSERT OR IGNORE INTO guild_settings (guild_id) VALUES (?)', guildId); + + if (subcommand === 'help') { + const embed = new EmbedBuilder() + .setTitle('👋 Welcome Hilfe') + .setColor(0x2ecc71) + .setDescription('Begrüße neue Mitglieder oder verabschiede dich von ihnen.') + .addFields( + { name: '`/welcome setchannel <#kanal>`', value: 'Setzt den Kanal für Willkommen/Abschied (Mod).\nStandard: Discord System-Channel.', inline: false }, + { name: '`/welcome add welcome `', value: 'Setzt die Willkommensnachricht (Mod).', inline: false }, + { name: '`/welcome add goodbye `', value: 'Setzt die Abschiedsnachricht (Mod).', inline: false }, + { name: '`/welcome remove welcome|goodbye`', value: 'Entfernt eine Nachricht (Mod).', inline: false }, + { name: '`/welcome list`', value: 'Zeigt aktuelle Einstellungen.', inline: false }, + { name: 'Variablen', value: '`{user}` - Nutzer erwähnen\n`{username}` - Nutzername\n`{server}` - Servername\n`{membercount}` - Mitgliederanzahl', inline: false }, + { name: 'Beispiel', value: '`/welcome add welcome Willkommen {user} auf {server}! Du bist der {membercount}. Nutzer.`', inline: false } + ) + .setTimestamp(); + + await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] }); + return; + } + + if (subcommand === 'list') { + const settings: any = DB.get('SELECT * FROM guild_settings WHERE guild_id = ?', guildId); + + const channel = settings?.welcome_channel + ? `<#${settings.welcome_channel}>` + : 'Nicht gesetzt'; + + const welcomeMsg = settings?.welcome_message || 'Nicht gesetzt'; + const goodbyeMsg = settings?.goodbye_message || 'Nicht gesetzt'; + + const embed = new EmbedBuilder() + .setTitle('👋 Welcome Einstellungen') + .setColor(0x2ecc71) + .setDescription(`Server: **${interaction.guild.name}**`) + .addFields( + { name: 'Kanal', value: channel, inline: true }, + { name: '👋 Willkommen', value: welcomeMsg, inline: false }, + { name: '👋 Abschied', value: goodbyeMsg, inline: false } + ) + .setTimestamp(); + + await interaction.reply({ embeds: [embed], flags: [MessageFlags.Ephemeral] }); + return; + } + + // Admin only for modify commands + if (!isAdmin) { + await interaction.reply({ content: '❌ Keine Berechtigung. Moderatoren erforderlich.', flags: [MessageFlags.Ephemeral] }); + return; + } + + if (subcommand === 'setchannel') { + const channel = interaction.options.getChannel('channel'); + + DB.run( + 'UPDATE guild_settings SET welcome_channel = ? WHERE guild_id = ?', + channel.id, + guildId + ); + + await interaction.reply({ content: `✅ Kanal für Willkommen/Abschied auf ${channel} gesetzt.`, flags: [MessageFlags.Ephemeral] }); + return; + } + + if (subcommand === 'add') { + const type = interaction.options.getString('type') as 'welcome' | 'goodbye'; + const message = interaction.options.getString('message')!; + + const column = type === 'welcome' ? 'welcome_message' : 'goodbye_message'; + + DB.run( + `UPDATE guild_settings SET ${column} = ? WHERE guild_id = ?`, + message, + guildId + ); + + const label = type === 'welcome' ? 'Willkommensnachricht' : 'Abschiedsnachricht'; + await interaction.reply({ content: `✅ ${label} gesetzt:\n\`\`\`\n${message}\n\`\`\``, flags: [MessageFlags.Ephemeral] }); + return; + } + + if (subcommand === 'remove') { + const type = interaction.options.getString('type') as 'welcome' | 'goodbye'; + const column = type === 'welcome' ? 'welcome_message' : 'goodbye_message'; + + DB.run( + `UPDATE guild_settings SET ${column} = NULL WHERE guild_id = ?`, + guildId + ); + + const label = type === 'welcome' ? 'Willkommensnachricht' : 'Abschiedsnachricht'; + await interaction.reply({ content: `✅ ${label} entfernt.`, flags: [MessageFlags.Ephemeral] }); + } + }, +}; + +export default command; \ No newline at end of file diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts new file mode 100644 index 0000000..0360143 --- /dev/null +++ b/src/events/guildMemberAdd.ts @@ -0,0 +1,34 @@ +import { Events, GuildMember, TextChannel, EmbedBuilder } from 'discord.js'; + +export default { + name: Events.GuildMemberAdd, + async execute(member: GuildMember) { + const DB = (member.client as any).DB; + const guildId = member.guild.id; + + const settings: any = DB.get('SELECT * FROM guild_settings WHERE guild_id = ?', guildId); + + // Use custom channel or fall back to Discord's system channel + const channelId = settings?.welcome_channel || member.guild.systemChannelId; + if (!channelId || !settings?.welcome_message) { + return; + } + + const channel = member.guild.channels.cache.get(channelId); + if (!channel || !channel.isTextBased()) { + return; + } + + const message = settings.welcome_message + .replace(/{user}/g, `<@${member.id}>`) + .replace(/{username}/g, member.user.username) + .replace(/{server}/g, member.guild.name) + .replace(/{membercount}/g, member.guild.memberCount.toString()); + + try { + await (channel as TextChannel).send(message); + } catch (error) { + console.error('[WELCOME] Error sending welcome message:', error); + } + }, +}; \ No newline at end of file diff --git a/src/events/guildMemberRemove.ts b/src/events/guildMemberRemove.ts new file mode 100644 index 0000000..63bd631 --- /dev/null +++ b/src/events/guildMemberRemove.ts @@ -0,0 +1,34 @@ +import { Events, GuildMember, TextChannel, EmbedBuilder } from 'discord.js'; + +export default { + name: Events.GuildMemberRemove, + async execute(member: GuildMember) { + const DB = (member.client as any).DB; + const guildId = member.guild.id; + + const settings: any = DB.get('SELECT * FROM guild_settings WHERE guild_id = ?', guildId); + + // Use custom channel or fall back to Discord's system channel + const channelId = settings?.welcome_channel || member.guild.systemChannelId; + if (!channelId || !settings?.goodbye_message) { + return; + } + + const channel = member.guild.channels.cache.get(channelId); + if (!channel || !channel.isTextBased()) { + return; + } + + const message = settings.goodbye_message + .replace(/{user}/g, `<@${member.id}>`) + .replace(/{username}/g, member.user.username) + .replace(/{server}/g, member.guild.name) + .replace(/{membercount}/g, member.guild.memberCount.toString()); + + try { + await (channel as TextChannel).send(message); + } catch (error) { + console.error('[WELCOME] Error sending goodbye message:', error); + } + }, +}; \ No newline at end of file diff --git a/src/structures/Database.ts b/src/structures/Database.ts index 86adbb6..c887dd2 100644 --- a/src/structures/Database.ts +++ b/src/structures/Database.ts @@ -25,6 +25,8 @@ export class DB { prefix TEXT DEFAULT '!', mod_log_channel TEXT, welcome_channel TEXT, + welcome_message TEXT, + goodbye_message TEXT, warn_threshold INTEGER DEFAULT 3, warn_action TEXT DEFAULT 'ban', warn_mute_duration INTEGER DEFAULT 1800