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.
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:
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.
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")
Agora clique no elefante do Gradle no canto superior direito da tela:
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.
Agora você precisa preencher o nome do seu bot:
Após isso, veja que na barra lateral esquerda, há um menu chamado Bot:
Nesse menu, terá um botão escrito Add Bot. Clique nesse botão para criar o bot nessa aplicação:
Após criar o bot, vá no menu OAuth2 na barra 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.
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.
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
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.*
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()
}
- 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.
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()
}
- 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ávelmessage
;
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)))
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()
}
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
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.*
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")
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){
}
- 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 osuspend
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
}
}
- 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(" ")
}
- Usamos a função
subList
que seleciona uma parte de uma lista. Inciando do índice 1 até o ultimo índice da lista commensagemSplit.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")
}
- 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)
}
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()
}
- 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 nossowhen
.
!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.*
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"
)
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){
}
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)
}
- 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()
}
!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()
}
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){
}
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
}
}
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)
}
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!")
}
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()
}
!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){
}
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")
}
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()
}
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
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 👀
Top comments (3)
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
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:
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.
Valeu mano, deu certo aqui!