Making of @SLUBbot, a Telegram bot


The idea

When exam time comes, students at my university are often frustrated about the situation in our library. If you come in late, you most probably do not find a seat available to work at anymore. Or they are very well hidden in the mass of people sitting around!

Given there are people inside who may suffer the same fate from time to time, maybe we can solve this problem with crowdsourcing? What if people inside had the possibility to report how crowded the place is? And even better: could this help to prevent students from coming in, just to leave after half an hour of unsuccessful search?

This is where I thought that a messenger like Telegram might be a very fitting use case, since it would not need any extra app.

Ways to interact via the chat interface

What was needed was simple: when first accessing the interface it should tell you what its goal is. Since the use case is very precise, this was an easy task.

But what about interaction? The app which Telegram offers for mobile phones has multiple solutions for this:

  • You can simply send a command to a bot and it responds,
  • send text and hope the bot understands your input,
  • the bot delivers a set of buttons that are built in the dialog,
  • a service from a bot is called from another chat by using the "@bot ..." call, which automatically signals the bot to provide inline responses.

I chose to use the third case, buttons. Here's a demo from the user perspective. The following video shows a demo of the bot interaction inside the Telegram app. This was done with Telegram web, but looks very similar in mobile clients. Please note, in the end I switch to english too.

You also see how people know what area they are in. It's a simple four-tile map, four area identifiers per level. The images - made from screenshots from the SLUB 3D-navigator - are uploaded by the bot and the received image resource is then cached and delivered each time the context needs the explanation.
Level areas englishLevel areas german

Stateful sessions: saving data, using caches

One big problem that asked to be solved was statefulness.

If you send a message to a bot from Telegram, your message is received, processed and possibly a reply is sent back. But there's no built-in session management.

For this, I chose a Redis cache that was synced with each interaction to update the current state with a particular user. It's a simple JSON message with the user's ID as the key. The following graph shows the state machine a user goes through when interacting with the bot.

States of the user

It's no loop, because the session is reset when it reaches an end state. Same with errors. The exchange is much more resiliant when errors trigger a reset - and compared to the possibility of corrupted data, I chose reset over error corrections.

Software stack: getting in, queueing, pushing out

You've been waiting for it, here it is: the architecture of this super complex system of message receiving, state handling and replying!

Architecture overview

First thing you might notice are the multiple Python workers. I chose the multi worker design, because Python does not have a real threading implementation. Each worker is its own process, as is the listener. It is only one listener, because that instance is connected to the Telegram API and regularly pulls for new messages. New messages are then queued in Redis and saved in the database. The Python code uses the Telepot library to handle Telegram API calls.

Now that a message is in the queue, one of the workers - an idle one - picks it up, removes it from the queue and processes it. As mentioned above, the cache also holds information about the current state of the user, so the worker knows in what context the message was sent. If the user is e.g. in state "How many seats are available?" and a new message with "7" comes in, the worker knows that this is a finished report for 7 of 10 free seats, constructs the report and saves it, and replies with a "Thank you" message to the user.

The results are calculated based on reports of that day and cached until a new report comes in. During development and after receiving feedback, I implemented some small adjustments that helped make the reports more usable:

  • Filter all reports that were sent while the SLUB was closed (how do you know…?)
  • Check, if not only reports from the day are available, but also give additional info if there were multiple reports the last 15, 30 or 60 minutes (more precise than using the whole day)
  • Don't display reports of areas with no free seats
  • Ignore reports for the entry area.

Additionally, the interface also offered a feedback function and was available in both German and English.

When clicking the feedback button, a user writes a simple message and it's delievered into an extra database table (and in fact directly sent to me, like you can see in the demo video above). I could then later on review the feedback and make adjustments!

Evaluation of usage after one year

In the end, I was curious how the bot was used. At the time of writing the bot is disabled for some time now and the data archived. Nevertheless, I exported the old database dump from Postgres, collected numbers and made some graphs. The numbers, cleaned up:

  • 5715 processed messages
  • 227 unique users
  • 291 reports
  • 22 helpful feedbacks

Of course the number of users is laughable considering the runtime of the bot. But what is the most interesting thing to have watched is how the bot spread over time, with absolutely no advertisement except one on the first day, posted in an anonymous campus chat app. And not to forget: Telegram is still not very widely used compared to WhatsApp or Facebook Messenger. And not everybody goes to the SLUB to work. The target group gets smaller and smaller…

In the following graph, the count of new users per day is visible. The purple areas are exam times, where the most people go to the SLUB to work. You can see that the counter spikes each time, solely based on word-of-mouth.
New users per day

Same for incoming reports, sent by users.
static
Reports per day

More graphs could be done by examining the reports, making graphs of what area is most crowded etc. But considering the small amount of reports over time, I fear that this data will not at all be realistic.

Near the end of this experiment, I wrote a script that allowed me to actively message the users from within the bot. All users that did not disable the bot (close the conversation) were still reachable for messages. In fact, I did hesitate if this would put people off if messaged unasked, but the feedback brought a lot of suggestions one could use to expand the service.

Thanks to everyone who used the bot and might read this post! Maybe the idea will one day be implemented into the official SLUB app, alongside with other features that make working in the SLUB more comfortable :)