Skip to main content
  1. My Blog Posts and Stories/

A Live Countdown Telegram Bot

·1109 words·6 mins

This blog post is based on the Telegram Live Timer Bot project.

In this blog post, I will be explaining how to make your own Telegram bot that can do live countdowns. Before we dive into the core of this project, let’s go through the prerequisites.

Prerequisites #

Before getting started on this project, you will need the following.

  1. Your API ID
  2. Your API Hash
  3. Your Bot token

Getting your API ID and API Hash #

To get your API ID and API Hash, visit the Telegram website.

Next, key in your Telegram Phone Number (Including country code). You will be given a code to verify your phone number. Once you have verified your phone number you will be brought to the main page as shown in the image below.

Main API Page
Main API Page

Click on API Development tools and fill it up with the relevant information.

Once you have created the app, you will be given an API ID and API Hash. Copy the API ID and API Hash and save it somewhere safe.

Getting your Bot Token #

To get the bot token, visit the BotFather and follow the instructions to create a bot. Once you have created the bot, you will be given a token. Copy the token and save it somewhere safe.

The Inspiration #

The main inspiration for this project was when a friend approached me to ask for an application that can help him track his deadlines. He wanted it to be live so that everyone in his group chat can see it slowly tick down to zero.

Initially I went online to find a tool for him. However, after searching around, I realize that there was no tool currently that is easily modifiable for someone who does not know programming. Hence, I decided to make my own.

Goals of the project #

The main goals of the project are.

  1. To create a Telegram bot that can do live countdowns.
  2. To make it easily modifiable even for those who are new to programming.

With that, let’s dive into the project.

The Project #

Making it easily modifiable #

To make it easily modifiable, I decided to make all the user config available in the .env file and constants.py file.

The .env file is to ensure that sensitive information are not accidentally committed into github if this project is forked.

The constants.py file is for less sensitive information like the polling interval, the format of the message, etc.

When I require those information in my code, I can simple retrieve it from constants.py or retrieve it from the environment variables.

The Project itself #

In this project, I decided to use pyrogram due to its ease of use and some familiarity with it. However, you can use any other telegram bot library that you are comfortable with.

Architecture
Architecture Diagram

The diagram above shows the architecture of the application.

The main telegram code depends on the Storage class and the MsgPack class. I will be going through the dependencies for the code below before going through the main code itself.

Storage Class #

The Storage class is responsible for storing the data related to the messages received by the bot. The data stored are the chat id, the deadline, and the event name itself.

Currently, in the repository, this exists as a dictionary. However, if you want to make it persistent, you can use a database like MongoDB or SQLite.

The code snippet is shown below.

class Storage(object):
    def __init__(self):
        """Stores the information for each user"""
        # Load the shelve db if possible
        self.storage = {}

    def add_event(self, chat_id: int, event_name: str, event_time: str) -> Optional[datetime.datetime]:
        """
        Throws ValueError when the format is incorrect
        """
        if chat_id not in self.storage:
            self.storage[chat_id] = {}
        deadline = datetime.datetime.strptime(
            event_time, '%d/%m/%Y %H:%M')
        self.storage[chat_id][event_name] = deadline
        return deadline

    def get_events(self, chat_id: int, event_name) -> Optional[datetime.datetime]:
        return self.storage.get(chat_id, {}).get(event_name, None)

    def delete_event(self, chat_id: int, event_name: str) -> bool:
        try:
            del self.storage[chat_id][event_name]
            return True
        except:
            return False

MsgPack Class #

The MsgPack class is a simple class which stores the format for the sent message. This is to ensure that the message sent is consistent across all the messages sent.

As Pyrogram supports markup, we can make use of it to beautify our markup content.

The code snippet is shown below.

from pyrogram.types import InlineKeyboardMarkup

class MsgPack(object):
    def __init__(self, text: str, markup: InlineKeyboardMarkup):
        self.msg = text
        self.markup = markup

    def get_msg(self):
        return self.msg

    def get_markup(self):
        return self.markup

The Bot Code #

The main bot code consists of several key functions.

  1. The timer function to create the deadline request
  2. The refresh function to refresh the message for the countdown

The timer function #

...
@app.on_message(filters.command('timer'))
async def start_timer(_, message):
    """The main method for the timer message"""
    try:
        # [command, date, time, event_name]
        _, date, time, event_name = message.text.split(' ', 3)
        deadline = storage.add_event(
            message.chat.id, event_name, f"{date} {time}")

        time_left: datetime.timedelta = deadline - datetime.datetime.now()
        if time_left < ZERO_TIME_DELTA:
            await message.reply(
                text=EVENT_ENDED_FORMAT.format(event_name=event_name),
            )
            return

        event_string = get_event_string(time_left, event_name)
        msg = await app.send_message(message.chat.id, event_string)

        await refresh_msg(msg, deadline, event_name)

    except (ValueError, TypeError):
        await message.reply(
            text=ERROR_CMD_MSG
        )
        return
...

The start_timer command that is used to parse the timer bot. The command is /timer <date> <time> <event_name>. The date and time should be in the format DD/MM/YYYY HH:MM.

We store the relevant information in the command within the storage class and calculate the time left to the deadline. If the deadline has already passed, we send a message to the user to inform them that the event has already ended.

Note: The refresh_msg command is called here to refresh the message and update the timestamp to count down to the deadline.

The refresh_msg function #

...
async def refresh_msg(msg, deadline: datetime.datetime, event_name: str):
    """Updates the event message until it is pass the deadline"""
    while True:
        sleep(POLLING_INTERVAL)
        time_left = deadline - datetime.datetime.now()
        if storage.get_events(msg.chat.id, event_name) is None:
            format = EVENT_CANCELLED_FORMAT
            break
        if time_left.total_seconds() < 0:
            format = EVENT_ENDED_FORMAT
            break
        event_string = get_event_string(time_left, event_name)
        await msg.edit(event_string)
    await msg.edit(format.format(event_name=event_name))
...

It essentially loops infinitely until the deadline is reached or the event is cancelled. It sleeps for the polling interval before updating the message to prevent overloading the system.

To see the full source code, you can check out the repository.

The bot will look something like this

Bot Response
Bot Response

Conclusion #

There are many optimizations that can be done for this project. However, as it is only use for a small number of groups for a personal project, I kept it simple.

I hope the article has been helpful in understanding how to create a similar telegram bot. Feel free to clone the repository and make changes to it to suit your needs.

  1. Telegram Bot API
  2. BotFather
  3. Telegram Apps
  4. Source Code