EXP Discord Bots

EXP is a video game clan which has a community discord server with over 11,000 current members with a suite of custom discord bots.

Across the bots, there are lots of features that the members can take advantage of:

  • The main feature is the bank: members can donate their in-game gems to the clan, and receive balance within the discord server upon which they can gain interest, and gift gems to other members. The bank holds trillions of gems for thousands of members.
  • Another feature is a robust giveaway system. There are 4 different types of giveaways that run multiple times a day, resulting in 1000s of dollars worth of in-game items given away per month. A new feature allows the users to host giveaways themselves with over 300B gems given away so far.
  • There is a moderation system the admins can use in order to keep the peace within the discord server. This allows the moderation actions to be logged and viewed which makes for a better organized server.
  • For each user feature, there are admin commands to allow the admins to manage everything.

The bots are hosted on a VPS with a custom CI/CD solution which allows for rapid auto deployments and minimal down time.

Stack

typescript

Typescript

bun

Bun

turso

TursoDB

What I learned

This project was the biggest production project I had worked on. As such, I had to take a lot into consideration when designing the architecture and the codebase.

A Production Codebase - Principles

Generally speaking, I practiced lots of production codebase concepts (consistent and detailed variable names, keep it stupid simple, separation of concerns, etc etc), and learned to think ahead with the code, ensuring it's easy to read, test, extract, etc, as it might need to be improved and updated in the future.

For example, I had originally use json files as local storage, but the code to handle that was embedded within one specific feature, which turned out to be a mistake, as I ended up extracting that logic into a custom class/functions. After that, I took extra care to think about whether to write global functions (making them easy to use in the future, but taking extra time to code), or to keep them within features (saving coding time, but increasing it if the functionality needs to be extracted in future features).

To put it simply, it taught me to consider the implications of coding something one way, rather than coding the first thing that comes to mind that works.

The Bank - Database

The original focus of these bots was the bank system. Users can donate gems to the clan, and the bot would keep track of their balance and would issue interest on an hourly basis.

It was important that this system worked well, as big issues end up costing the admins a lot of money, and could leave users unhappy.

One issue I came across was that when users would gift, the recipient might not receive the gift (but the sender was debited), or vice versa. I learned about database atomicty, and used database transactions for any features where balance was transferred.

Giveaways - Performance

The bots have two types of giveaways - clan and user giveaways. Clan giveaways would run a few times a day, and users would get the prizes from the clan admins. User giveaways were run by users, where other users could enter to win gems.

With clan giveaways, there were thousands of users who each had hundreds of entries, resulting in a massive array of user entries that would have to be shuffled, and then a user id would be selected. This original implementation was really slow and only worked fine for a few hundred users, but once there were multiple thousands of users, it could take up to 40 minutes to draw a winner.. I switched to storing the entries with a javascript map, with each user simply being a "entry" in the map, and a random number to select the winner which drastically reduced the time it took to draw.