Implement pagination in a Discord bot [ENG/ESP]

in Linux&SoftwareLibre23 hours ago (edited)



SPANISH

¡Hey, hey! 🌌 ¡Su queridísimo nekito íncubo favorito llega con algo especial! 😼 Como VTuber, sé que parte de lo divertido es ir más allá y compartirles también algo del mundo de la programación. Hoy les traigo un recurso que les será útil si quieren implementar paginación en un bot de Discord, especialmente si tienen contenido que merece ser explorado de manera organizada y dinámica.

¿De qué trata esto?
Voy a mostrarles cómo crear un componente reutilizable para manejar la paginación de contenido en un bot de Discord. Así podrán usar este sistema en múltiples partes de su bot, sobre todo si están mostrando listas extensas de información, como comandos o detalles de usuarios. Este código está escrito en JavaScript y utiliza la biblioteca de Discord.js, ¡lo que lo hace fácil de integrar para quienes ya están familiarizados con este entorno! 🎉

Vamos con el paso a paso: Explicación del código 🛠️
La función buttonPages es el centro de esta paginación interactiva, y permite al usuario navegar entre varias páginas de contenido a través de botones. A continuación, les doy una guía detallada para que no se pierdan de nada:

  1. Importación de componentes:
const {
    ActionRowBuilder,
    ButtonBuilder,
    ButtonStyle,
    ComponentType,
} = require("discord.js");
  1. Definición de la función buttonPages:
async function buttonPages(interaction, pages, time = 60000) {

La función recibe tres parámetros:

  • interaction: la interacción original del usuario con el bot.
  • pages: un array de embeds que representan las diferentes páginas.
  • time: el tiempo de espera en milisegundos antes de que los botones se deshabiliten automáticamente (por defecto 60 segundos).
  1. Validaciones iniciales:
if (!interaction) throw new Error("Please provide an interaction argument");
if (!pages) throw new Error("Please provide a page argument");
if (!Array.isArray(pages)) throw new Error("Pages must be an array");
if (typeof time !== "number") throw new Error("Time must be a number.");
if (parseInt(time) < 30000) throw new Error("Time must be greater than 30 Seconds");

Asegura que los parámetros pasados sean válidos, verificando que interaction y pages estén definidos, pages sea un array, y time sea un número mayor a 30 segundos.

  1. Deshabilitar respuesta si hay solo una página:
if (pages.length === 1) {
    const page = await interaction.editReply({
        embeds: pages,
        components: [],
        fetchReply: true,
    });
    return page;
}

Si pages contiene solo un elemento, simplemente muestra esa única página sin los botones de navegación.

  1. Configuración de botones:
const prev = new ButtonBuilder()
    .setCustomId("prev")
    .setEmoji("◀️")
    .setStyle(ButtonStyle.Primary)
    .setDisabled(true);

Crea tres botones:

  • prev: botón de "anterior", deshabilitado al inicio.
  • home: botón para ir a la página inicial.
  • next: botón de "siguiente".

Los tres botones se añaden a buttonRow, que se usará para mostrar la barra de navegación.

  1. Mostrar la primera página:
const currentPage = await interaction.editReply({
    embeds: [pages[index]],
    components: [buttonRow],
    fetchReply: true,
});
  1. Muestra la primera página (index = 0) con los botones habilitados.
const collector = await currentPage.createMessageComponentCollector({
    componentType: ComponentType.Button,
    time,
});

El recolector detecta las interacciones de los botones por el tiempo definido.

  1. Acción al recolectar un botón (collector.on("collect")):
collector.on("collect", async (i) => {

Al presionar un botón:

  • Se verifica si el usuario que interactuó es el mismo que inició la interacción.
  • Dependiendo del botón (prev, home, next), el índice de la página (index) cambia.
  • El botón prev se desactiva si estamos en la primera página, home se desactiva si estamos en la primera página, y next se desactiva en la última.
  • Actualiza el contenido de la respuesta con la página actual y los botones correspondientes.
  1. Reinicio del temporizador:
collector.resetTimer();

Cada vez que se interactúa con un botón, el temporizador de espera se reinicia.

  1. Finalización del recolector:
collector.on("end", async (i) => {
    await currentPage.edit({
        embeds: [pages[index]],
        components: [],
    });
    return currentPage;
});

Una vez que el recolector termina, los botones se deshabilitan para evitar nuevas interacciones.

  1. Exportación
module.exports = buttonPages;

La función buttonPages se exporta para que pueda ser usada en otros archivos del bot de Discord.

Codigo Completo

const {
    ActionRowBuilder,
    ButtonBuilder,
    ButtonStyle,
    ComponentType,
} = require("discord.js");

async function buttonPages(interaction, pages, time = 60000) {
    if (!interaction) throw new Error("Please provide an interaction argument");
    if (!pages) throw new Error("Please provide a page argument");
    if (!Array.isArray(pages)) throw new Error("Pages must be an array");
    if (typeof time !== "number") throw new Error("Time must be a number.");
    if (parseInt(time) < 30000) throw new Error("Time must be greater than 30 Seconds");

    await interaction.deferReply();
    if (pages.length === 1) {
        const page = await interaction.editReply({
            embeds: pages,
            components: [],
            fetchReply: true,

        });
        return page;
    }

    const prev = new ButtonBuilder()
        .setCustomId("prev")
        .setEmoji("◀️")
        .setStyle(ButtonStyle.Primary)
        .setDisabled(true);

    const home = new ButtonBuilder()
        .setCustomId("home")
        .setEmoji("💒")
        .setStyle(ButtonStyle.Danger)
        .setDisabled(true);

    const next = new ButtonBuilder()
        .setCustomId("next")
        .setEmoji("▶️")
        .setStyle(ButtonStyle.Primary);

    const buttonRow = new ActionRowBuilder().addComponents(prev, home, next);
    let index = 0;

    const currentPage = await interaction.editReply({
        embeds: [pages[index]],
        components: [buttonRow],
        fetchReply: true,

    })


    const collector = await currentPage.createMessageComponentCollector({
        componentTpe: ComponentType.Button,
        time,
    });

    collector.on("collect", async (i) => {

        if (i.user.id !== interaction.user.id)
            return i.reply({
                content: "You can't use these buttons",
                ephemeral: true,
            });

        await i.deferUpdate();
        if (i.customId === "prev") {
            if (index > 0) index--;
        } else if (i.customId === "home") {
            index = 0;
        } else if (i.customId === "next") {
            if (index < pages.length - 1) index++;
        }

        if (index === 0) prev.setDisabled(true);
        else prev.setDisabled(false);

        if (index === 0) home.setDisabled(true);
        else home.setDisabled(false);

        if (index === pages.length - 1) next.setDisabled(true)
        else next.setDisabled(false);

        await currentPage.edit({
            embeds: [pages[index]],
            components: [buttonRow],

        });

        collector.resetTimer()

    });

 
    collector.on("end", async (i) => {
        await currentPage.edit({
            embeds: [pages[index]],
            components: [],
        });
        return currentPage;
    });


}
module.exports = buttonPages;

Con esta función, hemos creado un sistema de paginación de contenido que no solo es dinámico, sino también fácil de usar en cualquier parte del bot de Discord donde se necesite una navegación por páginas. Este tipo de código resulta útil cuando quieres que los usuarios exploren varias opciones o información extensa sin saturarles con un solo mensaje.

Espero que esta guía no solo les sea útil, sino que también los inspire a probar nuevas ideas o resolver problemas similares en sus propios bots. ¡Nos vemos en el próximo tutorial, y no olviden que estaré aquí para ayudarles en sus aventuras en Discord! 🐾


ENGLISH

Hey, hey! 🌌 Your favorite dear little incubus is here with something special! 😼 As a VTuber, I know that part of the fun is going above and beyond and sharing some of the programming world with you as well. Today I'm bringing you a resource that will be useful if you want to implement pagination in a Discord bot, especially if you have content that deserves to be explored in an organized and dynamic way.

What's this about?
I'm going to show you how to create a reusable component to handle pagination of content in a Discord bot. This way, you can use this system in multiple parts of your bot, especially if you're displaying long lists of information, such as commands or user details. This code is written in JavaScript and uses the Discord.js library, making it easy to integrate for those who are already familiar with this environment! 🎉

Let's go step by step: Explanation of the code 🛠️
The buttonPages function is the center of this interactive pagination, and allows the user to navigate between several pages of content through buttons. Below, I give you a detailed guide so you don't miss anything:

  1. Importing components:
const {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ComponentType,
} = require("discord.js");
  1. Defining the buttonPages function:
async function buttonPages(interaction, pages, time = 60000) {

The function takes three parameters:

  • interaction: the original user interaction with the bot.
  • pages: an array of embeds representing the different pages.
  • time: the timeout in milliseconds before the buttons are automatically disabled (default 60 seconds).
  1. Initial validations:
if (!interaction) throw new Error("Please provide an interaction argument");
if (!pages) throw new Error("Please provide a page argument");
if (!Array. isArray(pages)) throw new Error("Pages must be an array");
if (typeof time !== "number") throw new Error("Time must be a number.");
if (parseInt(time) < 30000) throw new Error("Time must be greater than 30 Seconds");

Ensures that the parameters passed are valid by checking that interaction and pages are defined, pages is an array, and time is a number greater than 30 seconds.

  1. Disable response if there is only one page:
if (pages.length === 1) {
const page = await interaction.editReply({
embeds: pages,
components: [],
fetchReply: true,
});
return page;
}

If pages contains only one item, just show that single page without the navigation buttons.

  1. Button configuration:
const prev = new ButtonBuilder()
.setCustomId("prev")
.setEmoji("◀️")
.setStyle(ButtonStyle.Primary)
.setDisabled(true);

Create three buttons:

  • prev: "previous" button, disabled at startup.
  • home: button to go to the home page.
  • next: "next" button.

The three buttons are added to buttonRow, which will be used to display the navigation bar.

  1. Show the first page:
const currentPage = await interaction.editReply({
embeds: [pages[index]],
components: [buttonRow],
fetchReply: true,
});
  1. Show the first page (index = 0) with the buttons enabled.
const collector = await currentPage.createMessageComponentCollector({
componentType: ComponentType.Button,
time,
});

The collector detects button interactions for the defined time.

  1. Action when collecting a button (collector.on("collect")):
collector.on("collect", async (i) => {

When pressing a button:

  • Checks if the user who interacted is the same as the one who started the interaction.
  • Depending on the button (prev, home, next), the index of the page (index) changes.
  • The prev button is disabled if we are on the first page, home is disabled if we are on the first page, and next is disabled on the last page.
  • Updates the response content with the current page and the corresponding buttons.
  1. Timer reset:
collector.resetTimer();

Every time a button is interacted with, the wait timer is reset.

  1. Collector completion:
collector.on("end", async (i) => {
await currentPage.edit({
embeds: [pages[index]],
components: [],
});
return currentPage;
});

Once the collector finishes, the buttons are disabled to prevent further interactions.

  1. Export
module.exports = buttonPages;

The buttonPages function is exported so that it can be used in other Discord bot files.

Complete Code

const {
 ActionRowBuilder,
 ButtonBuilder,
 ButtonStyle,
 ComponentType,
} = require("discord.js");

async function buttonPages(interaction, pages, time = 60000) {
 if (!interaction) throw new Error("Please provide an interaction argument");
 if (!pages) throw new Error("Please provide a page argument");
 if (!Array.isArray(pages)) throw new Error("Pages must be an array");
 if (typeof time !== "number") throw new Error("Time must be a number.");
 if (parseInt(time) < 30000) throw new Error("Time must be greater than 30 Seconds");

 await interaction.deferReply();
 if (pages.length === 1) {
 const page = await interaction.editReply({
 embeds: pages,
 components: [],
 fetchReply: true,

 });
 return page;
 }

 const prev = new ButtonBuilder()
 .setCustomId("prev")
 .setEmoji("◀️")
 .setStyle(ButtonStyle.Primary)
 .setDisabled(true);

 const home = new ButtonBuilder()
 .setCustomId("home")
 .setEmoji("💒")
 .setStyle(ButtonStyle.Danger)
 .setDisabled(true);

 const next = new ButtonBuilder()
 .setCustomId("next")
 .setEmoji("▶️")
 .setStyle(ButtonStyle.Primary);

 const buttonRow = new ActionRowBuilder().addComponents(prev, home, next);
 let index = 0;

 const currentPage = await interaction.editReply({
 embeds: [pages[index]],
 components: [buttonRow],
 fetchReply: true,

 })


 const collector = await currentPage.createMessageComponentCollector({
 componentTpe: ComponentType.Button,
 time,
 });

 collector.on("collect", async (i) => {

 if (i.user.id !== interaction.user.id)
 return i.reply({
 content: "You can't use these buttons",
 ephemeral: true,
 });

 await i.deferUpdate();
 if (i.customId === "prev") {
 if (index > 0) index--;
 } else if (i.customId === "home") {
 index = 0;
 } else if (i.customId === "next") {
 if (index < pages.length - 1) index++;
 }

 if (index === 0) prev.setDisabled(true);
 else prev.setDisabled(false);

 if (index === 0) home.setDisabled(true);
 else home.setDisabled(false);

 if (index === pages.length - 1) next.setDisabled(true)
 else next.setDisabled(false);

 await currentPage.edit({
 embeds: [pages[index]],
 components: [buttonRow],

 });

 collector.resetTimer()

 });


 collector.on("end", async (i) => {
 await currentPage.edit({
 embeds: [pages[index]],
 components: [],
 });
 return currentPage;
 });


}
module.exports = buttonPages;

With this feature, we've created a content pagination system that is not only dynamic, but also easy to use anywhere in the Discord bot where page-based navigation is needed. This type of code is useful when you want users to explore multiple options or extensive information without overwhelming them with a single message.

I hope you find this guide not only useful, but also inspires you to try out new ideas or solve similar problems in your own bots. See you in the next tutorial, and don't forget that I'll be here to help you on your Discord adventures! 🐾


Portada realizada en photoshop
Separador realizado por @softy1231 softy1231
Vtuber, Paneles realizado por @panna-natha pannanatha
Logo realizado por KivaVT
Porta base realizada por @smile27

Redes Sociales

Sort:  

Congratulations @misticogama! You have completed the following achievement on the Hive blockchain And have been rewarded with New badge(s)

You distributed more than 19000 upvotes.
Your next target is to reach 20000 upvotes.

You can view your badges on your board and compare yourself to others in the Ranking
If you no longer want to receive notifications, reply to this comment with the word STOP

Check out our last posts:

LEO Power Up Day - November 15, 2024

Yo he estado desarrollando un bot de discord con python y encontré una forma de crear módulos para organizar el código, lo que hace es separar en archivos diferente las partes del bot (comandos) y ejecutar todo desde un archivo centrar. En mi blog hablé sobre eso. Gracias por compartir tu contenido.

Wooo haber comparte 👀 en link del post

Se ve muy sencillo 🥴

Cada persona tiene su fuerte🤗