diff --git a/.env.example b/.env.example index 766cf71..20fdbb6 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,11 @@ # Discord DISCORD_TOKEN= CHANNEL_ID=1389308172531011615 +CLIENT_ID=1389274917509398608 +TAG_NEEDS_TESTING=1396925576765374655 +TAG_NEEDS_REFACTOR=1396925644197331035 +TAG_READY_FOR_MERGE=1396925666296856667 +DEV_ROLE_ID=1391050123630477392 # GitHub GITHUB_TOKEN= diff --git a/scripts/registerCommands.js b/scripts/registerCommands.js new file mode 100644 index 0000000..6e9e6d2 --- /dev/null +++ b/scripts/registerCommands.js @@ -0,0 +1,31 @@ +// this script registers slash commands for a Discord bot using the Discord.js library +// needs to run once to set up commands, after they changed +import 'dotenv/config'; +import { REST, Routes } from 'discord.js'; +import { commands } from '../src/functions/commands.js'; + +const { DISCORD_TOKEN, CLIENT_ID, GUILD_ID } = process.env; + +if (!DISCORD_TOKEN) throw new Error('DISCORD_TOKEN missing'); +if (!CLIENT_ID) throw new Error('CLIENT_ID missing (Discord application ID)'); + +const rest = new REST({ version: '10' }).setToken(DISCORD_TOKEN); +const body = commands.map(c => c.data.toJSON()); + +async function main() { + try { + console.log('Registering slash commands...'); + if (GUILD_ID) { + await rest.put(Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID), { body }); + console.log(`Registered ${body.length} guild command(s) to ${GUILD_ID}`); + console.log(body); + } else { + await rest.put(Routes.applicationCommands(CLIENT_ID), { body }); + console.log(`Registered ${body.length} global command(s)`); + console.log(body); + } + } catch (err) { + console.error(err); + } +} +main(); \ No newline at end of file diff --git a/src/client.js b/src/client.js index 24235fe..29a5913 100644 --- a/src/client.js +++ b/src/client.js @@ -1,9 +1,10 @@ import './config.js'; -import { Client, GatewayIntentBits } from 'discord.js'; +import { Client, GatewayIntentBits, Collection, Events } from 'discord.js'; import parseLog from './functions/log_parser.js'; import initializeReleaseWatcher from './functions/release_watcher.js'; import initializeDescriptionWatcher from './functions/description_watcher.js'; +import { commands } from './functions/commands.js'; const { DISCORD_TOKEN } = process.env; @@ -20,15 +21,40 @@ const client = new Client({ ], }); +client.commands = new Collection(); +for (const command of commands) { + client.commands.set(command.data.name, command); +} + client.once('ready', () => { console.log(`Logged in as ${client.user.tag}!`); // Release Watcher disabled for now, after the RAID have to setup again - //initializeReleaseWatcher(client); + initializeReleaseWatcher(client); initializeDescriptionWatcher(client); }); client.on('messageCreate', parseLog); +client.on(Events.InteractionCreate, async interaction => { + if (!interaction.isChatInputCommand()) return; + + const command = client.commands.get(interaction.commandName); + if (!command) { + return interaction.reply({ content: 'Command not found.', ephemeral: true }); + } + + try { + await command.execute(interaction); + } catch (error) { + console.error(error); + if (interaction.deferred || interaction.replied) { + await interaction.followUp({ content: 'There was an error executing that command.', ephemeral: true }); + } else { + await interaction.reply({ content: 'There was an error executing that command.', ephemeral: true }); + } + } +}); + // Log in to Discord client.login(DISCORD_TOKEN); \ No newline at end of file diff --git a/src/functions/commands.js b/src/functions/commands.js index 9f61c4f..b8d9399 100644 --- a/src/functions/commands.js +++ b/src/functions/commands.js @@ -1,9 +1,11 @@ // Implements Discord commands for managing build threads in a forum channel // /src/functions/commands.js -import { SlashCommandBuilder } from 'discord.js'; +import 'dotenv/config'; +import { SlashCommandBuilder, ChannelType } from 'discord.js'; const { - BUILD_FORUM_CHANNEL_ID: FORUM_CHANNEL_ID, + CHANNEL_ID, + DEV_ROLE_ID, TAG_NEEDS_TESTING, TAG_NEEDS_REFACTOR, TAG_READY_FOR_MERGE, @@ -34,10 +36,13 @@ export const commands = [ { name: 'merge', value: 'merge' }, ) ), - async execute(interaction) { + execute: async interaction => { + if (!interaction.member.roles.cache.has(DEV_ROLE_ID)) { + return interaction.reply({ content: 'You do not have permission to use this command.', ephemeral: true }); + } const build = interaction.options.getString('build_number'); const action = interaction.options.getString('action'); - const forum = await interaction.client.channels.fetch(FORUM_CHANNEL_ID); + const forum = await interaction.client.channels.fetch(CHANNEL_ID); if (!forum || forum.type !== 15) return interaction.reply({ content: 'Forum channel not found.', ephemeral: true }); @@ -55,6 +60,8 @@ export const commands = [ tagSet.add(TAGS.needsTesting); } else if (action === 'merge') { tagSet.add(TAGS.readyForMerge); + tagSet.delete(TAGS.needsTesting); + tagSet.delete(TAGS.needsRefactor); } await thread.setAppliedTags([...tagSet]); @@ -68,12 +75,18 @@ export const commands = [ .addStringOption(option => option.setName('build_number') .setDescription('Build number') - .setRequired(true)), - async execute(interaction) { - const build = interaction.options.getString('build_number'); - const forum = await interaction.client.channels.fetch(FORUM_CHANNEL_ID); + .setRequired(true)) + , + execute: async interaction => { - if (!forum || forum.type !== 15) return interaction.reply({ content: 'Forum channel not found.', ephemeral: true }); + if (!interaction.member.roles.cache.has(DEV_ROLE_ID)) { + return interaction.reply({ content: 'You do not have permission to use this command.', ephemeral: true }); + } + + const build = interaction.options.getString('build_number'); + const forum = await interaction.client.channels.fetch(CHANNEL_ID); + + if (!forum || forum.type !== ChannelType.GuildForum) return interaction.reply({ content: 'Forum channel not found.', ephemeral: true }); const threads = await forum.threads.fetchActive(); const thread = threads.threads.find(t => t.name.includes(build));