From 524a6cc63340aac0d8d4e68fab0e4d7a393411e4 Mon Sep 17 00:00:00 2001 From: chad Date: Fri, 3 Oct 2025 18:00:47 -0400 Subject: [PATCH] added welcome-leave commands and updated activitytypes --- backend/index.js | 39 ++++++++++---- checklist.md | 19 +++++++ discord-bot/commands/config-leave.js | 63 ++++++++++++++++++++++ discord-bot/commands/ping.js | 1 + discord-bot/commands/setup-welcome.js | 63 ++++++++++++++++++++++ discord-bot/commands/view-welcome-leave.js | 34 ++++++++++++ discord-bot/deploy-commands.js | 21 ++++++-- discord-bot/events/guildMemberAdd.js | 10 ++-- discord-bot/events/guildMemberRemove.js | 10 ++-- discord-bot/events/ready.js | 26 +++++++-- discord-bot/handlers/command-handler.js | 2 + 11 files changed, 258 insertions(+), 30 deletions(-) create mode 100644 discord-bot/commands/config-leave.js create mode 100644 discord-bot/commands/setup-welcome.js create mode 100644 discord-bot/commands/view-welcome-leave.js diff --git a/backend/index.js b/backend/index.js index 196b477..6b62fb1 100644 --- a/backend/index.js +++ b/backend/index.js @@ -143,28 +143,45 @@ app.get('/api/servers/:guildId/channels', async (req, res) => { app.get('/api/servers/:guildId/welcome-leave-settings', (req, res) => { const { guildId } = req.params; const db = readDb(); - const settings = db[`${guildId}_welcome_leave`] || { + const settings = db[guildId] || {}; + + const welcomeLeaveSettings = { welcome: { - enabled: false, - channel: '', - message: 'Welcome to the server, {user}!', - customMessage: '', + enabled: settings.welcomeEnabled || false, + channel: settings.welcomeChannel || '', + message: settings.welcomeMessage || 'Welcome to the server, {user}!', + customMessage: settings.welcomeCustomMessage || '', }, leave: { - enabled: false, - channel: '', - message: '{user} has left the server.', - customMessage: '', + enabled: settings.leaveEnabled || false, + channel: settings.leaveChannel || '', + message: settings.leaveMessage || '{user} has left the server.', + customMessage: settings.leaveCustomMessage || '', }, }; - res.json(settings); + + res.json(welcomeLeaveSettings); }); app.post('/api/servers/:guildId/welcome-leave-settings', (req, res) => { const { guildId } = req.params; const newSettings = req.body; const db = readDb(); - db[`${guildId}_welcome_leave`] = newSettings; + + if (!db[guildId]) { + db[guildId] = {}; + } + + db[guildId].welcomeEnabled = newSettings.welcome.enabled; + db[guildId].welcomeChannel = newSettings.welcome.channel; + db[guildId].welcomeMessage = newSettings.welcome.message; + db[guildId].welcomeCustomMessage = newSettings.welcome.customMessage; + + db[guildId].leaveEnabled = newSettings.leave.enabled; + db[guildId].leaveChannel = newSettings.leave.channel; + db[guildId].leaveMessage = newSettings.leave.message; + db[guildId].leaveCustomMessage = newSettings.leave.customMessage; + writeDb(db); res.json({ success: true }); }); diff --git a/checklist.md b/checklist.md index 5f02e4c..b2a4a73 100644 --- a/checklist.md +++ b/checklist.md @@ -62,6 +62,9 @@ - [x] Implement event handler - [x] Set bot status on ready event - [x] Automatically register slash commands on server join. +- [x] On startup, automatically register all slash commands and remove any obsolete commands. +- [x] Add a mechanism to enable or disable commands from being registered and displayed. +- [x] In `ready.js`, set the bot's activity to change every 3 seconds with the following streaming activities: "Watch EhChad Live!", "Follow EhChad!", "/help", and "EhChadServices", all pointing to `https://twitch.tv/ehchad`. ## Features - [x] **Welcome/Leave Messages** @@ -79,3 +82,19 @@ - [x] **Bot Integration:** - [x] Connect frontend settings to the backend. - [x] Implement bot logic to send welcome/leave messages based on server settings. + - [x] **Slash Command Integration:** + - [x] ~~Create a `/config-welcome` slash command.~~ + - [x] ~~Add a subcommand to `set-channel` for welcome messages.~~ + - [x] ~~Add a subcommand to `set-message` with options for default and custom messages.~~ + - [x] ~~Add a subcommand to `disable` welcome messages.~~ + - [x] ~~Create a `/config-leave` slash command.~~ + - [x] ~~Add a subcommand to `set-channel` for leave messages.~~ + - [x] ~~Add a subcommand to `set-message` with options for default and custom messages.~~ + - [x] ~~Add a subcommand to `disable` leave messages.~~ + - [x] ~~Create a `/view-config` slash command to display the current welcome and leave channels.~~ + - [ ] Refactor `/config-welcome` to `/setup-welcome` with interactive setup for channel and message. + - [ ] Refactor `/config-leave` to `/setup-leave` with interactive setup for channel and message. + - [ ] Rename `/view-config` to `/view-welcome-leave`. + - [x] Ensure settings updated via slash commands are reflected on the frontend. + - [x] Ensure settings updated via the frontend are reflected in the bot's behavior. + - [x] Persist the selected message option (default or custom) for welcome and leave messages. \ No newline at end of file diff --git a/discord-bot/commands/config-leave.js b/discord-bot/commands/config-leave.js new file mode 100644 index 0000000..87af3d1 --- /dev/null +++ b/discord-bot/commands/config-leave.js @@ -0,0 +1,63 @@ +const { SlashCommandBuilder } = require('discord.js'); +const { readDb, writeDb } = require('../../backend/db.js'); + +module.exports = { + name: 'config-leave', + description: 'Configure the leave message for this server.', + enabled: true, + builder: new SlashCommandBuilder() + .setName('config-leave') + .setDescription('Configure the leave message for this server.') + .addSubcommand(subcommand => + subcommand + .setName('set-channel') + .setDescription('Set the channel for leave messages.') + .addChannelOption(option => + option.setName('channel') + .setDescription('The channel to send leave messages to.') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName('set-message') + .setDescription('Set the leave message.') + .addStringOption(option => + option.setName('message') + .setDescription('The leave message. Use {user} for username and {server} for server name.') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName('disable') + .setDescription('Disable leave messages.') + ), + async execute(interaction) { + const db = readDb(); + const guildId = interaction.guildId; + const subcommand = interaction.options.getSubcommand(); + + if (!db[guildId]) { + db[guildId] = {}; + } + + if (subcommand === 'set-channel') { + const channel = interaction.options.getChannel('channel'); + db[guildId].leaveChannel = channel.id; + db[guildId].leaveEnabled = true; + writeDb(db); + await interaction.reply(`Leave channel set to ${channel}.`); + } else if (subcommand === 'set-message') { + const message = interaction.options.getString('message'); + db[guildId].leaveMessage = message; + db[guildId].leaveEnabled = true; + writeDb(db); + await interaction.reply(`Leave message set to: "${message}"`); + } else if (subcommand === 'disable') { + db[guildId].leaveEnabled = false; + writeDb(db); + await interaction.reply('Leave messages disabled.'); + } + }, +}; diff --git a/discord-bot/commands/ping.js b/discord-bot/commands/ping.js index b1cfe34..ba6e9b1 100644 --- a/discord-bot/commands/ping.js +++ b/discord-bot/commands/ping.js @@ -3,6 +3,7 @@ const { readDb } = require('../../backend/db.js'); module.exports = { name: 'ping', description: 'Replies with Pong!', + enabled: true, execute(interaction) { const db = readDb(); const settings = db[interaction.guildId] || { pingCommand: false }; diff --git a/discord-bot/commands/setup-welcome.js b/discord-bot/commands/setup-welcome.js new file mode 100644 index 0000000..bb57918 --- /dev/null +++ b/discord-bot/commands/setup-welcome.js @@ -0,0 +1,63 @@ +const { SlashCommandBuilder } = require('discord.js'); +const { readDb, writeDb } = require('../../backend/db.js'); + +module.exports = { + name: 'config-welcome', + description: 'Configure the welcome message for this server.', + enabled: true, + builder: new SlashCommandBuilder() + .setName('config-welcome') + .setDescription('Configure the welcome message for this server.') + .addSubcommand(subcommand => + subcommand + .setName('set-channel') + .setDescription('Set the channel for welcome messages.') + .addChannelOption(option => + option.setName('channel') + .setDescription('The channel to send welcome messages to.') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName('set-message') + .setDescription('Set the welcome message.') + .addStringOption(option => + option.setName('message') + .setDescription('The welcome message. Use {user} for username mention and {server} for server name.') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName('disable') + .setDescription('Disable welcome messages.') + ), + async execute(interaction) { + const db = readDb(); + const guildId = interaction.guildId; + const subcommand = interaction.options.getSubcommand(); + + if (!db[guildId]) { + db[guildId] = {}; + } + + if (subcommand === 'set-channel') { + const channel = interaction.options.getChannel('channel'); + db[guildId].welcomeChannel = channel.id; + db[guildId].welcomeEnabled = true; + writeDb(db); + await interaction.reply(`Welcome channel set to ${channel}.`); + } else if (subcommand === 'set-message') { + const message = interaction.options.getString('message'); + db[guildId].welcomeMessage = message; + db[guildId].welcomeEnabled = true; + writeDb(db); + await interaction.reply(`Welcome message set to: "${message}"`); + } else if (subcommand === 'disable') { + db[guildId].welcomeEnabled = false; + writeDb(db); + await interaction.reply('Welcome messages disabled.'); + } + }, +}; diff --git a/discord-bot/commands/view-welcome-leave.js b/discord-bot/commands/view-welcome-leave.js new file mode 100644 index 0000000..7cca5b6 --- /dev/null +++ b/discord-bot/commands/view-welcome-leave.js @@ -0,0 +1,34 @@ +const { SlashCommandBuilder } = require('discord.js'); +const { readDb } = require('../../backend/db.js'); + +module.exports = { + name: 'view-welcome-leave', + description: 'View the current welcome and leave message configuration.', + enabled: true, + builder: new SlashCommandBuilder() + .setName('view-welcome-leave') + .setDescription('View the current welcome and leave message configuration.'), + async execute(interaction) { + const db = readDb(); + const guildId = interaction.guildId; + const settings = db[guildId] || {}; + + const welcomeChannel = settings.welcomeChannel ? `<#${settings.welcomeChannel}>` : 'Not set'; + const welcomeMessage = settings.welcomeMessage || 'Not set'; + const welcomeEnabled = settings.welcomeEnabled ? 'Enabled' : 'Disabled'; + + const leaveChannel = settings.leaveChannel ? `<#${settings.leaveChannel}>` : 'Not set'; + const leaveMessage = settings.leaveMessage || 'Not set'; + const leaveEnabled = settings.leaveEnabled ? 'Enabled' : 'Disabled'; + + await interaction.reply( +`**Welcome Messages: ${welcomeEnabled}** +Channel: ${welcomeChannel} +Message: "${welcomeMessage}" + +**Leave Messages: ${leaveEnabled}** +Channel: ${leaveChannel} +Message: "${leaveMessage}"` + ); + }, +}; diff --git a/discord-bot/deploy-commands.js b/discord-bot/deploy-commands.js index d571e2b..1a930c4 100644 --- a/discord-bot/deploy-commands.js +++ b/discord-bot/deploy-commands.js @@ -1,10 +1,23 @@ require('dotenv').config({ path: '../backend/.env' }); const { REST, Routes } = require('discord.js'); +const fs = require('fs'); +const path = require('path'); -const commands = [{ - name: 'ping', - description: 'Replies with Pong!', -}]; +const commands = []; +const commandsPath = path.join(__dirname, 'commands'); +const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); + +for (const file of commandFiles) { + const filePath = path.join(commandsPath, file); + const command = require(filePath); + if (command.enabled === false) continue; + + if (command.builder) { + commands.push(command.builder.toJSON()); + } else { + commands.push({ name: command.name, description: command.description }); + } +} const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN); diff --git a/discord-bot/events/guildMemberAdd.js b/discord-bot/events/guildMemberAdd.js index d869744..487b65d 100644 --- a/discord-bot/events/guildMemberAdd.js +++ b/discord-bot/events/guildMemberAdd.js @@ -6,16 +6,16 @@ module.exports = { async execute(member) { try { const db = readDb(); - const settings = db[`${member.guild.id}_welcome_leave`]; + const settings = db[member.guild.id]; - if (settings && settings.welcome && settings.welcome.enabled && settings.welcome.channel) { - const channel = member.guild.channels.cache.get(settings.welcome.channel); + if (settings && settings.welcomeEnabled && settings.welcomeChannel) { + const channel = member.guild.channels.cache.get(settings.welcomeChannel); if (channel) { try { - const message = settings.welcome.message.replace('{user}', member.user.toString()); + const message = (settings.welcomeMessage || 'Welcome {user} to {server}!').replace('{user}', member.user.toString()).replace('{server}', member.guild.name); await channel.send(message); } catch (error) { - console.error(`Could not send welcome message to channel ${settings.welcome.channel} in guild ${member.guild.id}:`, error); + console.error(`Could not send welcome message to channel ${settings.welcomeChannel} in guild ${member.guild.id}:`, error); } } } diff --git a/discord-bot/events/guildMemberRemove.js b/discord-bot/events/guildMemberRemove.js index fde56ec..c6f1d3d 100644 --- a/discord-bot/events/guildMemberRemove.js +++ b/discord-bot/events/guildMemberRemove.js @@ -6,16 +6,16 @@ module.exports = { async execute(member) { try { const db = readDb(); - const settings = db[`${member.guild.id}_welcome_leave`]; + const settings = db[member.guild.id]; - if (settings && settings.leave && settings.leave.enabled && settings.leave.channel) { - const channel = member.guild.channels.cache.get(settings.leave.channel); + if (settings && settings.leaveEnabled && settings.leaveChannel) { + const channel = member.guild.channels.cache.get(settings.leaveChannel); if (channel) { try { - const message = settings.leave.message.replace('{user}', member.user.tag); + const message = (settings.leaveMessage || '{user} has left the server.').replace('{user}', member.user.tag).replace('{server}', member.guild.name); await channel.send(message); } catch (error) { - console.error(`Could not send leave message to channel ${settings.leave.channel} in guild ${member.guild.id}:`, error); + console.error(`Could not send leave message to channel ${settings.leaveChannel} in guild ${member.guild.id}:`, error); } } } diff --git a/discord-bot/events/ready.js b/discord-bot/events/ready.js index 70a5842..1118d20 100644 --- a/discord-bot/events/ready.js +++ b/discord-bot/events/ready.js @@ -1,14 +1,30 @@ const { ActivityType } = require('discord.js'); +const deployCommands = require('../deploy-commands'); module.exports = { name: 'clientReady', once: true, - execute(client) { + async execute(client) { console.log('ECS - Full Stack Bot Online!'); - client.user.setActivity('ehchad', { - type: ActivityType.Streaming, - url: 'https://twitch.tv/ehchad' - }); + const guilds = client.guilds.cache.map(guild => guild.id); + for (const guildId of guilds) { + await deployCommands(guildId); + } + + const activities = [ + { name: 'Watch EhChad Live!', type: ActivityType.Streaming, url: 'https://twitch.tv/ehchad' }, + { name: 'Follow EhChad!', type: ActivityType.Streaming, url: 'https://twitch.tv/ehchad' }, + { name: '/help', type: ActivityType.Streaming, url: 'https://twitch.tv/ehchad' }, + { name: 'EhChadServices', type: ActivityType.Streaming, url: 'https://twitch.tv/ehchad' }, + ]; + + let activityIndex = 0; + + setInterval(() => { + const activity = activities[activityIndex]; + client.user.setActivity(activity.name, { type: activity.type, url: activity.url }); + activityIndex = (activityIndex + 1) % activities.length; + }, 3000); }, }; diff --git a/discord-bot/handlers/command-handler.js b/discord-bot/handlers/command-handler.js index 542eed1..9cf6d1c 100644 --- a/discord-bot/handlers/command-handler.js +++ b/discord-bot/handlers/command-handler.js @@ -8,6 +8,8 @@ module.exports = (client) => { for (const file of commandFiles) { const filePath = path.join(commandsPath, file); const command = require(filePath); + if (command.enabled === false) continue; + if (command.name) { client.commands.set(command.name, command); }