Notice: This project has been relocated to the rpitv/glimpse monorepo
An RPI TV Discord Bot that uses the Discord API and the Glimpse API intended to ease the operations in RPI TV.
This repository requires a temporary database in firebase's firestore and discord's developer portal to create a bot. Make sure to create a .env file in the root directory.
Head over to firebase and click "Go to console" near the top right.
You should be greeted with a "Welcome to Firebase!" page. Underneath that message has a button labeled "Create a project" and click it.
Enter any project name you wish, accept the terms and confirm your usage for Firebase, then click continue.
It's optional for you to choose whether or not to enable google analytics in this step.
If you check it off, just click "Create Project". Otherwise, accept terms and click "Create Project".
Over on the left is a "Build" dropdown. Click on it and then click on the "Firestore Database".
On that page, there should be a button "Create Database". Click on it, and you'll be greeted with a popup. Just click "Next", and "Enable".
After it finishes setting up, you've now setup a firestore database! Now we need to click on the cog wheel at the top left right next to "Project Overview" and click on "Project Settings".
At the top, click on "Service accounts". Scroll a bit down and click "Generate new private key" and then "Generate Key". This will download a json file. Rename it to serviceAccountKey and drop it into the root directory of your cloned repo.
Head over to the Discord Developer Portal and login if necessary. Over at the top right, click "New Application", give it any name, and accept the terms.
Over on the left, click on "Bot", then over on the right, click "Add Bot", it will prompt you, click "Yes, do it!". Then click on "Reset Token" and copy it. Go to the .env file of the root directory and type in "TOKEN=" and paste in the token that you copied.
Scroll down to "Privileged Gateway Intents" and turn on "Presence Intent", "Server Members Intent", and "Message Content Intent". Make sure to save the changes.
Going back to the left, click on OAuth2 and then URL Generator underneath it. Under scopes, check on "bot", scroll a bit down to "Bot permissions", and check on "Administrator".
Now copy the generated URL all the way at the bottom and paste it into the search bar. This will prompt an invite for the bot to a server where you can invite the bot. Make sure to invite it to a server where you can test it. Once you've invited it, you've finished this step!
- Ensure your Discord Account is in developer mode. This enables you to check Ids of pretty much everything. To do so open up Discord (desktop) and click on the cog wheel at the bottom left. Under "App Settings", there is a button named "Advanced". Click on it, and then at the top, turn on Developer Mode.
- Set up the other env variables. Currently, there are two other environment variables to set up. GUILD_ID and CLIENT_ID. The GUILD_ID will be the server where your tests will run. To get the ID, simply hover over the server icon on the left of your Discord application, right click on it, and click "Copy ID". Paste it into the .env file formatted like such: "GUILD_ID=" and then the id. The CLIENT_ID is the ID of the bot. To get the ID, go the server members on the right and right click on your bot. Click "Copy ID" and paste it into the .env file formatted like such: "CLIENT_ID=" and then the id.
- Remember to run
npm i
to install the necessary packages to run the bot. - By default, no commands on the bot will be registered to the server even after running
node index
. In order to add the commands currently on the bot to your server, runnode deploy-commands
. - To turn on the bot run
node index
.
This project is using discord.js version 14, dotenv, moment.js, and firebase-admin. Therefore, knowledge on javascript/typescript is important to add features to the bot.
firebase-admin is important to be able to interact with the firestore database (TEMPORARY).
discord.js is important to simplify the interaction with the Discord API.
Due to the sheer size of discord.js, I'd recommend reading the discord.js documentation and/or discord.js guide.
dotenv is important to use environment variables
We are able to access our environment variables with process.env.{name of variable}.
moment.js is nice to have to format time
View its documentation here
firebase-admin is important to be able to interact with the firestore database.
View the FIRESTORE documentation here
A majority code written is nested in folders and seperated into multiple files for organization.
Take a look at the index file in the commands directory. If we were to add a command, we'd also have to make sure it is "required" and sorted alphabetically in our index file in the commands directory.
We add handlers to ensure that the files are read:
const { Client, Collection, GatewayIntentBits } = require('discord.js');
const commandFiles = require('./commands/index');
const customIdFiles = require('./customIds/index');
const eventFiles = require('./events/index');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMembers,
]
});
client.commands = new Collection();
client.customIds = new Collection();
// Command handler
for (const command of commandFiles) {
client.commands.set(command.data.name, command);
}
// Event handler
for (const event of eventFiles) {
if (event.once) {
client.once(event.name, (...args) => event.execute(...args));
} else {
client.on(event.name, (...args) => event.execute(...args));
}
}
// CustomId handler
for (const customId of customIdFiles) {
client.customIds.set(customId.name, customId);
}
By far the easiest code to write is for events. As of writing this documentation, there is no need for any additional event listeners from discord.js. Therefore, you can just leave it untouched.
Commands must undergo the basic structure of:
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder
.setName(*NAME OF FILE*),
async execute(interaction) {
/*
Insert code here
*/
}
}
You can read more about the SlashCommandBuilder here. Unfortunately the documentation does not contain any information about it. If you're interested in what interaction does, simply console log it. This will be an object with tons of data and will contain functions that you can use to do something with data. However, it's impossible to cover all the methods so the best bet is to just go on stackoverflow.
When a user makes an interaction, very likely that interaction has a custom id. The only exception I know at the moment is ChatInputCommands/slash commands. An example of an interaction is clicking on a button or selecting something from the select menu.
To distinguish the button or the selection from other buttons or selections in our bot is to give it customIds because both of these events emit the Event.InteractionCreate and it'd be really bad if we have button whose function is to make the bot say yes and a selection in the menu whose function makes the bot say no both say yes. It should be noted that there is a way to check if one of these interactions is a button click or a menu select, so let's replace the selection with another button and this is why custom ids are important.
The format for writing a custom id is as follows:
module.exports = {
name: *NAME OF THE CUSTOM ID*,
async execute(interaction) {
/*
Insert code here
*/
}
}
Note that if you don't know what interaction contains, you can always console log it before resorting to stack overflow.
First off, if you don't know what discord js collectors are, you can completely ignore this part of the documentation.
Early on in the discord bot, I've considered using collectors as the primary way to listen for interactions to execute code. This avoids creating a directory for custom ids, less code, and we can even filter the input!
However, as I continued to play around with it, and this is from what I saw, you cannot have more than one unique collector per channel, meaning that if you ran the same command twice, the bot crashes. If you prevented the crash, you'd have two collectors which run into the problem of un-unique ids.
There was also the thought of what if the bot needs a restart and there's a production live? The thing about collectors compared to regular event listeners for discord is that they don't persist after restarts. When restarting the bot, it runs the index file which runs the interactionCreate listener which listens to when events are called to execute code from the specific files. This means that once the bot has fully restarted, it can resume its duties.
Collectors perish after restarts and this is certainly fine for commands but not for buttons that need to stay active for long periods of time and is why there will be no collectors for this bot. Maybe in the future, but we have yet gotten to that point in time.