Conectando um bot do Discord com a API do Gemini usando Golang

Lucas Herlon
5 min readMay 31, 2024

--

Neste artigo irei continuar a documentar a empreitada cujo relato iniciei no texto anterior. Nele conto como criei uma conexão com a API do Gemini para traduzir textos na minha linha de comando. Neste novo capítulo irei introduzir mais uma API, a do Discord, e contarei como conectei ambas as interfaces utilizando a linguagem Go.

A ideia do projeto continua sendo a de traduzir textos, mas agora utilizando o ambiente do Discord, no qual um bot recebe uma requisição de tradução e a direciona para a API do Gemini, que, por sua vez, devolve a resposta ao bot.

1. Criando o Bot

Para criar um aplicativo do Discord é necessário ir ao site discord.dev, e uma vez nele devemos clicar no botão New Application. Feito isso, teremos que criar um nome para nossa aplicação e poderemos adicionar uma descrição a ela. O próximo passo é navegar pela aba esquerda da página e selecionar a opção Bot.

Nessa seção você poderá criar um nome para o seu bot e escolher um ícone de usuário para ele, mas a importância dessa seção está nas duas seguintes ações que você terá que tomar nela:

  • gerar um novo token: programas que consomem APIs privadas precisam apresentar um token que autorize a conexão. Ao clicar em reset token, você receberá um novo token secreto que deverá ser guardado para ser utilizado na aplicação
  • habilitar o message content intent: que permitirá que o bot consiga receber mensagens

Já na aba Oauth2, você irá na seção Oauth2 URL Generator e irá selecionar a opção bot. Logo em seguida surgirão as opções de permissões que a sua aplicação poderá ter. Por questões de praticidade, pode-se selecionar a opção Administrator que irá conceder permissões máximas ao bot.

Superados esses passos você irá receber uma url para sua aplicação, que ao ser carregada no navegador conectará seu bot a um servidor de sua escolha no qual você seja administrador.

2. O script em Go

Antes de seguir é necessário reforçar que a premissa básica de uma aplicação que consome APIs privadas é a posse das chaves secretas. No texto anterior mostrei como conseguir a API Key do Gemini. Essa chave e o token do bot do discord, devem ser salvos como variáveis de ambiente em um arquivo .env na pasta do projeto. No código da aplicação mostrada aqui essas chaves são referenciadas como GEMINI_KEY e BOT_KEY.

Criando o projeto:

mkdir traduz.ai
cd traduz.ai/
go mod init traduz.ai

O projeto então será montado com dois pacotes, um chamado main e outro chamado geminiapi. No pacote main ficará a função main que será o ponto de partida da aplicação e toda a parte do código relativa à API do discord.

Para começar é necessário adicionar o pacote discordgo às dependências do projeto, fazemos isso abrindo o terminal na pasta principal do projeto e digitando:

go get github.com/bwmarrin/discordgo

Também é necessário baixar a dependência para ler o arquivo .env:

go get github.com/joho/godotenv

Adicionando esses pacotes externos e outros internos que serão usados no projeto, os imports do arquivo main devem ficar assim:

import (
"fmt"
"log"
"os"
"strings"
"traduz.ai/geminiapi"

"github.com/bwmarrin/discordgo"
"github.com/joho/godotenv"
)

A função main deve criar uma nova sessão (variável session) para o nosso aplicativo, recebendo como paramêtro a string “Bot ” concatenada com o token secreto. Uma vez criada essa sessão, poderemos usá-la para adicionar comportamentos ao nosso bot.

func main() {   
const command string = "!traduz"
err := godotenv.Load(".env")
if err != nil {
return
}
token := os.Getenv("BOT_KEY")
session, err := discordgo.New("Bot " + token)
...

O comportamento que nos interessa aqui é que o bot consiga ler e enviar mensagens. Para viabilizar isso iremos usar o método addHandler.

Aqui começaremos lendo a mensagem recebida pelo bot, contida na variável m, e iremos verificar se a mensagem está no formato correto para ser respondida. Precisamos saber, na verdade, se a mensagem começa com o comando !traduz, comando esse que foi declarado em uma constante, conforme mostrado no código acima. Em caso afirmativo, iremos direcionar a mensagem para a API do Gemini, que nos devolverá uma resposta que receberemos como uma string. De posse dessa resposta, usaremos uma variável chamada s para enviá-la ao bot que irá exibi-la na interface do discord.

session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
args := strings.Split(m.Content, " ")
if args[0] != command {
return
}

if m.Author.ID == s.State.User.ID {
return
}

input := m.Content[1:]
response := geminiapi.GeneratePrompt(input)

_, err := s.ChannelMessageSend(m.ChannelID, response)
if err != nil {
log.Fatal(err)
}

})

Para finalizar devemos deixar explícita a intenção do bot de receber mensagens, setando as chamadas Intents. Depois, devemos abrir a sessão lembrando também de usar a cláusula defer para fechar a sessão ao final do processamento.

session.Identify.Intents = discordgo.IntentsAllWithoutPrivileged
err = session.Open()
if err != nil {
log.Fatal(err)
}
defer func(session *discordgo.Session) {
err := session.Close()
if err != nil {
}
}(session)

fmt.Println("Traduz.ai is running")

No pacote geminiapi iremos encontrar um cenário parecido com aquele que foi descrito no texto anterior. As funções presentes são similares, exceto por alguns fatores.

O primeiro deles é que agora queremos que a função GeneratePrompt retorne a resposta da API do gemini como uma string, em vez de simplesmente exibi-la na tela do console. Além disso, essa função passará a receber como parâmetro o texto enviado pelo bot na forma de string, em vez de receber o idioma. Assim, essa versão da aplicação apresenta a limitação de apenas traduzir para o português brasileiro.

// Versão simplificada da função generate prompt
func GeneratePrompt(input string) string {
erro := godotenv.Load(".env")
if erro != nil {
fmt.Println("Error loading .env file:", erro)
}

idiomaSaida := "português brasileiro"
ctx := context.Background()
client, err := genai.NewClient(ctx, option.WithAPIKey(os.Getenv("GEMINI_KEY")))
if err != nil {
log.Fatal(err)
}
model := client.GenerativeModel("gemini-1.5-flash-latest")

prompt := fmt.Sprintf("Por gentileza, traduza o seguinte texto para o %v: %v", idiomaSaida, input)

resp, err := model.GenerateContent(ctx, genai.Text(prompt))
response := showResponse(resp)
return response
}

Repare que na prática quem transforma a resposta do Gemini em uma string é a função showResponse, que nada mais é do que uma versão ligeiramente modificada da função printResponse.

func showResponse(resp *genai.GenerateContentResponse) string {
var response string
for _, cand := range resp.Candidates {
if cand.Content != nil {
for _, part := range cand.Content.Parts {
response = response + fmt.Sprintf("%v", part)
}
}
}
return response
}

3. Colocando em funcionamento

O código completo da aplicação, à exceção dos tokens, encontra-se aqui.

Para conversar com o bot é necessário rodar a aplicação Go:

go run main.go

ou

go build
./traduz.ai

Depois basta digitar o comando !traduz no servidor do Discord seguido da frase a ser traduzida:

--

--