I built a Discord bot to teach me Portuguese
Tags: webdev, node, googlecloud, discord
Published: 2022-12-26 01:06:06

I've been trying to learn Portuguese on my own for three years. I dream of becoming fluent and impressing my partner's family with wit and charm. Unfortunately, it takes a lot of effort to learn a language. Something that helps with picking up a language is expanding your vocabulary. I have a Google Sheet of about 700 words and phrases that I've been learning, but there are better ways to learn than looking at a spreadsheet. I was inspired by this [article](https://dev.to/happping_min/working-alone-is-so-exhausting-so-i-created-my-own-assistant-4mki) to build a bot with some personality to teach me Portuguese. This article will briefly discuss the technology I learned to make my bot and some challenges I ran into.

## Table of contents
1. Goals for the project
2. Having fun
3. Learning the Discord API
4. Learning Google Sheets API
5. Creating a command to fetch vocab words
6. Setting up a cron job
8. Deploy to Heroku

## Goals for the project

My primary goal was to build a bot that would send me some vocab words every day, and my secondary goal was to make it fun to interact with. My first decision was to use Discord because it's an app I use every day and have open on all my devices, and it has a [Javascript API](https://discord.js.org/#/) for building bots. My second decision was to keep my vocabulary list in Google Sheets and use the [Google Sheets API](https://developers.google.com/sheets/api/quickstart/nodejs) to connect to my data. I wanted as little friction as possible to get my bot up and running.

## Having fun

The best way to trick me into doing something is to have fun while doing it. I wanted to build a bot that would inspire me to engage with Brazillian culture, so I chose a beloved folklore character to teach me Portuguese - Cuca 🐊.

![Image of Cuca. She's a large, confident crocodile with a blond wig. She's smiling for the camera while she sits in her best dress.](https://64.media.tumblr.com/863f5d3c625bbe691c19bf44a7738494/6e2bb157619c841f-32/s400x600/59f974408ed5ae3886a67d9ee6ceb4f1863c47f3.jpg)

Cuca is best known as a crocodile from the children's show Sítio do Picapau Amarelo. If you've ever watched Cidade Invisível on Netflix, you may know her as the lady who can enter people's dreams. However, I prefer her crocodile form 😂.


![Screenshot of Cuca bot icon on Discord app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/trksyno1h5c7q9mfjede.png)



Every vocab list Cuca sends me comes with a fun gif. And there are [a lot of fun gifs](https://giphy.com/search/cuca) to choose from.

![GIF of Cuca dancing](https://media.giphy.com/media/3o6vY6D85LOQLCxNQI/giphy.gif)

## Learning the Discord API for NodeJs

Setting up the Discord bot was very simple. There's a [helpful guide](https://discordjs.guide/preparations/#installing-node-js) that takes you through the basics of installing `discord.js` through npm and creating a new bot on the [developer's portal](https://discord.com/developers/applications). The most important thing to note is your bot's security token. You can invite your bot to your server by generating an invite link. The invite link is a URL with your bot's client_id and some permissions and scopes appended to it. Scopes and permissions allow your bot certain functions, so be sure to choose what you need. For my bot, I wanted to create **slash commands**, so I selected the `bot`, `guild` and `applications.commands` scope and `Embed Links` and `Mention Everyone` permissions. 

This is an example of my generated link:

```
https://discord.com/api/oauth2/authorize?client_id=XXXXXXXXX&permissions=147456&scope=applications.commands%20bot%20guilds
```

## Learning Google Sheets API

My most significant pain point in the project was understanding how to authenticate for the Google Sheets API. Typically for other APIs I've used, I would be given an API key to send along with my request. Google Cloud gives you two authentication options: OAuth 2.0 Client IDs and **Service Accounts**. There were more [tutorials](https://hackernoon.com/how-to-use-google-sheets-api-with-nodejs-cz3v316f) explaining Service Accounts, so I went down that route. When you create a service account, you're prompted to download a JSON file that holds your account details and private key. That file is what you use to authenticate your API requests. Your service account also acts like a google account, one that you can share your google sheet documents with (like any other google account).

## Creating a slash command to retrieve vocab words

I created a slash command that connects to my Google API service and returns a randomized list of vocab words and their translations. This `/vocab` command connects to my Google API [service code](https://hackernoon.com/how-to-use-google-sheets-api-with-nodejs-cz3v316f) and passes my spreadsheet id and the range of values I want to return. My vocab list is two columns: one column for the Portuguese words and the next for the translation, so I select the range of values and randomly select 20 values.

```js
async function getWordsTranslationsMeanings() {
    try {
        const auth = await getAuthToken();
        const response = await getRangeSpreadSheetValues({
            spreadsheetId,
            sheetName,
            auth,
            range
        })

        const translationsArray = response.data.values;
        const translationsArrayLength = translationsArray.length;

        // Selecting random number of words
        const randomWords = []
        for (let index = 0; index < numOfWords; index++) { // numOfWords = 20
            const randomWordIndex = Math.floor(Math.random() * translationsArrayLength);
            randomWords[index] = translationsArray[randomWordIndex]
        }
        
        return randomWords;

    } catch (error) {
        console.log(error.message, error.stack);
    }
}
```

To send my vocab words to Discord, I used `embed` messages. These are nifty blocks of messages that can hold different media like images and gifs.

<!-- Vid of slash command and embed -->

![Embed message from Cucabot. It lists a number of Portuguese words with their translations](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2b8n4ukxh0khrwoj2zun.png)

Here's a snippet of the embed code:

```js
	const embed = new MessageEmbed();

    await getWordsTranslationsMeanings().then( words => {
            let fields = words.map(word => { return { name: word[0], value: word[1] } })
            embed.setColor('#2A8947')
                .setTitle('Palavras de vocabulario')
                .setDescription('Agora vai!')
                .addField('\u200B', '\u200B')
                .addFields(fields)
                .setImage(getGucaGif())
                .setTimestamp()
                .setFooter({ text: 'Feito por Anneta Wamono');

            if (interaction) {
                interaction.channel.send({ embeds: [embed] });
            } 
        },
        err => {
            console.log(err);
        }
    );
```

And the `SlashCommandBuilder` code:

```js
const { SlashCommandBuilder } = require('@discordjs/builders');
const { createEmbedWith20Words } = require('../actions/send20Words.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('vocab')
        .setDescription('Replies with a list of vocabulary words!'),
    async execute(interaction) {
        await createEmbedWith20Words({ interaction });
        await interaction.reply('Aqui estão suas palavras!');
    },
};
```

## Setting up the cron job

The ideal situation would be for Cuca bot to send me my vocab list daily at a convenient time to practise. The best solution was [cron](https://github.com/kelektiv/node-cron). The cron syntax is quite straightforward once you understand it. 


![Cron ranges. Starting from left to right - seconds, minutes, hours, day of month, months and day of week.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g8iimm9qdzsjdvpauisd.png)

For my purposes, I wanted to send a message every day at 8am.


![My cron string. Starting from left to right - 00, 00, 08, asteriks, asteriks, asteriks.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnm7hi28zp6rv4gskzbd.png)

The asterisks indicate that your job will run for every value in that range.

## Deployment

I used Heroku to deploy my bot, even though I've struggled with it before. That's because I've never known what a **procfile** was and how to use it. [Procfiles](https://devcenter.heroku.com/articles/procfile) are used to configure the dyno your code will run on. In my case, setting the dyno as a worker was important. By default, dynos are configured for web processes to receive HTTP traffic. Worker dynos are for [background jobs, queueing systems, and timed jobs](https://devcenter.heroku.com/articles/dynos#dyno-configurations), and you won't receive a timeout error when running your bot.

## Project takeaways

This was a really fun project to do. I got the opportunity to learn more about new APIs and gained more confidence when deploying my projects. Am I fluent in Portuguese now? Nope, but every day I get a little bit better. I want to add more features to Cuca bot, like a command that issues a test and keeps track of my score.

## Resources

- [Inspiration project](https://dev.to/happping_min/working-alone-is-so-exhausting-so-i-created-my-own-assistant-4mki)
- [Discord API](https://discord.js.org/#/)
- [Google sheets API](https://developers.google.com/sheets/api/quickstart/nodejs)
- [How to use Google Sheets API with Nodejs](https://hackernoon.com/how-to-use-google-sheets-api-with-nodejs-cz3v316f)

---
*Thank you for reading my article. If there is anything you would like more details on, leave a comment below. I'd like to continue improving the quality of my articles, so any feedback is appreciated.*

*See you at the next one!*