DEV Community

Cover image for Criando um bot para Discord com Kord (Kotlin)
Lissa Ferreira for Kotlinautas

Posted on • Updated on

Criando um bot para Discord com Kord (Kotlin)

Kotlinautas

Esse conteúdo é oferecido e distribuído pela comunidade Kotlinautas, uma comunidade brasileira que busca oferecer conteúdo gratuito sobre a linguagem Kotlin em um espaço plural.

capa Kotlinautas

O quê é Discord?

Discord é uma plataforma de socialização que permite a criação de servidores, que funcionam como um grupo, onde é possível de criar cargos, canais de texto, canais de voz,etc.

O Discord é usado geralmente em comunidades de jogos, mas pode ser usado para qualquer tipo de grupo de pessoas, como uma comunidade de tecnologia.

O quê são bots para Discord?

Bots para Discord são automatizações que buscam facilitar processos de um servidor. Como por exemplo bots que tocam música em canais de voz, ou bots que retornam o clima de uma cidade caso um comando seja enviado em um canal de texto.

Qualquer pessoa pode criar um bot para Discord. Isso pode ser feito com linguagens de programação como Python, Node, Java, ou Kotlin que será o caso.

O quê é Kord?

Kord é uma biblioteca para Kotlin que permite a criação de bots para o Discord sem muitas complicações. A idéia é usar o poder das corotinas do Kotlin (Caso você queira saber mais sobre corotinas no Kotlin, veja no artigo Básico de corotinas em Kotlin da Kotlinautas) junto com a simplicidade do Kotlin.

Kord no momento que esse artigo está sendo publicado, suporta 100% da API não relacionada á voz do Discord. Logo a API de voz do Discord ainda não é suportada.

O quê iremos criar?

Iremos criar um bot de discord simples, com quatro comandos:

  • !enquete [enquete] irá criar uma enquete com emojis do Discord (✅ para sim e ❌ para não);
  • !curiosidade irá pegar uma curiosidade aleatória sobre Kotlin;
  • !guardar [texto] irá adicionar um texto á uma lista de textos;
  • !mostrar irá mostrar os textos que foram guardados;

Com esses quatro comandos poderemos ter uma idéia melhor de como o Kord funciona, e como criar um bot para Discord.

Criando o projeto

Vá no seu intelliJ, e clique no botão New Project para criar um novo projeto:

Botão new project do intelliJ

Após isso, na interface de configurações do Gradle deverão ficar assim, habilitando o Kotlin DSL build script, e também habilitar a opção Kotlin/JVM. Opicionalmente você pode remover a opção Java, pois não iremos usar Java nesse projeto.

Configurações do Gradle

Clique agora em Next para escolher um nome para o projeto, pode ser qualquer nome que você quiser. Caso não tenha nenhuma ideia, pode ser algo como kord por exemplo.

Instalando o Kord

O Kord pode ser instalado adicionando três dependências. Vá ao arquivo build.gradle.kts, e adicione essas três dependências:

implementation("dev.kord:kord-core:0.8.0-M5")
implementation("org.slf4j:slf4j-api:1.6.1")
implementation("org.slf4j:slf4j-simple:1.6.1")
Enter fullscreen mode Exit fullscreen mode

Agora clique no elefante do Gradle no canto superior direito da tela:

Elefante do Gradle do canto superior direito

Com isso, todas as dependências serão devidamente instaladas.

Criando a conta do Discord do bot

Temos que criar uma conta do Discord que irá representar o nosso bot. Essa conta será usada para conectar o Kord ao servidor do Discord. Essa conta pode ser criada na pagina de aplicações do Discord.

botão new project da pagina de aplicações do Discord

Agora você precisa preencher o nome do seu bot:

Caixa de diálogo para preencher o nome do bot

Após isso, veja que na barra lateral esquerda, há um menu chamado Bot:

Menu bot na barra lateral esquerda

Nesse menu, terá um botão escrito Add Bot. Clique nesse botão para criar o bot nessa aplicação:

botão add bot no menu bot

Após criar o bot, vá no menu OAuth2 na barra esquerda:

menu oauth2 na barra lateral esquerda

Vá na lista de opções OAuth2 e marque a opção bot. O link gerado abaixo será o link para adicioanr o bot em algum servidor que você tenha um cargo de administração. De preferência, enquanto estiver desenvolvendo o bot, crie um servidor de testes apenas seu e adicione o bot para esse servidor.

Lista de permissões Oauth2 com a opção bot marcada

Agora, volte á página inicial da aplicação do bot e terá uma seção chamada Token. Clique no botão Copy para copiar esse token. Iremos usar esse token dentro do nosso código para conectar o Kord ao nosso bot.

seção do token do bot na pagina inicial da aplicação do seu bot

Esse token não deve ser compartilhado com ninguém, e não deve ser postado no Github pois com esse token uma pessoa má intencionada teria controle total do seu bot. Para saber como guardar esse token de maneira segura, veja o artigo Variáveis de ambiente no Kotlin

Conectando o Kord ao nosso bot

Vamos criar um arquivo chamado main.kt dentro de src/main/kotlin/, esse será o arquivo principal do nosso bot.

Dentro desse arquivo vamos criar um pacote chamado komando que será o nome do nosso bot. Você pode escolher qualquer outro nome, mas caso não tenha idéia, deixe komando mesmo.

package komando
Enter fullscreen mode Exit fullscreen mode

Agora vamos importar algumas partes do Kord no nosso código:

package komando

import dev.kord.core.*
import dev.kord.core.entity.*
import dev.kord.core.event.message.*
Enter fullscreen mode Exit fullscreen mode

Agora vamos criar a função main do nosso código, que inicialmente irá conectar ao Discord apenas:

...
suspend fun main() {
    val cliente = Kord("<SEU TOKEN>")

    cliente.login()
}
Enter fullscreen mode Exit fullscreen mode
  • Substitua o texto <SEU TOKEN> pelo token do site do Discord;
  • Iniciamos a instância do bot usando a classe Kord, inserindo o token do nosso bot como parâmetro;
  • Iniciamos o bot com a função client.login(), fazendo uma tentativa de se logar no Discord;

Para iniciar o bot, clique na seta verde ao lado da função main, que o próprio IntelliJ irá iniciar o bot.

Seta verde ao lado da função main

Recebendo mensagens

Agora vamos fazer que o nosso bot receba mensagens enviadas em qualquer canal do(s) servidor(res) que está conectado. Isso pode ser feito dessa forma:

...
suspend fun main() {
    val cliente = Kord("<SEU TOKEN>")

    cliente.on<MessageCreateEvent> {
    println(message)
  }

    cliente.login()
}
Enter fullscreen mode Exit fullscreen mode
  • Usamos o método clinte.on onde podemos inserir um código que será executado toda vez que chegar uma mensagem no servidor. Essa mensagem é guardada na variável message;

Agora, tente enviar uma mensagem no seu servidor. O output será algo do tipo:

Message(data=MessageData(id=Snowflake(value=880821494043770890), channelId=Snowflake(value=770303744679215104), guildId=OptionalSnowflake.Value(snowflake=Snowflake(value=759906666060840991)), author=UserData(id=Snowflake(value=244129089806532608), username=Ederson Ferreira, discriminator=2011, avatar=5680a7721a80f529b39d1cbdfe6f4d93, bot=OptionalBoolean.Missing, publicFlags=Optional.Something(content=UserFlags(code=0)), banner=null, accentColor=null), content=teste, timestamp=2021-08-27T14:30:09.052000+00:00, editedTimestamp=null, tts=false, mentionEveryone=false, mentions=[], mentionRoles=[], mentionedChannels=Optional.Missing, attachments=[], embeds=[], reactions=Optional.Missing, nonce=Optional.Something(content=880821493397716992), pinned=false, webhookId=OptionalSnowflake.Missing, type=dev.kord.common.entity.MessageType$Default@73885605, activity=Optional.Missing, application=Optional.Missing, applicationId=OptionalSnowflake.Missing, messageReference=Optional.Missing, flags=Optional.Something(content=MessageFlags(flags=[])), stickers=Optional.Missing, referencedMessage=Optional.Null, interaction=Optional.Missing, components=Optional.Something(content=[])), kord=Kord(resources=ClientResources(shards=Shards(totalShards=1, indices=0..0), httpClient=HttpClient[io.ktor.client.engine.cio.CIOEngine@54da3d21], defaultStrategy=EntitySupplyStrategy.cacheWithRestFallback, intents=Intents(code=DiscordBitSet(101111110110111))), cache=dev.kord.cache.api.delegate.DelegatingDataCache@25a6997a, gateway=MasterGateway(gateways={0=CachingGateway(cache=DataCacheView(cache=dev.kord.cache.api.delegate.DelegatingDataCache@25a6997a), gateway=dev.kord.gateway.DefaultGateway@1b268405)}), rest=dev.kord.rest.service.RestClient@276b2278, selfId=Snowflake(value=768959134556880997)), supplier=FallbackEntitySupplier(first=CacheEntitySupplier(cache=dev.kord.cache.api.delegate.DelegatingDataCache@25a6997a), second=RestEntitySupplier(rest=dev.kord.rest.service.RestClient@276b2278)))
Enter fullscreen mode Exit fullscreen mode

Agora estamos recebendo um objeto que representa a mensagem, e podemos obter diversos dados sobre a mensagem. Como a pessoa que enviou a mensagem, o conteúdo da mensagem em si,etc.

Podemos por exemplo, mostrar apenas o conteúdo da mensagem, isso pode feito dessa maneira:

...
suspend fun main() {
    val cliente = Kord("<SEU TOKEN>")

    cliente.on<MessageCreateEvent> {
    println(message.content)
  }

    cliente.login()
}
Enter fullscreen mode Exit fullscreen mode

Agora, o resultado do nosso código não será um objeto amontado de dados e de difícil interpretação. Aogra será o texto que enviarmos no servidor em sua forma pura:

testando Kord
Enter fullscreen mode Exit fullscreen mode

Criando comandos

Iremos inserir cada comando em um arquivo diferente mas sempre do mesmo pacote komando, para assim poder usar todas as funções criadas dentro do arquivo main.kt.

!enquete

Primeiro, crie um arquivo chamado enquete.kt em src/main/kotlin. Dentro desse arquivo vamos fazer uma única importação:

package komando

import dev.kord.core.entity.*
Enter fullscreen mode Exit fullscreen mode

Agora vamos criar duas variáveis globais desse código, sendo a variável sim e a variável não que irão guardar os emojis ✅ para sim e ❌para não. Podemos criar essas variáveis dessa maneira:

package komando

import dev.kord.core.entity.*

val sim = ReactionEmoji.Unicode("\u2705")
val não = ReactionEmoji.Unicode("\u274C")
Enter fullscreen mode Exit fullscreen mode

Agora vamos criar uma função chamada enquete, essa função irá receber dois argumentos. Sendo a mensagemSplit, que é o conteúdo da mensagem em forma de uma lista, onde cada elemento da lista é uma palavra do conteúdo da mensagem, e também uma variável chamada mensagemDados que será o objeto do Kord que representa a mensagem. Logo, a declaração da função será assim:

suspend fun enquete(mensagemSplit: List<String>, mensagemDados: Message){
}
Enter fullscreen mode Exit fullscreen mode
  • Essa função é marcada com suspend pois para o envio da mensagem é usada uma corotina, e como iremos usar corotinas nessa função, precisamos adicionar o suspend antes da função. (Caso você queria saber sobre corotinas, leia esse arquivo da Kotlinautas: Básico de corotinas em Kotlin)

Agora vamos pensar: Se a enquete precisa ter um título, logo o comando que será enviado seria algo como !enquete título da enquete. Com isso, podemos concluir que o número mínimo de índices que mensagemSplit precisa ter são dois. Pois o primeiro sempre será !enquete, e caso a enquete tenha uma palavra como título, seriam apenas dois elementos nessa lista. Logo, podemos filtrar para caso seja enviada uma !enquete sem nenhum título esse comando seja recusado. Isso pode ser feito dessa forma:

suspend fun enquete(mensagemSplit: List<String>, mensagemDados: Message){
    if (mensagemSplit.size < 2){
        mensagemDados.channel.createMessage("Envie o comando na maneira correta, colocando !enquete [nome da enquete]")
        return
    }

}
Enter fullscreen mode Exit fullscreen mode
  • Caso o número de índices da mensagem seja menor que dois, será enviada uma mensagem no chat, dizendo que o comando precisa ser enviado da maneira correta, e a função será retornada.
  • A função que usamos para enviar a mensagem é a .createMessage, que cria uma mensagem em um canal de texto específico (no caso o canal de texto que a enquete foi enviada)

Agora, vamos criar uma variável chamada enquete, que irá guardar o conteúdo da mensagem sem o comando !enquete, armazenando apenas o título da enquete.

suspend fun enquete(mensagemSplit: List<String>, mensagemDados: Message){
    if (mensagemSplit.size < 2){
        mensagemDados.channel.createMessage("Envie o comando na maneira correta, colocando !enquete [nome da enquete]")
        return
    }

    val enquete = mensagemSplit.subList(1, mensagemSplit.size).joinToString(" ")
}
Enter fullscreen mode Exit fullscreen mode
  • Usamos a função subList que seleciona uma parte de uma lista. Inciando do índice 1 até o ultimo índice da lista com mensagemSplit.size;
  • Juntamos toda essa lista para uma string usando a função joinToString, unindo todos os elementos separando com um espaço " ";

Agora, precisamos enviar no canal a enquete. Isso pode ser feito usando aquela função .createMessage:

suspend fun enquete(mensagemSplit: List<String>, mensagemDados: Message){
    if (mensagemSplit.size < 2){
        mensagemDados.channel.createMessage("Envie o comando na maneira correta, colocando !enquete [nome da enquete]")
        return
    }

    val enquete = mensagemSplit.subList(1, mensagemSplit.size).joinToString(" ")
    val resposta = mensagemDados.channel.createMessage("Iniciando enquete: $enquete")
}
Enter fullscreen mode Exit fullscreen mode
  • Guardamos a mensagem em uma variável chamada resposta, pois iremos mexer com essa mensagem depois de seu envio.

Agora precisamos aplicar os emojis á mensagem. Isso pode ser feito usando a variável addReaction dessa maneira:

suspend fun enquete(mensagemSplit: List<String>, mensagemDados: Message){
    if (mensagemSplit.size < 2){
        mensagemDados.channel.createMessage("Envie o comando na maneira correta, colocando !enquete [nome da enquete]")
        return
    }

    val enquete = mensagemSplit.subList(1, mensagemSplit.size).joinToString(" ")
    val resposta = mensagemDados.channel.createMessage("Iniciando enquete: $enquete")

    resposta.addReaction(sim)
    resposta.addReaction(não)
}
Enter fullscreen mode Exit fullscreen mode

Agora vamos voltar ao arquivo main.kt, e vamos criar um bloco when para determinar qual será o comando utilizado, e criar a variável mensagemSplit:

...
suspend fun main() {
    val cliente = Kord("<SEU TOKEN>")

    cliente.on<MessageCreateEvent> {
        val mensagemSplit = message.content.split(" ")
        when(mensagemSplit[0]){
            "!enquete" -> enquete(mensagemSplit, message)
        }
  }

    cliente.login()
}
Enter fullscreen mode Exit fullscreen mode
  • Por enquanto, dentro do when temos apenas uma condição (comando), sendo a !enquete. Com essa estrutura, podemos adicionar novos comandos apenas adicionando condições ao nosso when.

!curiosidade

Agora vamos criar o comando !curiosidade, que irá pegar uma curiosidade aleatória sobre Kotlin, e enviar essa curiosidade no canal de texto.

Primeiro, crie um arquivo chamado curiosidade.kt em src/main/kotlin. Dentro desse arquivo vamos fazer uma única importação:

package komando

import dev.kord.core.entity.*
Enter fullscreen mode Exit fullscreen mode

Agora vamos criar uma variável global chamada curiosidades que será uma lista que irá armazenar as curiosidades:

package komando

import dev.kord.core.entity.*

val curiosidades = listOf(
    "Kotlin é completamente interoperativo com Java",
    "Kotlin pode ser usada tanto orientada á objetos, quanto de maneira funcional",
    "Kotlin pode ser usada para criação de backend, frontend, mobile e desktop",
    "Com Kotlin você escreve menos código comparado ao Java",
    "Kotlin tem uma curva de aprendizado menor que outras linguagens"
)
Enter fullscreen mode Exit fullscreen mode

Agora vamos criar a função curiosidade que apenas irá ter mensagemDados como parâmetro, que é o objeto da mensagem:

suspend fun curiosidade(mensagemDados: Message){

}
Enter fullscreen mode Exit fullscreen mode

Agora vamos pegar uma curiosidade aleatória, e enviar essa curiosidade no canal de texto.

...
suspend fun curiosidade(mensagemDados: Message){
    val curiosidade = curiosidades.random()
    mensagemDados.channel.createMessage(curiosidade)
}
Enter fullscreen mode Exit fullscreen mode
  • No Kotlin podemos usar a função .random() em uma lista para pegar um elemento aleatório em uma lista.

Agora vamos voltar ao arquivo main.kt e adicionar esse comando em nosso when

...
suspend fun main() {
    val cliente = Kord("<SEU TOKEN>")

    cliente.on<MessageCreateEvent> {
        val mensagemSplit = message.content.split(" ")
        when(mensagemSplit[0]){
            "!enquete" -> enquete(mensagemSplit, message)
            "!curiosidade" -> curiosidade(message)
        }
  }

    cliente.login()
}
Enter fullscreen mode Exit fullscreen mode

!guardar

Agora vamos criar o comando !guardar que irá adicionar um texto á uma lista de textos.

Primeiro, vamos á main.kt e vamos adicionar uma variável global chamada textos que será a lista mutável que vamos armazenar os textos:

...

val textos = mutableListOf<String>()

suspend fun main() {
    val cliente = Kord("<SEU TOKEN>")

    cliente.on<MessageCreateEvent> {
        val mensagemSplit = message.content.split(" ")
        when(mensagemSplit[0]){
            "!enquete" -> enquete(mensagemSplit, message)
            "!curiosidade" -> curiosidade(message)
        }
  }

    cliente.login()
}
Enter fullscreen mode Exit fullscreen mode

Agora vamos criar um arquivo chamado guardarTexto.kt que irá ter um comportamento muito parecido com a enquete, pois o argumento do comando também tem várias palavras. A função também irá receber os mesmos argumentos da enquete. Vamos adicionar a importação do Kord e a função:

package komando

import dev.kord.core.entity.*

suspend fun guardarTexto(mensagemSplit: List<String>, mensagemDados: Message){

}
Enter fullscreen mode Exit fullscreen mode

Agora vamos criar a mesma condição que filtra se o comando !guardar será usado de maneira correta, e se não, será enviada uma mensagem avisando que o comando foi usado de maneira incorreta.

package komando

import dev.kord.core.entity.*

suspend fun guardarTexto(mensagemSplit: List<String>, mensagemDados: Message){
    if (mensagemSplit.size < 2){
        mensagemDados.channel.createMessage("Envie o comando na maneira correta, colocando !guardar [texto]")
        return
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora precisamos pegar o texto (a mensagem inteira sem !guadar) e armazenar na variável textos da main.kt:

package komando

import dev.kord.core.entity.*

suspend fun guardarTexto(mensagemSplit: List<String>, mensagemDados: Message){
    if (mensagemSplit.size < 2){
        mensagemDados.channel.createMessage("Envie o comando na maneira correta, colocando !guardar [texto]")
        return
    }

    val texto = mensagemSplit.subList(1, mensagemSplit.size).joinToString(" ")
    textos.add(texto)
}
Enter fullscreen mode Exit fullscreen mode

Agora vamos enviar uma mensagem no canal de texto informando que o texto foi guardado com sucesso:

package komando

import dev.kord.core.entity.*

suspend fun guardarTexto(mensagemSplit: List<String>, mensagemDados: Message){
    if (mensagemSplit.size < 2){
        mensagemDados.channel.createMessage("Envie o comando na maneira correta, colocando !guardar [texto]")
        return
    }

    val texto = mensagemSplit.subList(1, mensagemSplit.size).joinToString(" ")
    textos.add(texto)

    mensagemDados.channel.createMessage("Mensagem salva!")
}
Enter fullscreen mode Exit fullscreen mode

Agora vamos adicionar esse comando ao when:

...

val textos = mutableListOf<String>()

suspend fun main() {
    val cliente = Kord("<SEU TOKEN>")

    cliente.on<MessageCreateEvent> {
        val mensagemSplit = message.content.split(" ")
        when(mensagemSplit[0]){
            "!enquete" -> enquete(mensagemSplit, message)
            "!curiosidade" -> curiosidade(message)
            "!guardar" -> guardarTexto(mensagemSplit, message)
        }
  }

    cliente.login()
}
Enter fullscreen mode Exit fullscreen mode

!mostrar

Agora vamos criar o último comando que irá mostrar os textos que foram guardados pelo guardarTexto. Primeiro, crie um arquivo chamado mostrarTextos.kt, e vamos importar o Kord e criar a função mostrarTextos:

package komando

import dev.kord.core.entity.*

suspend fun mostrarTextos(mensagemDados: Message){

}
Enter fullscreen mode Exit fullscreen mode

Agora vamos pegar a lista textos, transformar em uma string com todos esses textos e enviar esse texto no canal:

package komando

import dev.kord.core.entity.*

suspend fun mostrarTextos(mensagemDados: Message){
    val textosString = textos.joinToString(", ")
    mensagemDados.channel.createMessage("As mensagens que foram guardadas são: $textosString")
}
Enter fullscreen mode Exit fullscreen mode

Agora, só falta adicionarmos esse comando ao when que criamos em main.kt:

...

val textos = mutableListOf<String>()

suspend fun main() {
    val cliente = Kord("<SEU TOKEN>")

    cliente.on<MessageCreateEvent> {
        val mensagemSplit = message.content.split(" ")
        when(mensagemSplit[0]){
            "!enquete" -> enquete(mensagemSplit, message)
            "!curiosidade" -> curiosidade(message)
            "!guardar" -> guardarTexto(mensagemSplit, message)
            "!mostrar" -> mostrarTextos(message)
        }
  }

    cliente.login()
}
Enter fullscreen mode Exit fullscreen mode

Testando o bot na prática

Agora vamos testar como os comandos do nosso bot estão funcionando. Inicie o bot clicando na seta verde dita anteriormente, vá á algum canal do seu servidor, e comece a testar os comandos criados, como no exemplo abaixo:

Texto

Lissa: !enquete teste de enquete

Bot: Iniciando enquete: teste de enquete

Lissa: !curiosidade

Bot: Kotlin pode ser usada tanto orientada á objetos, quanto de maneira funcional

Lissa: !guardar guardando esse texto aqui

Bot: Mensagem salva!

Lissa: !guardar esse outro também

Bot: Mensagem salva!

Lissa: !mostrar

Bot: As mensagens que foram guardadas são: guardando esse texto aqui, esse outro também
Enter fullscreen mode Exit fullscreen mode

Finalização

Agora temos um bot para Discord feito completamente em Kotlin e totalmente funcional. Que pode processar comandos enviados e responder á esses comandos.

Muito obrigada por ler ❤️🏳️‍⚧️ e me segue nas redes, é tudo @lissatransborda 👀

Latest comments (3)

Collapse
 
certeixeira profile image
Carlos Eduardo

Na primeira tentativa de iniciar o bot está dando esse erro:

[DefaultDispatcher-worker-4] WARN io.ktor.util.random - NativePRNGNonBlocking is not found, fallback to SHA1PRNG

Collapse
 
lissatransborda profile image
Lissa Ferreira

Uma dúvida, isso não é só um aviso por causa do "WARN" de "WARNING" escrito?

Pesquisei aqui e aqui nessa issue do ktor um comentário com essa solução:

System.setProperty("io.ktor.random.secure.random.provider", "DRBG")
Security.setProperty("securerandom.drbg.config", "HMAC_DRBG,SHA-512,256,pr_and_reseed")
Enter fullscreen mode Exit fullscreen mode

Creio eu que esse código acima deveria ser colocado no topo do arquivo. Caso não encontre nenhuma solução, recomendo ir no github do projeto do Kord e criar uma issue, falando sobre o erro e como reproduzir esse erro. Porque aí vão ser as pessoas desenvolvedoras do kord que vão te ajudar, abraço.

Collapse
 
certeixeira profile image
Carlos Eduardo • Edited

Valeu mano, deu certo aqui!