From 72487019b3f255cece40a8073bbad8bc9968a3b7 Mon Sep 17 00:00:00 2001 From: "Alex Pooley (@zuedev)" Date: Wed, 22 Jul 2026 15:32:57 +0100 Subject: major code refactor, add uex lookup --- 174bg/discord-bot/Dockerfile | 2 +- 174bg/discord-bot/commands/_index.js | 7 -- 174bg/discord-bot/commands/close-ticket.js | 55 --------------- 174bg/discord-bot/commands/oncall-check.js | 51 -------------- 174bg/discord-bot/commands/requisition.js | 42 ----------- 174bg/discord-bot/controllers/http.js | 25 ------- 174bg/discord-bot/controllers/pocketbase.js | 40 ----------- 174bg/discord-bot/main.js | 78 --------------------- 174bg/discord-bot/package.json | 6 +- 174bg/discord-bot/source/commands/_index.js | 8 +++ 174bg/discord-bot/source/commands/close-ticket.js | 55 +++++++++++++++ 174bg/discord-bot/source/commands/oncall-check.js | 51 ++++++++++++++ 174bg/discord-bot/source/commands/requisition.js | 42 +++++++++++ 174bg/discord-bot/source/commands/uex.js | 81 ++++++++++++++++++++++ 174bg/discord-bot/source/controllers/http.js | 25 +++++++ 174bg/discord-bot/source/controllers/pocketbase.js | 40 +++++++++++ 174bg/discord-bot/source/controllers/uex.js | 29 ++++++++ 174bg/discord-bot/source/main.js | 64 +++++++++++++++++ 174bg/discord-bot/source/utilities/createTicket.js | 55 +++++++++++++++ .../source/utilities/notifyRoleByName.js | 19 +++++ 174bg/discord-bot/utilities/createTicket.js | 55 --------------- 174bg/discord-bot/utilities/notifyRoleByName.js | 19 ----- 22 files changed, 473 insertions(+), 376 deletions(-) delete mode 100644 174bg/discord-bot/commands/_index.js delete mode 100644 174bg/discord-bot/commands/close-ticket.js delete mode 100644 174bg/discord-bot/commands/oncall-check.js delete mode 100644 174bg/discord-bot/commands/requisition.js delete mode 100644 174bg/discord-bot/controllers/http.js delete mode 100644 174bg/discord-bot/controllers/pocketbase.js delete mode 100644 174bg/discord-bot/main.js create mode 100644 174bg/discord-bot/source/commands/_index.js create mode 100644 174bg/discord-bot/source/commands/close-ticket.js create mode 100644 174bg/discord-bot/source/commands/oncall-check.js create mode 100644 174bg/discord-bot/source/commands/requisition.js create mode 100644 174bg/discord-bot/source/commands/uex.js create mode 100644 174bg/discord-bot/source/controllers/http.js create mode 100644 174bg/discord-bot/source/controllers/pocketbase.js create mode 100644 174bg/discord-bot/source/controllers/uex.js create mode 100644 174bg/discord-bot/source/main.js create mode 100644 174bg/discord-bot/source/utilities/createTicket.js create mode 100644 174bg/discord-bot/source/utilities/notifyRoleByName.js delete mode 100644 174bg/discord-bot/utilities/createTicket.js delete mode 100644 174bg/discord-bot/utilities/notifyRoleByName.js (limited to '174bg') diff --git a/174bg/discord-bot/Dockerfile b/174bg/discord-bot/Dockerfile index 5bcc897..bfa512a 100644 --- a/174bg/discord-bot/Dockerfile +++ b/174bg/discord-bot/Dockerfile @@ -4,4 +4,4 @@ RUN apk add --no-cache git python3 make g++ COPY package*.json ./ RUN npm install COPY . . -CMD ["node", "main.js"] \ No newline at end of file +CMD ["node", "source/main.js"] \ No newline at end of file diff --git a/174bg/discord-bot/commands/_index.js b/174bg/discord-bot/commands/_index.js deleted file mode 100644 index 2421b4d..0000000 --- a/174bg/discord-bot/commands/_index.js +++ /dev/null @@ -1,7 +0,0 @@ -import requisition from "./requisition.js"; -import closeTicket from "./close-ticket.js"; -import oncallCheck from "./oncall-check.js"; - -export { requisition, closeTicket, oncallCheck }; - -export default [requisition, closeTicket, oncallCheck]; diff --git a/174bg/discord-bot/commands/close-ticket.js b/174bg/discord-bot/commands/close-ticket.js deleted file mode 100644 index ca3040e..0000000 --- a/174bg/discord-bot/commands/close-ticket.js +++ /dev/null @@ -1,55 +0,0 @@ -import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js"; - -export default { - data: new SlashCommandBuilder() - .setName("close-ticket") - .setDescription( - "Closes the current ticket by logging the contents and deleting the channel.", - ) - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), - execute: async (interaction) => { - const channel = interaction.channel; - - if (!channel || !channel.name.startsWith("ticket-")) { - return await interaction.reply({ - content: "This command can only be used in a ticket channel.", - ephemeral: true, - }); - } - - // log the contents of the ticket and send to the logs channel if it exists - const messages = await channel.messages.fetch({ limit: 100 }); - const log = messages - .map((message) => `${message.author.tag}: ${message.content}`) - .reverse() - .join("\n"); - - console.log(`Ticket log for ${channel.name}:\n${log}`); - - const logsChannel = interaction.guild.channels.cache.find( - (ch) => ch.name === "logs" && ch.isTextBased(), - ); - - if (logsChannel) - await logsChannel.send({ - content: `# Ticket log for ${channel.name}`, - files: [ - { - name: `${channel.name}.txt`, - attachment: Buffer.from( - messages - .map( - (message) => - `${message.author.tag}/${message.author.id}: ${message.content}`, - ) - .reverse() - .join("\n"), - ), - }, - ], - }); - - // delete the channel - await channel.delete(); - }, -}; diff --git a/174bg/discord-bot/commands/oncall-check.js b/174bg/discord-bot/commands/oncall-check.js deleted file mode 100644 index 7a3886d..0000000 --- a/174bg/discord-bot/commands/oncall-check.js +++ /dev/null @@ -1,51 +0,0 @@ -import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js"; -import { login } from "../controllers/pocketbase.js"; - -export default { - data: new SlashCommandBuilder() - .setName("oncall-check") - .setDescription("Returns a list of members who are currently on call.") - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), - execute: async (interaction) => { - const date = new Date(); - const dateUTCHours = date.getUTCHours(); - const dateUTCMinutes = date.getUTCMinutes(); - const dateUTCDay = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][date.getUTCDay()]; - - const pb = await login(); - - const members = await pb.collection("members").getFullList(); - - let membersOnCall = []; - - for (const member of members) { - try { - if (member.onCallSchedule) { - const available = member.onCallSchedule[dateUTCDay]?.available || false; - - if (available) { - const start = member.onCallSchedule[dateUTCDay].start; - const startHours = start.split(":")[0]; - const startMinutes = start.split(":")[1]; - const end = member.onCallSchedule[dateUTCDay].end; - const endHours = end.split(":")[0]; - const endMinutes = end.split(":")[1]; - - console.log(`Checking on-call schedule for member ${member.name}: ${start} - ${end} (UTC)`); - - if ((dateUTCHours > startHours || (dateUTCHours === startHours && dateUTCMinutes >= startMinutes)) && (dateUTCHours < endHours || (dateUTCHours === endHours && dateUTCMinutes <= endMinutes))) { - membersOnCall.push(member); - } - } - } - } catch (error) { - console.error(`Error checking on-call schedule for member ${member.name}:`, error); - } - } - - await interaction.reply({ - content: `${membersOnCall.map((member) => `- <@${member.discordId}>`).join("\n") || "No members are currently on call."}`, - ephemeral: true, - }); - }, -}; diff --git a/174bg/discord-bot/commands/requisition.js b/174bg/discord-bot/commands/requisition.js deleted file mode 100644 index 02c7699..0000000 --- a/174bg/discord-bot/commands/requisition.js +++ /dev/null @@ -1,42 +0,0 @@ -import { SlashCommandBuilder } from "discord.js"; -import createTicket from "../utilities/createTicket.js"; -import notifyRoleByName from "../utilities/notifyRoleByName.js"; - -export default { - data: new SlashCommandBuilder() - .setName("requisition") - .setDescription( - "Request items from the quartermaster. A new requisition ticket will be created.", - ) - .addStringOption((option) => - option - .setName("contents") - .setDescription("What are you requesting? Be as specific as possible.") - .setRequired(true), - ), - execute: async (interaction) => { - const contents = interaction.options.getString("contents"); - - const ticket = await createTicket(interaction, { - roleNamesWithAccess: ["quartermaster"], - ticketNamePrefix: "req-", - }); - - await ticket.send({ - content: `**New requisition request from <@${interaction.user.id}>**: ${contents}`, - }); - - // reply to the user - await interaction.reply({ - content: `Your requisition ticket has been created: <#${ticket.id}>`, - ephemeral: true, - }); - - // notify quartermasters - await notifyRoleByName( - interaction.guild, - "quartermaster", - `New requisition request from <@${interaction.user.id}>: ${contents}`, - ); - }, -}; diff --git a/174bg/discord-bot/controllers/http.js b/174bg/discord-bot/controllers/http.js deleted file mode 100644 index 816b73c..0000000 --- a/174bg/discord-bot/controllers/http.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Controller module for interacting with HTTP resources, mainly for fetching JSON data from external APIs. - */ - -/** - * Fetches data from a specified URL and returns the response as JSON or text. - * @param {string} url - The URL to fetch data from. - * @returns {Promise} A Promise that resolves to the parsed JSON object, text, or null if the request fails. - */ -export async function get(url) { - const response = await fetch(url); - - if (!response.ok) return null; - - // is it json? - const contentType = response.headers.get("content-type"); - if (contentType && contentType.includes("application/json")) { - return await response.json(); - } - - // fallback to text - return await response.text(); -}; - -return { get }; \ No newline at end of file diff --git a/174bg/discord-bot/controllers/pocketbase.js b/174bg/discord-bot/controllers/pocketbase.js deleted file mode 100644 index 4046391..0000000 --- a/174bg/discord-bot/controllers/pocketbase.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Controller module for interacting with the PocketBase API. - */ - -import PocketBase from "pocketbase"; - -const { POCKETBASE_URL, POCKETBASE_SUPERUSER_EMAIL, POCKETBASE_SUPERUSER_PASSWORD } = process.env; - -/** - * Logs in to the PocketBase API using the superuser credentials. - * @returns {Promise} A Promise that resolves to the authenticated PocketBase instance. - */ -export async function login() { - const pb = new PocketBase(POCKETBASE_URL); - await pb.collection("_superusers").authWithPassword(POCKETBASE_SUPERUSER_EMAIL, POCKETBASE_SUPERUSER_PASSWORD); - return pb; -} - -/** - * Logs out of the PocketBase API by clearing the authentication store. - * @param {PocketBase} pb - The PocketBase instance to log out from. - * @returns {Promise} A Promise that resolves when the logout is complete. - */ -export async function logout(pb) { - await pb.authStore.clear(); -} - -/** - * Retrieves the full list of records from a specified collection in the PocketBase API. - * @param {string} collectionName - The name of the collection to retrieve records from. - * @returns {Promise} A Promise that resolves to an array of records from the specified collection. - */ -export async function getFullList(collectionName) { - const pb = await login(); - const data = await pb.collection(collectionName).getFullList(); - await logout(pb); - return data; -} - -export default { login, logout, getFullList } \ No newline at end of file diff --git a/174bg/discord-bot/main.js b/174bg/discord-bot/main.js deleted file mode 100644 index b0de475..0000000 --- a/174bg/discord-bot/main.js +++ /dev/null @@ -1,78 +0,0 @@ -import { - Client, - Events, - GatewayIntentBits, - REST, - Routes, - ChannelType, - PermissionFlagsBits, - SlashCommandBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, -} from "discord.js"; -import commands from "./commands/_index.js"; - -const client = new Client({ - intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers], -}); - -client.on(Events.ClientReady, async (readyClient) => { - console.log(`Logged in as ${readyClient.user.tag}!`); - - await registerCommands(); -}); - -client.on(Events.InteractionCreate, async (interaction) => { - // only respond to interactions in the specified guild - if (interaction.guild.id !== process.env.DISCORD_GUILD_ID) return; - - if (interaction.isChatInputCommand()) { - if ( - commands.some((command) => command.data.name === interaction.commandName) - ) { - const command = commands.find( - (command) => command.data.name === interaction.commandName, - ); - await command.execute(interaction); - } - } -}); - -client.login(process.env.DISCORD_BOT_TOKEN); - -async function registerCommands() { - 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); - - 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); - } -} \ No newline at end of file diff --git a/174bg/discord-bot/package.json b/174bg/discord-bot/package.json index 911e71c..849a19b 100644 --- a/174bg/discord-bot/package.json +++ b/174bg/discord-bot/package.json @@ -1,8 +1,8 @@ { "scripts": { - "start": "node main.js", - "dev": "node --env-file=.env main.js", - "dev:watch": "node --env-file=.env --watch main.js" + "start": "node source/main.js", + "dev": "node --env-file=.env source/main.js", + "dev:watch": "node --env-file=.env --watch source/main.js" }, "dependencies": { "@discordjs/voice": "^0.19.2", diff --git a/174bg/discord-bot/source/commands/_index.js b/174bg/discord-bot/source/commands/_index.js new file mode 100644 index 0000000..5fea200 --- /dev/null +++ b/174bg/discord-bot/source/commands/_index.js @@ -0,0 +1,8 @@ +import requisition from "./requisition.js"; +import closeTicket from "./close-ticket.js"; +import oncallCheck from "./oncall-check.js"; +import uex from "./uex.js"; + +export { requisition, closeTicket, oncallCheck, uex }; + +export default [requisition, closeTicket, oncallCheck, uex]; diff --git a/174bg/discord-bot/source/commands/close-ticket.js b/174bg/discord-bot/source/commands/close-ticket.js new file mode 100644 index 0000000..ca3040e --- /dev/null +++ b/174bg/discord-bot/source/commands/close-ticket.js @@ -0,0 +1,55 @@ +import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js"; + +export default { + data: new SlashCommandBuilder() + .setName("close-ticket") + .setDescription( + "Closes the current ticket by logging the contents and deleting the channel.", + ) + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), + execute: async (interaction) => { + const channel = interaction.channel; + + if (!channel || !channel.name.startsWith("ticket-")) { + return await interaction.reply({ + content: "This command can only be used in a ticket channel.", + ephemeral: true, + }); + } + + // log the contents of the ticket and send to the logs channel if it exists + const messages = await channel.messages.fetch({ limit: 100 }); + const log = messages + .map((message) => `${message.author.tag}: ${message.content}`) + .reverse() + .join("\n"); + + console.log(`Ticket log for ${channel.name}:\n${log}`); + + const logsChannel = interaction.guild.channels.cache.find( + (ch) => ch.name === "logs" && ch.isTextBased(), + ); + + if (logsChannel) + await logsChannel.send({ + content: `# Ticket log for ${channel.name}`, + files: [ + { + name: `${channel.name}.txt`, + attachment: Buffer.from( + messages + .map( + (message) => + `${message.author.tag}/${message.author.id}: ${message.content}`, + ) + .reverse() + .join("\n"), + ), + }, + ], + }); + + // delete the channel + await channel.delete(); + }, +}; diff --git a/174bg/discord-bot/source/commands/oncall-check.js b/174bg/discord-bot/source/commands/oncall-check.js new file mode 100644 index 0000000..7a3886d --- /dev/null +++ b/174bg/discord-bot/source/commands/oncall-check.js @@ -0,0 +1,51 @@ +import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js"; +import { login } from "../controllers/pocketbase.js"; + +export default { + data: new SlashCommandBuilder() + .setName("oncall-check") + .setDescription("Returns a list of members who are currently on call.") + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), + execute: async (interaction) => { + const date = new Date(); + const dateUTCHours = date.getUTCHours(); + const dateUTCMinutes = date.getUTCMinutes(); + const dateUTCDay = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][date.getUTCDay()]; + + const pb = await login(); + + const members = await pb.collection("members").getFullList(); + + let membersOnCall = []; + + for (const member of members) { + try { + if (member.onCallSchedule) { + const available = member.onCallSchedule[dateUTCDay]?.available || false; + + if (available) { + const start = member.onCallSchedule[dateUTCDay].start; + const startHours = start.split(":")[0]; + const startMinutes = start.split(":")[1]; + const end = member.onCallSchedule[dateUTCDay].end; + const endHours = end.split(":")[0]; + const endMinutes = end.split(":")[1]; + + console.log(`Checking on-call schedule for member ${member.name}: ${start} - ${end} (UTC)`); + + if ((dateUTCHours > startHours || (dateUTCHours === startHours && dateUTCMinutes >= startMinutes)) && (dateUTCHours < endHours || (dateUTCHours === endHours && dateUTCMinutes <= endMinutes))) { + membersOnCall.push(member); + } + } + } + } catch (error) { + console.error(`Error checking on-call schedule for member ${member.name}:`, error); + } + } + + await interaction.reply({ + content: `${membersOnCall.map((member) => `- <@${member.discordId}>`).join("\n") || "No members are currently on call."}`, + ephemeral: true, + }); + }, +}; diff --git a/174bg/discord-bot/source/commands/requisition.js b/174bg/discord-bot/source/commands/requisition.js new file mode 100644 index 0000000..02c7699 --- /dev/null +++ b/174bg/discord-bot/source/commands/requisition.js @@ -0,0 +1,42 @@ +import { SlashCommandBuilder } from "discord.js"; +import createTicket from "../utilities/createTicket.js"; +import notifyRoleByName from "../utilities/notifyRoleByName.js"; + +export default { + data: new SlashCommandBuilder() + .setName("requisition") + .setDescription( + "Request items from the quartermaster. A new requisition ticket will be created.", + ) + .addStringOption((option) => + option + .setName("contents") + .setDescription("What are you requesting? Be as specific as possible.") + .setRequired(true), + ), + execute: async (interaction) => { + const contents = interaction.options.getString("contents"); + + const ticket = await createTicket(interaction, { + roleNamesWithAccess: ["quartermaster"], + ticketNamePrefix: "req-", + }); + + await ticket.send({ + content: `**New requisition request from <@${interaction.user.id}>**: ${contents}`, + }); + + // reply to the user + await interaction.reply({ + content: `Your requisition ticket has been created: <#${ticket.id}>`, + ephemeral: true, + }); + + // notify quartermasters + await notifyRoleByName( + interaction.guild, + "quartermaster", + `New requisition request from <@${interaction.user.id}>: ${contents}`, + ); + }, +}; diff --git a/174bg/discord-bot/source/commands/uex.js b/174bg/discord-bot/source/commands/uex.js new file mode 100644 index 0000000..f2f654f --- /dev/null +++ b/174bg/discord-bot/source/commands/uex.js @@ -0,0 +1,81 @@ +import { SlashCommandBuilder, MessageFlags } from "discord.js"; +import { items } from "../controllers/uex.js"; + +export default { + data: new SlashCommandBuilder() + .setName("uex") + .setDescription( + "Commands for interacting with UEX.", + ) + .addSubcommandGroup((subcommandGroup) => + subcommandGroup + .setName("lookup") + .setDescription("Lookup UEX information.") + .addSubcommand((subcommand) => + subcommand + .setName("items") + .setDescription("Perform UEX lookup for Star Citizen items, including ship components, weapons, and more.") + .addStringOption((option) => + option + .setName("query") + .setDescription( + "The item name or partial name to search for.", + ) + .setRequired(true), + ), + ) + ), + execute: async (interaction) => { + const subcommandGroup = interaction.options.getSubcommandGroup(); + const subcommand = interaction.options.getSubcommand(); + + switch (subcommandGroup) { + case "lookup": + switch (subcommand) { + case "items": + await uex_lookup_items(interaction); + break; + default: + await interaction.reply({ + content: "Unknown subcommand.", + ephemeral: true, + }); + break; + } + break; + default: + await interaction.reply({ + content: "Unknown subcommand group.", + ephemeral: true, + }); + break; + } + }, +}; + +async function uex_lookup_items(interaction) { + const query = interaction.options.getString("query"); + + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); + + const allItems = await items(); + + const matchingItems = allItems.filter((item) => + item.name.toLowerCase().includes(query.toLowerCase()), + ); + + if (matchingItems.length === 0) { + await interaction.editReply({ + content: `No items found matching "${query}".`, + }); + return; + } + + const itemList = matchingItems + .map((item) => `- ${item.name} (ID: ${item.id})`) + .join("\n"); + + await interaction.editReply({ + content: `Found ${matchingItems.length} item(s) matching "${query}":\n${itemList}`, + }); +} \ No newline at end of file diff --git a/174bg/discord-bot/source/controllers/http.js b/174bg/discord-bot/source/controllers/http.js new file mode 100644 index 0000000..1472b18 --- /dev/null +++ b/174bg/discord-bot/source/controllers/http.js @@ -0,0 +1,25 @@ +/** + * Controller module for interacting with HTTP resources, mainly for fetching JSON data from external APIs. + */ + +/** + * Fetches data from a specified URL and returns the response as JSON or text. + * @param {string} url - The URL to fetch data from. + * @returns {Promise} A Promise that resolves to the parsed JSON object, text, or null if the request fails. + */ +export async function get(url) { + const response = await fetch(url); + + if (!response.ok) return null; + + // is it json? + const contentType = response.headers.get("content-type"); + if (contentType && contentType.includes("application/json")) { + return await response.json(); + } + + // fallback to text + return await response.text(); +}; + +export default { get }; \ No newline at end of file diff --git a/174bg/discord-bot/source/controllers/pocketbase.js b/174bg/discord-bot/source/controllers/pocketbase.js new file mode 100644 index 0000000..4046391 --- /dev/null +++ b/174bg/discord-bot/source/controllers/pocketbase.js @@ -0,0 +1,40 @@ +/** + * Controller module for interacting with the PocketBase API. + */ + +import PocketBase from "pocketbase"; + +const { POCKETBASE_URL, POCKETBASE_SUPERUSER_EMAIL, POCKETBASE_SUPERUSER_PASSWORD } = process.env; + +/** + * Logs in to the PocketBase API using the superuser credentials. + * @returns {Promise} A Promise that resolves to the authenticated PocketBase instance. + */ +export async function login() { + const pb = new PocketBase(POCKETBASE_URL); + await pb.collection("_superusers").authWithPassword(POCKETBASE_SUPERUSER_EMAIL, POCKETBASE_SUPERUSER_PASSWORD); + return pb; +} + +/** + * Logs out of the PocketBase API by clearing the authentication store. + * @param {PocketBase} pb - The PocketBase instance to log out from. + * @returns {Promise} A Promise that resolves when the logout is complete. + */ +export async function logout(pb) { + await pb.authStore.clear(); +} + +/** + * Retrieves the full list of records from a specified collection in the PocketBase API. + * @param {string} collectionName - The name of the collection to retrieve records from. + * @returns {Promise} A Promise that resolves to an array of records from the specified collection. + */ +export async function getFullList(collectionName) { + const pb = await login(); + const data = await pb.collection(collectionName).getFullList(); + await logout(pb); + return data; +} + +export default { login, logout, getFullList } \ No newline at end of file diff --git a/174bg/discord-bot/source/controllers/uex.js b/174bg/discord-bot/source/controllers/uex.js new file mode 100644 index 0000000..615b517 --- /dev/null +++ b/174bg/discord-bot/source/controllers/uex.js @@ -0,0 +1,29 @@ +import { get } from "./http.js"; + +export async function categories() { + return await get("https://api.uexcorp.uk/2.0/categories"); +} + +export async function items(id_category) { + let items = []; + + if (!id_category) { + const { data: categories } = await get("https://api.uexcorp.uk/2.0/categories"); + + for (const category of categories) { + const { data: categoryItems } = await get(`https://api.uexcorp.uk/2.0/items?id_category=${category.id}`); + + if (!categoryItems) continue; + + items = items.concat(categoryItems); + } + } else { + const { data } = await get(`https://api.uexcorp.uk/2.0/items?category=${id_category}`); + + items = data; + } + + return items; +} + +export default { categories, items }; \ No newline at end of file diff --git a/174bg/discord-bot/source/main.js b/174bg/discord-bot/source/main.js new file mode 100644 index 0000000..1fcf9c1 --- /dev/null +++ b/174bg/discord-bot/source/main.js @@ -0,0 +1,64 @@ +import { + Client, + Events, + GatewayIntentBits, + REST, + Routes, + ChannelType, + PermissionFlagsBits, + SlashCommandBuilder, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, +} from "discord.js"; + +import commands from "./commands/_index.js"; + +const client = new Client({ + intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers], +}); + +client.on(Events.ClientReady, async (readyClient) => { + console.log(`Logged in as ${readyClient.user.tag}!`); + + await registerCommands(); +}); + +client.on(Events.InteractionCreate, async (interaction) => { + if (interaction.guild.id !== process.env.DISCORD_GUILD_ID) return; + + if (interaction.isChatInputCommand()) { + if ( + commands.some((command) => command.data.name === interaction.commandName) + ) { + const command = commands.find( + (command) => command.data.name === interaction.commandName, + ); + await command.execute(interaction); + } + } +}); + +client.login(process.env.DISCORD_BOT_TOKEN); + +async function registerCommands() { + 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: [], + }); + + // 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()), + }, + ); +} \ No newline at end of file diff --git a/174bg/discord-bot/source/utilities/createTicket.js b/174bg/discord-bot/source/utilities/createTicket.js new file mode 100644 index 0000000..6289715 --- /dev/null +++ b/174bg/discord-bot/source/utilities/createTicket.js @@ -0,0 +1,55 @@ +import { ChannelType, PermissionFlagsBits } from "discord.js"; + +export default async ( + interaction, + options = { roleNamesWithAccess: [], ticketNamePrefix: "ticket-" }, +) => { + const ticketsCategory = interaction.guild.channels.cache.find( + (channel) => + channel.name.toLowerCase() === "tickets" && + channel.type === ChannelType.GuildCategory, + ); + + if (!ticketsCategory) + return await interaction.reply("Tickets category not found."); + + // get the roles that should have access to this ticket + const rolesWithAccess = options.roleNamesWithAccess.map((roleName) => { + return interaction.guild.roles.cache.find( + (role) => role.name.toLowerCase() === roleName.toLowerCase(), + ); + }); + + // create a new text channel under the "tickets" category with the following format: ticket- + const timestamp = new Date() + .toISOString() + .replace(/[-:.T]/g, "") + .slice(2, 16); + + const channelName = `${options.ticketNamePrefix}${timestamp}`; + + const newChannel = await interaction.guild.channels.create({ + name: channelName, + type: ChannelType.GuildText, + parent: ticketsCategory.id, + // set permissions so that only the user who created the ticket and the roles with access can view it + permissionOverwrites: [ + { + id: interaction.user.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + ], + }, + ...rolesWithAccess.map((role) => ({ + id: role.id, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages, + ], + })), + ], + }); + + return newChannel; +}; diff --git a/174bg/discord-bot/source/utilities/notifyRoleByName.js b/174bg/discord-bot/source/utilities/notifyRoleByName.js new file mode 100644 index 0000000..1b2b82a --- /dev/null +++ b/174bg/discord-bot/source/utilities/notifyRoleByName.js @@ -0,0 +1,19 @@ +export default async (guild, roleName, message) => { + // get the role by name + const role = guild.roles.cache.find( + (role) => role.name.toLowerCase() === roleName.toLowerCase(), + ); + + if (!role) { + console.error(`Role "${roleName}" not found.`); + return; + } + + // get all members with the role + const membersWithRole = role.members; + + // send a DM to each member with the role + for (const member of membersWithRole.values()) { + await member.send(message); + } +}; diff --git a/174bg/discord-bot/utilities/createTicket.js b/174bg/discord-bot/utilities/createTicket.js deleted file mode 100644 index 6289715..0000000 --- a/174bg/discord-bot/utilities/createTicket.js +++ /dev/null @@ -1,55 +0,0 @@ -import { ChannelType, PermissionFlagsBits } from "discord.js"; - -export default async ( - interaction, - options = { roleNamesWithAccess: [], ticketNamePrefix: "ticket-" }, -) => { - const ticketsCategory = interaction.guild.channels.cache.find( - (channel) => - channel.name.toLowerCase() === "tickets" && - channel.type === ChannelType.GuildCategory, - ); - - if (!ticketsCategory) - return await interaction.reply("Tickets category not found."); - - // get the roles that should have access to this ticket - const rolesWithAccess = options.roleNamesWithAccess.map((roleName) => { - return interaction.guild.roles.cache.find( - (role) => role.name.toLowerCase() === roleName.toLowerCase(), - ); - }); - - // create a new text channel under the "tickets" category with the following format: ticket- - const timestamp = new Date() - .toISOString() - .replace(/[-:.T]/g, "") - .slice(2, 16); - - const channelName = `${options.ticketNamePrefix}${timestamp}`; - - const newChannel = await interaction.guild.channels.create({ - name: channelName, - type: ChannelType.GuildText, - parent: ticketsCategory.id, - // set permissions so that only the user who created the ticket and the roles with access can view it - permissionOverwrites: [ - { - id: interaction.user.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - ], - }, - ...rolesWithAccess.map((role) => ({ - id: role.id, - allow: [ - PermissionFlagsBits.ViewChannel, - PermissionFlagsBits.SendMessages, - ], - })), - ], - }); - - return newChannel; -}; diff --git a/174bg/discord-bot/utilities/notifyRoleByName.js b/174bg/discord-bot/utilities/notifyRoleByName.js deleted file mode 100644 index 1b2b82a..0000000 --- a/174bg/discord-bot/utilities/notifyRoleByName.js +++ /dev/null @@ -1,19 +0,0 @@ -export default async (guild, roleName, message) => { - // get the role by name - const role = guild.roles.cache.find( - (role) => role.name.toLowerCase() === roleName.toLowerCase(), - ); - - if (!role) { - console.error(`Role "${roleName}" not found.`); - return; - } - - // get all members with the role - const membersWithRole = role.members; - - // send a DM to each member with the role - for (const member of membersWithRole.values()) { - await member.send(message); - } -}; -- cgit v1.2.3