🎯Project Goals
In my undergraduate, I participated in my university’s debate society. The two main formats of debate in Canada are British Parliamentary and Canadian Parliamentary. British Parliamentary consist of 8 speakers delivering 7-minute speeches with 1 minute of “protected” time at the beginning and end of their speech where the opposition speakers are not allowed to offer a question. At the high school level speeches are 5 minutes long with 30 seconds of protected time. The second format is Canadian Parliamentary which consists of 4 speakers but speakers now have the choice to deliver a 7-minute speech, a 6-minute speech, a 10-minute speech, a 3-minute speech, or a 4-minute speech depending on their speaker position in the debate with varying “protected” time.
This can get confusing, but conventionally the adjudicators of each debate round would keep track of time and deliver “time signals” where an adjudicator would bang the table to indicate that the speech has entered/exited protected time.
This system works for the most part when debates occur in person, but over the COVID-19 pandemic debates were forced to be held online; mainly on Zoom or Discord.
A clear problem of debating online was: “How does an adjudicator efficiently indicate time signals now?” Typing in chat wouldn’t be so easy: debaters would have to keep their eye on the chat while delivering a speech, and some adjudicators felt that typing “protected time” would break their note-taking flow. Interrupting a speech by unmuting your microphone also has its challenge: if the speaker wasn’t paying attention they may mistake an interruption as a signal that the opponent was offering a question.
In March 2020, a tournament called Lord Discord Cup was held. A member of the community named Keeghan Lucas made a game-changing Discord bot named the TDBot
(referencing the Tournament Directory role) that would automate time signals for debaters.
I then realized a massive opportunity: why should this be restricted to a single tournament? Expanding this technology could truly help organize tournaments by managing participants’ roles, creating timers that could pause, resume, and stop, and adding functionality to accommodate a variety of speech lengths and time signals would help universities all over the country, and eventually the globe. So that’s what I did. Except, I wanted to distinguish my product, so I named it NotTDBot
for clarity.
⚙️How it works
When NotTDBot
is added to the Discord server it subscribes to each Discord text channel and listens for entries that start with the +
symbol. If the +
symbol is detected, NotTDBot
offers a couple of commands:
Timing
NotTDBot
’s bread and butter is to time a speech.
+start
creates a 7-minute speech timer+pause
pauses the current timer+resume
resumes the current timer+end
ends the current timer
+start
comes with a few presets:
+start {10}
creates a 10-minute speech timer+start {8}
creates an 8-minute speech timer+start {6}
creates a 6-minute speech timer+start {7}
creates a 7-minute speech timer+start {4}
creates a 4-minute speech timer+start {3}
creates a 3-minute speech timer
Why +resume
and not +start
again? Initially, I foresaw an issue where there would be some debate rounds where no one understood how to use the NotTDBot
. I envisioned that an adjudicator or tournament organizer would be able to start a timer for another room, thereby becoming the owner of multiple concurrent timers.
I developed an indexing system for managing timers that would follow the following use case:
- Bob starts a timer with
+start
- Bob needs to start a concurrent timer, and uses
+start
again - Bob needs to pause his first timer and uses
+pause {0}
to indicate which timer to pause - Bob needs to restart his first timer and uses
+resume {0}
- Bob needs to end his second timer early, and uses
+end {1}
- Bob wants to see which timers are still active, so he uses
+timers
and sees his first timer running
This indexing system become a legacy feature because the main use case became users owning at most one timer.
You can also create custom timers:
In the above example, the total length of the timer is 7 minutes, the first signal that “protected” time has ended will be at 0.5 minutes (30 seconds) and the last signal that “protected” time has started again will be at 6.5 minutes (6 min & 30 sec).
Polls
Debaters love democracy, so it became common to vote on things.
You could use the +poll
command to make a poll:
The most common use case was voting on how “you wanted to participate in a practice” so I made a preset:
Which would generate the following poll:
Developer Tools
I also made some tools for myself:
+dev terminate
a kill switch to shut off the bot+dev servers
return a list of all servers the bot is on+dev wake
returns a message if the bot is active+dev commands
returns the list of commands available+dev announce {some announcement}
send a direct message to all administrators of the servers the bot is added to+dev admins
return a list of all administrators of the servers the bot is added to
🔍Taking a look under the hood
There were two main languages dominating the online documentation for creating Discord bots: JavaScript, and Python.
You may or may not know, I’m a JavaScript defender so the choice was obvious. (Actually, there was more support for JavaScript development at the time)
Initiating the bot
This process may have changed as the Discord API
has evolved, but when I was originally developing this program it was necessary to instantiate a Discord.Client
with a Partial
for the bot to handle events correctly for uncached messages (that is, messages that were made before the bot was turned on). (source)
Instantiating the bot looked something like:
The further setup would be to bind a commands
key of the bot
to a Discord.Collection
, which is essentially a utility class for the JavaScript Map
structure with additional functions. (source)
Processing Commands
As I mentioned in how it works, the bot basically monitors channel activity for messages that begin with the +
character. Initially, the prefix was !
but there were many jukebox bots and admin bots that used !
so I changed it to the uncommon +
to avoid conflicts. These days Discord requires commands to start with /
, but I don’t intend to update NotTDBot
to handle this change.
The entry point JavaScript file bot.js
had the following logic:
- load bot commands from modules
- monitor for chat messages
- check if incoming message has a
+
prefix and matches a loaded command- if yes, execute command module
- check if incoming message has a
- login bot with a provided Discord application token
Load Commands
The project directory is organized similarly to:
This file structure is convenient for the loading commands step. If each module in /.../commands
exports a single class that has an execute
function, then we can load the classes with the node:fs
library similar to:
Command Class
There is a single Command
class template that other commands extended from which enables the command loading seen above:
As you can see, details about the specific command can be passed from the options
parameter, where the bot itself is passed from the bot
parameter so that we can access the bot
Managers like guilds
(servers), users
, and messages
for servers the bot is on.
New commands can extend Command
:
Timer
JavaScript has a built-in setTimeout
method, but unfortunately, it’s not so easy to pause a timeout. You can clearTimeout
in JavaScript but you have to do some type of wrapper for keeping track of the elapsed and remaining time:
Most of these class functions are self-explanatory, but I want to touch on a big design choice:
“What is even going on with the tick function??”
The tick function follows the following logic:
- execute
tick
whenstart
is executed - check if the timer is still running
- check if any protected time signals need to be sent
- check if the speech is over
- increase the
count
(the elapsed seconds) - start a
setTimeout
that executes a recursivetick
call after 1 second
All timer signals are sent to the user through the Discord reply function of the Message class:
The indexing system worked by holding an object of user id
keys that would be bound to an array of their current timers:
Poll
The logic when receiving a +poll
command goes as follows:
- check whether there are more than 1 arguments
- if yes, check if the command is calling for a preset poll template (i.e.,
+poll DEBATE
) - check whether the second argument is correctly wrapped in a curly bracket - if yes, use the contents of the bracket as the
question
- if there is a valid question, parse the command arguments for answers options wrapped in square brackets
- if yes, check if the command is calling for a preset poll template (i.e.,
NotTDBot
utilized an embedded message to send a poll:
Where letters
is an array of alphabet chars ['A', 'B', ..., 'Z']
A user is expected to “react” to the poll question with an emoji to indicate their answer. To make it easier for users, I implemented some logic to have NotTDBot
react to its own message with the available options:
Monitor Messages
Discord.Client
has a message listener which can be used to check for the prefix I talked about in processing commands. We can then split the command from the arguments and do a simple if ... else
statement to execute the correct command:
Deployment
The deployment was simple:
- make an application on https://discord.com/developers/applications
- retrieve your application token
-
use
Discord.Client
login -
retrieve your client id
-
generate an invitation link for your bot using https://discordapi.com/permissions.html and your client id
-
deploy the bot on a server. I used Linode and Docker to containerize the bot with a simple Dockerfile:
Start using NotTDBot and my takeaways
Well, you can’t, really. Discord has a policy that bots cannot be added to more than 100 servers unless the bot is verified.
When NotTDBot
approached 100 servers I went to do the verification process, when I was notified my bot was flagged for inorganic growth:
At the beginning of online debate, there weren’t too many tech-savvy debate members, so only a few members in the community would be responsible for creating servers for their club and debate events. I.e., my club would ask me to create the club’s Discord server and also create the club’s debate event Discord server.
So, because there were too many common moderators in multiple servers, my bot was flagged as inorganic growth.
When I tried to appeal this or find some workaround, Discord support suggested I remove my bot from a significant amount of servers until the warning went away:
While I was dealing with this roadblock, NotTDBot
couldn’t be added to any new servers. It soon became more convenient to use an alternative technology that was rapidly developing like Tabtastic or Hear! Hear!, and soon NotTDBot
became obsolete.
While NotTDBot
didn’t achieve world dominance, I did learn a great deal about product development, and a surprising amount of business (supply and demand babyy!!)