|
|
|
@@ -17,13 +17,13 @@ Quoting their main page:
|
|
|
|
|
|
|
|
|
|
.. epigraph::
|
|
|
|
|
|
|
|
|
|
The Bot API is an HTTP-based interface created for developers keen on building bots for Telegram.
|
|
|
|
|
The :term:`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 <https://core.telegram.org/bots>`_
|
|
|
|
|
and `Bot FAQ <https://core.telegram.org/bots/faq>`_.
|
|
|
|
|
|
|
|
|
|
Bot API is simply an HTTP endpoint offering a custom HTTP API.
|
|
|
|
|
:term:`Bot API` is simply an HTTP endpoint offering a custom HTTP API.
|
|
|
|
|
Underneath, it uses `tdlib <https://core.telegram.org/tdlib>`_ to talk to Telegram's servers.
|
|
|
|
|
|
|
|
|
|
You can configure your bot details via `@BotFather <https://t.me/BotFather>`_.
|
|
|
|
@@ -51,6 +51,19 @@ This name was chosen because it gives you "raw" access to the MTProto API.
|
|
|
|
|
Telethon's :class:`Client` and other custom types are implemented using the :term:`Raw API`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Why is an API ID and hash needed for bots with MTProto?
|
|
|
|
|
-------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
When talking to Telegram's API directly, you need an API ID and hash to sign in to their servers.
|
|
|
|
|
API access is forbidden without an API ID, and the sign in can only be done with the API hash.
|
|
|
|
|
|
|
|
|
|
When using the :term:`Bot API`, that layer talks to the MTProto API underneath.
|
|
|
|
|
To do so, it uses its own private API ID and hash.
|
|
|
|
|
|
|
|
|
|
When you cut on the intermediary, you need to provide your own.
|
|
|
|
|
In a similar manner, the authorization key which remembers that you logged-in must be kept locally.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Advantages of MTProto over Bot API
|
|
|
|
|
----------------------------------
|
|
|
|
|
|
|
|
|
@@ -109,9 +122,306 @@ and you were making API requests manually, or if you used a wrapper library like
|
|
|
|
|
or `pyTelegramBotAPI <https://pytba.readthedocs.io/en/latest/index.html>`.
|
|
|
|
|
You will surely be pleased with Telethon!
|
|
|
|
|
|
|
|
|
|
If you were using an asynchronous library like `aiohttp <https://docs.aiohttp.org/en/stable>`_
|
|
|
|
|
or a wrapper like `aiogram <https://docs.aiohttp.org/en/stable>`_, the switch will be even easier.
|
|
|
|
|
If you were using an asynchronous library like `aiohttp <https://docs.aiohttp.org/en/stable/>`_
|
|
|
|
|
or a wrapper like `aiogram <https://docs.aiogram.dev/en/latest/>`_, the switch will be even easier.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Migrating from TODO
|
|
|
|
|
^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
Migrating from PTB v13.x
|
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
|
|
|
|
Using one of the examples from their v13 wiki with the ``.ext`` module:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
from telegram import Update
|
|
|
|
|
from telegram.ext import Updater, CallbackContext, CommandHandler
|
|
|
|
|
|
|
|
|
|
updater = Updater(token='TOKEN', use_context=True)
|
|
|
|
|
dispatcher = updater.dispatcher
|
|
|
|
|
|
|
|
|
|
def start(update: Update, context: CallbackContext):
|
|
|
|
|
context.bot.send_message(chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!")
|
|
|
|
|
|
|
|
|
|
start_handler = CommandHandler('start', start)
|
|
|
|
|
dispatcher.add_handler(start_handler)
|
|
|
|
|
|
|
|
|
|
updater.start_polling()
|
|
|
|
|
|
|
|
|
|
The code creates an ``Updater`` instance.
|
|
|
|
|
This will take care of polling updates for the bot associated with the given token.
|
|
|
|
|
Then, a ``CommandHandler`` using our ``start`` function is added to the dispatcher.
|
|
|
|
|
At the end, we block, telling the updater to do its job.
|
|
|
|
|
|
|
|
|
|
In Telethon:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
from telethon import Client
|
|
|
|
|
from telethon.events import NewMessage, filters
|
|
|
|
|
|
|
|
|
|
updater = Client('bot', api_id, api_hash)
|
|
|
|
|
|
|
|
|
|
async def start(update: NewMessage):
|
|
|
|
|
await update.client.send_message(chat=update.chat.id, text="I'm a bot, please talk to me!")
|
|
|
|
|
|
|
|
|
|
start_filter = filters.Command('/start')
|
|
|
|
|
updater.add_event_handler(start, NewMessage, start_filter)
|
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
|
|
async with updater:
|
|
|
|
|
await updater.interactive_login('TOKEN')
|
|
|
|
|
await updater.run_until_disconnected()
|
|
|
|
|
|
|
|
|
|
asyncio.run(main())
|
|
|
|
|
|
|
|
|
|
Key differences:
|
|
|
|
|
|
|
|
|
|
* Telethon only has a :class:`~telethon.Client`, not separate ``Bot`` or ``Updater`` classes.
|
|
|
|
|
* There is no separate dispatcher. The :class:`~telethon.Client` is capable of dispatching updates.
|
|
|
|
|
* Telethon handlers only have one parameter, the event.
|
|
|
|
|
* There is no context, but the :attr:`~telethon.events.Event.client` property exists in all events.
|
|
|
|
|
* Handler types are :mod:`~telethon.events.filters` and don't have a ``Handler`` suffix.
|
|
|
|
|
* Telethon must define the update type (:class:`~telethon.events.NewMessage`) and filter.
|
|
|
|
|
* The setup to run the client (and dispatch updates) is a bit more involved with :mod:`asyncio`.
|
|
|
|
|
|
|
|
|
|
Here's the above code in idiomatic Telethon:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
from telethon import Client, events
|
|
|
|
|
from telethon.events import filters
|
|
|
|
|
|
|
|
|
|
client = Client('bot', api_id, api_hash)
|
|
|
|
|
|
|
|
|
|
@client.on(events.NewMessage, filters.Command('/start'))
|
|
|
|
|
async def start(event):
|
|
|
|
|
await event.respond("I'm a bot, please talk to me!")
|
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
|
|
async with client:
|
|
|
|
|
await client.interactive_login('TOKEN')
|
|
|
|
|
await client.run_until_disconnected()
|
|
|
|
|
|
|
|
|
|
asyncio.run(main())
|
|
|
|
|
|
|
|
|
|
Events can be added using decorators and methods such as :meth:`types.Message.respond` help reduce the verbosity.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Migrating from PTB v20.x
|
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
|
|
|
|
Using one of the examples from their v13 wiki with the ``.ext`` module:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
from telegram import Update
|
|
|
|
|
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler
|
|
|
|
|
|
|
|
|
|
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
|
|
|
await context.bot.send_message(chat_id=update.effective_chat.id, text="I'm a bot, please talk to me!")
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
application = ApplicationBuilder().token('TOKEN').build()
|
|
|
|
|
|
|
|
|
|
start_handler = CommandHandler('start', start)
|
|
|
|
|
application.add_handler(start_handler)
|
|
|
|
|
|
|
|
|
|
application.run_polling()
|
|
|
|
|
|
|
|
|
|
No need to import the :mod:`asyncio` module directly!
|
|
|
|
|
Now instead there are builders to help set stuff up.
|
|
|
|
|
|
|
|
|
|
In Telethon:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
from telethon import Client
|
|
|
|
|
from telethon.events import NewMessage, filters
|
|
|
|
|
|
|
|
|
|
async def start(update: NewMessage):
|
|
|
|
|
await update.client.send_message(chat=update.chat.id, text="I'm a bot, please talk to me!")
|
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
|
|
application = Client('bot', api_id, api_hash)
|
|
|
|
|
|
|
|
|
|
start_filter = filters.Command('/start')
|
|
|
|
|
application.add_event_handler(start, NewMessage, start_filter)
|
|
|
|
|
|
|
|
|
|
async with application:
|
|
|
|
|
await application.interactive_login('TOKEN')
|
|
|
|
|
await application.run_until_disconnected()
|
|
|
|
|
|
|
|
|
|
asyncio.run(main())
|
|
|
|
|
|
|
|
|
|
Key differences:
|
|
|
|
|
|
|
|
|
|
* No builders. Telethon tries to get out of your way on how you structure your code.
|
|
|
|
|
* The client must be connected before it can run, hence the ``async with``.
|
|
|
|
|
|
|
|
|
|
Here's the above code in idiomatic Telethon:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
from telethon import Client, events
|
|
|
|
|
from telethon.events import filters
|
|
|
|
|
|
|
|
|
|
@client.on(events.NewMessage, filters.Command('/start'))
|
|
|
|
|
async def start(event):
|
|
|
|
|
await event.respond("I'm a bot, please talk to me!")
|
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
|
|
async with Client('bot', api_id, api_hash) as client:
|
|
|
|
|
await client.interactive_login('TOKEN')
|
|
|
|
|
client.add_event_handler(start, NewMessage, filters.Command('/start'))
|
|
|
|
|
await client.run_until_disconnected()
|
|
|
|
|
|
|
|
|
|
asyncio.run(main())
|
|
|
|
|
|
|
|
|
|
Note how the client can be created and started in the same line.
|
|
|
|
|
This makes it easy to have clean disconnections once the script exits.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Migrating from asynchronous TeleBot
|
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
|
|
|
|
Using one of the examples from their v4 pyTelegramBotAPI documentation:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
from telebot.async_telebot import AsyncTeleBot
|
|
|
|
|
bot = AsyncTeleBot('TOKEN')
|
|
|
|
|
|
|
|
|
|
# Handle '/start' and '/help'
|
|
|
|
|
@bot.message_handler(commands=['help', 'start'])
|
|
|
|
|
async def send_welcome(message):
|
|
|
|
|
await bot.reply_to(message, """\
|
|
|
|
|
Hi there, I am EchoBot.
|
|
|
|
|
I am here to echo your kind words back to you. Just say anything nice and I'll say the exact same thing to you!\
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# Handle all other messages with content_type 'text' (content_types defaults to ['text'])
|
|
|
|
|
@bot.message_handler(func=lambda message: True)
|
|
|
|
|
async def echo_message(message):
|
|
|
|
|
await bot.reply_to(message, message.text)
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
asyncio.run(bot.polling())
|
|
|
|
|
|
|
|
|
|
This showcases a command handler and a catch-all echo handler, both added with decorators.
|
|
|
|
|
|
|
|
|
|
In Telethon:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
|
|
|
|
|
from telethon import Client, events
|
|
|
|
|
from telethon.events.filters import Any, Command, TextOnly
|
|
|
|
|
bot = Client('bot', api_id, api_hash)
|
|
|
|
|
|
|
|
|
|
# Handle '/start' and '/help'
|
|
|
|
|
@bot.on(events.NewMessage, Any(Command('/help'), Command('/start')))
|
|
|
|
|
async def send_welcome(message: NewMessage):
|
|
|
|
|
await message.reply("""\
|
|
|
|
|
Hi there, I am EchoBot.
|
|
|
|
|
I am here to echo your kind words back to you. Just say anything nice and I'll say the exact same thing to you!\
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# Handle all other messages with only 'text'
|
|
|
|
|
@bot.on(events.NewMessage, TextOnly())
|
|
|
|
|
async def echo_message(message: NewMessage):
|
|
|
|
|
await message.reply(message.text)
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
async def main():
|
|
|
|
|
async with bot:
|
|
|
|
|
await bot.interactive_login('TOKEN')
|
|
|
|
|
await bot.run_until_disconnected()
|
|
|
|
|
asyncio.run(main())
|
|
|
|
|
|
|
|
|
|
Key differences:
|
|
|
|
|
|
|
|
|
|
* The handler type is defined using the event type instead of being a specific method in the client.
|
|
|
|
|
* Filters are also separate instances instead of being tied to specific event types.
|
|
|
|
|
* The ``reply_to`` helper is in the message, not the client instance.
|
|
|
|
|
* Setup is a bit more involved because the connection is not implicit.
|
|
|
|
|
|
|
|
|
|
For the most part, it's a 1-to-1 translation and the result is idiomatic Telethon.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Migrating from aiogram
|
|
|
|
|
``````````````````````
|
|
|
|
|
Using one of the examples from their v3 documentation with logging and comments removed:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
import asyncio
|
|
|
|
|
|
|
|
|
|
from aiogram import Bot, Dispatcher, types
|
|
|
|
|
from aiogram.enums import ParseMode
|
|
|
|
|
from aiogram.filters import CommandStart
|
|
|
|
|
from aiogram.types import Message
|
|
|
|
|
from aiogram.utils.markdown import hbold
|
|
|
|
|
|
|
|
|
|
dp = Dispatcher()
|
|
|
|
|
|
|
|
|
|
@dp.message(CommandStart())
|
|
|
|
|
async def command_start_handler(message: Message) -> None:
|
|
|
|
|
await message.answer(f"Hello, {hbold(message.from_user.full_name)}!")
|
|
|
|
|
|
|
|
|
|
@dp.message()
|
|
|
|
|
async def echo_handler(message: types.Message) -> None:
|
|
|
|
|
try:
|
|
|
|
|
await message.send_copy(chat_id=message.chat.id)
|
|
|
|
|
except TypeError:
|
|
|
|
|
await message.answer("Nice try!")
|
|
|
|
|
|
|
|
|
|
async def main() -> None:
|
|
|
|
|
bot = Bot(TOKEN, parse_mode=ParseMode.HTML)
|
|
|
|
|
await dp.start_polling(bot)
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
asyncio.run(main())
|
|
|
|
|
|
|
|
|
|
We can see a specific handler for the ``/start`` command and a catch-all echo handler:
|
|
|
|
|
|
|
|
|
|
In Telethon:
|
|
|
|
|
|
|
|
|
|
.. code-block:: python
|
|
|
|
|
import asyncio, html
|
|
|
|
|
|
|
|
|
|
from telethon import Client, RpcError, types, events
|
|
|
|
|
from telethon.events.filters import Command
|
|
|
|
|
from telethon.types import Message
|
|
|
|
|
|
|
|
|
|
client = Client("bot", api_id, api_hash)
|
|
|
|
|
|
|
|
|
|
@client.on(events.NewMessage, Command("/start"))
|
|
|
|
|
async def command_start_handler(message: Message) -> None:
|
|
|
|
|
await message.respond(html=f"Hello, <b>{html.escape(message.sender.full_name)}</b>!")
|
|
|
|
|
|
|
|
|
|
@dp.message()
|
|
|
|
|
async def echo_handler(message: types.Message) -> None:
|
|
|
|
|
try:
|
|
|
|
|
await message.respond(message)
|
|
|
|
|
except RpcError:
|
|
|
|
|
await message.respond("Nice try!")
|
|
|
|
|
|
|
|
|
|
async def main() -> None:
|
|
|
|
|
async with bot:
|
|
|
|
|
await bot.interactive_login(TOKEN)
|
|
|
|
|
await bot.run_until_disconnected()
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
asyncio.run(main())
|
|
|
|
|
|
|
|
|
|
Key differences:
|
|
|
|
|
|
|
|
|
|
* There is no separate dispatcher. Handlers are added to the client.
|
|
|
|
|
* There is no specific handler for the ``/start`` command.
|
|
|
|
|
* The ``answer`` method is for callback queries. Messages have :meth:`~types.Message.respond`.
|
|
|
|
|
* Telethon doesn't have functions to format messages. Instead, markdown or HTML are used.
|
|
|
|
|
* Telethon cannot have a default parse mode. Instead, it should be specified when responding.
|
|
|
|
|
* Telethon doesn't have ``send_copy``. Instead, :meth:`Client.send_message` accepts :class:`~types.Message`.
|
|
|
|
|
* If sending a message fails, the error will be :class:`RpcError`, because it comes from Telegram.
|
|
|
|
|