diff options
| author | Alex Pooley (@zuedev) <zuedev@gmail.com> | 2026-07-15 13:56:24 +0100 |
|---|---|---|
| committer | Alex Pooley (@zuedev) <zuedev@gmail.com> | 2026-07-15 13:56:24 +0100 |
| commit | a8cd84b8291990265dbdddcdaa079886234180a1 (patch) | |
| tree | 20dec2454dc7ee331f29d53b339f0f26a384b56a /174bg/discord-bot/main.js | |
| parent | c488c4840588fd88fa840306c40ab99ebb3512b9 (diff) | |
| download | unnamed-group-a8cd84b8291990265dbdddcdaa079886234180a1.tar unnamed-group-a8cd84b8291990265dbdddcdaa079886234180a1.tar.gz unnamed-group-a8cd84b8291990265dbdddcdaa079886234180a1.tar.bz2 unnamed-group-a8cd84b8291990265dbdddcdaa079886234180a1.tar.xz unnamed-group-a8cd84b8291990265dbdddcdaa079886234180a1.zip | |
refactor codebase for dry
Diffstat (limited to '174bg/discord-bot/main.js')
| -rw-r--r-- | 174bg/discord-bot/main.js | 323 |
1 files changed, 31 insertions, 292 deletions
diff --git a/174bg/discord-bot/main.js b/174bg/discord-bot/main.js index 30d2a1d..fc4da76 100644 --- a/174bg/discord-bot/main.js +++ b/174bg/discord-bot/main.js @@ -4,175 +4,15 @@ import { GatewayIntentBits, REST, Routes, + ChannelType, PermissionFlagsBits, SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, } from "discord.js"; -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const commands = [ - { - data: new SlashCommandBuilder() - .setName("scope") - .setDescription("Returns the current scope of the bot."), - execute: async (interaction) => { - await interaction.reply(codewrap("json", JSON.stringify({ - guildIdFromEnv: process.env.DISCORD_GUILD_ID, - guildIdFromInteraction: interaction.guild.id, - doGuildIdsMatch: - process.env.DISCORD_GUILD_ID === interaction.guild.id, - }, null, 2))); - } - }, - { - data: new SlashCommandBuilder() - .setName("create-requisition-ticket") - .setDescription( - "Creates a requisition ticket for the quartermaster to review.", - ) - .addStringOption((option) => - option - .setName("contents") - .setDescription( - "What are you requesting? Be as specific as possible.", - ) - .setRequired(true), - ), - execute: async (interaction) => { - // extract options - const [contents] = [interaction.options.getString("contents")]; - - // get "tickets" category - const ticketsCategory = interaction.guild.channels.cache.find( - (channel) => - channel.name.toLowerCase() === "tickets" && channel.type === 4, - ); - - if (!ticketsCategory) - return await interaction.reply("Tickets category not found."); - - // get the quartermaster role id - let quartermasterRole = interaction.guild.roles.cache.find( - (role) => role.name.toLowerCase() === "quartermaster", - ); - - if (!quartermasterRole) - { - // no qm role? make it - const newRole = await interaction.guild.roles.create({ - name: "Quartermaster", - color: "Blue", - }); - - quartermasterRole = newRole; - } - - // create a new text channel under the "tickets" category with the following format: - // req-<timestamp_short> - const timestamp = new Date() - .toISOString() - .replace(/[-:.T]/g, "") - .slice(2, 16); // YYMMDDHHMMSS - const channelName = `req-${timestamp}`; - - const newChannel = await interaction.guild.channels.create({ - name: channelName, - type: 0, // text channel - parent: ticketsCategory.id, - // set permissions so that only the user who created the ticket and the quartermaster role can view it - permissionOverwrites: [ - { - id: interaction.user.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - ], - }, - { - id: quartermasterRole.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - ], - }, - ], - }); - - // send a normal message to the new channel with a close button that only the quartermaster role can see - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId("close_req_ticket") - .setLabel("Close Ticket") - .setStyle(ButtonStyle.Danger) - .setDisabled(false), - ); - - await newChannel.send({ - content: `**Requisition Ticket #${timestamp}**\n**Contents:** ${contents}\n**Created by:** <@${interaction.user.id}>`, - components: [row], - }); - - // reply to the user - await interaction.reply({ - content: `Your requisition ticket has been created: <#${newChannel.id}>`, - ephemeral: true, - }); - - // log - sendMessageToLogsChannel( - `Requisition ticket <#${newChannel.id}> created by <@${interaction.user.id}>`, - ); - - // notify all members with the quartermaster role if it exists - // (member cache is kept up to date via the GuildMembers intent, so no - // need to re-fetch all members here) - const quartermasterMembers = quartermasterRole.members; - for (const member of quartermasterMembers.values()) { - await member.send( - `A new requisition ticket has been created: <#${newChannel.id}>`, - ); - } - }, - }, - { - data: new SlashCommandBuilder() - .setName("delete-all-requisition-tickets") - .setDescription("Deletes all requisition tickets.") - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), - execute: async (interaction) => { - // get "tickets" category - const ticketsCategory = interaction.guild.channels.cache.find( - (channel) => - channel.name.toLowerCase() === "tickets" && channel.type === 4, - ); - - if (!ticketsCategory) - return await interaction.reply("Tickets category not found."); - - // get all channels under the "tickets" category that start with "req-" - const requisitionTickets = ticketsCategory.children.cache.filter( - (channel) => channel.name.startsWith("req-"), - ); - - // delete all requisition tickets - for (const ticket of requisitionTickets.values()) { - await ticket.delete(); - } - - // log - sendMessageToLogsChannel( - `<@${interaction.user.id}> used \`/delete-all-requisition-tickets\` and deleted **${requisitionTickets.size}** requisition tickets.`, - ); - }, - }, -]; +import commands from "./commands/_index.js"; const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers], @@ -181,41 +21,7 @@ const client = new Client({ client.on(Events.ClientReady, async (readyClient) => { console.log(`Logged in as ${readyClient.user.tag}!`); - try { - const rest = new REST({ version: "10" }).setToken( - process.env.DISCORD_BOT_TOKEN, - ); - - // clear global commands - await rest.put(Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), { - body: [], - }); - - console.log("Cleared global application commands."); - - // register guild-scoped commands - await rest.put(Routes.applicationGuildCommands(process.env.DISCORD_CLIENT_ID, process.env.DISCORD_GUILD_ID), { - body: commands.map((command) => command.data.toJSON()), - }); - - const guild = await client.guilds.fetch(process.env.DISCORD_GUILD_ID); - - // fetch all members once so the cache stays populated; the GuildMembers - // intent keeps it in sync afterward via gateway events, avoiding repeated - // opcode 8 (Request Guild Members) calls that can get rate limited - await guild.members.fetch(); - - console.log(`Successfully registered application commands for guild ${process.env.DISCORD_GUILD_ID} (${guild ? guild.name : "Unknown"}).`); - } catch (error) { - console.error(error); - } - - cacheUexData(); - - // every hour - setInterval(async () => { - await cacheUexData(); - }, 60 * 60 * 1000); // 1 hour + await registerCommands(); }); client.on(Events.InteractionCreate, async (interaction) => { @@ -232,109 +38,42 @@ client.on(Events.InteractionCreate, async (interaction) => { await command.execute(interaction); } } - - if (interaction.isButton()) { - if (interaction.customId === "close_req_ticket") { - // get the quartermaster role id - const quartermasterRole = interaction.guild.roles.cache.find( - (role) => role.name.toLowerCase() === "quartermaster", - ); - - if (!quartermasterRole) - return await interaction.reply("Quartermaster role not found."); - - // check if the user has the quartermaster role or is an administrator - if ( - !interaction.member.roles.cache.has(quartermasterRole.id) && - !interaction.member.permissions.has(PermissionFlagsBits.Administrator) - ) { - return await interaction.reply({ - content: "You do not have permission to close this ticket.", - ephemeral: true, - }); - } - - // fetch all messages in the channel - const messages = await interaction.channel.messages.fetch(); - - // capture channel info before it's deleted (and removed from cache) - const channelId = interaction.channel.id; - const channelName = interaction.channel.name; - - // delete the channel - await interaction.channel.delete(); - - // log - sendMessageToLogsChannel({ - content: `<#${channelId}> closed by <@${interaction.user.id}>`, - files: [ - { - name: `${channelName}.txt`, - attachment: Buffer.from( - messages - .map( - (message) => - `${message.author.tag}/${message.author.id}: ${message.content}`, - ) - .reverse() - .join("\n"), - ), - }, - ], - }); - } - } }); client.login(process.env.DISCORD_BOT_TOKEN); -function sendMessageToLogsChannel(message) { - const logsChannel = client.channels.cache.find( - (channel) => channel.guild.id === process.env.DISCORD_GUILD_ID && channel.name === "logs" && channel.type === 0, - ); - - if (!logsChannel) return console.error("Logs channel not found."); - - logsChannel.send(message); -} - -function codewrap(language, content) { - return "```" + language + "\n" + content + "\n```"; -} - -async function cacheUexData() { - const list = [ - "https://api.uexcorp.uk/2.0/commodities_raw_prices_all", - "https://api.uexcorp.uk/2.0/items_prices_all", - "https://api.uexcorp.uk/2.0/fuel_prices_all", - "https://api.uexcorp.uk/2.0/commodities_prices_all", - "https://api.uexcorp.uk/2.0/vehicles_purchases_prices_all", - "https://api.uexcorp.uk/2.0/vehicles_rentals_prices_all" - ]; - - const cacheDir = path.join(__dirname, "uex-cache"); - fs.mkdirSync(cacheDir, { recursive: true }); +async function registerCommands() { + try { + const rest = new REST({ version: "10" }).setToken( + process.env.DISCORD_BOT_TOKEN, + ); - for (const url of list) { - try { - const response = await fetch(url); + // clear global commands + await rest.put(Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), { + body: [], + }); - if (!response.ok) { - throw new Error(`Received status ${response.status}`); - } + console.log("Cleared global application commands."); - const data = await response.json(); + // register guild-scoped commands + await rest.put( + Routes.applicationGuildCommands( + process.env.DISCORD_CLIENT_ID, + process.env.DISCORD_GUILD_ID, + ), + { + body: commands.map((command) => command.data.toJSON()), + }, + ); - if (data === null || typeof data !== "object") { - throw new Error("Response was not a JSON object/array"); - } + const guild = await client.guilds.fetch(process.env.DISCORD_GUILD_ID); - // write to file - const filePath = path.join(cacheDir, `${url.split("/").pop()}.json`); + await guild.members.fetch(); - fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); - } catch (error) { - console.error(`Error fetching ${url}:`, error); - } + console.log( + `Successfully registered application commands for guild ${process.env.DISCORD_GUILD_ID} (${guild ? guild.name : "Unknown"}).`, + ); + } catch (error) { + console.error(error); } -}
\ No newline at end of file +} |
