From 7e7ddcb45f57273911216d9c7ac2ff9e2ab34b8a Mon Sep 17 00:00:00 2001 From: dlinux-host Date: Thu, 3 Oct 2024 18:00:03 -0400 Subject: [PATCH] Adding User App Support --- commands/Info/modalExample.js | 69 +++++++++---------- commands/Info/ping.js | 4 +- events/interactionCreate.js | 121 ++++++++++++++++------------------ handler/index.js | 31 +++++++-- 4 files changed, 112 insertions(+), 113 deletions(-) diff --git a/commands/Info/modalExample.js b/commands/Info/modalExample.js index 9b136c7..21efe9f 100644 --- a/commands/Info/modalExample.js +++ b/commands/Info/modalExample.js @@ -7,60 +7,51 @@ module.exports = { run: async (client, interaction) => { - // Declare a random var for the main modal - Each Session - let rand = Math.floor(Math.random() * 99999999999999).toString(); + // Declare a random ID for the modal to ensure unique interactions + const modalId = `modal-${Math.floor(Math.random() * 99999999999999).toString()}`; - // Check if this is a chatInput - if (!interaction.isChatInputCommand()) return; + // Check if this is a chatInput command interaction + if (!interaction.isChatInputCommand()) return; - // await interaction.deferReply(); + // Create the modal const modal = new ModalBuilder() - .setCustomId(rand) + .setCustomId(modalId) .setTitle('This is an example modal'); - // TODO: Add components to modal... + // Create a text input component for the modal const modalInputData = new TextInputBuilder() .setCustomId('modalInput') - // The label is the prompt the user sees for this input .setLabel("What text do you want to send?") - // Short means only a single line of text - .setStyle(TextInputStyle.Paragraph); + .setStyle(TextInputStyle.Paragraph); // Allows for multi-line text - // An action row only holds one text input, - // so you need one action row per text input. - const modalInputRow = new ActionRowBuilder().addComponents([modalInputData]); + // Wrap the text input in an action row + const modalInputRow = new ActionRowBuilder().addComponents(modalInputData); - // Add inputs to the modal - modal.addComponents([modalInputRow]); + // Add the input row to the modal + modal.addComponents(modalInputRow); + // Show the modal to the user await interaction.showModal(modal); - client.on('interactionCreate', interaction => { + // Handle the modal submission within the same interaction listener + client.once('interactionCreate', async (modalInteraction) => { - // Do not continue if its a modal - if (interaction.type == "modal") return - // Interaction type is 5 == Modal - if (interaction.type === 5) { + // Ensure we are handling the correct modal submission + if (!modalInteraction.isModalSubmit() || modalInteraction.customId !== modalId) return; - // Make sure we are working with our users modal only - if (interaction.customId === rand) { + // Retrieve the data entered by the user + const modalInputDataString = modalInteraction.fields.getTextInputValue('modalInput'); - // Get the data entered by the user - let modalInputDataString = interaction.fields.getTextInputValue('modalInput'); - - console.log(modalInputDataString) - - const embed = new EmbedBuilder() - // Set color to blue - .setColor("#FF0000") - .setTitle("Your input!") - .setDescription(`You said: ${modalInputDataString}`) - .setTimestamp() - .setFooter({ text: `Requested by ${interaction.user.tag}`, iconURL: `${interaction.user.displayAvatarURL()}` }); - interaction.reply({ embeds: [embed] }); - } - } - }) + // Create an embed to display the user input + const embed = new EmbedBuilder() + .setColor("#FF0000") + .setTitle("Your input!") + .setDescription(`You said: ${modalInputDataString}`) + .setTimestamp() + .setFooter({ text: `Requested by ${modalInteraction.user.tag}`, iconURL: modalInteraction.user.displayAvatarURL() }); + // Reply to the modal submission with the embed + await modalInteraction.reply({ embeds: [embed] }); + }); } -} +}; diff --git a/commands/Info/ping.js b/commands/Info/ping.js index 9a6b3d6..9eba91f 100644 --- a/commands/Info/ping.js +++ b/commands/Info/ping.js @@ -2,14 +2,14 @@ const { EmbedBuilder } = require('discord.js'); module.exports = { name: "ping", - private: true, + private: true, // Mark this command as private for ephemeral replies description: "Returns websocket latency", run: async (client, interaction) => { const embed = new EmbedBuilder() .setColor("#FF0000") .setTitle("🏓 Pong!") - .setDescription(`Latency : ${client.ws.ping}ms`) + .setDescription(`Latency: ${client.ws.ping}ms`) .setTimestamp() .setFooter({ text: `Requested by ${interaction.user.tag}`, iconURL: `${interaction.user.displayAvatarURL()}` }); interaction.followUp({ embeds: [embed] }); diff --git a/events/interactionCreate.js b/events/interactionCreate.js index 62c3e50..0a425c9 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -5,93 +5,84 @@ const { promisify } = require("util"); const globPromise = promisify(glob); client.on("interactionCreate", async (interaction) => { - // Slash Commands const slashCommands = await globPromise(`${process.cwd()}/commands/*/*.js`); const arrayOfSlashCommands = []; - + // Map the slash commands into data to be processed slashCommands.map((value) => { - const file = require(value); - const splitted = value.split("/"); - const directory = splitted[splitted.length - 2]; + const file = require(value); + const splitted = value.split("/"); + const directory = splitted[splitted.length - 2]; - if (!file?.name) return; + if (!file?.name) return; - const properties = { - directory, - ...file - }; - client.slashCommands.set(file.name, properties); + const properties = { directory, ...file }; + client.slashCommands.set(file.name, properties); - if (["MESSAGE", "USER"].includes(file.type)) delete file.description; + if (["MESSAGE", "USER"].includes(file.type)) delete file.description; - // Push the data - arrayOfSlashCommands.push(file); + // Push the data + const JSONCommand = { + ...file, + integration_types: [0, 1], // 0 for guild, 1 for user + contexts: [0, 1, 2], // 0 for guild, 1 for app DMs, 2 for GDMs and other DMs + }; + arrayOfSlashCommands.push(JSONCommand); }); - // Slash Command Handling if (interaction.isChatInputCommand()) { + let commandData = []; - // Grabbing Command Data for this interaction - let commandData = [] - - // We use ForEach here to filter our array into the single commands info. - await arrayOfSlashCommands.forEach(command => { - if (command.name == interaction.commandName) { - commandData.push(command) - } - }); - - // Process and Parse Data - let dataToProcess = JSON.stringify(commandData[0]) - let parsedData = JSON.parse(dataToProcess) - - - if (interaction.commandName == "modal-example"){ - console.log("Modal - Skipping defer") - } else { - // If the command is private, set ephemeral true else, set false - console.log(parsedData) - if (parsedData.private == true) { - await interaction.deferReply({ - ephemeral: true - }).catch(() => {}); - - } else { - await interaction.deferReply({ - ephemeral: false - }).catch(() => {}); + // Filter to find the command for this interaction + await arrayOfSlashCommands.forEach(command => { + if (command.name === interaction.commandName) { + commandData.push(command); } + }); + + // Process and parse the command data + const parsedData = commandData[0]; + + // Defer reply based on privacy settings + if (interaction.commandName === "modal-example") { + console.log("Modal - Skipping defer"); + } else { + const isPrivate = parsedData?.private; + await interaction.deferReply({ + ephemeral: !!isPrivate, + }).catch(() => {}); } - const cmd = client.slashCommands.get(interaction.commandName); - if (!cmd) - return interaction.followUp({ - content: "An error has occurred " - }); - const args = []; + const cmd = client.slashCommands.get(interaction.commandName); + if (!cmd) { + return interaction.followUp({ content: "An error has occurred" }); + } - for (let option of interaction.options.data) { - if (option.type === "SUB_COMMAND") { - if (option.name) args.push(option.name); - option.options?.forEach((x) => { - if (x.value) args.push(x.value); - }); - } else if (option.value) args.push(option.value); - } + const args = []; + for (let option of interaction.options.data) { + if (option.type === "SUB_COMMAND") { + if (option.name) args.push(option.name); + option.options?.forEach((x) => { + if (x.value) args.push(x.value); + }); + } else if (option.value) args.push(option.value); + } + + // Check if the interaction is in a guild and assign member accordingly + if (interaction.inGuild()) { interaction.member = interaction.guild.members.cache.get(interaction.user.id); + } - cmd.run(client, interaction, args); + // Run the command + cmd.run(client, interaction, args); } // Context Menu Handling if (interaction.isContextMenuCommand()) { - await interaction.deferReply({ - ephemeral: false - }); - const command = client.slashCommands.get(interaction.commandName); - if (command) command.run(client, interaction); + await interaction.deferReply({ ephemeral: false }); + const command = client.slashCommands.get(interaction.commandName); + if (command) command.run(client, interaction); } -}); \ No newline at end of file +}); diff --git a/handler/index.js b/handler/index.js index 3cff50e..8761cc7 100644 --- a/handler/index.js +++ b/handler/index.js @@ -2,11 +2,16 @@ require("dotenv").config(); const { glob } = require("glob"); const { promisify } = require("util"); const globPromise = promisify(glob); +const { REST } = require('@discordjs/rest'); +const Discord = require('discord.js'); module.exports = async (client) => { + const rest = new REST({ version: '10' }).setToken(process.env.TOKEN); + // Slash Commands const slashCommands = await globPromise(`${process.cwd()}/commands/*/*.js`); const arrayOfSlashCommands = []; + slashCommands.map((value) => { const file = require(value); const splitted = value.split("/"); @@ -18,7 +23,15 @@ module.exports = async (client) => { client.slashCommands.set(file.name, properties); if (["MESSAGE", "USER"].includes(file.type)) delete file.description; - arrayOfSlashCommands.push(file); + + // Add integration types and contexts to support user apps + const JSONCommand = { + ...file, + integration_types: [0, 1], // 0 for guild, 1 for user + contexts: [0, 1, 2], // 0 for guild, 1 for DMs, 2 for GDMs and other DMs + }; + + arrayOfSlashCommands.push(JSONCommand); }); // Events @@ -27,11 +40,15 @@ module.exports = async (client) => { // Slash Commands Register client.on("ready", async () => { - // // Register for a single guild - // await client.guilds.cache.get("GUIDIDHERE").commands.set(arrayOfSlashCommands); - - // Register for all the guilds the bot is in - await client.application.commands.set(arrayOfSlashCommands); + try { + // Register for all the guilds the bot is in + await rest.put( + Discord.Routes.applicationCommands(client.user.id), + { body: arrayOfSlashCommands } + ); + console.log("Successfully registered application commands."); + } catch (error) { + console.error("Error while registering application commands:", error); + } }); - };