From 820ed99021468f9f970d5cb86fe04a96c6ee32ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Gro=C3=9F?= Date: Wed, 14 Sep 2022 18:25:31 +0200 Subject: [PATCH] Edit: Add command to edit existing records. This currently requires global admin permissions. Each edit gets persisted in the history table. --- commands/edit.js | 197 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 commands/edit.js diff --git a/commands/edit.js b/commands/edit.js new file mode 100644 index 0000000..fd490b4 --- /dev/null +++ b/commands/edit.js @@ -0,0 +1,197 @@ +const { SlashCommandBuilder, ComponentType, ActionRowBuilder, ButtonBuilder, ButtonStyle, ModalBuilder, TextInputStyle, TextInputBuilder } = require("discord.js"); +const { Character, Band, RecordHistory } = require("../models"); +const { UserUtils, GeneralUtils } = require("../util"); +const axios = require("axios"); +const fs = require("fs"); + +module.exports = { + data: new SlashCommandBuilder() + .setName("edit") + .setDescription("Admin command to edit records") + .addStringOption((option) => + option + .setName("type") + .setDescription("The thing to edit") + .setRequired(true) + .addChoices( + { name: 'character', value: 'character' }, + { name: 'band', value: 'band' } + ) + ) + .addStringOption((option) => + option + .setName("id") + .setDescription("Thing ID") + .setRequired(true) + ) + .addAttachmentOption((option) => + option + .setName("attachement") + .setDescription("Attachement to be used") + .setRequired(false) + ), + permissionLevel: 2, + async execute(interaction) { + await interaction.deferReply(); + let user = await UserUtils.getUserByDiscordId(interaction.member.id); + + let options = []; + let record; + switch (interaction.options.getString("type")) { + case "character": + record = await Character.findByPk(interaction.options.getString("id"), { include: Band }); + if (!record) { + interaction.editReply({ content: "Character not found" }); + return; + } + options = [ "name", "description", "imageIdentifier" ]; + break; + case "band": + options = [ "name", "description" ]; + break; + default: + interaction.reply({ + content: `Your permission level is ${await UserUtils.getPermissionLevel(interaction.member)}`, + ephemeral: false + }); + break; + } + + //row of button components to select what property to edit + const row = new ActionRowBuilder(); + for (option of options) { + row.addComponents( + new ButtonBuilder() + .setLabel(`Edit ${option}`) + .setCustomId(`${option}-${interaction.id}`) + .setStyle(ButtonStyle.Primary) + ); + } + + //show buttons + let message = await interaction.editReply({ content: `You've selected [${record.identifier ?? record.id}] ${record.name}`, components: [row], fetchReply: true }); + + //filter only events from the user who triggered the command + const filter = (m) => m.author.id === interaction.author.id && m.customId.endsWith(interaction.id); + const collector = message.createMessageComponentCollector({ componentType: ComponentType.Button, time: 65000 }) + + collector.on('collect', async (m) => { + let option = m.customId.split("-")[0]; + switch (option) { + case 'name': + let newName = await this.openStringModal(m, `Edit ${option} for ${record.name}`, ""); + if (newName) { + await this.updateRecord(user, record, option, newName.value); + } + break; + case 'description': + let newDesc = await this.openParagraphModal(m, `Edit ${option} for ${record.name}`, ""); + if (newDesc) { + await this.updateRecord(user, record, option, newDesc.value); + } + break; + case 'imageIdentifier': + m.deferReply(); + let allowedContentTypes = [ "image/png", "image/jpeg", "image/gif" ]; + let image = interaction.options.getAttachment("attachement"); + if (!image) { + await m.reply({ content: "You must attach an image", ephemeral: true }); + return; + } + if (!allowedContentTypes.includes(image.contentType)) { + await m.reply({ content: "An invalid image has been attached", ephemeral: true }); + return; + } + let identifier = `${record.Band.name}/${image.name}`; + let path = `/app/assets/cards/${identifier}`; + await this.downloadImage(image.attachment, path); + await this.updateRecord(user, record, option, identifier); + m.editReply({ content: "Attached image has been applied" }); + break; + default: + m.reply({ content: "Invalid selection" }); + break; + } + }); + }, + async downloadImage(url, path) { + let imageBuffer = await axios.get(url, { responseType: 'arraybuffer' }); + fs.writeFileSync(path, imageBuffer.data); + }, + + async openParagraphModal(interaction, title, placeholder) { + const modal = new ModalBuilder() + .setCustomId('paragraphModal') + .setTitle(title.substr(0, 44)); + + let row = new ActionRowBuilder(); + let textInput = new TextInputBuilder() + .setCustomId(`input-${interaction.id}`) + .setLabel("Enter new value") + .setStyle(TextInputStyle.Paragraph) + .setRequired(false) + .setMaxLength(255) + .setPlaceholder(placeholder ?? "We've been trying to get a hold of you about your car's extended warranty."); + row.addComponents(textInput); + modal.addComponents(row); + + let message = await interaction.showModal(modal); + + let submitted = await interaction.awaitModalSubmit({ + time: 300000, + filter: i => i.user.id === interaction.user.id, + }).catch(error => { + console.error(error) + return null + }) + + if (submitted) { + submitted.reply({ content: `You submitted: ${submitted.fields.getTextInputValue(`input-${interaction.id}`)}`}) + return { i: submitted, value: submitted.fields.getTextInputValue(`input-${interaction.id}`)}; + } + }, + async openStringModal(interaction, title, placeholder) { + const modal = new ModalBuilder() + .setCustomId('textModal') + .setTitle(title.substr(0, 44)); + + let row = new ActionRowBuilder(); + let textInput = new TextInputBuilder() + .setCustomId(`input-${interaction.id}`) + .setLabel("Enter new value") + .setStyle(TextInputStyle.Short) + .setRequired(false) + .setMaxLength(255) + .setPlaceholder(placeholder ?? ""); + row.addComponents(textInput); + modal.addComponents(row); + + let message = await interaction.showModal(modal); + + let submitted = await interaction.awaitModalSubmit({ + time: 300000, + filter: i => i.user.id === interaction.user.id, + }).catch(error => { + console.error(error) + return null + }) + + if (submitted) { + submitted.reply({ content: `You submitted: ${submitted.fields.getTextInputValue(`input-${interaction.id}`)}` }); + return { i: submitted, value: submitted.fields.getTextInputValue(`input-${interaction.id}`)}; + } + }, + async updateRecord(user, record, property, value) { + let history = { + affectedId: record.id, + userId: user.id, + type: record.constructor.name, + property: property, + oldValue: record[property], + newValue: value + } + record[property] = value; + await record.save(); + await RecordHistory.create(history); + } +} \ No newline at end of file