From 0500004ab76bde43c26f1922a25adf9622497b60 Mon Sep 17 00:00:00 2001 From: Dominic Ruggiero Date: Mon, 30 Dec 2024 20:03:16 -0500 Subject: [PATCH] adds improved order editing admin tools --- .vscode/settings.json | 3 + src/modules/devtools.ts | 5 +- src/modules/edit-order.ts | 342 +++++++++++++++++++++++++++++++++++++ src/modules/message.ts | 2 +- src/orders/types.d.ts | 2 +- src/orders/updateStatus.ts | 102 +++++++---- src/outlet.ts | 31 ++++ src/utils/log.ts | 7 +- 8 files changed, 453 insertions(+), 41 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/modules/edit-order.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7124977 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cSpell.words": ["janky"] +} diff --git a/src/modules/devtools.ts b/src/modules/devtools.ts index f598fbc..6adbca7 100644 --- a/src/modules/devtools.ts +++ b/src/modules/devtools.ts @@ -49,6 +49,10 @@ messagesClient.addGlobalCommand( .setCustomId("devtools:dm") .setLabel("Message user") .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId("devtools:edit-order") + .setLabel("edit order") + .setStyle(ButtonStyle.Primary), ]); const a3 = new ActionRowBuilder().addComponents([ new ButtonBuilder() @@ -60,7 +64,6 @@ messagesClient.addGlobalCommand( .setLabel("fire drill") .setStyle(ButtonStyle.Secondary), ]); - return interaction.reply({ components: [a1, a2, a3], ephemeral: true }); } ); diff --git a/src/modules/edit-order.ts b/src/modules/edit-order.ts new file mode 100644 index 0000000..e633d62 --- /dev/null +++ b/src/modules/edit-order.ts @@ -0,0 +1,342 @@ +import { + ModalBuilder, + ActionRowBuilder, + TextInputBuilder, + TextInputStyle, + ButtonBuilder, + ButtonStyle, + UserSelectMenuBuilder, +} from "discord.js"; +import bot, { messagesClient } from ".."; +import { getOrder, updateOrder } from "../orders/cache"; +import sendLogMessage from "../utils/log"; +import updateOrderStatus, { sendOrderForFilling } from "../orders/updateStatus"; +import { orderStatus } from "@prisma/client"; + +messagesClient.registerButton("devtools:edit-order", async (interaction) => { + const modal = new ModalBuilder() + .setTitle("Edit order") + .setCustomId("devtools:edit-order:modal") + .addComponents([ + new ActionRowBuilder().addComponents([ + new TextInputBuilder() + .setCustomId("order") + .setLabel("Order ID") + .setRequired(true) + .setStyle(TextInputStyle.Short) + .setMinLength(1), + ]), + ]); + return interaction.showModal(modal); +}); + +messagesClient.registerModal( + "devtools:edit-order:modal", + async (interaction) => { + const id = interaction.fields.getTextInputValue("order"); + const order = await getOrder(parseInt(id)); + if (!order) + return interaction.reply({ + content: "Order not found", + }); + const a1 = new ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setCustomId(`devtools:edit-order:${order.id}:order`) + .setLabel("order") + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId(`devtools:edit-order:${order.id}:file`) + .setLabel("file") + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId(`devtools:edit-order:${order.id}:status`) + .setLabel("status") + .setStyle(ButtonStyle.Secondary), + ]); + return interaction.reply({ + components: [a1], + ephemeral: true, + content: `updating order **${order.id}** for **${order.order}**\n${order.status}`, + }); + } +); + +messagesClient.registerButton( + /devtools:edit-order:(\d+):order/, + async (interaction) => { + const id = parseInt(interaction.customId.split(":")[2]); + const order = await getOrder(id); + if (!order) + return interaction.reply({ + content: "Order not found", + ephemeral: true, + }); + const modal = new ModalBuilder() + .setTitle("Edit order") + .setCustomId(`devtools:edit-order:${order.id}:order:modal`) + .addComponents([ + new ActionRowBuilder().addComponents([ + new TextInputBuilder() + .setCustomId("order") + .setLabel("Order") + .setRequired(true) + .setStyle(TextInputStyle.Paragraph) + .setValue(order.order), + ]), + ]); + return interaction.showModal(modal); + } +); + +messagesClient.registerModal( + /devtools:edit-order:(\d+):order:modal/, + async (interaction) => { + const id = parseInt(interaction.customId.split(":")[2]); + const newOrder = interaction.fields.getTextInputValue("order"); + const order = await updateOrder(id, { order: newOrder }); + await sendLogMessage( + "materialEdit", + `<@!${interaction.user.id}> changed order **#${order.id}** to **${order.order}**`, + interaction.user.id + ); + + if (order.status == orderStatus.ORDERED) await sendOrderForFilling(order); + if (order.status == orderStatus.FILLING) + await updateOrderStatus({ + id: order.id, + status: orderStatus.FILLING, + chef: order.chefId!, + chefUsername: order.chefUsername!, + interactionMessageId: order.relatedKitchenMessages[0].split(":")[1], + admin: interaction.user.id, + }); + if (order.status == orderStatus.PACKED) + await updateOrderStatus({ + id: order.id, + status: orderStatus.PACKED, + chef: interaction.user.id, + chefUsername: interaction.user.username, + admin: interaction.user.id, + }); + + return interaction.reply({ + content: `Order **${order.id}** updated`, + ephemeral: true, + }); + } +); + +messagesClient.registerButton( + /devtools:edit-order:(\d+):file/, + async (interaction) => { + const id = parseInt(interaction.customId.split(":")[2]); + const order = await getOrder(id); + if (!order) + return interaction.reply({ + content: "Order not found", + ephemeral: true, + }); + const modal = new ModalBuilder() + .setTitle("Edit order") + .setCustomId(`devtools:edit-order:${order.id}:file:modal`) + .addComponents([ + new ActionRowBuilder().addComponents([ + new TextInputBuilder() + .setCustomId("file") + .setLabel("File URL") + .setRequired(false) + .setStyle(TextInputStyle.Short) + .setValue(order.fileUrl ?? ""), + ]), + ]); + return interaction.showModal(modal); + } +); + +messagesClient.registerModal( + /devtools:edit-order:(\d+):file:modal/, + async (interaction) => { + const id = parseInt(interaction.customId.split(":")[2]); + const newFile = interaction.fields.getTextInputValue("file"); + const order = await updateOrder(id, { fileUrl: newFile }); + await sendLogMessage( + "materialEdit", + `<@!${interaction.user.id}> updated the file URL for order **#${order.id}**`, + interaction.user.id + ); + + return interaction.reply({ + content: `Order **${order.id}** updated`, + ephemeral: true, + }); + } +); + +messagesClient.registerButton( + /devtools:edit-order:(\d+):status/, + async (interaction) => { + const id = parseInt(interaction.customId.split(":")[2]); + const a1 = new ActionRowBuilder().addComponents([ + new UserSelectMenuBuilder() + .setCustomId(`devtools:edit-order:${id}:status:user`) + .setPlaceholder("on behalf of who?"), + ]); + return interaction.update({ + content: `updating status of order **${id}**`, + components: [a1], + }); + } +); + +messagesClient.registerUserSelectMenu( + /devtools:edit-order:(\d+):status:user/, + async (interaction) => { + const id = parseInt(interaction.customId.split(":")[2]); + const userId = interaction.values[0]; + const order = await getOrder(id); + + if (!order) return interaction.update({ content: "Order not found" }); + + const a1 = new ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setCustomId(`devtools:edit-order:${id}:status:${userId}:ordered`) + .setLabel("ordered") + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId(`devtools:edit-order:${id}:status:${userId}:filling`) + .setLabel("filling") + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId(`devtools:edit-order:${id}:status:${userId}:packing`) + .setLabel("packing") + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId(`devtools:edit-order:${id}:status:${userId}:packed`) + .setLabel("packed") + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId(`devtools:edit-order:${id}:status:${userId}:delivering`) + .setLabel("delivering") + .setStyle(ButtonStyle.Secondary), + ]); + const a2 = new ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setCustomId(`devtools:edit-order:${id}:status:${userId}:delivered`) + .setLabel("delivered") + .setStyle(ButtonStyle.Secondary), + new ButtonBuilder() + .setCustomId(`devtools:edit-order:${id}:status:${userId}:rejected`) + .setLabel("rejected") + .setStyle(ButtonStyle.Danger), + ]); + return interaction.update({ + content: `updating status of order **${order.id}** for **${order.order}**\n${order.status}`, + components: [a1, a2], + }); + } +); + +messagesClient.registerButton( + /devtools:edit-order:(\d+):status:(\d+):(.+)/, + async (interaction) => { + const id = parseInt(interaction.customId.split(":")[2]); + const userId = interaction.customId.split(":")[4]; + const status = interaction.customId + .split(":")[5] + .toUpperCase() as orderStatus; + const order = await getOrder(id); + if (!order) + return interaction.reply({ + content: "Order not found", + ephemeral: true, + }); + if (order.status == orderStatus.PACKING) { + return interaction.update({ + content: + "not implemented, the packing scheduling thing rn is too janky to update the status of a packing order (:", + }); + } + + if (status == orderStatus.ORDERED) { + await sendOrderForFilling(order); + await sendLogMessage( + "materialEdit", + `<@!${userId}> created order **#${id}** for **${order.order}**`, + interaction.user.id + ); + await interaction.update({ + content: `Order **${id}** updated`, + }); + } else if ( + status == orderStatus.PACKING || + status == orderStatus.REJECTED || + status == orderStatus.PACKED + ) { + //im too tired to make this work independently and fetch all the data again so its just going to be like this + const modalId = `devtools:edit-order:${id}:status:${userId}:${status}:modal:${Date.now()}`; + const modal = new ModalBuilder() + .setTitle("Edit order status") + .setCustomId(modalId) + .addComponents([ + new ActionRowBuilder().addComponents([ + new TextInputBuilder() + .setCustomId("content") + .setLabel(status == orderStatus.REJECTED ? "Reason" : "File URL") + .setRequired(true) + .setStyle(TextInputStyle.Short) + .setValue( + status == orderStatus.REJECTED + ? order.rejectedReason ?? "" + : order.fileUrl ?? "" + ), + ]), + ]); + await interaction.showModal(modal); + bot.registerModal(modalId, async (interaction) => { + const content = interaction.fields.getTextInputValue("content"); + if (status == orderStatus.REJECTED) { + await updateOrderStatus({ + id, + status, + chef: userId, + reason: content, + }); + } else { + await updateOrderStatus({ + id, + status, + chef: userId, + chefUsername: await bot.client.users + .fetch(userId) + .then((u) => u.username), + fileUrl: content, + }); + } + await interaction.reply({ + content: `Order **${id}** updated`, + ephemeral: true, + }); + }); + } else { + console.log("status", status); + const update = await updateOrderStatus({ + id, + status, + chef: userId, + chefUsername: await bot.client.users + .fetch(userId) + .then((u) => u.username), + admin: interaction.user.id, + }); + if (update.success) { + await interaction.update({ + content: `Order **${id}** updated`, + }); + } else { + await interaction.update({ + content: `Order **${id}** failed to update\n${update.message}`, + }); + } + } + } +); diff --git a/src/modules/message.ts b/src/modules/message.ts index afe8ae9..9700526 100644 --- a/src/modules/message.ts +++ b/src/modules/message.ts @@ -41,7 +41,7 @@ bot.registerButton("message-set", async (interaction) => { .setLabel("Message") .setRequired(true) .setStyle(TextInputStyle.Paragraph) - .setValue(exists?.message || "") + .setValue(exists?.message ?? "") .setMaxLength(1000), ]), ]); diff --git a/src/orders/types.d.ts b/src/orders/types.d.ts index c51680d..ffb983c 100644 --- a/src/orders/types.d.ts +++ b/src/orders/types.d.ts @@ -27,7 +27,7 @@ declare type updateOrderStatusParams = chefUsername: string; status: "DELIVERING" | "FILLING"; admin?: string; - interactionMessageId: string; + interactionMessageId?: string; } | { id: number; diff --git a/src/orders/updateStatus.ts b/src/orders/updateStatus.ts index 6f80e1b..d7ffac0 100644 --- a/src/orders/updateStatus.ts +++ b/src/orders/updateStatus.ts @@ -5,7 +5,9 @@ import { ButtonBuilder, ButtonStyle, EmbedBuilder, + Message, TextBasedChannel, + WebhookMessageCreateOptions, } from "discord.js"; import env from "../utils/env"; import { @@ -93,11 +95,16 @@ const updateOrderStatus = async ( //KITCHEN ACTION and db update switch (p.status) { case orderStatus.FILLING: - order = await updateOrder(id, { - status, - chefId: chef, - chefUsername: p.chefUsername, - }); + const startingStatus = order.status; + order = await updateOrder( + id, + { + status, + chefId: chef, + chefUsername: p.chefUsername, + }, + !p.interactionMessageId && true + ); const orderFillingActionRow = new ActionRowBuilder().addComponents([ new ButtonBuilder() @@ -113,23 +120,33 @@ const updateOrderStatus = async ( .setTitle(`Order from **${order.customerUsername}**`) .setDescription(order.order) .setFooter({ text: `Order ID: ${id}` }); - await editKitchenMessage(KitchenChannel.orders, p.interactionMessageId, { + + const fillingBody: WebhookMessageCreateOptions = { embeds: [orderFillingEmbed], components: [orderFillingActionRow], content: `<@!${chef}>`, - }); + }; + const fillingMessage = p.interactionMessageId + ? await editKitchenMessage( + KitchenChannel.orders, + p.interactionMessageId, + fillingBody + ) + : await sendKitchenMessage(KitchenChannel.orders, fillingBody, id); - const ordersChannel = (await messagesClient.client.channels.fetch( - env.NEW_ORDERS_CHANNEL_ID - )) as TextBasedChannel; - const orderFillMessage = await ordersChannel.messages.fetch( - p.interactionMessageId - ); - await orderFillMessage.startThread({ - name: `Order #${id}`, - autoArchiveDuration: 60, - reason: `Order ${id} claimed by ${chef}`, - }); + if (startingStatus != orderStatus.FILLING || !p.interactionMessageId) { + const ordersChannel = (await messagesClient.client.channels.fetch( + env.NEW_ORDERS_CHANNEL_ID + )) as TextBasedChannel; + const orderFillMessage = await ordersChannel.messages.fetch( + fillingMessage.id + ); + await orderFillMessage.startThread({ + name: `Order #${id}`, + autoArchiveDuration: 60, + reason: `Order ${id} claimed by ${chef}`, + }); + } break; case orderStatus.PACKING: order = await updateOrder( @@ -143,9 +160,13 @@ const updateOrderStatus = async ( setTimeout(() => finishPackOrder(id), 1000 * 60 * 5); break; case orderStatus.PACKED: - order = await updateOrder(id, { - status, - }); + order = await updateOrder( + id, + { + status, + }, + true + ); const deliveryActionRow = new ActionRowBuilder().addComponents([ new ButtonBuilder() @@ -172,11 +193,15 @@ const updateOrderStatus = async ( ); break; case orderStatus.DELIVERING: - order = await updateOrder(id, { - status, - deliveryId: chef, - deliveryUsername: p.chefUsername, - }); + order = await updateOrder( + id, + { + status, + deliveryId: chef, + deliveryUsername: p.chefUsername, + }, + !p.interactionMessageId && true + ); try { var guild = await bot.client.guilds.fetch(order.guildId); } catch (e) { @@ -223,15 +248,20 @@ const updateOrderStatus = async ( .setTitle(`Order from **${order.customerUsername}**`) .setDescription(order.order) .setFooter({ text: `Order ID: ${id}` }); - await editKitchenMessage( - KitchenChannel.deliveries, - p.interactionMessageId, - { - embeds: [deliveringEmbed], - components: [deliveringActionRow], - content: `<@!${chef}>`, - } - ); + const deliveringBody: WebhookMessageCreateOptions = { + embeds: [deliveringEmbed], + components: [deliveringActionRow], + content: `<@!${chef}>`, + }; + if (p.interactionMessageId) { + await editKitchenMessage( + KitchenChannel.deliveries, + p.interactionMessageId, + deliveringBody + ); + } else { + await sendKitchenMessage(KitchenChannel.deliveries, deliveringBody, id); + } order = await updateOrder(id, { status, invite: invite.url, @@ -354,7 +384,7 @@ const updateOrderStatus = async ( emoji = "materialError"; break; } - await sendLogMessage(emoji, logMessage); + await sendLogMessage(emoji, logMessage, admin); updateProcessingOrders(status, id); diff --git a/src/outlet.ts b/src/outlet.ts index ab9825f..579d563 100644 --- a/src/outlet.ts +++ b/src/outlet.ts @@ -33,6 +33,10 @@ export default class Powercord { customId: RegExp | string; callback: (interaction: Discord.StringSelectMenuInteraction) => void; }[]; + private userSelectMenus: { + customId: RegExp | string; + callback: (interaction: Discord.UserSelectMenuInteraction) => void; + }[]; private modals: { customId: RegExp | string; callback: (interaction: Discord.ModalSubmitInteraction) => void; @@ -42,6 +46,7 @@ export default class Powercord { this.globalCommands = []; this.buttons = []; this.stringSelectMenus = []; + this.userSelectMenus = []; this.modals = []; this.token = token; @@ -90,6 +95,17 @@ export default class Powercord { this.stringSelectMenus.push({ customId, callback }); } + registerUserSelectMenu( + customId: RegExp | string, + callback: (interaction: Discord.UserSelectMenuInteraction) => void + ) { + if (this.userSelectMenus.find((select) => select.customId === customId)) { + console.log(`UserSelectMenu ${customId} already registered`); + return; + } + this.userSelectMenus.push({ customId, callback }); + } + registerModal( customId: RegExp | string, callback: (interaction: Discord.ModalSubmitInteraction) => void @@ -172,6 +188,21 @@ export default class Powercord { ); return; } + if (interaction.isUserSelectMenu()) { + const userSelectMenu = this.userSelectMenus.find( + (btn) => + interaction.customId.match(btn.customId)?.[0] == + interaction.customId + ); + if (!userSelectMenu) { + console.log(`UserSelectMenu not found ${interaction.customId}`); + return; + } + await userSelectMenu.callback( + interaction as Discord.UserSelectMenuInteraction + ); + return; + } break; case Discord.InteractionType.ModalSubmit: const modal = this.modals.find( diff --git a/src/utils/log.ts b/src/utils/log.ts index cc1636b..30c6afd 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -3,10 +3,13 @@ import { sendKitchenMessage, KitchenChannel } from "./kitchenChannels"; const sendLogMessage = async ( emoji: keyof typeof emojiInline, - message: string + message: string, + admin?: string ) => { + let content = `${emojiInline[emoji]} ${message}`; + if (admin) content += `\n-# manual action by admin <@!${admin}>`; await sendKitchenMessage(KitchenChannel.logs, { - content: `${emojiInline[emoji]} ${message}`, + content, allowedMentions: { parse: [] }, }); };