mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-08 04:52:30 +00:00
Complete migration guide from other bot libraries
This commit is contained in:
@@ -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.
|
||||
|
@@ -43,10 +43,10 @@ In the schema definitions, there are two boxed types, :tl:`User` and :tl:`Chat`.
|
||||
A boxed :tl:`User` can only be the bare :tl:`user`, but the boxed :tl:`Chat` can be either a bare :tl:`chat` or a bare :tl:`channel`.
|
||||
|
||||
A bare :tl:`chat` always refers to small groups.
|
||||
A bare :tl:`channel` can have either the ``broadcast`` or the ``megagroup`` flag set to ``True``.
|
||||
A bare :tl:`channel` can have either the ``broadcast`` or the ``megagroup`` flag set to :data:`True`.
|
||||
|
||||
A bare :tl:`channel` with the ``broadcast`` flag set to ``True`` is known as a broadcast channel.
|
||||
A bare :tl:`channel` with the ``megagroup`` flag set to ``True`` is known as a supergroup.
|
||||
A bare :tl:`channel` with the ``broadcast`` flag set to :data:`True` is known as a broadcast channel.
|
||||
A bare :tl:`channel` with the ``megagroup`` flag set to :data:`True` is known as a supergroup.
|
||||
|
||||
A bare :tl:`chat` with has less features than a bare :tl:`channel` ``megagroup``.
|
||||
Official clients are very good at hiding this difference.
|
||||
|
@@ -44,6 +44,7 @@ Glossary
|
||||
|
||||
.. seealso:: The :doc:`../concepts/botapi-vs-mtproto` concept.
|
||||
|
||||
Bot API
|
||||
HTTP Bot API
|
||||
Telegram's simplified HTTP API to control bot accounts only.
|
||||
|
||||
|
@@ -45,7 +45,7 @@ For this reason, filters cannot be asynchronous.
|
||||
This reduces the chance a filter will do slow IO and potentially fail.
|
||||
|
||||
A filter is simply a callable function that takes an event as input and returns a boolean.
|
||||
If the filter returns ``True``, the handler will be called.
|
||||
If the filter returns :data:`True`, the handler will be called.
|
||||
Using this knowledge, you can create custom filters too.
|
||||
If you need state, you can use a class with a ``__call__`` method defined:
|
||||
|
||||
@@ -74,3 +74,39 @@ You can use :func:`isinstance` if your filter can only deal with certain types o
|
||||
|
||||
If you need to perform asynchronous operations, you can't use a filter.
|
||||
Instead, manually check for those conditions inside your handler.
|
||||
|
||||
|
||||
Setting priority on handlers
|
||||
----------------------------
|
||||
|
||||
There is no explicit way to define a different priority for different handlers.
|
||||
|
||||
Instead, the library will call all your handlers in the order you added them.
|
||||
This means that, if you want a "catch-all" handler, it should be registered last.
|
||||
|
||||
By default, the library will stop calling the rest of handlers after one is called:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@client.on(events.NewMessage)
|
||||
async def first(event):
|
||||
print('This is always called on new messages!')
|
||||
|
||||
@client.on(events.NewMessage)
|
||||
async def second(event):
|
||||
print('This will never be called, because "first" already ran.')
|
||||
|
||||
This is often the desired behaviour if you're using filters.
|
||||
|
||||
If you have more complicated filters executed *inside* the handler,
|
||||
Telethon believes your handler completed and will stop calling the rest.
|
||||
If that's the case, you can instruct Telethon to check all your handlers:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
client = Client(..., check_all_handlers=True)
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
# Now the code above will call both handlers
|
||||
|
||||
If you need a more complicated setup, consider sorting all your handlers beforehand.
|
||||
Then, use :meth:`Client.add_event_handler` on all of them to ensure the correct order.
|
||||
|
Reference in New Issue
Block a user