Background
TLE is an awesome discord bot for competitive programming. We at PClub, however, use Slack for our work. But I wanted to somehow use this bot on our Slack workspace. Searching for “Slack bot for competitve programming” didn’t give me any useful results. So I decided to create a port of this bot for Slack.
Getting off on the wrong foot
You must be aware of this amazing repository called build-your-own-x. From here, I got a link to this tutorial about building a Slack bot in python. This was good, since the original TLE bot was also in python, and this would make porting easy.
I happily followed the steps, only to get a Failed RTM connect
error. Some digging around revealed that the tutorial I followed was deprecated, and I needed to use the Slack Events API instead of RTM. Following the steps on the repo, I finally created a Slack bot that could meow back to you.
Trying to copy the entire code at once :p
Now I just needed to connect the event handlers for appropriate commands to the already existing functions in the Discord bot’s code. This was supposed to be easy. However, TLE bot’s code uses the discord
library at a lot of places. I found some hacks around it (like creating a custom error class instead of using discord’s error class) and removed the files that I didn’t need.
But then there was another bummer. The code had lots (I mean LOTS) of circular dependencies. I don’t understand how the bot even works with those! Here’s an example: tle.util.codeforces_common imports tle.util.cache_system2, which in turn imports tle.util.codeforces_common.
I tried to resolve a few of these by shifting functions across files, but there were just too many! So then I decided to use the files that could be used standalone, and copy the rest of the code one command at a time.
Sync vs Async
And now comes the main problem. The original TLE bot’s code handles things asynchronously. I had never written async code in Python. Initially, I thought it would be similar to Javascript. But nope. It was more complex than I thought.
After reading a few blogs and some StackOverflow answers, I somehow got it working.
Step by Step
First I implemented the fetch
command - it would fetch the details of a Codeforces account and post them to the channel in the raw format. This worked pretty nicely.
Then I implemented the rating
command - it would draw the Codeforces rating graph of one or more users. I thought this worked pretty well too, but …
Did you see what happened there? It stopped responding. Instead, it raised an exception - RuntimeError: This event loop is already running
. This was because of my poorly written async code.
Why is python asyncio so hard? 😭😭
And then it begain - I started trying everything I could.
- I tried sharing the same event loop for every event. Got some exception.
- I tried creating a new loop for every event. Got some exception.
- I tried using
asyncio.Queue
, butqueue.get()
would keep waiting forever (even when the queue has items in it!). - I tried creating Tasks in the main event loop - the tasks would never get completed.
- I tried creating two loops: one for flask, and one for handling commands. Still the commands would go unanswered.
These are the things I remember. I’m pretty sure I tried many more.
Finally I gave up. I need to properly learn how to write async code in python, with a smaller project perhaps - one in which I write all the code.
Regarding the bot
I’ve decided to shift to Go. I’ll probably try to use the discord bot’s code as a microservice. This is still just an idea, I haven’t started out on this. But I believe this should be easier than trying to fix python’s asyncio errors :p