Skip to content

Leveling

Jace Manshadi edited this page Sep 15, 2024 · 1 revision

the CSSS Discord Guild used to have the MEE6 bot and was very much into its Leaderboard component.

However, there was a common desire to get rid of MEE6 and bring that functionality to wall_e.

This required the following logic:

Data Management

there are 2 different kinds of data that wall_e keeps track of for the Leveling Cog.

Level

UserPoint

the details for each user and how many points they have

User Image

When designing the XP leveling system, there was a need for a place to store the user avatar image. This is because I wanted to allow for the website to always have an avatar image, even for a user that has deleted their account. I decided that the best place to store the images that makes the most sense is right on discord itself. This is the reason for the function get_leveling_avatar_channel.

So once the user's avatar image get upload to that channel, I store 2 details about the message

  1. the URL for the image attached to the message
  2. the ID for the message that contains the message: this is recorded so that if a user updates their avatar image, the current message can be deleted and be replaced by the new message with the new image.

Given all that, the data that is needed is stored in 2 places: the database and the cache

Data Initialization

Level Data

Fresh DB

When someone is spinning up wall_e on their own discord guild, the system needs to be able to import the level data into both the cache and database [assuming it's a fresh DB], which is the purpose of this conditional.

Although if you setup your wall_e instance using the run_walle.sh, then that script automatically loads the Levels into the database so most times, the existing DB section is what will be executed.

An Existing DB

if the DB is already up and has the necessary XP levels, then the code has to load the levels into the cache, which is the purpose of this conditional

User Data

Fresh DB

when spinning up wall_e, the run_walle.sh script also loads the user points into the database. And the cache is populated here

An Existing DB

Again, the cache is populated here

Listeners

When message is sent on the guild

on_message listens for each message that is sent on the guild and does the necessary logic for bumping a user's info/points if necessary.

When user re-joins the guild

Since a user discord roles get reset when they leave and then join the guild again, the function assign_roles_and_member_join ensures that [if it is a valid user], it gives them the discord roles that map to their XP levels again

Verification of Discord XP Level Roles

everyday at around 5 am, there is a recurring task that ensures each level with a name exists and is updated with a new name [if need be] and has the right users has the right users: https://github.com/CSSS/wall_e/blob/af6cb0365c98ab31ff4dfbee3b9f0a5fb6d4525f/wall_e/extensions/leveling.py#L223-L275

Data Refreshers

Any discord guild has 2 kinds of users: lurkers and actives.

Actives

I wanted to be able to never miss any recent updates for a user [name, nickname, avatar image, etc]. Even if I make a push to wall_e and therefore the bot goes down for about 3-4 minutes. Hence wall_e_member_update_listener was born which is always connected and records each user that has updated their info as needing to have their info in the database updated.

Once wall_e_member_update_listener marks a user as needing an update, process_leveling_profile_data_for_active_users, will get the list of users that need to be update and updates them.

Lurkers

On the rare off-chance that either the update for an active was missed or a lurker had their info updated, I also implement a function that runs in the background [process_leveling_profile_data_for_lurkers]. Since it had to make sure all users get their profile checked on a consistent basis outside of the active schedule, I decided that I would need to determine how many users to update in an hour since it'd be a terrible idea to update all 7000+ users in one go.

Bucket Numbers

So instead of updating all 7000+ users in one go, I had to find a way to divide them into smaller update groups. and since the CDN links [the link to the user avatar images that are stored on discord] expire every 2 weeks, I had to make sure that all users have their avatar image link updated every-time the current CDN link expires. and since I wanted to do the update once an hour, that means doing an update once every 14 * 24 == 336 hours. which means 336 buckets.

So if a new user appear in the DB without a bucket_number, this function determine which bucket number currently has the lowest number of users and assigns that bucket number to the user.

With that bucket_number set, process_leveling_profile_data_for_lurkers get the latest bucket_number via ProfileBucketInProgress, uses that to get the next batch of users to update and iterate through them to see if any of them need an update.

Commands

set_level_name

set the name for the role associated with the specified XP level or create a role with the specified name for the specified XP level

remove_level_name

Removes the role associated with the specified XP level

rank

Get the calling user's rank or another user's rank, if that user has not hidden their rank.

levels

Lists the XP levels and their associated discord role [if any] for the Moderators and Minions of the guild

ranks

Show the leaderboard via a paginated embed message

hide_xp

Allows a user to hide their level from both the .ranks as well as when .rank is called on them by another user

show_xp

Allows a user to make their level publicly available for both the .ranks as well as when .rank is called called on them by another user.

Clone this wiki locally