DEV Community

Cover image for Como criar um bot do discord com javascript
Falcão
Falcão

Posted on

Como criar um bot do discord com javascript

Se você é familiar com o Discord você provavelmente conhece algum bot usado na palataforma, seja a Loritta, o MEE6 ou algum outro. De fato, usar eles no seu servidor é uma parte crucial da experiência do Discord hoje em dia. Pensando nisso, vamos hoje ver como podemos criar nosso próprio bot!

Table of Contents

Por que criar um bot?

Como eu disse anteriormente, hoje em dia no Discord temos milhares de bots com as mais diversas funcionalidades, de moderação até RPG ou algo completamente novo, então por que se preocupar em criar o seu?

Existem várias razões que podem te atrair para o desenvolvimento de bots, a primeira delas é simplesmente a curiosidade, talvez você se interesse em como eles são feitos, uma outra é a necessidade, talvez você teve uma ideia mirabolante que nenhum outro bot implementou ainda e quer criar isso (eu por exemplo tenho um bot que só é disponível no servidor da turma da minha faculdade).

De qualquer modo, qualquer um desses motivos te leva ao estudo e a prática, desenvolver projetos reais e estudar áreas desconhecidas é a chave para se tornar um desenvolvedor melhor, e talvez quem sabe, você se apaixone pela área :) ok, chega de enrolação e vamos colocar a mão na massa!

Preparando o Ambiente de Desenvolvimento

Primeiro de tudo, precisamos instalar o Node.js e uma IDE ou editor de texto, eu gosto do Visual Studio Code.

Agora crie uma nova pasta com o nome que desejar, seu projeto ficará dentro dessa pasta, entre na sua pasta criada com o seu editor de código de preferência e use o comando npm init -y, isso vai criar um projeto em javascript para você.

Conteúdo do arquivo package.json

Como pode ver, esse comando criou o arquivo package.json, esse arquivo guarda as informações do seu projeto, como o foco desse artigo não é ensinar javascript, não irei me aprofundar na funcionalidade de cada campo, o importante é saber que esse arquivo é essencial para o funcionamento correto do seu projeto.

Criando uma nova aplicação

Antes de fazer qualquer outra coisa no nosso projeto, precisamos registrar uma nova aplicação no Portal de Desenvolvedores do Discord

Essa é a página de aplicações do Discord, aqui todos as suas aplicações podem ser vistas e editadas, "aplicações" é o termo que o Discord usa para se referir a bots, caso você nunca tenha criado um anteriormente, essa página estará vazia, vamos clicar em "Nova Aplicação" no canto superior direito.

Página Inicial do Portal de Desenvolvedores do Discord

O Discord agora vai te pedir um nome para seu bot, não se preocupe, ele pode ser editado mais tarde!

Nome da Aplicação

Depois disso, a página de informações gerais vai aparecer, ela é assim:

Informações gerais sobre o bot

Essa página contém informações como o nome, descrição, foto, tags, número de servidores do seu bot entre outras, sinta-se livre para deixar o bot com a sua cara, pela simplicidade, eu deixarei tudo como está.

O próximo passo é criar um link de convite do seu bot, assim podemos adicionar ele em qualquer servidor que desejarmos, para isso precisamos selecionar o botão OAuth2 no menu lateral, quando o menu abrir embaixo desse botão escolha o gerador de URL.

Botão OAuth2

Em "Scopes", vamos selecionar "bot" e "applications.commands", isso vai dizer para o Discord que queremos adicionar um Bot que tem permissão para registrar comandos.

Escopos do Bot

Assim que você clicar em "bot", uma nova seção de permissões do bot vai aparecer, isso serve para o Discord criar automaticamente um cargo para o seu bot quando ele entrar em um servidor, assim você garante que seu bot terá certas permissões que podem ser essenciais para seu funcionamento (se quem adicionar o bot permitir), nesse tutorial não precisamos selecionar nenhuma, pois nosso bot só mandará mensagens, coisa que o cargo "everyone" já pode fazer.

Desça até o final da página e copie o link que o Discord gerou, esse é o link que será usado para adicionar seu bot nos servidores, adicione seu bot em algum servidor seu que sirva para fazer testes (recomendo criar um novo servidor para isso).

Link de convite

Acesse novamente o menu lateral e agora clique na página "Bot".

Botão página bot

Nessa página temos uma parte muito importante, o "Build-A-Bot", ela controla como os usuários veem seu bot, a sua foto, seu username e ela também guarda o seu token.

O token é o que diz para o Discord qual é o seu bot, não compartilhe ele, quem tiver acesso ao seu token tem acesso ao seu bot, vá em frente e clique para resetar o token, não se esqueça de anotá-lo pois o Discord só mostra ele uma vez!

Seção Build-A-Bot
(não perca seu tempo tentando controlar meu bot, ele já foi resetado)

Por fim, desça até a seção "Priviled Gateway Intents", na mesma página, e ative a permissão, "Message Content Intent", essa seção contém permissões especiais, que caso seu bot esteja em mais de 100 servidores, requer uma análise do Discord para ser ativada, mas não se preocupe, não é o nosso caso. Essa permissão é necessária para que o bot possa ler as mensagens que os usuários mandam, assim ele pode responder a elas.

Seção de permissões especiais

Se você chegou até aqui, parabéns! Você registrou um novo bot no Discord com sucesso, agora vamos ver como programá-lo.

Dando vida ao Bot

Anteriormente criamos um novo projeto javascript, vamos acessá-lo novamente.

Usaremos aqui uma biblioteca chamada discord.js, ela é uma das bibliotecas mais usadas para a criação de bots e vai ser nossa principal ferramenta.

Podemos instalar ela rodando o comando npm i discord.js, note que uma pasta chamada node_modules foi criada no seu projeto, ela guarda todos as bibliotecas que você instalar, não mexa nela a não ser que saiba o que está fazendo!

Agora vamos criar um novo arquivo chamado .env, ele será responsável por guardar o token do nosso bot de uma maneira segura, crie ele da seguinte maneira, coloque "TOKEN=" e em seguida o seu token, assim criamos uma variável de ambiente chamada TOKEN.

Conteúdo do arquivo .env

Para conseguirmos usar essa variável no projeto, precisamos baixar uma biblioteca chamada dotenv, npm i dotenv.

Ademais, precisamos criar um arquivo index.js ele vai ser o arquivo principal do nosso bot, responsável por deixá-lo online, registrar comandos, etc.

Inicialmente o conteúdo do nosso arquivo index será o seguinte:

// Importamos as dependências necessárias
const { Client, GatewayIntentBits } = require("discord.js")
require("dotenv").config()

// Criamos uma instância do client
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] })

// Quando o client (bot) estiver pronto, escrevemos no console
client.once("ready", () => {
    console.log("Olá mundo!")
})

// Logamos o bot
client.login(process.env.TOKEN)

Enter fullscreen mode Exit fullscreen mode

Esse é um código bem direto ao ponto, importamos o dotenv e as classes necessárias do discord.js, criamos um novo "client" que é o cliente do nosso bot, esses GatewayIntentBits são eventos (intents) que você declara que deseja receber no nosso caso estamos interessados em eventos relacionados a mensagens e servidores (guilds), assim como no conteúdo da mensagem, que é aquela permissão especial que ativamos anteriormente, para mais informações consulte a Documentação do Discord.

Depois disso declaramos então a função client.once("ready"), quando o bot estiver logado, todo o código dentro dela será executado uma única vez, note a diferença entre client.on e client.once, o primeiro executa o código toda vez que aquele evento aconteça, enquanto o segundo executa somente da primeira vez que esse evento acontecer.

Por fim invocamos client.login passando o token que colocamos no .env, se tudo estiver correto, quando você usar node . no terminal, o bot deve te responder e ficar online no discord.

Bot respondendo no terminal
Bot online no discord

Criando seu primeiro comando

Chegou a hora que todos esperavam, vamos criar nosso primeiro comando! Para isso, abra novamente seu arquivo index.js e vamos adicionar o seguinte código:

client.on("messageCreate", (message) => {
    if (message.content === "!ping") {
        message.reply(`Pong! ${client.ws.ping}ms`)
    }
})
Enter fullscreen mode Exit fullscreen mode

Está é a estrutura padrão de o que chamamos de "comandos de prefixo", já que usamos prefixos para diferenciar mensagens comuns de chamadas de comandos, nesse caso o prefixo "!", como pode notar, usamos aqui o client.on, pois como é um comando, queremos que ele seja sempre que necessário, e usamos o messageCreate, evento que acontece toda vez que uma mensagem é enviada.

Dentro do evento, verificamos se a mensagem é "!ping", caso seja, respondemos com "Pong!" e o ping do bot, que é o tempo que o bot leva para responder a mensagem, em milissegundos. Se tudo estiver correto, quando você digitar "!ping" no chat do discord, o bot deve responder com "Pong!" e o ping:

Bot respondendo: Pong! 273ms

Esse é um comando bem simples, mas que ilustra muito bem como esses tipos de comandos funcionam, no entanto, no tópico seguinte aprenderemos sobre comandos de barra, que são muito mais poderosos e fáceis de usar, além de ser a forma que o Discord recomenda para criar comandos.

Comandos de barra

Note que da maneira que criamos um comando anteriormente, adicionar um novo comando seria uma dor de cabeça, seria preciso um encadeamento de "ifs" ou um switch case enorme que basicamente encapsularia todo seu projeto, o que é péssimo para o desenvolvedor e usuário.

Com o objetivo de mitigar esse problema, criaremos uma pasta chamada src e dentro dela uma pasta commands, assim, cada comando será seu próprio arquivo, vamos começar com um comando chamado user.js.

Estrutura do projeto, com o arquivo user.js dentro de uma pasta commands dentro de uma pasta src

Um comando de barra nada mais é que um objeto com as propriedades "data" e "execute" (claro que se pode adicionar mais propriedades, mas essas são as essenciais), a "data" se refere as informações do seu comando, como nome, descrição, argumentos, etc. Como o nome do arquivo sugere, vamos fazer um comando que mostra algumas informações sobre um usuário, começamos assim:

const { SlashCommandBuilder, EmbedBuilder, time } = require("discord.js")

module.exports = {
    data: new SlashCommandBuilder()
        // declaramos o nome e a descrição em inglês e usamos os metódos de localização para colocar em português
        // assim quem tiver o Discord em português verá em português e se for qualquer outra língua verá em inglês
        .setName("user")
        .setNameLocalization("pt-BR", "usuário")
        .setDescription("See some information about a user")
        .setDescriptionLocalization("pt-BR", "Veja algumas informações sobre um usuário")
        // declaramos as permissões, nesse caso não queremos que o comando seja usado em DM
        .setDMPermission(false)
        // declaramos os argumentos, aqui declaramos uma opção obrigatória chamada user
        .addUserOption((option) =>
            option
                .setName("user")
                .setNameLocalization("pt-BR", "usuário")
                .setDescription("The user to get information")
                .setDescriptionLocalization("pt-BR", "O usuário para pegar as informações")
                .setRequired(true)
        ),
Enter fullscreen mode Exit fullscreen mode

Calma, é muita informação nova de uma vez, não se apresse e leia tudo e os comentários, a maioria dos métodos são triviais e se explicam pelo nome.

Aqui estamos declarando um comando usando o SlashCommandBuilder, perceba que passamos nome e descrição em inglês e usamos os métodos de "Localization" para colocar o nome em português, essa é uma ótima ferramenta de internacionalização do discord, assim quem estiver com o aplicativo em português verá o comando em português, caso contrário, o comando será mostrado em inglês e para nós desenvolvedores nada muda.

Usamos .setDMPermission(false) para indicar que não queremos que esse comando possa ser usado em DM, adicionalmente, declaramos um argumento que o usuário precisará fornecer na hora de usar o comando, do tipo usuário.

Agora vamos escrever a propriedade execute abaixo do código que já temos:

    execute: async ({ interaction }) => {
        // eu sempre gosto de fazer isso para ter mais tempo para responder, fica aquela mensagem que o bot está pensando
        await interaction.deferReply().catch(() => {})

        // pegamos o usuário que foi passado como argumento
        const member = interaction.options.getMember("user")

        // pegamos informações do usuário e colocamos numa embed
        const embed = new EmbedBuilder()
            .setTitle(member.displayName)
            .setColor(member.displayColor)
            .setThumbnail(member.displayAvatarURL())
            .addFields(
                {
                    name: "Membro do Discord desde",
                    value: time(member.user.createdAt),
                    inline: true,
                },
                {
                    name: "Membro desse servidor desde",
                    value: time(member.joinedAt),
                    inline: true,
                }
            )
            .setFooter({ text: member.user.tag, iconURL: member.displayAvatarURL() })

        // temos que editar a resposta e não só responder porque deferReply() foi usado anteriormente
        interaction.editReply({ embeds: [embed] })
    },
}

Enter fullscreen mode Exit fullscreen mode

Como o nome implica, esse é o pedaço de código que será executado toda vez que um usuário usar esse comando, em resumo, criamos aqui uma embed com algumas informações do usuário, como imagem, nome, quando criou a conta do discord, etc.

Se você é como eu, talvez ao terminar esse comando já tenha reiniciado o bot e tentando usar o comando, caso tenha feito isso, você percebeu que ele não aparece, porque?

Lidando com comandos e eventos

Nosso comando ainda não aparece porque comandos de barra são diferentes dos comandos de prefixo, isso porque temos que registrá-los na API do Discord, vamos aprender como fazer isso.

Dentro da pasta src mas fora da pasta commands, vamos criar um novo arquivo functions.js

Estrutura do projeto com o arquivo functions.js fora da pasta commands e dentro da pasta src

Dentro desse arquivo, iremos declarar a função loadFiles:

const fs = require("fs")
const { promisify } = require("util")

const readdir = promisify(fs.readdir)

async function loadFiles(dirName) {
    const basePath = `${process.cwd().replace(/\\/g, "/")}/src/${dirName}`

    const files = []
    const items = await readdir(basePath)
    for (const item of items) {
        const itemPath = `${basePath}/${item}`
        if (itemPath.endsWith(".js")) {
            files.push(itemPath)
            delete require.cache[require.resolve(itemPath)]
        }
    }

    return files
}
Enter fullscreen mode Exit fullscreen mode

Talvez esse código pareça alienígena, mas ele serve para carregar todos os arquivos de uma pasta dentro do diretório src.

Em sequência, vamos escrever a função loadCommands, que como o nome sugere serve para carregarmos todos os comandos e registrarmos eles:

async function loadCommands(client) {
    await client.commands.clear()

    const commandsArray = []

    const Files = await loadFiles("commands")

    Files.forEach((file) => {
        const command = require(file)
        client.commands.set(command.data.name, command)
        commandsArray.push(command.data.toJSON())

        console.log(`Comando: ${command.data.name} ✅`)
    })

    client.application.commands.set(commandsArray)
}
Enter fullscreen mode Exit fullscreen mode

Analogamente, vamos declarar a loadEvents, assim podemos declarar eventos como declaramos comandos, um por arquivo dentro de sua própria pasta:

async function loadEvents(client) {
    await client.events.clear()

    const Files = await loadFiles("events")

    Files.forEach((file) => {
        const event = require(file)

        const execute = (...args) => event.execute(...args, client)
        client.events.set(event.name, execute)

        if (event.once) {
            client.once(event.name, execute)
        } else {
            client.on(event.name, execute)
        }

        console.log(`Evento: ${event.name} ✅`)
    })
}

module.exports = {
    loadFiles,
    loadEvents,
    loadCommands,
}
Enter fullscreen mode Exit fullscreen mode

Perceba que caso na declaração do evento exista a propriedade once: true, declaramos ele como client.once e do contrário como client.on, diferença que discutimos anteriormente. Por fim, exportamos as 3 funções.

Talvez agora você se encontre meio confuso, acabei de te mostrar 3 funções e entrei em pouco detalhe sobre elas, peço paciência, logo logo a importância delas ficará clara.

Agora que temos essas funções, vamos voltar ao index.js e mudar o código:

// Importamos as dependências necessárias
const { Client, GatewayIntentBits, Collection } = require("discord.js")
require("dotenv").config()

// Importamos as funções que criamos
const { loadEvents, loadCommands } = require("./src/functions")

// Criamos uma instância do client
const client = new Client({
    intents: [GatewayIntentBits.Guilds],
})

// Quando o client (bot) estiver pronto, escrevemos no console
client.once("ready", () => {
    console.log("Olá mundo!")
})

// Logamos o bot
client.login(process.env.TOKEN)
Enter fullscreen mode Exit fullscreen mode

Leitores atentos perceberam que importamos as funções que acabamos de criar, leitores mais atentos perceberam que removemos alguns dos intents que existiam anteriormente isso é porque não precisamos mais ler mensagens (o que implica também que aquela permissão especial que ativamos no portal do Discord também não é necessária nesse tipo de comando), e leitores ainda mais atentos perceberam que adicionamos Collection as classes que importamos do discord.

Uma Collection é uma estrutura de dados criada pelo discord.js, que é basicamente um Map com métodos extras, usaremos ela dentro do evento "ready" da seguinte maneira:

// Quando o client (bot) estiver pronto, escrevemos no console
client.once("ready", () => {
    console.log("Olá mundo!")

    client.commands = new Collection()
    client.events = new Collection()

    loadEvents(client)
    loadCommands(client)
})
Enter fullscreen mode Exit fullscreen mode

Os comandos e eventos serão armazenados dentro client usando as Collections, usamos a loadEvents e loadCommands para carregá-los.

Talvez, você esteja pensando porque eu criei uma função chamada loadEvents e a chamei dentro do arquivo principal, sendo que não temos nenhum evento, lembra como nos comandos de prefixo era necessário o uso do messageCreate? Então, para controlar os comandos de barra, também precisamos de um evento, o interactionCreate.

Criemos então a pasta events dentro da pasta src e dentro dela o arquivo interactions.js

Estrutura do projeto com o arquivo interactions.js dentro da pasta events dentro da pasta src

Dentro desse arquivo, vamos declarar o evento interactionCreate:

module.exports = {
    name: "interactionCreate",
    execute: async (interaction, client) => {
        // se a interação for um comando de chat, continuamos
        if (interaction.isChatInputCommand()) {
            // pegamos o comando pelo nome
            const command = client.commands.get(interaction.commandName)

            // invocamos a função execute do comando, passando algumas informações úteis
            command.execute({
                interaction,
                client,
                member: interaction.member,
                guild: interaction.guild,
                user: interaction.user,
                channel: interaction.channel,
            })
        }
    },
}
Enter fullscreen mode Exit fullscreen mode

Essa é a estrutura de um evento, name se refere ao tipo de evento, nesse caso interactionCreate e novamente temos o execute como o código que será executado toda vez que esse evento for invocado.

Usamos o interaction.IsChatInputCommand() para garantir que se trata de um comando que é invocado pelo chat, pois temos outros tipos de comandos que são tratados dentro desse mesmo evento, após isso verificamos qual comando é e invocamos o seu execute passando algumas propriedades úteis.

Ufa! Agora sim, quando rodarmos o projeto novamente com node ., veremos o bot no terminal nos dizendo que o comando user e o evento interactionCreate foram carregados com sucesso.

Terminal com as mensagens de sucesso

Agora, se formos para o Discord e digitar /user, veremos o comando! (lembre-se que se seu Discord está em português o comando aparecerá como "/usuário", de qualquer modo digitar /user vai mostrar o comando desejado)

Comando aparecendo no chat do discord

Escolha um usuário e veja a mágica acontecer!

Comando funcionando no chat do discord, mostrando uma embed com algumas informações do usuário escolhido

Parabéns! Você criou seu primeiro bot do Discord!

Essa é a estrutura básica de um bot, existem ainda muitos conceitos mais avançados, cooldowns, sub comandos, testes... mas com essa estrutura você já pode começar sua jornada nesse universo!

Dicas

Deixarei aqui algumas dicas soltas para você que quer seguir desenvolvendo nessa área:

  • Crie um segundo bot, deixe seu primeiro bot como um ambiente de "produção" e faça seus testes no segundo bot, assim você pode desenvolver sem afetar seus usuários (podemos alternar entre os dois só mudando o token)
  • O site top.gg contém milhares de bots diferentes e é possível filtrar por categorias, é um bom lugar para pesquisar outros bots parecidos com o seu e também é possível colocar o seu bot lá para mais exposição
  • Pense em criar um site para o seu bot, caso você deseje verificar seu bot, você tem que preencher alguns requisitos, dois deles é fornecer o link para sua Política de Privacidade e Termos de Serviço, uma boa forma de fazer isso é com um site, que também é um bom veículo para promover seu bot
  • Meu jeito de criar bots não é o supremo! A biblioteca discord.js é bem não-opinativa e existem infinitos modos de se declarar um bot (é possível até criar bots com Typescript com a mesma biblioteca), use o Github! Pesquise!

Aprenda mais

Se quer aprender mais e levar seu bot ao próximo nível, aqui estão alguns materiais úteis:

  • Guia discord.js esse é um guia muito completo e bem escrito que cobre tudo que eu cobri aqui e MUITO mais! O único detalhe é que ele é escrito em inglês.
  • Documentação da biblioteca discord.js a documentação é um ótimo site para você deixar salvo e conferir sempre que precisar, é muito simples de achar o que você quer dentro dela e ver todos os métodos e propriedades daquela classe
  • Um pouco de marketing da minha parte, mas que também pode ser útil como referência são os repositórios do meu bot principal, minha própria biblioteca de minigames pro discord e meu template de discord bots que é um pouco mais completo que o que desenvolvemos juntos aqui
  • O código desenvolvido nesse artigo também estará disponível no meu github

Conclusão

Se você chegou até aqui, muito obrigado! Espero que você tenha pelo menos aprendido alguma coisa nova, esse é meu primeiro artigo aqui então qualquer crítica (construtiva), comentário, dúvida ou elogio por favor deixe nos comentários :)

Top comments (2)

Collapse
 
igorraphael profile image
Igor Raphael

Parabéns pelo post, sempre bom ver brs por aqui

Collapse
 
falcao_g profile image
Falcão

obrigado pelo comentário!