HTTP Bot API vs MTProto

Telethon is more than just another viable alternative when developing bots for Telegram. If you haven’t decided which wrapper library for bots to use yet, using Telethon from the beginning may save you some headaches later.

What is Bot API?

The Telegram Bot API, also known as HTTP Bot API and from now on referred to as simply “Bot API” is Telegram’s official way for developers to control their own Telegram bots. Quoting their main page:

The Bot API is an HTTP-based interface created for developers keen on building bots for Telegram.

To learn how to create and set up a bot, please consult our Introduction to Bots and Bot FAQ.

Bot API is simply an HTTP endpoint which translates your requests to it into MTProto calls through tdlib, their bot backend.

Configuration of your bot, such as its available commands and auto-completion, is configured through @BotFather.

What is MTProto?

MTProto is Telegram’s own protocol to communicate with their API when you connect to their servers.

Telethon is an alternative MTProto-based backend written entirely in Python and much easier to setup and use.

Both official applications and third-party clients (like your own applications) logged in as either user or bots can use MTProto to communicate directly with Telegram’s API (which is not the HTTP bot API).

When we talk about MTProto, we often mean “MTProto-based clients”.

Advantages of MTProto over Bot API

MTProto clients (like Telethon) connect directly to Telegram’s servers, which means there is no HTTP connection, no “polling” or “web hooks”. This means less overhead, since the protocol used between you and the server is much more compact than HTTP requests with responses in wasteful JSON.

Since there is a direct connection to Telegram’s servers, even if their Bot API endpoint is down, you can still have connection to Telegram directly.

Using a MTProto client, you are also not limited to the public API that they expose, and instead, you have full control of what your bot can do. Telethon offers you all the power with often much easier usage than any of the available Python Bot API wrappers.

If your application ever needs user features because bots cannot do certain things, you will be able to easily login as a user and even keep your bot without having to learn a new library.

If less overhead and full control didn’t convince you to use Telethon yet, check out the wiki page MTProto vs HTTP Bot API with a more exhaustive and up-to-date list of differences.

Migrating from Bot API to Telethon

It doesn’t matter if you wrote your bot with requests and you were making API requests manually, or if you used a wrapper library like python-telegram-bot or pyTelegramBotAPI. It’s never too late to migrate to Telethon!

If you were using an asynchronous library like aiohttp or a wrapper like aiogram or dumbot, it will be even easier, because Telethon is also an asynchronous library.

Next, we will see some examples from the most popular libraries.

Migrating from python-telegram-bot

Let’s take their echobot.py example and shorten it a bit:

from telegram.ext import Updater, CommandHandler, MessageHandler, Filters

def start(update, context):
    """Send a message when the command /start is issued."""
    update.message.reply_text('Hi!')

def echo(update, context):
    """Echo the user message."""
    update.message.reply_text(update.message.text)

def main():
    """Start the bot."""
    updater = Updater("TOKEN")
    dp = updater.dispatcher
    dp.add_handler(CommandHandler("start", start))
    dp.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))

    updater.start_polling()

    updater.idle()

if __name__ == '__main__':
    main()

After using Telethon:

from telethon import TelegramClient, events

bot = TelegramClient('bot', 11111, 'a1b2c3d4').start(bot_token='TOKEN')

@bot.on(events.NewMessage(pattern='/start'))
async def start(event):
    """Send a message when the command /start is issued."""
    await event.respond('Hi!')
    raise events.StopPropagation

@bot.on(events.NewMessage)
async def echo(event):
    """Echo the user message."""
    await event.respond(event.text)

def main():
    """Start the bot."""
    bot.run_until_disconnected()

if __name__ == '__main__':
    main()

Key differences:

  • The recommended way to do it imports fewer things.
  • All handlers trigger by default, so we need events.StopPropagation.
  • Adding handlers, responding and running is a lot less verbose.
  • Telethon needs async def and await.
  • The bot isn’t hidden away by Updater or Dispatcher.

Migrating from pyTelegramBotAPI

Let’s show another echobot from their README:

import telebot

bot = telebot.TeleBot("TOKEN")

@bot.message_handler(commands=['start'])
def send_welcome(message):
    bot.reply_to(message, "Howdy, how are you doing?")

@bot.message_handler(func=lambda m: True)
def echo_all(message):
    bot.reply_to(message, message.text)

bot.polling()

Now we rewrite it to use Telethon:

from telethon import TelegramClient, events

bot = TelegramClient('bot', 11111, 'a1b2c3d4').start(bot_token='TOKEN')

@bot.on(events.NewMessage(pattern='/start'))
async def send_welcome(event):
    await event.reply('Howdy, how are you doing?')

@bot.on(events.NewMessage)
async def echo_all(event):
    await event.reply(event.text)

bot.run_until_disconnected()

Key differences:

  • Instead of doing bot.reply_to(message), we can do event.reply. Note that the event behaves just like their message.
  • Telethon also supports func=lambda m: True, but it’s not necessary.

Migrating from aiogram

From their GitHub:

from aiogram import Bot, Dispatcher, executor, types

API_TOKEN = 'BOT TOKEN HERE'

# Initialize bot and dispatcher
bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)

@dp.message_handler(commands=['start'])
async def send_welcome(message: types.Message):
    """
    This handler will be called when client send `/start` command.
    """
    await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")

@dp.message_handler(regexp='(^cat[s]?$|puss)')
async def cats(message: types.Message):
    with open('data/cats.jpg', 'rb') as photo:
        await bot.send_photo(message.chat.id, photo, caption='Cats is here 😺',
                             reply_to_message_id=message.message_id)

@dp.message_handler()
async def echo(message: types.Message):
    await bot.send_message(message.chat.id, message.text)

if __name__ == '__main__':
    executor.start_polling(dp, skip_updates=True)

After rewrite:

from telethon import TelegramClient, events

# Initialize bot and... just the bot!
bot = TelegramClient('bot', 11111, 'a1b2c3d4').start(bot_token='TOKEN')

@bot.on(events.NewMessage(pattern='/start'))
async def send_welcome(event):
    await event.reply('Howdy, how are you doing?')

@bot.on(events.NewMessage(pattern='(^cat[s]?$|puss)'))
async def cats(event):
    await event.reply('Cats is here 😺', file='data/cats.jpg')

@bot.on(events.NewMessage)
async def echo_all(event):
    await event.reply(event.text)

if __name__ == '__main__':
    bot.run_until_disconnected()

Key differences:

  • Telethon offers convenience methods to avoid retyping bot.send_photo(message.chat.id, ...) all the time, and instead let you type event.reply.
  • Sending files is a lot easier. The methods for sending photos, documents, audios, etc. are all the same!

Migrating from dumbot

Showcasing their subclassing example:

from dumbot import Bot

class Subbot(Bot):
    async def init(self):
        self.me = await self.getMe()

    async def on_update(self, update):
        await self.sendMessage(
            chat_id=update.message.chat.id,
            text='i am {}'.format(self.me.username)
        )

Subbot(token).run()

After rewriting:

from telethon import TelegramClient, events

class Subbot(TelegramClient):
    def __init__(self, *a, **kw):
        super().__init__(*a, **kw)
        self.add_event_handler(self.on_update, events.NewMessage)

    async def connect():
        await super().connect()
        self.me = await self.get_me()

    async def on_update(event):
        await event.reply('i am {}'.format(self.me.username))

bot = Subbot('bot', 11111, 'a1b2c3d4').start(bot_token='TOKEN')
bot.run_until_disconnected()

Key differences:

  • Telethon method names are snake_case.
  • dumbot does not offer friendly methods like update.reply.
  • Telethon does not have an implicit on_update handler, so we need to manually register one.