From 60f4c513530f0c426a3c3ba735f5228dd9cbe567 Mon Sep 17 00:00:00 2001 From: Minzkraut Date: Sun, 18 Sep 2022 00:49:30 +0200 Subject: [PATCH] View: Add mixed view with autocomplete for cards and characters --- commands/view.js | 127 +++++++++++++++++++++++++++++----- events/autocompleteRequest.js | 61 ++++++++++++++++ 2 files changed, 169 insertions(+), 19 deletions(-) create mode 100644 events/autocompleteRequest.js diff --git a/commands/view.js b/commands/view.js index 7c6e219..4ed83b0 100644 --- a/commands/view.js +++ b/commands/view.js @@ -1,6 +1,8 @@ -const { SlashCommandBuilder, AttachmentBuilder, EmbedBuilder } = require("discord.js"); +const { SlashCommandBuilder, AttachmentBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } = require("discord.js"); const { Card, User, Band, Character } = require("../models"); -const Rendering = require("../util/rendering"); +const { Rendering, UserUtils } = require("../util"); +const fs = require("fs"); +const edit = require("./edit"); //fetch all cards owned by the user and list them module.exports = { @@ -9,33 +11,51 @@ module.exports = { .setDescription("View a specific card") .addStringOption((option) => option - .setName("card") - .setDescription("Card identifier") - .setRequired(false) + .setName("type") + .setDescription("The thing to view") + .setRequired(true) + .addChoices( + { name: 'card', value: 'card' }, + { name: 'character', value: 'character' }, + { name: 'band', value: 'band' } + ) + ) + .addStringOption((option) => + option + .setName("id") + .setDescription("Thing identifier") + .setRequired(true) + .setAutocomplete(true) ), async execute(interaction) { await interaction.deferReply(); - const cardId = interaction.options.getString('card'); - const card = await Card.findOne({ + + switch (interaction.options.getString("type")) { + case "card": + this.viewCard(interaction, interaction.options.getString("id")); + break; + case "character": + this.viewCharacter(interaction, interaction.options.getString("id")); + break; + case "band": + interaction.editReply({ content: "Band view is not yet implemented" }); + break; + + } + }, + + async viewCard(interaction, cardIdentifier) { + let card = await Card.findOne({ where: { - identifier: cardId + identifier: cardIdentifier }, include: [ - { model: Character, include: - [Band] }, - User + { model: Character, include: [{ model: Band }] }, + { model: User} ] }); - if (!card) { - interaction.reply({ - content: "Card not found", - ephemeral: true - }); - return; - } let cardImage = await Rendering.renderCard(card); - let attachment = new AttachmentBuilder(card); //get base filename let filename = cardImage.split("/").pop(); @@ -62,12 +82,81 @@ module.exports = { .addFields( { name: "Owned by", value: `<@${card.User.discordId}>` }, { name: "Band", value: `${card.Character.Band.name}` }, + { name: "Character ID", value: `${card.Character.id}` }, { name: 'Print Number', value: `${card.printNr}`, inline: true }, { name: 'Quality', value: `${card.quality}`, inline: true } ) .setColor(0x00ff00) .setFooter({ text: `${card.identifier}`, iconURL: 'https://cdn.discordapp.com/attachments/856904078754971658/1017431187234508820/fp.png' }) .setTimestamp(card.createdAt); + const message = await interaction.editReply({ embeds: [embed], files: [cardImage], fetchReply: true }); + }, + + async viewCharacter(interaction, characterId) { + let isAdmin = await UserUtils.getPermissionLevel(interaction.member) == 2; + let character = await Character.findOne({ + where: { id: characterId }, + include: [Band] + }); + if (!character) { + interaction.editReply({ content: "Character not found" }); + return; + } + let imagePath = `./assets/cards/${character.imageIdentifier}`; + //if image doesn't exist, use placeholder + if (!fs.existsSync(imagePath)) { + imagePath = "./assets/cards/missing_image.png"; + } + + //get base filename + let filename = imagePath.split("/").pop(); + + let description = ""; + //Add a new line after every 4th (long) word or after a full stop + let words = character.description.split(" "); + let count = 0; + for (let i = 0; i < words.length; i++) { + description += words[i] + " "; + if (words[i].length > 3) { + count++; + } + if (count >= 4 || words[i].endsWith(".")) { + description += "\n"; + count = 0; + } + } + + const embed = new EmbedBuilder() + .setTitle(`${character.name}`) + .setDescription(description) + .setImage(`attachment://${filename}`) + .setThumbnail(character.Band.imageURL) + .addFields( + { name: "Band", value: `${character.Band.name}` }, + { name: "Character ID", value: `${character.id}` }, + ) + .setColor(0x00ff00) + + let row; + if (isAdmin) { + row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(`edit-char-${character.id}`) + .setLabel("Edit") + .setStyle(ButtonStyle.Danger) + ); + } + const message = await interaction.editReply({ embeds: [embed], files: [imagePath], components: [row], fetchReply: true }); + const filter = (m) => m.member.user.id === interaction.member.user.id; + const collector = message.createMessageComponentCollector({ filter, componentType: ComponentType.Button, time: 120000 }); + + collector.on('collect', async (m) => { + console.log(`Collected ${m.customId}`); + if (m.customId === `edit-char-${character.id}`) { + await m.reply({ content: "Editing not implemented", ephemeral: true }); + } + }); } } \ No newline at end of file diff --git a/events/autocompleteRequest.js b/events/autocompleteRequest.js new file mode 100644 index 0000000..b817665 --- /dev/null +++ b/events/autocompleteRequest.js @@ -0,0 +1,61 @@ +const { InteractionType } = require('discord.js'); +const { UserUtils } = require('../util'); +const { Card, Character, User } = require('../models'); +const Sequelize = require('sequelize'); +module.exports = { + name: "interactionCreate", + async execute (interaction) { + let isRegistered = await UserUtils.registrationCheck(interaction); + if (!isRegistered) return; + if (interaction.type !== InteractionType.ApplicationCommandAutocomplete) return; + console.log(`Autocomplete request from ${interaction.user.tag} (${interaction.user.id}) for ${interaction.commandName} with ${interaction.options.getFocused(true).value}`); + if (interaction.commandName === 'view') { + const viewType = interaction.options.getString('type'); + let focusedOption = interaction.options.getFocused(true); + + let choices = []; + + switch (viewType) { + case 'card': + const cards = await Card.findAll({ + where: { + identifier: { + [Sequelize.Op.like]: `%${focusedOption.value}%` + } + }, + include: [{ model: Character }, { model: User }], + limit: 10 + }); + for (let i = 0; i < cards.length; i++) { + choices.push({ + name: `${cards[i].identifier} - ${cards[i].Character.name}`, + value: cards[i].identifier + }); + } + break; + case 'character': + if(focusedOption.value.length < 3) break; + const characters = await Character.findAll({ + where: { + name: { + [Sequelize.Op.like]: `%${focusedOption.value}%` + } + }, + limit: 10 + }); + for (let i = 0; i < characters.length; i++) { + choices.push({ + name: characters[i].name, + value: `${characters[i].id}` + }); + } + break; + case 'band': + break; + } + + await interaction.respond(choices); + return; + } + } +} \ No newline at end of file