3 minute read

A collection of notes from my experience developing and deploying my first ever Discord bot: the UvUManager.


UvUManager is a Discord bot used for Longhorn Riichi’s special intercollegiate tournament “UTA vs UTD”. The main technical challenges are:

  1. Discord API (discord.py is relatively well documented but still have some ambiguities)
  2. Mahjong Soul API (there is no official API; I ended up using and contributing to mjsoul.py)
  3. Google Sheets (the Official Google Sheets API was a mess; gspread was MUCH more user-friendly)

The UvUManager repo has information on how to obtain and set up the secrets for the 3 modules above.


  1. Reloading an extension doesn’t re-import the Python files (i.e., to load the changed imported file, one must restart the bot altogether)
  2. Restarting the application in the same process doesn’t reload the dotenv variables (this is likely due to the fact that dotenv doesn’t override existing environment variables – the environment variable values remain the same as the previous process).
  3. ephemeral behaves different for different Interactions. For Component interactions, the followup message can be ephemeral regardless of how ephemeral was set in defer(), but for slash command interactions, the followup message’s ephemeral is governed by the ephemeral in defer(). More testing may be necessary.
  4. need to call View.stop() before deleting messages that have a View, especially if the View has a timeout (View.stop is a clean-up that cancels timeout tasks, etc.). This requirement isn’t listed on the discord.py doc as of writing.

Mahjong Soul API

Reverse-engineering API

  1. The .proto file (like the one here) contains all the readable and searchable Protobuf API. Developers typically need to search here for their desired interaction message with the Mahjong Soul server (and examine the format of the Request and Response objects).
  2. you can learn some protobuf field’s possible values with WebSocket Inspector (e.g., the fact that Twitter OAuth2 protobuf request type field should be 10). You can decode Protobuf with this online decoder, but it’s probably better to decode with protoc given sensitive info and that you have the .proto file.

API discoveries

  1. open_live field of createContestGame doesn’t actually do anything. In fact, the same toggle on the management website doesn’t work either, as a result.
  2. known but undocumented error: ERROR CODE 1209, which happens when trying to pause an already paused tournament game
  3. mjsoul.py raises Exceptions if the response contains an Error object. I modified the relevant module locally (like this version) so GeneralMajsoulError has a field errorCode, to help with catching and acting on specific errors (I’ll merge this change to the mjsoul.py repo soon) .
  4. logging out through the tournament manager website (or closing the browser tab after logging in) also logs out the bot. Now the bot has to make a new WSS connection before retrying login – trying to login through the old WSS connection results in a 2504 : "ERR_CONTEST_MGR_HAS_LOGINED. Okay this is further confirmed by the switch-contest behavior on browser – it also switched the contest for all WSS connections. It’s like there is only one state for each account no matter how many connections there are. In other words, each account can manage at most one contest at a time.



  1. To run the bot without having it die on SSH disconnection, create a screen session. (could also use nohup as a more lightweight solution).
  2. while in a screen, use ^A^D to detach from screen and let it run in the background.
  3. screen -ls to list all screens, screen -r to resume a session, and killall screen to terminate all screens.
  4. GCE VM-specific bug: uploading files through SSH-in-browser while in a screen session crashes the SSH connection…

Develop with local SSH

I.e., not using the SSH-in-browser on Google Cloud.

Set up SSH with gcloud

gcloud auth login
gcloud compute config-ssh

it seems the default account for the GCE VM console SSH can be different from your local account: doc.

SSH with VSCode for GUI Editor

Guide here.

WARNING: VSCode can crash your server! Relevant reports: here and here.

The crash causes the SSH connection to die and prevent further SSH connections into the VM of any form (e.g., Google’s SSH-in-browser). This can be resolved by restarting the VM (STOP and then START).

(can we figure out a better way to edit on VM with a GUI?)