Advertisement
TommyYOyoyo

StackOverflow Question - DIscord bot Help Command

Jul 30th, 2023
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 16.06 KB | Source Code | 0 0
  1. // Source code of the slash command "help"
  2.  
  3. const {
  4.     SlashCommandBuilder,
  5.     EmbedBuilder,
  6.     StringSelectMenuBuilder,
  7.     StringSelectMenuOptionBuilder,
  8. } = require("@discordjs/builders");
  9. const {
  10.     ActionRowBuilder,
  11.     ButtonBuilder,
  12.     ButtonStyle,
  13.     MessageButton,
  14.     MessageActionRow,
  15. } = require("discord.js");
  16. const fs = require("node:fs");
  17. const path = require("node:path");
  18.  
  19. const { handleError } = require("../../utils/errorHandling.js");
  20.  
  21. // These values will be used in the menu object
  22. const author = {
  23.     name: "/help",
  24.     iconURL: "https://i.postimg.cc/rwXj33rv/sonnnn.png",
  25. };
  26. const color = [50, 222, 138];
  27.  
  28. // Absolute path of the command directory
  29. const commandsDir = path.resolve("./src/commands/");
  30.  
  31. // An object that stores all the categories and pages of the Help Menu
  32. let menu = {
  33.     init: [
  34.         new EmbedBuilder()
  35.             .setAuthor(author)
  36.             .setTitle("Select a category of commands")
  37.             .setColor([50, 222, 138])
  38.             .addFields(
  39.                 {
  40.                     name: "Fun Commands",
  41.                     value: "Slash commands with fun purposes, such as /kill, /nuke and /yeet.",
  42.                 },
  43.                 {
  44.                     name: "Utility Commands",
  45.                     value: "Useful slash commands, such as /info and /invites.",
  46.                 },
  47.                 {
  48.                     name: "Welcome-Goodbye Commands",
  49.                     value: "Slash commands dedicated to personalize the welcome-goodbye features, such as the Welcome Image and the Goodbye message.",
  50.                 }
  51.             )
  52.             .setFooter({
  53.                 text: "Page 1/1. \nHave you found any issues? Or... any suggestions? Join this server to post your reports and your ideas! https://discord.gg/wRtZ6fRhZC",
  54.             }),
  55.     ],
  56. };
  57.  
  58. // ========================================================================================================================================================================
  59. // Dynamically adding the commands in the help menu
  60. // This part is executed when the bot is launched
  61. try {
  62.     const commandFolders = fs.readdirSync(commandsDir);
  63.  
  64.     for (const folder of commandFolders) {
  65.         const commandsPath = path.join(commandsDir, folder); // commands folder path
  66.         const files = fs // Array of all .js command files in the folder
  67.             .readdirSync(commandsPath)
  68.             .filter((file) => file.endsWith(".js"));
  69.         // A file index to help me easily track the number of files fetched
  70.         let fileIndex = 0;
  71.         // A variable that keeps me tracked with the embed page in which I should add command details
  72.         let embedIndex = 0;
  73.         // Retrieve the number of files in a folder
  74.         const folderLength = fs.readdirSync(commandsPath).length;
  75.  
  76.         for (const file of files) {
  77.             fileIndex += 1;
  78.  
  79.             // The following part will be IGNORED if the fileIndex does not match the conditions to create a new page
  80.             // Create a new page at each time we reach 5 commands
  81.             // e.g : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
  82.             //       ^              ^               ^
  83.             if (fileIndex % 5 == 1) {
  84.                 // Embed page creation
  85.                 // Create an empty array if not created
  86.                 if (menu[`${folder}`] == undefined) menu[`${folder}`] = [];
  87.  
  88.                 menu[`${folder}`].push(
  89.                     new EmbedBuilder()
  90.                         .setAuthor(author)
  91.                         .setTitle(
  92.                             folder.charAt(0).toUpperCase().toString() +
  93.                                 folder.slice(1).replace("_", "-") +
  94.                                 " Commands"
  95.                         )
  96.                         .setColor(color)
  97.                 );
  98.  
  99.                 // Update the embed index if the current index is not 0
  100.                 //      (increasing it in this case would cause it to be 1 index
  101.                 //       higher than the expected one and would cause errors)
  102.                 menu[`${folder}`].length <= 1
  103.                     ? (embedIndex = embedIndex)
  104.                     : embedIndex++;
  105.  
  106.                 // The setFooter part is seperated from the main embed creation code in order to avoid incorrect paging issues
  107.                 menu[`${folder}`][embedIndex].setFooter({
  108.                     text: `Page ${embedIndex + 1}/${
  109.                         // Determine the number of pages that will be created
  110.                         folderLength.length % 5 == 0
  111.                             ? folderLength / 5
  112.                             : Math.ceil(folderLength / 5)
  113.                     }. \nTo report bugs or to post ideas, please visit the following server at #bugs-report. https://discord.gg/wRtZ6fRhZC`,
  114.                 });
  115.             }
  116.             // Import datas of the command file
  117.             const command = require(path.join(commandsPath, file));
  118.             // Add fields that contain the name and description of every command into embeds
  119.             if ("name" in command && "description" in command) {
  120.                 menu[`${folder}`][embedIndex].addFields({
  121.                     name: `/${command.name}`,
  122.                     value: command.description,
  123.                 });
  124.             } else {
  125.                 // In case the command file is incomplete
  126.                 console.log(
  127.                     `[WARNING] Help Generation - Name and Description not found in ${file} \n`
  128.                 );
  129.             }
  130.         }
  131.     }
  132.     console.log(
  133.         `${new Date().toString()} [HELP COMMAND] Help menu generated successfully. \n`
  134.     );
  135. } catch (err) {
  136.     handleError(undefined, err);
  137. }
  138.  
  139. // =============================================================================================================================
  140.  
  141. module.exports = {
  142.     data: new SlashCommandBuilder()
  143.         .setName("help")
  144.         .setDescription(
  145.             "Are you confused about how to use this bot? Well, this command will help you out!"
  146.         ),
  147.  
  148.     async execute(interaction) {
  149.         // Two values that will help me to easily track the page index and help category
  150.         let currentPage = 0;
  151.         let currentCategory = menu.init; // It is temporarily put with an array of one value in order to make its length 1. The category will change to a real category (e.g: menu.init) with further interactions.
  152.  
  153.         // Build the select menu
  154.         const selectMenu = new StringSelectMenuBuilder()
  155.             .setCustomId("menu")
  156.             .setPlaceholder("Select the category of help you want to receive.")
  157.             .addOptions(
  158.                 (selection_fun = new StringSelectMenuOptionBuilder()
  159.                     .setLabel("Fun")
  160.                     .setDescription(
  161.                         "Fun slash commands, such as /nuke and /yeet."
  162.                     )
  163.                     .setValue("fun")),
  164.                 (selection_utils = new StringSelectMenuOptionBuilder()
  165.                     .setLabel("Utilities")
  166.                     .setDescription(
  167.                         "Utility commands, such as /invites and /info."
  168.                     )
  169.                     .setValue("utils")),
  170.                 (selection_welcomeBye = new StringSelectMenuOptionBuilder()
  171.                     .setLabel("Welcome-Goodbye")
  172.                     .setDescription(
  173.                         "Commands dedicated to setup the welcome-goodbye system."
  174.                     )
  175.                     .setValue("welcomebye"))
  176.             );
  177.         // Place the select menu into the action row
  178.         const selectMenuRow = new ActionRowBuilder().addComponents(selectMenu);
  179.         // Creating the buttons and place them into the action row
  180.         const buttonsRow = new ActionRowBuilder().addComponents(
  181.             (firstBtn = new ButtonBuilder()
  182.                 .setCustomId("first")
  183.                 .setLabel("First Page")
  184.                 .setStyle(ButtonStyle.Primary)
  185.                 .setDisabled(true)),
  186.             (previousBtn = new ButtonBuilder()
  187.                 .setCustomId("previous")
  188.                 .setLabel("⬅️")
  189.                 .setStyle(ButtonStyle.Primary)
  190.                 .setDisabled(true)),
  191.             (nextBtn = new ButtonBuilder()
  192.                 .setCustomId("next")
  193.                 .setLabel("➡️")
  194.                 .setStyle(ButtonStyle.Primary)).setDisabled(true),
  195.             (lastBtn = new ButtonBuilder()
  196.                 .setCustomId("last")
  197.                 .setLabel("Last Page")
  198.                 .setStyle(ButtonStyle.Primary)
  199.                 .setDisabled(true))
  200.         );
  201.         // An array of buttons to ease the loops
  202.         const buttons = [previousBtn, nextBtn, firstBtn, lastBtn];
  203.         // Filter for the interaction collector
  204.         const filter = (i) => i.deferred == false && !i.user.bot;
  205.  
  206.         try {
  207.             // Sending the embed message, the drop menu and the buttons as a reply
  208.             const response = await interaction.reply({
  209.                 embeds: [menu.init[0]],
  210.                 components: [selectMenuRow, buttonsRow],
  211.             });
  212.             // Interaction collector
  213.             const collector = response.createMessageComponentCollector({
  214.                 filter: filter,
  215.                 time: 120000,
  216.             });
  217.             // Handling received interactions from the collector
  218.             collector.on("collect", async (i) => {
  219.                 if (i.user.id == interaction.user.id) {
  220.                     // If the interaction received is not from the buttons nor from the select menu, do nothing.
  221.                     if (!i.isButton() && !i.isStringSelectMenu()) return;
  222.                     // Defer the reply since the command takes longer time than usual to complete
  223.                     await i.deferUpdate();
  224.                     // Button handling starts here
  225.                     if (i.isButton()) {
  226.                         // If the user is still on the starting menu, disable all the buttons
  227.                         if (currentCategory == menu.init) {
  228.                             for (const i of buttons) {
  229.                                 await i.setDisabled(true);
  230.                             }
  231.                             // User is not on the starting menu
  232.                         } else {
  233.                             // Update the page according to the interaction of the user
  234.                             switch (i.customId) {
  235.                                 case "previous":
  236.                                     currentPage -= 1;
  237.                                     break;
  238.                                 case "next":
  239.                                     currentPage += 1;
  240.                                     break;
  241.                                 case "first":
  242.                                     currentPage = 0;
  243.                                     break;
  244.                                 case "last":
  245.                                     currentPage = currentCategory.length - 1;
  246.                                     break;
  247.                                 default:
  248.                                     handleError(
  249.                                         i,
  250.                                         "Unknown button interaction."
  251.                                     );
  252.                             }
  253.  
  254.                             // Disabled and enable certain buttons to avoid errors
  255.                             // First page
  256.                             if (currentPage == 0) {
  257.                                 await previousBtn.setDisabled(true);
  258.                                 await firstBtn.setDisabled(true);
  259.                                 // Re-enable in case disabled
  260.                                 await nextBtn.setDisabled(false);
  261.                                 await lastBtn.setDisabled(false);
  262.                                 // Last page
  263.                             } else if (
  264.                                 currentPage ==
  265.                                 currentCategory.length - 1
  266.                             ) {
  267.                                 await nextBtn.setDisabled(true);
  268.                                 await lastBtn.setDisabled(true);
  269.                                 // Re-enable in case disabled
  270.                                 await previousBtn.setDisabled(false);
  271.                                 await firstBtn.setDisabled(false);
  272.                                 // Middle pages
  273.                             } else {
  274.                                 // Reenable all buttons in case disabled
  275.                                 for (const button of buttons) {
  276.                                     await button.setDisabled(false);
  277.                                 }
  278.                             }
  279.  
  280.                             console.log("BTN:", currentPage);
  281.  
  282.                             // Update the embed message;
  283.                             await i.editReply({
  284.                                 embeds: [currentCategory[currentPage]],
  285.                                 components: [selectMenuRow, buttonsRow],
  286.                             });
  287.                         }
  288.                         // Select Menu Handling
  289.                     } else if (i.isStringSelectMenu()) {
  290.                         // i.values[0] is the command category that the user had chosen
  291.                         switch (i.values[0]) {
  292.                             case "fun":
  293.                                 currentCategory = menu.fun;
  294.                                 break;
  295.                             case "utils":
  296.                                 currentCategory = menu.utilities;
  297.                                 break;
  298.                             case "welcomebye":
  299.                                 currentCategory = menu.welcome_goodbye;
  300.                                 break;
  301.                             default:
  302.                                 currentCategory = menu.init;
  303.                                 break;
  304.                         }
  305.                         currentPage = 0; // Reset the page number
  306.                         // Disable the previous and next buttons to avoid unexpected interactions
  307.                         await firstBtn.setDisabled(true);
  308.                         await previousBtn.setDisabled(true);
  309.                         // If there are more than one page of help in that category, allow the user to move on the next pages
  310.                         if (currentCategory.length > 1) {
  311.                             await nextBtn.setDisabled(false);
  312.                             await lastBtn.setDisabled(false);
  313.                             // If there is only one page of help, disable all the buttons to prevent causing errors
  314.                         } else {
  315.                             await nextBtn.setDisabled(true);
  316.                             await lastBtn.setDisabled(true);
  317.                         }
  318.                         await interaction.editReply({
  319.                             embeds: [currentCategory[currentPage]],
  320.                             components: [selectMenuRow, buttonsRow],
  321.                         });
  322.                     }
  323.                     // A non-command-caller user tries to interact with the command
  324.                 } else {
  325.                     await i.deferUpdate();
  326.                     await i.followUp({
  327.                         content: `This is not for you.`,
  328.                         ephemeral: true,
  329.                     });
  330.                 }
  331.             });
  332.             collector.on("end", async () => {
  333.                 // Disable the buttons to avoid making the users interact with buttons which can no longer receiving interactions
  334.                 for (const button of buttons)
  335.                     await button
  336.                         .setStyle(ButtonStyle.Secondary)
  337.                         .setDisabled(true);
  338.                 // Refresh page
  339.                 await interaction.editReply({
  340.                     embeds: [currentCategory[currentPage]],
  341.                     components: [selectMenuRow, buttonsRow],
  342.                 });
  343.                 return;
  344.             });
  345.         } catch (err) {
  346.             // An error has occured;
  347.             handleError(interaction, err);
  348.         }
  349.     },
  350. };
  351.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement