Creating a Ronda Rousy quote bot for Telegram using Python

Creating a Ronda Rousy quote bot for Telegram using Python

A quick intro to creating Telegram chatbots using python

2022-02-24

The popular chat app Telegram has 550 million monthly active users, making it the 7th most downloaded app on iOS and Android (data from backlinko). From hackers to journalists, every subculture these days seems to be thriving on Telegram’s end-to-end encrypted chat rooms.
You probably already use Telegram, and if you don’t, you’re missing out.

For highly technical power users and tinkerers, the first question when joining Telegram is how we can automate it, push the app to its limits, and make it do things it wasn’t originally created to do.
Luckily for people like us, Telegram designed the app to be supremely hackable by providing a free, sublimely documented API for developing bots.
What can bots do? Well, they can do almost anything you could imagine, and many things you probably wouldn’t have thought of.
So whether you want to show off to your friends, create useful tools to manage rooms, or just make something weird for fun, Telegram bots are what you want.

Ready to try our hand at creating our first Telegram bot together? Let’s go for it!

Creating a bot

Telegram bots are diverse. From fully featured commercial HTML 5 games, to financial services, to integrating with FAANG and enterprise technology, there are bots for every kind of functionality imaginable. But I’ve alwasy been of the opinion that the best bots are the ones made just for fun by hobbyists in their free time. So what fun, silly bot can we dream up?
Well, in my case, I do Judo with my daughter, and we’re big fans of the legendary Judo player Ronda Rousey. She inspired my daughter and I to train Judo in the first place.
So how about we try a simple bot that displays a random quote from Ronda Rousey whenever you interact with it? Should be easy enough, but let’s see!

First things first, the core functionality of grabbing a random Ronda Rousey quote has nothing to do with Telegram bots, so we can write that code right off the bat using only our knowledge of Python.
In order to get the quotes, we’ll use the Wikiquote JSON API, which returns the quotes in a surprisingly ugly mix of JSON and HTML. We’ll write a hairy parser that’s good enough for our purposes:

import urllib.parse
import random
import requests
from bs4 import BeautifulSoup

def ronda_quote():
    # make a GET request to the Wikiquote API
    r = requests.get(f'https://en.wikiquote.org/w/api.php?format=json&action=parse&page=Ronda Rousey')
    # parse the results using BeautifulSoup
    soup = BeautifulSoup(r.json()['parse']['text']['*'], 'html.parser')
    # loop through the results and add valid quotes to this list
    quotes = []
    for li in soup.find_all('li'):
        # we have to eliminate 'quotes' ending with a parenthesis, because those are really attributions, not quotes
        # (this is hacky, but good enough for our purposes here. Feel free to try a more intelligent parsing strategy)
        quotes += [l for l in li.text.split('\n') if not l.endswith(')')]
    # send back a random quote from the list
    return random.choice([q for q in quotes if q])

Far from an elegant work of art, but it gets the job done.
Now we get to the real work: making the bot. Since our bot just responds to everything, we can modify the code skeleton of echobot. This bot just repeats messages back to you. We’ll do the same thing, but instead of responding with the same message, we’ll serve a Ronda Rousey quote. So let’s take a look at the original echobot code we’ll be modifying for our own purposes: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/echobot.py

#!/usr/bin/env python
# pylint: disable=C0116,W0613
# This program is dedicated to the public domain under the CC0 license.

"""
Simple Bot to reply to Telegram messages.

First, a few handler functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.

Usage:
Basic Echobot example, repeats messages.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""

import logging

from telegram import Update, ForceReply
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackContext

# Enable logging
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
)

logger = logging.getLogger(__name__)


# Define a few command handlers. These usually take the two arguments update and
# context.
def start(update: Update, context: CallbackContext) -> None:
    """Send a message when the command /start is issued."""
    user = update.effective_user
    update.message.reply_markdown_v2(
        fr'Hi {user.mention_markdown_v2()}\!',
        reply_markup=ForceReply(selective=True),
    )


def help_command(update: Update, context: CallbackContext) -> None:
    """Send a message when the command /help is issued."""
    update.message.reply_text('Help!')


def echo(update: Update, context: CallbackContext) -> None:
    """Echo the user message."""
    update.message.reply_text(update.message.text)


def main() -> None:
    """Start the bot."""
    # Create the Updater and pass it your bot's token.
    updater = Updater("Your token here")

    # Get the dispatcher to register handlers
    dispatcher = updater.dispatcher

    # on different commands - answer in Telegram
    dispatcher.add_handler(CommandHandler("start", start))
    dispatcher.add_handler(CommandHandler("help", help_command))

    # on non command i.e message - echo the message on Telegram
    dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))

    # Start the Bot
    updater.start_polling()

    # Run the bot until you press Ctrl-C or the process receives SIGINT,
    # SIGTERM or SIGABRT. This should be used most of the time, since
    # start_polling() is non-blocking and will stop the bot gracefully.
    updater.idle()


if __name__ == '__main__':
    main()

So given that code, how do we morph it from its current state as an echobot into the rondabot we want?
I’ll tell you, but first I encourage you to just poke around the code and see if you can figure out where the relevant functionality is.

All done trying it out on your own? Okay, if you weren’t able to figure it out (no shame in that! It’s tricky!), time to give you the answer. So, on line 65, we register the echo() function as a handler. We just need to replace that with our own Ronda function. That’s literally all there is to it! Let’s take a look at how the code will look after replacing the echo() function with the ronda_quote() code we cobbled together earlier. So now line 65 will look like this:

    # on non command i.e message - send a Ronda Rousey quote
    dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, serve_ronda_quote))

Where the function serve_ronda_quote() just calls our Ronda quote method and also receives the message the user sent to us (which we don’t really need anymore since we won’t be echoing it, but we’ll log it just for fun). Plus, it shuts down the bot completely if the user sends a message saying “exit”.

def serve_ronda_quote(update, context):
    # print the user's message
    if update.message.text == 'exit':
        # exit command received...
        exit()
    print(f'Message received from user: {update.message.text}')
    quote = ronda_quote()
    update.message.reply_text(quote)

Not so bad, right! So we’re ready to go, right? Almost… we still need to get an API token to register our bot with Telegram. We do this in the Telegram app by contacting, well, a bot! The bot’s name is The BotFather.

BotFather

Contact this bot and follow the instructions he gives you in order to receive your token. If you get stuck, feel free to read the official Telegram docs that teach you how to interact correctly with the BotFather: https://core.telegram.org/bots#6-botfather. But the BotFather itself is pretty self-explanatory, so I encourage you to simply message the bot directly and do as it tells you. Anyway, once you have the token you can put it into the code where it says Your token here. And voila! Let’s message our bot and see it in action:

Bot in action

Ready for the full code?

#!/usr/bin/env python
# pylint: disable=C0116,W0613
# This program is dedicated to the public domain under the CC0 license.

"""
Simple Bot to reply to Telegram messages.

First, a few handler functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.

Usage:
Basic Echobot example, repeats messages.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""

import logging

from telegram import Update, ForceReply
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackContext

import urllib.parse
import random
import requests
from bs4 import BeautifulSoup

def ronda_quote():
    # make a GET request to the Wikiquote API
    r = requests.get(f'https://en.wikiquote.org/w/api.php?format=json&action=parse&page=Ronda Rousey')
    soup = BeautifulSoup(r.json()['parse']['text']['*'], 'html.parser')
    quotes = []
    for li in soup.find_all('li'):
        quotes += [l for l in li.text.split('\n') if not l.endswith(')')]
    return random.choice([q for q in quotes if q])
# Enable logging
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
)

logger = logging.getLogger(__name__)

def serve_ronda_quote(update, context):
    # print the user's message
    if update.message.text == 'exit':
        # exit command received...
        exit()
    print(f'Message received from user: {update.message.text}')
    quote = ronda_quote()
    update.message.reply_text(quote)


# Define a few command handlers. These usually take the two arguments update and
# context.
def start(update: Update, context: CallbackContext) -> None:
    """Send a message when the command /start is issued."""
    user = update.effective_user
    update.message.reply_markdown_v2(
        fr'Hi {user.mention_markdown_v2()}\!',
        reply_markup=ForceReply(selective=True),
    )


def help_command(update: Update, context: CallbackContext) -> None:
    """Send a message when the command /help is issued."""
    update.message.reply_text('Help!')


def main() -> None:
    """Start the bot."""
    # Create the Updater and pass it your bot's token.
    updater = Updater("TOKEN")

    # Get the dispatcher to register handlers
    dispatcher = updater.dispatcher

    # on different commands - answer in Telegram
    dispatcher.add_handler(CommandHandler("start", start))
    dispatcher.add_handler(CommandHandler("help", help_command))

    # on non command i.e message - send a Ronda Rousey quote
    dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, serve_ronda_quote))

    # Start the Bot
    updater.start_polling()

    # Run the bot until you press Ctrl-C or the process receives SIGINT,
    # SIGTERM or SIGABRT. This should be used most of the time, since
    # start_polling() is non-blocking and will stop the bot gracefully.
    updater.idle()


if __name__ == '__main__':
    main()

Yay! Here’s how the terminal output ends up looking when we actually run the bot:

PS C :\ Users \ Khety \ Documents> python3 .\rondabot.py
Message received from user: give me a quote please :)
Message received from user: Id like more!
Message received from user: One more quote...
Interrupt signal received, shutting down bot.

Be creative and try wierd things! There’s way more that can be done with bots. Here are some interesting bots to give you ideas:

  • Monero Tip Bot (there are bots for every cryptocurrency under the sun, but Monero is a particularly fun one)
  • Three Tap Heroes (simple HTML 5 game)
  • Gmail Bot (manage your email entirely from Telegram - I use this so often)

Unfortunately, the BotFather himself is not open source. That would be some pretty interesting code to read, though! Telegram bots are easy to make and free to register, and you can even use your own backend if you get frustrated with the limits of Telegram’s free bot API.

Deploying a Telegram bot

We can run our bot and that’s great, but realistically, are we going to leave the terminal window open 24/7 so our bot can stay up? To accomplish this, all we need is a server. We put our bot code on the server, in my case via FTP, and run it as a detached background process so it will continue running after we close our session:

$ nohup python3 ronda_bot.py &> log.txt &

Since it’s inconvenient to do this everytime the server reboots, we can run the code on startup using systemd, which you can learn to do with the article How to Run a Linux Program at Startup with systemd.

That’s a pretty barebones intro, to get deeper into Telegram bot deployment, check out How to build and deploy a Telegram bot with Flask - LogRocket Blog.

Conclusion

Writing Telegram bots is really, really fun in a way that reinvigorates one’s desire to go out there and code.
Even compared to the nostalgic, halcyon glory days of IRC bots, Telegram bots are more fun because of the extremely friendly API and great libraries.
In fact, there is even a Telegram bot that serves a bridge to IRC, and the code is easy to read (check it out yourself: https://github.com/RITlug/teleirc).
Thanks to the friendliness of tools like the BotFather, hackers can have a blast creating mind-bending, fun bots with only a minimal understanding of how Telegram itself works.

Botting isn’t just for hackers, there’s real money to be made.
Keeping the code proprietary is easy. In fact, it’s the default behavior, just run the bot and never publish the code. HTML 5 games with options to buy in-games items are plentiful, and there are boundless contracts on sites like Upwork. But whether you’re in it for money or pleasure, Telegram botting is a fun hobby that will help you fall in love with programming all over again