Criando um bot para Discord com Kord (Kotlin)
Tags: kotlin, backend, discord, braziliandevs
Published: 2021-09-06 03:37:12

## Kotlinautas

Esse conteúdo é oferecido e distribuído pela comunidade [Kotlinautas](https://twitter.com/kotlinautas/), uma comunidade brasileira que busca oferecer conteúdo gratuito sobre a linguagem Kotlin em um espaço plural.

![capa Kotlinautas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jreawpvk6whxigcpmctf.png)

## O quê é Discord?

[Discord](https://discord.com/) é 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](https://github.com/kordlib/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](https://dev.to/kotlinautas/basico-de-corotinas-em-kotlin-50a8) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xgiruu8u0b0i9ya2c0f3.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dtv82a82t30i9dvuatgq.png)

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:

```kotlin
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:

![Elefante do Gradle do canto superior direito](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6wuvzsk1bnfqo2jzclk.png)

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](https://discord.com/developers/applications).


![botão new project da pagina de aplicações do Discord](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fd8vx9d4lrnggse57zms.png)


Agora você precisa preencher o nome do seu bot:

![Caixa de diálogo para preencher o nome do bot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7tb42ewplo12rnybr6xd.png)


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

![Menu bot na barra lateral esquerda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0e7j08y9sweej8vzlpg5.png)


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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/maa1vfzpoue81gjxr83v.png)

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

![menu oauth2 na barra lateral esquerda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mt72kyi5lhetdk9r9tec.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rj0jjuiopuxn6m1k10ow.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mttfadinamsfn1xbd4fk.png)

> 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](https://dev.to/kotlinautas/variaveis-de-ambiente-no-kotlin-3557)

## 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.

```kotlin
package komando
```

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

```kotlin
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.

![Seta verde ao lado da função main](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ekq6do040aqdwuqg17cb.png)

## 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á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)))
```

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:

```kotlin
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:

```kotlin
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:

```kotlin
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 o `suspend` antes da função. (Caso você queria saber sobre corotinas, leia esse arquivo da Kotlinautas: [Básico de corotinas em Kotlin](https://dev.to/kotlinautas/basico-de-corotinas-em-kotlin-50a8))

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:

```kotlin
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.

```kotlin
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 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`:

```kotlin
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:

```kotlin
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`:

```kotlin
...
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 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:

```kotlin
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:

```kotlin
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:

```kotlin
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`

```kotlin
...
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:

```kotlin
...

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:

```kotlin
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.

```kotlin
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`:

```kotlin
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:

```kotlin
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`:

```kotlin
...

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`:

```kotlin
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:

```kotlin
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`:

```kotlin
...

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](https://twitter.com/lissatransborda) 👀