diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 00000000..72ee1f7d --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,15 @@ +name: Build-docs + +on: push + +jobs: + build: + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Build + run: bash build-docs.sh + env: + DOCS_KEY: ${{ secrets.DOCS_KEY }} diff --git a/Makefile b/Makefile index 930d3be4..e3d5d265 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,10 @@ venv: $(PYTHON) -m pip install -U -r requirements.txt -r dev-requirements.txt @echo "Created venv with $$($(PYTHON) --version)" +clean-docs: + $(RM) docs/build + $(RM) docs/source/api/bound-methods docs/source/api/methods docs/source/api/types docs/source/telegram + clean-build: $(RM) *.egg-info build dist @@ -39,4 +43,4 @@ tag: dtag: git tag -d $(TAG) - git push origin -d $(TAG) \ No newline at end of file + git push origin -d $(TAG) diff --git a/build-docs.sh b/build-docs.sh new file mode 100644 index 00000000..23bbc4fb --- /dev/null +++ b/build-docs.sh @@ -0,0 +1,20 @@ +#!/bin/bash +export DOCS_KEY +export VENV=$(pwd)/venv + +make clean +make clean-docs +make venv +make api +"$VENV"/bin/pip install -r docs/requirements.txt +cd compiler/docs && "$VENV"/bin/python compiler.py +cd ../.. +"$VENV"/bin/sphinx-build -b html "docs/source" "docs/build/html" -j auto +git clone https://wulan17:"$DOCS_KEY"@github.com/Mayuri-Chan/pyrofork-docs.git +cp -r docs/build/html/* pyrofork-docs +cd pyrofork-docs +git config --local user.name "Mayuri-Chan" +git config --local user.email "mayuri@mayuri.my.id" +git add --all +git commit -a -m "docs: Update docs $(date '+%Y-%m-%d | %H:%m:%S %p %Z')" --signoff +git push -u origin --all diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..83797de6 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,6 @@ +pyston_lite-autoload +sphinx +sphinx_rtd_theme==1.0.0 +sphinx_copybutton +sphinx-autobuild +tgcrypto diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst new file mode 100644 index 00000000..3db28693 --- /dev/null +++ b/docs/source/api/client.rst @@ -0,0 +1,24 @@ +Pyrogram Client +=============== + +You have entered the API Reference section where you can find detailed information about Pyrogram's API. The main Client +class, all available methods and types, filters, handlers, decorators and bound-methods detailed descriptions can be +found starting from this page. + +This page is about the Client class, which exposes high-level methods for an easy access to the API. + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + with app: + app.send_message("me", "Hi!") + +----- + +Details +------- + +.. autoclass:: pyrogram.Client() diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst new file mode 100644 index 00000000..2e7a04f2 --- /dev/null +++ b/docs/source/api/decorators.rst @@ -0,0 +1,68 @@ +Decorators +========== + +Decorators are able to register callback functions for handling updates in a much easier and cleaner way compared to +:doc:`Handlers `; they do so by instantiating the correct handler and calling +:meth:`~pyrogram.Client.add_handler` automatically. All you need to do is adding the decorators on top of your +functions. + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + + @app.on_message() + def log(client, message): + print(message) + + + app.run() + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +.. currentmodule:: pyrogram + +Index +----- + +.. hlist:: + :columns: 3 + + - :meth:`~Client.on_message` + - :meth:`~Client.on_edited_message` + - :meth:`~Client.on_callback_query` + - :meth:`~Client.on_inline_query` + - :meth:`~Client.on_chosen_inline_result` + - :meth:`~Client.on_chat_member_updated` + - :meth:`~Client.on_chat_join_request` + - :meth:`~Client.on_deleted_messages` + - :meth:`~Client.on_user_status` + - :meth:`~Client.on_poll` + - :meth:`~Client.on_disconnect` + - :meth:`~Client.on_raw_update` + +----- + +Details +------- + +.. Decorators +.. autodecorator:: pyrogram.Client.on_message() +.. autodecorator:: pyrogram.Client.on_edited_message() +.. autodecorator:: pyrogram.Client.on_callback_query() +.. autodecorator:: pyrogram.Client.on_inline_query() +.. autodecorator:: pyrogram.Client.on_chosen_inline_result() +.. autodecorator:: pyrogram.Client.on_chat_member_updated() +.. autodecorator:: pyrogram.Client.on_chat_join_request() +.. autodecorator:: pyrogram.Client.on_deleted_messages() +.. autodecorator:: pyrogram.Client.on_user_status() +.. autodecorator:: pyrogram.Client.on_poll() +.. autodecorator:: pyrogram.Client.on_disconnect() +.. autodecorator:: pyrogram.Client.on_raw_update() diff --git a/docs/source/api/enums/ChatAction.rst b/docs/source/api/enums/ChatAction.rst new file mode 100644 index 00000000..b66df5fd --- /dev/null +++ b/docs/source/api/enums/ChatAction.rst @@ -0,0 +1,8 @@ +ChatAction +========== + +.. autoclass:: pyrogram.enums.ChatAction() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/ChatEventAction.rst b/docs/source/api/enums/ChatEventAction.rst new file mode 100644 index 00000000..0403e781 --- /dev/null +++ b/docs/source/api/enums/ChatEventAction.rst @@ -0,0 +1,8 @@ +ChatEventAction +=============== + +.. autoclass:: pyrogram.enums.ChatEventAction() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/ChatMemberStatus.rst b/docs/source/api/enums/ChatMemberStatus.rst new file mode 100644 index 00000000..bff23eda --- /dev/null +++ b/docs/source/api/enums/ChatMemberStatus.rst @@ -0,0 +1,8 @@ +ChatMemberStatus +================ + +.. autoclass:: pyrogram.enums.ChatMemberStatus() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/ChatMembersFilter.rst b/docs/source/api/enums/ChatMembersFilter.rst new file mode 100644 index 00000000..5a970ffc --- /dev/null +++ b/docs/source/api/enums/ChatMembersFilter.rst @@ -0,0 +1,8 @@ +ChatMembersFilter +================= + +.. autoclass:: pyrogram.enums.ChatMembersFilter() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/ChatType.rst b/docs/source/api/enums/ChatType.rst new file mode 100644 index 00000000..dd653055 --- /dev/null +++ b/docs/source/api/enums/ChatType.rst @@ -0,0 +1,8 @@ +ChatType +======== + +.. autoclass:: pyrogram.enums.ChatType() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/MessageEntityType.rst b/docs/source/api/enums/MessageEntityType.rst new file mode 100644 index 00000000..c7a8965f --- /dev/null +++ b/docs/source/api/enums/MessageEntityType.rst @@ -0,0 +1,8 @@ +MessageEntityType +================= + +.. autoclass:: pyrogram.enums.MessageEntityType() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/MessageMediaType.rst b/docs/source/api/enums/MessageMediaType.rst new file mode 100644 index 00000000..04e439d2 --- /dev/null +++ b/docs/source/api/enums/MessageMediaType.rst @@ -0,0 +1,8 @@ +MessageMediaType +================ + +.. autoclass:: pyrogram.enums.MessageMediaType() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/MessageServiceType.rst b/docs/source/api/enums/MessageServiceType.rst new file mode 100644 index 00000000..2de56818 --- /dev/null +++ b/docs/source/api/enums/MessageServiceType.rst @@ -0,0 +1,8 @@ +MessageServiceType +================== + +.. autoclass:: pyrogram.enums.MessageServiceType() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/MessagesFilter.rst b/docs/source/api/enums/MessagesFilter.rst new file mode 100644 index 00000000..09090707 --- /dev/null +++ b/docs/source/api/enums/MessagesFilter.rst @@ -0,0 +1,8 @@ +MessagesFilter +============== + +.. autoclass:: pyrogram.enums.MessagesFilter() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/NextCodeType.rst b/docs/source/api/enums/NextCodeType.rst new file mode 100644 index 00000000..46164e47 --- /dev/null +++ b/docs/source/api/enums/NextCodeType.rst @@ -0,0 +1,8 @@ +NextCodeType +============ + +.. autoclass:: pyrogram.enums.NextCodeType() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/ParseMode.rst b/docs/source/api/enums/ParseMode.rst new file mode 100644 index 00000000..1bcc74da --- /dev/null +++ b/docs/source/api/enums/ParseMode.rst @@ -0,0 +1,8 @@ +ParseMode +========= + +.. autoclass:: pyrogram.enums.ParseMode() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/PollType.rst b/docs/source/api/enums/PollType.rst new file mode 100644 index 00000000..d00f9ce8 --- /dev/null +++ b/docs/source/api/enums/PollType.rst @@ -0,0 +1,8 @@ +PollType +======== + +.. autoclass:: pyrogram.enums.PollType() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/SentCodeType.rst b/docs/source/api/enums/SentCodeType.rst new file mode 100644 index 00000000..d738b195 --- /dev/null +++ b/docs/source/api/enums/SentCodeType.rst @@ -0,0 +1,8 @@ +SentCodeType +============ + +.. autoclass:: pyrogram.enums.SentCodeType() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/UserStatus.rst b/docs/source/api/enums/UserStatus.rst new file mode 100644 index 00000000..c9a77e1b --- /dev/null +++ b/docs/source/api/enums/UserStatus.rst @@ -0,0 +1,8 @@ +UserStatus +========== + +.. autoclass:: pyrogram.enums.UserStatus() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/cleanup.html b/docs/source/api/enums/cleanup.html new file mode 100644 index 00000000..bb9db780 --- /dev/null +++ b/docs/source/api/enums/cleanup.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/docs/source/api/enums/index.rst b/docs/source/api/enums/index.rst new file mode 100644 index 00000000..bd9f8b1d --- /dev/null +++ b/docs/source/api/enums/index.rst @@ -0,0 +1,47 @@ +Enumerations +============ + +This page is about Pyrogram enumerations. +Enumerations are types that hold a group of related values to be used whenever a constant value is required. +They will help you deal with those values in a type-safe way and also enable code completion so that you can be sure +to apply only a valid value among the expected ones. + +----- + +.. currentmodule:: pyrogram.enums + +.. autosummary:: + :nosignatures: + + ChatAction + ChatEventAction + ChatMemberStatus + ChatMembersFilter + ChatType + MessageEntityType + MessageMediaType + MessageServiceType + MessagesFilter + ParseMode + PollType + SentCodeType + NextCodeType + UserStatus + +.. toctree:: + :hidden: + + ChatAction + ChatEventAction + ChatMemberStatus + ChatMembersFilter + ChatType + MessageEntityType + MessageMediaType + MessageServiceType + MessagesFilter + ParseMode + PollType + SentCodeType + NextCodeType + UserStatus \ No newline at end of file diff --git a/docs/source/api/errors/bad-request.rst b/docs/source/api/errors/bad-request.rst new file mode 100644 index 00000000..ab13fdab --- /dev/null +++ b/docs/source/api/errors/bad-request.rst @@ -0,0 +1,7 @@ +400 - BadRequest +---------------- + +.. csv-table:: + :file: ../../../../compiler/errors/source/400_BAD_REQUEST.tsv + :delim: tab + :header-rows: 1 diff --git a/docs/source/api/errors/flood.rst b/docs/source/api/errors/flood.rst new file mode 100644 index 00000000..e01e0122 --- /dev/null +++ b/docs/source/api/errors/flood.rst @@ -0,0 +1,7 @@ +420 - Flood +----------- + +.. csv-table:: + :file: ../../../../compiler/errors/source/420_FLOOD.tsv + :delim: tab + :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/forbidden.rst b/docs/source/api/errors/forbidden.rst new file mode 100644 index 00000000..c2a8dcb8 --- /dev/null +++ b/docs/source/api/errors/forbidden.rst @@ -0,0 +1,7 @@ +403 - Forbidden +--------------- + +.. csv-table:: + :file: ../../../../compiler/errors/source/403_FORBIDDEN.tsv + :delim: tab + :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/index.rst b/docs/source/api/errors/index.rst new file mode 100644 index 00000000..be2b80d4 --- /dev/null +++ b/docs/source/api/errors/index.rst @@ -0,0 +1,37 @@ +RPC Errors +========== + +All Pyrogram API errors live inside the ``errors`` sub-package: ``pyrogram.errors``. +The errors ids listed here are shown as *UPPER_SNAKE_CASE*, but the actual exception names to import from Pyrogram +follow the usual *PascalCase* convention. + +.. code-block:: python + + from pyrogram.errors import FloodWait + + try: + ... + except FloodWait as e: + ... + +.. hlist:: + :columns: 1 + + - :doc:`see-other` + - :doc:`bad-request` + - :doc:`unauthorized` + - :doc:`forbidden` + - :doc:`not-acceptable` + - :doc:`flood` + - :doc:`internal-server-error` + +.. toctree:: + :hidden: + + see-other + bad-request + unauthorized + forbidden + not-acceptable + flood + internal-server-error \ No newline at end of file diff --git a/docs/source/api/errors/internal-server-error.rst b/docs/source/api/errors/internal-server-error.rst new file mode 100644 index 00000000..996d77eb --- /dev/null +++ b/docs/source/api/errors/internal-server-error.rst @@ -0,0 +1,7 @@ +500 - InternalServerError +------------------------- + +.. csv-table:: + :file: ../../../../compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv + :delim: tab + :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/not-acceptable.rst b/docs/source/api/errors/not-acceptable.rst new file mode 100644 index 00000000..79cfa8bc --- /dev/null +++ b/docs/source/api/errors/not-acceptable.rst @@ -0,0 +1,7 @@ +406 - NotAcceptable +------------------- + +.. csv-table:: + :file: ../../../../compiler/errors/source/406_NOT_ACCEPTABLE.tsv + :delim: tab + :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/see-other.rst b/docs/source/api/errors/see-other.rst new file mode 100644 index 00000000..629b9557 --- /dev/null +++ b/docs/source/api/errors/see-other.rst @@ -0,0 +1,7 @@ +303 - SeeOther +-------------- + +.. csv-table:: + :file: ../../../../compiler/errors/source/303_SEE_OTHER.tsv + :delim: tab + :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/unauthorized.rst b/docs/source/api/errors/unauthorized.rst new file mode 100644 index 00000000..c56abeec --- /dev/null +++ b/docs/source/api/errors/unauthorized.rst @@ -0,0 +1,7 @@ +401 - Unauthorized +------------------ + +.. csv-table:: + :file: ../../../../compiler/errors/source/401_UNAUTHORIZED.tsv + :delim: tab + :header-rows: 1 diff --git a/docs/source/api/filters.rst b/docs/source/api/filters.rst new file mode 100644 index 00000000..eb3c9522 --- /dev/null +++ b/docs/source/api/filters.rst @@ -0,0 +1,11 @@ +Update Filters +============== + +Filters are objects that can be used to filter the content of incoming updates. +:doc:`Read more about how filters work <../topics/use-filters>`. + +Details +------- + +.. automodule:: pyrogram.filters + :members: diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst new file mode 100644 index 00000000..8a8ac714 --- /dev/null +++ b/docs/source/api/handlers.rst @@ -0,0 +1,66 @@ +Update Handlers +=============== + +Handlers are used to instruct Pyrogram about which kind of updates you'd like to handle with your callback functions. +For a much more convenient way of registering callback functions have a look at :doc:`Decorators ` instead. + +.. code-block:: python + + from pyrogram import Client + from pyrogram.handlers import MessageHandler + + app = Client("my_account") + + + def dump(client, message): + print(message) + + + app.add_handler(MessageHandler(dump)) + + app.run() + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +.. currentmodule:: pyrogram.handlers + +Index +----- + +.. hlist:: + :columns: 3 + + - :class:`MessageHandler` + - :class:`EditedMessageHandler` + - :class:`DeletedMessagesHandler` + - :class:`CallbackQueryHandler` + - :class:`InlineQueryHandler` + - :class:`ChosenInlineResultHandler` + - :class:`ChatMemberUpdatedHandler` + - :class:`UserStatusHandler` + - :class:`PollHandler` + - :class:`DisconnectHandler` + - :class:`RawUpdateHandler` + +----- + +Details +------- + +.. Handlers +.. autoclass:: MessageHandler() +.. autoclass:: EditedMessageHandler() +.. autoclass:: DeletedMessagesHandler() +.. autoclass:: CallbackQueryHandler() +.. autoclass:: InlineQueryHandler() +.. autoclass:: ChosenInlineResultHandler() +.. autoclass:: ChatMemberUpdatedHandler() +.. autoclass:: UserStatusHandler() +.. autoclass:: PollHandler() +.. autoclass:: DisconnectHandler() +.. autoclass:: RawUpdateHandler() diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 00000000..3071cdc1 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,91 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import os +import sys + +sys.path.insert(0, os.path.abspath("../..")) + +from pyrogram import __version__ + +from pygments.styles.friendly import FriendlyStyle + +FriendlyStyle.background_color = "#f3f2f1" + +project = "Pyrogram" +copyright = f"2017-present, Dan" +author = "Dan" + +version = ".".join(__version__.split(".")[:-1]) + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.autosummary", + "sphinx.ext.intersphinx", + "sphinx_copybutton" +] + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None) +} + +master_doc = "index" +source_suffix = ".rst" +autodoc_member_order = "bysource" + +templates_path = ["../resources/templates"] +html_copy_source = False + +napoleon_use_rtype = False +napoleon_use_param = False + +pygments_style = "friendly" + +copybutton_prompt_text = "$ " + +suppress_warnings = ["image.not_readable"] + +html_title = "Pyrogram Documentation" +html_theme = "sphinx_rtd_theme" +html_static_path = ["../resources/static"] +html_show_sourcelink = True +html_show_copyright = False +html_theme_options = { + "canonical_url": "https://docs.pyrogram.org/", + "collapse_navigation": True, + "sticky_navigation": False, + "logo_only": True, + "display_version": False, + "style_external_links": True +} + +html_logo = "../resources/static/img/pyrogram.png" +html_favicon = "../resources/static/img/favicon.ico" + +latex_engine = "xelatex" +latex_logo = "../resources/static/img/pyrogram.png" + +latex_elements = { + "pointsize": "12pt", + "fontpkg": r""" + \setmainfont{Open Sans} + \setsansfont{Bitter} + \setmonofont{Ubuntu Mono} + """ +} diff --git a/docs/source/faq/client-started-but-nothing-happens.rst b/docs/source/faq/client-started-but-nothing-happens.rst new file mode 100644 index 00000000..ab85f518 --- /dev/null +++ b/docs/source/faq/client-started-but-nothing-happens.rst @@ -0,0 +1,11 @@ +Client started, but nothing happens +=================================== + +A possible cause might be network issues, either yours or Telegram's. To check this, add the following code at +the top of your script and run it again. You should see some error mentioning a socket timeout or an unreachable +network: + +.. code-block:: python + + import logging + logging.basicConfig(level=logging.INFO) \ No newline at end of file diff --git a/docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst b/docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst new file mode 100644 index 00000000..37d47a64 --- /dev/null +++ b/docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst @@ -0,0 +1,12 @@ +Code hangs when calling stop, restart, add/remove_handler +========================================================= + +You tried to ``.stop()``, ``.restart()``, ``.add_handler()`` or ``.remove_handler()`` inside a running handler, but +that can't be done because the way Pyrogram deals with handlers would make it hang. + +When calling one of the methods above inside an event handler, Pyrogram needs to wait for all running handlers to finish +in order to continue. Since your handler is blocking the execution by waiting for the called method to finish +and since Pyrogram needs to wait for your handler to finish, you are left with a deadlock. + +The solution to this problem is to pass ``block=False`` to such methods so that they return immediately and the actual +code called asynchronously. \ No newline at end of file diff --git a/docs/source/faq/how-to-avoid-flood-waits.rst b/docs/source/faq/how-to-avoid-flood-waits.rst new file mode 100644 index 00000000..06d1cdc2 --- /dev/null +++ b/docs/source/faq/how-to-avoid-flood-waits.rst @@ -0,0 +1,23 @@ +How to avoid Flood Waits? +========================= + +Slow things down and make less requests. Moreover, exact limits are unknown and can change anytime based on normal +usages. + +When a flood wait happens the server will tell you how much time to wait before continuing. +The following shows how to catch the exception in your code and wait the required seconds. + +.. code-block:: python + + import asyncio + from pyrogram.errors import FloodWait + + ... + try: + ... # Your code + except FloodWait as e: + await asyncio.sleep(e.value) # Wait "value" seconds before continuing + ... + + +More info about error handling can be found :doc:`here <../start/errors>`. diff --git a/docs/source/faq/how-to-use-webhooks.rst b/docs/source/faq/how-to-use-webhooks.rst new file mode 100644 index 00000000..b0dd4008 --- /dev/null +++ b/docs/source/faq/how-to-use-webhooks.rst @@ -0,0 +1,9 @@ +How to use webhooks? +==================== + +There is no webhook in Pyrogram, simply because there is no HTTP involved. However, a similar technique is +being used to make receiving updates efficient. + +Pyrogram uses persistent connections via TCP sockets to interact with the server and instead of actively asking for +updates every time (polling), Pyrogram will sit down and wait for the server to send updates by itself the very moment +they are available (server push). diff --git a/docs/source/faq/index.rst b/docs/source/faq/index.rst new file mode 100644 index 00000000..3d4a0036 --- /dev/null +++ b/docs/source/faq/index.rst @@ -0,0 +1,45 @@ +Frequently Asked Questions +========================== + +This FAQ page provides answers to common questions about Pyrogram and, to some extent, Telegram in general. + +**Contents** + +- :doc:`why-is-the-api-key-needed-for-bots` +- :doc:`how-to-use-webhooks` +- :doc:`using-the-same-file-id-across-different-accounts` +- :doc:`using-multiple-clients-at-once-on-the-same-account` +- :doc:`client-started-but-nothing-happens` +- :doc:`what-are-the-ip-addresses-of-telegram-data-centers` +- :doc:`migrating-the-account-to-another-data-center` +- :doc:`why-is-the-client-reacting-slowly-in-supergroups-channels` +- :doc:`peer-id-invalid-error` +- :doc:`code-hangs-when-calling-stop-restart-add-remove-handler` +- :doc:`unicodeencodeerror-codec-cant-encode` +- :doc:`uploading-with-urls-gives-error-webpage-curl-failed` +- :doc:`sqlite3-operationalerror-database-is-locked` +- :doc:`sqlite3-interfaceerror-error-binding-parameter` +- :doc:`socket-send-oserror-timeouterror-connection-lost-reset` +- :doc:`how-to-avoid-flood-waits` +- :doc:`the-account-has-been-limited-deactivated` + +.. toctree:: + :hidden: + + why-is-the-api-key-needed-for-bots + how-to-use-webhooks + using-the-same-file-id-across-different-accounts + using-multiple-clients-at-once-on-the-same-account + client-started-but-nothing-happens + what-are-the-ip-addresses-of-telegram-data-centers + migrating-the-account-to-another-data-center + why-is-the-client-reacting-slowly-in-supergroups-channels + peer-id-invalid-error + code-hangs-when-calling-stop-restart-add-remove-handler + unicodeencodeerror-codec-cant-encode + uploading-with-urls-gives-error-webpage-curl-failed + sqlite3-operationalerror-database-is-locked + sqlite3-interfaceerror-error-binding-parameter + socket-send-oserror-timeouterror-connection-lost-reset + how-to-avoid-flood-waits + the-account-has-been-limited-deactivated \ No newline at end of file diff --git a/docs/source/faq/migrating-the-account-to-another-data-center.rst b/docs/source/faq/migrating-the-account-to-another-data-center.rst new file mode 100644 index 00000000..7ae76a1e --- /dev/null +++ b/docs/source/faq/migrating-the-account-to-another-data-center.rst @@ -0,0 +1,10 @@ +Migrating the account to another data center +============================================ + +This question is asked by people who find their account always being connected to one DC (data center), but are +connecting from a place far away, thus resulting in slower interactions when using the API because of the greater +physical distance between the user and the associated DC. + +When registering an account for the first time, is up to Telegram to decide which DC the new user is going to be +created in. It's also up to the server to decide whether to automatically migrate a user in case of prolonged usages +from a distant location. \ No newline at end of file diff --git a/docs/source/faq/peer-id-invalid-error.rst b/docs/source/faq/peer-id-invalid-error.rst new file mode 100644 index 00000000..197ea837 --- /dev/null +++ b/docs/source/faq/peer-id-invalid-error.rst @@ -0,0 +1,14 @@ +PEER_ID_INVALID error +===================== + +This error could mean several things: + +- The chat id you tried to use is simply wrong, check it again. +- The chat id refers to a group or channel you are not a member of. +- The chat id argument you passed is in form of a string; you have to convert it into an integer with ``int(chat_id)``. +- The chat id refers to a user or chat your current session hasn't met yet. + +About the last point: in order for you to meet a user and thus communicate with them, you should ask yourself how to +contact people using official apps. The answer is the same for Pyrogram too and involves normal usages such as searching +for usernames, meeting them in a common group, having their phone contacts saved, getting a message mentioning them +or obtaining the dialogs list. \ No newline at end of file diff --git a/docs/source/faq/socket-send-oserror-timeouterror-connection-lost-reset.rst b/docs/source/faq/socket-send-oserror-timeouterror-connection-lost-reset.rst new file mode 100644 index 00000000..85c50650 --- /dev/null +++ b/docs/source/faq/socket-send-oserror-timeouterror-connection-lost-reset.rst @@ -0,0 +1,12 @@ +socket.send(), OSError(), TimeoutError(), Connection lost/reset +=============================================================== + +If you get any of these errors chances are you ended up with a slow or inconsistent network connection. +Another reason could be because you are blocking the event loop for too long. + +You can consider the following: + +- Use Pyrogram asynchronously in its intended way. +- Use shorter non-asynchronous processing loops. +- Use ``asyncio.sleep()`` instead of ``time.sleep()``. +- Use a stable network connection. diff --git a/docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst b/docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst new file mode 100644 index 00000000..5d148186 --- /dev/null +++ b/docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst @@ -0,0 +1,13 @@ +sqlite3.InterfaceError: Error binding parameter +=============================================== + +This error occurs when you pass a chat id value of the wrong type when trying to call a method. Most likely, you +accidentally passed the whole user or chat object instead of the id or username. + +.. code-block:: python + + # Wrong. You passed the whole Chat instance + app.send_message(chat, "text") + + # Correct + app.send_message(chat.id, "text") \ No newline at end of file diff --git a/docs/source/faq/sqlite3-operationalerror-database-is-locked.rst b/docs/source/faq/sqlite3-operationalerror-database-is-locked.rst new file mode 100644 index 00000000..b5cb2d82 --- /dev/null +++ b/docs/source/faq/sqlite3-operationalerror-database-is-locked.rst @@ -0,0 +1,17 @@ +sqlite3.OperationalError: database is locked +============================================ + +This error occurs when more than one process is using the same session file, that is, when you run two or more clients +at the same time using the same session name or in case another program has accessed the file. + +For example, it could occur when a background script is still running and you forgot about it. In this case, you either +restart your system or find and kill the process that is locking the database. On Unix based systems, you can try the +following: + +#. ``cd`` into your session file directory. +#. ``fuser my_account.session`` to find the process id. +#. ``kill 1234`` to gracefully stop the process. +#. If the last command doesn't help, use ``kill -9 1234`` instead. + +If you want to run multiple clients on the same account, you must authorize your account (either user or bot) +from the beginning every time, and use different session names for each parallel client you are going to use. \ No newline at end of file diff --git a/docs/source/faq/the-account-has-been-limited-deactivated.rst b/docs/source/faq/the-account-has-been-limited-deactivated.rst new file mode 100644 index 00000000..79d589ea --- /dev/null +++ b/docs/source/faq/the-account-has-been-limited-deactivated.rst @@ -0,0 +1,16 @@ +The account has been limited/deactivated +======================================== + +Pyrogram is a framework that interfaces with Telegram; it is at your commands, meaning it only does what you tell it to +do, the rest is up to you and Telegram (see `Telegram's ToS`_). + +If you found your account being limited/deactivated, it could be due spam/flood/abuse of the API or the usage of certain +virtual/VoIP numbers. + +If you think your account was limited/deactivated by mistake, you can write to recover@telegram.org, contact +`@SpamBot`_ or use `this form`_. + +.. _@SpamBot: https://t.me/spambot +.. _this form: https://telegram.org/support +.. _Telegram's ToS: https://telegram.org/tos + diff --git a/docs/source/faq/unicodeencodeerror-codec-cant-encode.rst b/docs/source/faq/unicodeencodeerror-codec-cant-encode.rst new file mode 100644 index 00000000..a4511ce5 --- /dev/null +++ b/docs/source/faq/unicodeencodeerror-codec-cant-encode.rst @@ -0,0 +1,7 @@ +UnicodeEncodeError: '...' codec can't encode ... +================================================ + +Where ```` might be *ascii*, *cp932*, *charmap* or anything else other than *utf-8*. This error usually +shows up when you try to print something and has very little to do with Pyrogram itself as it is strictly related to +your own terminal. To fix it, either find a way to change the encoding settings of your terminal to UTF-8 or switch to +another terminal altogether. diff --git a/docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst b/docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst new file mode 100644 index 00000000..2b7c5a7e --- /dev/null +++ b/docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst @@ -0,0 +1,7 @@ +Uploading with URLs gives error WEBPAGE_CURL_FAILED +=================================================== + +When uploading media files using an URL, the server automatically tries to download the media and uploads it to the +Telegram cloud. This error usually happens in case the provided URL is not publicly accessible by Telegram itself or the +media file is too large. In such cases, your only option is to download the media yourself and upload it from your +local machine. \ No newline at end of file diff --git a/docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst b/docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst new file mode 100644 index 00000000..ab73b29c --- /dev/null +++ b/docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst @@ -0,0 +1,7 @@ +Using multiple clients at once on the same account +================================================== + +Both user and bot accounts are able to run multiple sessions in parallel. However, you must not use the same session +in more than one client at the same time. The correct way to run multiple clients on the same account is by authorizing +your account (either user or bot) from the beginning each time, and use one separate session for each parallel client. + diff --git a/docs/source/faq/using-the-same-file-id-across-different-accounts.rst b/docs/source/faq/using-the-same-file-id-across-different-accounts.rst new file mode 100644 index 00000000..00305ef1 --- /dev/null +++ b/docs/source/faq/using-the-same-file-id-across-different-accounts.rst @@ -0,0 +1,6 @@ +Using the same file_id across different accounts +================================================ + +Telegram file_id strings are bound to the account which generated them. An attempt in using a foreign file id will +result in errors such as ``[400 MEDIA_EMPTY]``. The only exception are stickers' file ids; you can use them across +different accounts without any problem. \ No newline at end of file diff --git a/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst b/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst new file mode 100644 index 00000000..c951230b --- /dev/null +++ b/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst @@ -0,0 +1,30 @@ +What are the IP addresses of Telegram Data Centers? +=================================================== + +Telegram is currently composed by a decentralized, multi-DC infrastructure (currently 5 DCs, each of which can +work independently) spread across different locations worldwide. However, some of the less busy DCs have been lately +dismissed and their IP addresses are now kept as aliases to the nearest one. + +.. csv-table:: Production Environment + :header: ID, Location, IPv4, IPv6 + :widths: auto + :align: center + + DC1, "MIA, Miami FL, USA", ``149.154.175.53``, ``2001:b28:f23d:f001::a`` + DC2, "AMS, Amsterdam, NL", ``149.154.167.51``, ``2001:67c:4e8:f002::a`` + DC3*, "MIA, Miami FL, USA", ``149.154.175.100``, ``2001:b28:f23d:f003::a`` + DC4, "AMS, Amsterdam, NL", ``149.154.167.91``, ``2001:67c:4e8:f004::a`` + DC5, "SIN, Singapore, SG", ``91.108.56.130``, ``2001:b28:f23f:f005::a`` + +.. csv-table:: Test Environment + :header: ID, Location, IPv4, IPv6 + :widths: auto + :align: center + + DC1, "MIA, Miami FL, USA", ``149.154.175.10``, ``2001:b28:f23d:f001::e`` + DC2, "AMS, Amsterdam, NL", ``149.154.167.40``, ``2001:67c:4e8:f002::e`` + DC3*, "MIA, Miami FL, USA", ``149.154.175.117``, ``2001:b28:f23d:f003::e`` + +.. centered:: More info about the Test Environment can be found :doc:`here <../topics/test-servers>`. + +***** Alias DC \ No newline at end of file diff --git a/docs/source/faq/why-is-the-api-key-needed-for-bots.rst b/docs/source/faq/why-is-the-api-key-needed-for-bots.rst new file mode 100644 index 00000000..2e062d40 --- /dev/null +++ b/docs/source/faq/why-is-the-api-key-needed-for-bots.rst @@ -0,0 +1,12 @@ +Why is the API key needed for bots? +=================================== + +Requests against the official bot API endpoints are made via JSON/HTTP and are handled by an intermediate server +application that implements the MTProto protocol and uses its own API key to communicate with the MTProto servers. + +.. figure:: //_static/img/mtproto-vs-bot-api.png + :align: center + +Using MTProto is the only way to communicate with the actual Telegram servers, and the main API requires developers to +identify applications by means of a unique key; the bot token identifies a bot as a user and replaces the user's phone +number only. \ No newline at end of file diff --git a/docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst b/docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst new file mode 100644 index 00000000..4d196164 --- /dev/null +++ b/docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst @@ -0,0 +1,18 @@ +Why is the client reacting slowly in supergroups/channels? +========================================================== + +Because of how Telegram works internally, every message you receive and send must pass through the creator's DC, and in +the worst case where you, the creator and another member all belong to three different DCs, the other member messages +have to go through from their DC to the creator's DC and finally to your DC. This is applied to each message and member +of a supergroup/channel and the process will inevitably take its time. + +Another reason that makes responses come slowly is that messages are dispatched by priority. Depending on the kind +of member, some users receive messages faster than others and for big and busy supergroups the delay might become +noticeable, especially if you are among the lower end of the priority list: + +1. Creator. +2. Administrators. +3. Bots. +4. Mentioned users. +5. Recent online users. +6. Everyone else. \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 00000000..d96223cb --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,172 @@ +Welcome to Pyrogram +=================== + +.. raw:: html + + + +

+ Telegram MTProto API Framework for Python + +
+ + Homepage + + • + + Development + + • + + Releases + + • + + News + +

+ +.. code-block:: python + + from pyrogram import Client, filters + + app = Client("my_account") + + + @app.on_message(filters.private) + async def hello(client, message): + await message.reply("Hello from Pyrogram!") + + + app.run() + +**Pyrogram** is a modern, elegant and asynchronous :doc:`MTProto API ` framework. +It enables you to easily interact with the main Telegram API through a user account (custom client) or a bot identity +(bot API alternative) using Python. + +Support +------- + +If you'd like to support Pyrogram, you can consider: + +- `Become a GitHub sponsor `_. +- `Become a LiberaPay patron `_. +- `Become an OpenCollective backer `_. + +How the Documentation is Organized +---------------------------------- + +Contents are organized into sections composed of self-contained topics which can be all accessed from the sidebar, or by +following them in order using the :guilabel:`Next` button at the end of each page. +You can also switch to Dark or Light theme or leave on Auto (follows system preferences) by using the dedicated button +in the top left corner. + +Here below you can, instead, find a list of the most relevant pages for a quick access. + +First Steps +^^^^^^^^^^^ + +.. hlist:: + :columns: 1 + + - :doc:`Quick Start `: Overview to get you started quickly. + - :doc:`Invoking Methods `: How to call Pyrogram's methods. + - :doc:`Handling Updates `: How to handle Telegram updates. + - :doc:`Error Handling `: How to handle API errors correctly. + +API Reference +^^^^^^^^^^^^^ + +.. hlist:: + :columns: 1 + + - :doc:`Pyrogram Client `: Reference details about the Client class. + - :doc:`Available Methods `: List of available high-level methods. + - :doc:`Available Types `: List of available high-level types. + - :doc:`Enumerations `: List of available enumerations. + - :doc:`Bound Methods `: List of convenient bound methods. + +Meta +^^^^ + +.. hlist:: + :columns: 1 + + - :doc:`Pyrogram FAQ `: Answers to common Pyrogram questions. + - :doc:`Support Pyrogram `: Ways to show your appreciation. + - :doc:`Release Notes `: Release notes for Pyrogram releases. + +.. toctree:: + :hidden: + :caption: Introduction + + intro/quickstart + intro/install + +.. toctree:: + :hidden: + :caption: Getting Started + + start/setup + start/auth + start/invoking + start/updates + start/errors + start/examples/index + +.. toctree:: + :hidden: + :caption: API Reference + + api/client + api/methods/index + api/types/index + api/bound-methods/index + api/enums/index + api/handlers + api/decorators + api/errors/index + api/filters + +.. toctree:: + :hidden: + :caption: Topic Guides + + topics/use-filters + topics/create-filters + topics/more-on-updates + topics/client-settings + topics/speedups + topics/text-formatting + topics/synchronous + topics/smart-plugins + topics/storage-engines + topics/serializing + topics/proxy + topics/scheduling + topics/mtproto-vs-botapi + topics/debugging + topics/test-servers + topics/advanced-usage + topics/voice-calls + +.. toctree:: + :hidden: + :caption: Meta + + faq/index + support + releases/index + +.. toctree:: + :hidden: + :caption: Telegram Raw API + + telegram/functions/index + telegram/types/index + telegram/base/index \ No newline at end of file diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst new file mode 100644 index 00000000..c45c3844 --- /dev/null +++ b/docs/source/intro/install.rst @@ -0,0 +1,50 @@ +Install Guide +============= + +Being a modern Python framework, Pyrogram requires an up to date version of Python to be installed in your system. +We recommend using the latest versions of both Python 3 and pip. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Install Pyrogram +---------------- + +- The easiest way to install and upgrade Pyrogram to its latest stable version is by using **pip**: + + .. code-block:: text + + $ pip3 install -U pyrogram + +- or, with :doc:`TgCrypto <../topics/speedups>` as extra requirement (recommended): + + .. code-block:: text + + $ pip3 install -U pyrogram tgcrypto + +Bleeding Edge +------------- + +You can install the development version from the git ``master`` branch using this command: + +.. code-block:: text + + $ pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip + +Verifying +--------- + +To verify that Pyrogram is correctly installed, open a Python shell and import it. +If no error shows up you are good to go. + +.. parsed-literal:: + + >>> import pyrogram + >>> pyrogram.__version__ + 'x.y.z' + +.. _`Github repo`: http://github.com/pyrogram/pyrogram diff --git a/docs/source/intro/quickstart.rst b/docs/source/intro/quickstart.rst new file mode 100644 index 00000000..29c355e7 --- /dev/null +++ b/docs/source/intro/quickstart.rst @@ -0,0 +1,56 @@ +Quick Start +=========== + +The next few steps serve as a quick start to see Pyrogram in action as fast as possible. + +Get Pyrogram Real Fast +---------------------- + +.. admonition :: Cloud Credits + :class: tip + + If you need a cloud server to host your applications, try Hetzner Cloud. You can sign up with + `this link `_ to get €20 in cloud credits. + +1. Install Pyrogram with ``pip3 install -U pyrogram``. + +2. Get your own Telegram API key from https://my.telegram.org/apps. + +3. Open the text editor of your choice and paste the following: + + .. code-block:: python + + import asyncio + from pyrogram import Client + + api_id = 12345 + api_hash = "0123456789abcdef0123456789abcdef" + + + async def main(): + async with Client("my_account", api_id, api_hash) as app: + await app.send_message("me", "Greetings from **Pyrogram**!") + + + asyncio.run(main()) + +4. Replace *api_id* and *api_hash* values with your own. + +5. Save the file as ``hello.py``. + +6. Run the script with ``python3 hello.py`` + +7. Follow the instructions on your terminal to login. + +8. Watch Pyrogram send a message to yourself. + +Enjoy the API +------------- + +That was just a quick overview. In the next few pages of the introduction, we'll take a much more in-depth look of what +we have just done above. + +If you are feeling eager to continue you can take a shortcut to :doc:`../start/invoking` and come back +later to learn some more details. + +.. _community: https://t.me/Pyrogram diff --git a/docs/source/start/auth.rst b/docs/source/start/auth.rst new file mode 100644 index 00000000..ba28ac69 --- /dev/null +++ b/docs/source/start/auth.rst @@ -0,0 +1,93 @@ +Authorization +============= + +Once a :doc:`project is set up `, you will still have to follow a few steps before you can actually use Pyrogram to make +API calls. This section provides all the information you need in order to authorize yourself as user or bot. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +User Authorization +------------------ + +In order to use the API, Telegram requires that users be authorized via their phone numbers. +Pyrogram automatically manages this process, all you need to do is create an instance of the +:class:`~pyrogram.Client` class by passing to it a ``name`` of your choice (e.g.: "my_account") and call +the :meth:`~pyrogram.Client.run` method: + +.. code-block:: python + + from pyrogram import Client + + api_id = 12345 + api_hash = "0123456789abcdef0123456789abcdef" + + app = Client("my_account", api_id=api_id, api_hash=api_hash) + + app.run() + +This starts an interactive shell asking you to input your **phone number**, including your `Country Code`_ (the plus +``+`` and minus ``-`` symbols can be omitted) and the **phone code** you will receive in your devices that are already +authorized or via SMS: + +.. code-block:: text + + Enter phone number: +1-123-456-7890 + Is "+1-123-456-7890" correct? (y/n): y + Enter phone code: 12345 + Logged in successfully + +After successfully authorizing yourself, a new file called ``my_account.session`` will be created allowing Pyrogram to +execute API calls with your identity. This file is personal and will be loaded again when you restart your app. +You can now remove the api_id and api_hash values from the code as they are not needed anymore. + +.. note:: + + The code above does nothing except asking for credentials and keeping the client online, hit :guilabel:`CTRL+C` now + to stop your application and keep reading. + +Bot Authorization +----------------- + +Bots are a special kind of users that are authorized via their tokens (instead of phone numbers), which are created by +the `Bot Father`_. Bot tokens replace the users' phone numbers only — you still need to +:doc:`configure a Telegram API key <../start/setup>` with Pyrogram, even when using bots. + +The authorization process is automatically managed. All you need to do is choose a ``name`` (can be anything, +usually your bot username) and pass your bot token using the ``bot_token`` parameter. The session file will be named +after the session name, which will be ``my_bot.session`` for the example below. + +.. code-block:: python + + from pyrogram import Client + + api_id = 12345 + api_hash = "0123456789abcdef0123456789abcdef" + bot_token = "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" + + app = Client( + "my_bot", + api_id=api_id, api_hash=api_hash, + bot_token=bot_token + ) + + app.run() + +.. _Country Code: https://en.wikipedia.org/wiki/List_of_country_calling_codes +.. _Bot Father: https://t.me/botfather + +.. note:: + + The API key (api_id and api_hash) and the bot_token are not required anymore after a successful authorization. + This means you can now simply use the following: + + .. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + app.run() \ No newline at end of file diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst new file mode 100644 index 00000000..402fea8b --- /dev/null +++ b/docs/source/start/errors.rst @@ -0,0 +1,101 @@ +Error Handling +============== + +Errors can be correctly handled with ``try...except`` blocks in order to control the behaviour of your application. +Pyrogram errors all live inside the ``errors`` package: + +.. code-block:: python + + from pyrogram import errors + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +RPCError +-------- + +The father of all errors is named ``RPCError`` and is able to catch all Telegram API related errors. +This error is raised every time a method call against Telegram's API was unsuccessful. + +.. code-block:: python + + from pyrogram.errors import RPCError + +.. warning:: + + Avoid catching this error everywhere, especially when no feedback is given (i.e. by logging/printing the full error + traceback), because it makes it impossible to understand what went wrong. + +Error Categories +---------------- + +The ``RPCError`` packs together all the possible errors Telegram could raise, but to make things tidier, Pyrogram +provides categories of errors, which are named after the common HTTP errors and are subclass-ed from the ``RPCError``: + +.. code-block:: python + + from pyrogram.errors import BadRequest, Forbidden, ... + +- :doc:`303 - SeeOther <../api/errors/see-other>` +- :doc:`400 - BadRequest <../api/errors/bad-request>` +- :doc:`401 - Unauthorized <../api/errors/unauthorized>` +- :doc:`403 - Forbidden <../api/errors/forbidden>` +- :doc:`406 - NotAcceptable <../api/errors/not-acceptable>` +- :doc:`420 - Flood <../api/errors/flood>` +- :doc:`500 - InternalServerError <../api/errors/internal-server-error>` + +Single Errors +------------- + +For a fine-grained control over every single error, Pyrogram does also expose errors that deal each with a specific +issue. For example: + +.. code-block:: python + + from pyrogram.errors import FloodWait + +These errors subclass directly from the category of errors they belong to, which in turn subclass from the father +``RPCError``, thus building a class of error hierarchy such as this: + +- RPCError + - BadRequest + - ``MessageEmpty`` + - ``UsernameOccupied`` + - ``...`` + - InternalServerError + - ``RpcCallFail`` + - ``InterDcCallError`` + - ``...`` + - ``...`` + +.. _Errors: api/errors + +Unknown Errors +-------------- + +In case Pyrogram does not know anything about a specific error yet, it raises a generic error from its known category, +for example, an unknown error with error code ``400``, will be raised as a ``BadRequest``. This way you can catch the +whole category of errors and be sure to also handle these unknown errors. + +Errors with Values +------------------ + +Exception objects may also contain some informative values. For example, ``FloodWait`` holds the amount of seconds you +have to wait before you can try again, some other errors contain the DC number on which the request must be repeated on. +The value is stored in the ``value`` attribute of the exception object: + +.. code-block:: python + + import asyncio + from pyrogram.errors import FloodWait + + ... + try: + ... # Your code + except FloodWait as e: + await asyncio.sleep(e.value) # Wait N seconds before continuing + ... \ No newline at end of file diff --git a/docs/source/start/examples/bot_keyboards.rst b/docs/source/start/examples/bot_keyboards.rst new file mode 100644 index 00000000..b774a80e --- /dev/null +++ b/docs/source/start/examples/bot_keyboards.rst @@ -0,0 +1,68 @@ +bot_keyboards +============= + +This example will show you how to send normal and inline keyboards (as bot). + +You must log-in as a regular bot in order to send keyboards (use the token from @BotFather). +Any attempt in sending keyboards with a user account will be simply ignored by the server. + +send_message() is used as example, but a keyboard can be sent with any other send_* methods, +like send_audio(), send_document(), send_location(), etc... + +.. code-block:: python + + from pyrogram import Client + from pyrogram.types import (ReplyKeyboardMarkup, InlineKeyboardMarkup, + InlineKeyboardButton) + + # Create a client using your bot token + app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") + + + async def main(): + async with app: + await app.send_message( + "me", # Edit this + "This is a ReplyKeyboardMarkup example", + reply_markup=ReplyKeyboardMarkup( + [ + ["A", "B", "C", "D"], # First row + ["E", "F", "G"], # Second row + ["H", "I"], # Third row + ["J"] # Fourth row + ], + resize_keyboard=True # Make the keyboard smaller + ) + ) + + await app.send_message( + "me", # Edit this + "This is a InlineKeyboardMarkup example", + reply_markup=InlineKeyboardMarkup( + [ + [ # First row + InlineKeyboardButton( # Generates a callback query when pressed + "Button", + callback_data="data" + ), + InlineKeyboardButton( # Opens a web URL + "URL", + url="https://docs.pyrogram.org" + ), + ], + [ # Second row + InlineKeyboardButton( # Opens the inline interface + "Choose chat", + switch_inline_query="pyrogram" + ), + InlineKeyboardButton( # Opens the inline interface in the current chat + "Inline here", + switch_inline_query_current_chat="pyrogram" + ) + ] + ] + ) + ) + + + app.run(main()) \ No newline at end of file diff --git a/docs/source/start/examples/callback_queries.rst b/docs/source/start/examples/callback_queries.rst new file mode 100644 index 00000000..64da57b3 --- /dev/null +++ b/docs/source/start/examples/callback_queries.rst @@ -0,0 +1,21 @@ +callback_queries +================ + +This example shows how to handle callback queries, i.e.: queries coming from inline button presses. +It uses the @on_callback_query decorator to register a CallbackQueryHandler. + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") + + + @app.on_callback_query() + async def answer(client, callback_query): + await callback_query.answer( + f"Button contains: '{callback_query.data}'", + show_alert=True) + + + app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/echo_bot.rst b/docs/source/start/examples/echo_bot.rst new file mode 100644 index 00000000..de8288b5 --- /dev/null +++ b/docs/source/start/examples/echo_bot.rst @@ -0,0 +1,21 @@ +echo_bot +======== + +This simple echo bot replies to every private text message. + +It uses the ``@on_message`` decorator to register a ``MessageHandler`` and applies two filters on it: +``filters.text`` and ``filters.private`` to make sure it will reply to private text messages only. + +.. code-block:: python + + from pyrogram import Client, filters + + app = Client("my_account") + + + @app.on_message(filters.text & filters.private) + async def echo(client, message): + await message.reply(message.text) + + + app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/get_chat_history.rst b/docs/source/start/examples/get_chat_history.rst new file mode 100644 index 00000000..59939948 --- /dev/null +++ b/docs/source/start/examples/get_chat_history.rst @@ -0,0 +1,20 @@ +get_history +=========== + +This example shows how to get the full message history of a chat, starting from the latest message. + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + + async def main(): + async with app: + # "me" refers to your own chat (Saved Messages) + async for message in app.get_chat_history("me"): + print(message) + + + app.run(main()) \ No newline at end of file diff --git a/docs/source/start/examples/get_chat_members.rst b/docs/source/start/examples/get_chat_members.rst new file mode 100644 index 00000000..26636ca3 --- /dev/null +++ b/docs/source/start/examples/get_chat_members.rst @@ -0,0 +1,22 @@ +get_chat_members +================ + +This example shows how to get all the members of a chat. + +.. code-block:: python + + from pyrogram import Client + + # Target channel/supergroup + TARGET = -100123456789 + + app = Client("my_account") + + + async def main(): + async with app: + async for member in app.get_chat_members(TARGET): + print(member) + + + app.run(main()) \ No newline at end of file diff --git a/docs/source/start/examples/get_dialogs.rst b/docs/source/start/examples/get_dialogs.rst new file mode 100644 index 00000000..e5b10609 --- /dev/null +++ b/docs/source/start/examples/get_dialogs.rst @@ -0,0 +1,19 @@ +get_dialogs +=========== + +This example shows how to get the full dialogs list (as user). + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + + async def main(): + async with app: + async for dialog in app.get_dialogs(): + print(dialog.chat.title or dialog.chat.first_name) + + + app.run(main()) \ No newline at end of file diff --git a/docs/source/start/examples/hello_world.rst b/docs/source/start/examples/hello_world.rst new file mode 100644 index 00000000..2902241e --- /dev/null +++ b/docs/source/start/examples/hello_world.rst @@ -0,0 +1,20 @@ +hello_world +=========== + +This example demonstrates a basic API usage + +.. code-block:: python + + from pyrogram import Client + + # Create a new Client instance + app = Client("my_account") + + + async def main(): + async with app: + # Send a message, Markdown is enabled by default + await app.send_message("me", "Hi there! I'm using **Pyrogram**") + + + app.run(main()) diff --git a/docs/source/start/examples/index.rst b/docs/source/start/examples/index.rst new file mode 100644 index 00000000..d47b60e8 --- /dev/null +++ b/docs/source/start/examples/index.rst @@ -0,0 +1,46 @@ +Examples +======== + +This page contains example scripts to show you how Pyrogram looks like. + +Every script is working right away (provided you correctly set up your credentials), meaning you can simply copy-paste +and run. The only things you have to change are session names and target chats, where applicable. + +The examples listed below can be treated as building blocks for your own applications and are meant to be simple enough +to give you a basic idea. + +----- + +.. csv-table:: + :header: Example, Description + :widths: auto + :align: center + + :doc:`hello_world`, "Demonstration of basic API usage" + :doc:`echo_bot`, "Echo every private text message" + :doc:`welcome_bot`, "The Welcome Bot in @PyrogramChat" + :doc:`get_chat_history`, "Get the full message history of a chat" + :doc:`get_chat_members`, "Get all the members of a chat" + :doc:`get_dialogs`, "Get all of your dialog chats" + :doc:`callback_queries`, "Handle callback queries (as bot) coming from inline button presses" + :doc:`inline_queries`, "Handle inline queries (as bot) and answer with results" + :doc:`use_inline_bots`, "Query an inline bot (as user) and send a result to a chat" + :doc:`bot_keyboards`, "Send normal and inline keyboards using regular bots" + :doc:`raw_updates`, "Handle raw updates (old, should be avoided)" + +For more advanced examples, see https://snippets.pyrogram.org. + +.. toctree:: + :hidden: + + hello_world + echo_bot + welcome_bot + get_chat_history + get_chat_members + get_dialogs + callback_queries + inline_queries + use_inline_bots + bot_keyboards + raw_updates diff --git a/docs/source/start/examples/inline_queries.rst b/docs/source/start/examples/inline_queries.rst new file mode 100644 index 00000000..b78c6e1c --- /dev/null +++ b/docs/source/start/examples/inline_queries.rst @@ -0,0 +1,59 @@ +inline_queries +============== + +This example shows how to handle inline queries. + +Two results are generated when users invoke the bot inline mode, e.g.: @pyrogrambot hi. +It uses the @on_inline_query decorator to register an InlineQueryHandler. + +.. code-block:: python + + from pyrogram import Client + from pyrogram.types import (InlineQueryResultArticle, InputTextMessageContent, + InlineKeyboardMarkup, InlineKeyboardButton) + + app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") + + + @app.on_inline_query() + async def answer(client, inline_query): + await inline_query.answer( + results=[ + InlineQueryResultArticle( + title="Installation", + input_message_content=InputTextMessageContent( + "Here's how to install **Pyrogram**" + ), + url="https://docs.pyrogram.org/intro/install", + description="How to install Pyrogram", + reply_markup=InlineKeyboardMarkup( + [ + [InlineKeyboardButton( + "Open website", + url="https://docs.pyrogram.org/intro/install" + )] + ] + ) + ), + InlineQueryResultArticle( + title="Usage", + input_message_content=InputTextMessageContent( + "Here's how to use **Pyrogram**" + ), + url="https://docs.pyrogram.org/start/invoking", + description="How to use Pyrogram", + reply_markup=InlineKeyboardMarkup( + [ + [InlineKeyboardButton( + "Open website", + url="https://docs.pyrogram.org/start/invoking" + )] + ] + ) + ) + ], + cache_time=1 + ) + + + app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/raw_updates.rst b/docs/source/start/examples/raw_updates.rst new file mode 100644 index 00000000..463a45a8 --- /dev/null +++ b/docs/source/start/examples/raw_updates.rst @@ -0,0 +1,18 @@ +raw_updates +=========== + +This example shows how to handle raw updates. + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + + @app.on_raw_update() + async def raw(client, update, users, chats): + print(update) + + + app.run() # Automatically start() and idle() diff --git a/docs/source/start/examples/use_inline_bots.rst b/docs/source/start/examples/use_inline_bots.rst new file mode 100644 index 00000000..8a2a72ac --- /dev/null +++ b/docs/source/start/examples/use_inline_bots.rst @@ -0,0 +1,25 @@ +use_inline_bots +=============== + +This example shows how to query an inline bot (as user). + +.. code-block:: python + + from pyrogram import Client + + # Create a new Client + app = Client("my_account") + + + async def main(): + async with app: + # Get bot results for "hello" from the inline bot @vid + bot_results = await app.get_inline_bot_results("vid", "hello") + + # Send the first result to your own chat (Saved Messages) + await app.send_inline_bot_result( + "me", bot_results.query_id, + bot_results.results[0].id) + + + app.run(main()) \ No newline at end of file diff --git a/docs/source/start/examples/welcome_bot.rst b/docs/source/start/examples/welcome_bot.rst new file mode 100644 index 00000000..4e30ea7f --- /dev/null +++ b/docs/source/start/examples/welcome_bot.rst @@ -0,0 +1,30 @@ +welcome_bot +=========== + +This example uses the ``emoji`` module to easily add emoji in your text messages and ``filters`` +to make it only work for specific messages in a specific chat. + +.. code-block:: python + + from pyrogram import Client, emoji, filters + + # Target chat. Can also be a list of multiple chat ids/usernames + TARGET = -100123456789 + # Welcome message template + MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.org/)'s group chat {}!" + + app = Client("my_account") + + + # Filter in only new_chat_members updates generated in TARGET chat + @app.on_message(filters.chat(TARGET) & filters.new_chat_members) + async def welcome(client, message): + # Build the new members list (with mentions) by using their first_name + new_members = [u.mention for u in message.new_chat_members] + # Build the welcome message by using an emoji and the list we built above + text = MESSAGE.format(emoji.SPARKLES, ", ".join(new_members)) + # Send the welcome message, without the web page preview + await message.reply_text(text, disable_web_page_preview=True) + + + app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst new file mode 100644 index 00000000..415ef848 --- /dev/null +++ b/docs/source/start/invoking.rst @@ -0,0 +1,110 @@ +Invoking Methods +================ + +At this point, we have successfully :doc:`installed Pyrogram <../intro/install>` and :doc:`authorized ` our +account; we are now aiming towards the core of the framework. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Basic Usage +----------- + +Making API calls with Pyrogram is very simple. Here's a basic example we are going to examine step by step: + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + + async def main(): + async with app: + await app.send_message("me", "Hi!") + + + app.run(main()) + +Step-by-step +^^^^^^^^^^^^ + +#. Let's begin by importing the Client class. + + .. code-block:: python + + from pyrogram import Client + +#. Now instantiate a new Client object, "my_account" is a session name of your choice. + + .. code-block:: python + + app = Client("my_account") + +#. Async methods must be invoked within an async context. + Here we define an async function and put our code inside. Also notice the ``await`` keyword in front of the method + call; this is required for all asynchronous methods. + + .. code-block:: python + + async def main(): + async with app: + await app.send_message("me", "Hi!") + +#. Finally, we tell Python to schedule our ``main()`` async function by using Pyrogram's :meth:`~pyrogram.Client.run` + method. + + .. code-block:: python + + app.run(main()) + +Context Manager +--------------- + +The ``async with`` statement starts a context manager, which is used as a shortcut for starting, executing and stopping +the Client, asynchronously. It does so by automatically calling :meth:`~pyrogram.Client.start` and +:meth:`~pyrogram.Client.stop` in a more convenient way which also gracefully stops the client, even in case of +unhandled exceptions in your code. + +Below there's the same example as above, but without the use of the context manager: + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + + async def main(): + await app.start() + await app.send_message("me", "Hi!") + await app.stop() + + + app.run(main()) + +Using asyncio.run() +------------------- + +Alternatively to the :meth:`~pyrogram.Client.run` method, you can use Python's ``asyncio.run()`` to execute the main +function, with one little caveat: the Client instance (and possibly other asyncio resources you are going to use) must +be instantiated inside the main function. + +.. code-block:: python + + import asyncio + from pyrogram import Client + + + async def main(): + app = Client("my_account") + + async with app: + await app.send_message("me", "Hi!") + + + asyncio.run(main()) \ No newline at end of file diff --git a/docs/source/start/setup.rst b/docs/source/start/setup.rst new file mode 100644 index 00000000..b8fd6eff --- /dev/null +++ b/docs/source/start/setup.rst @@ -0,0 +1,40 @@ +Project Setup +============= + +We have just :doc:`installed Pyrogram <../intro/install>`. In this page we'll discuss what you need to do in order to set up a +project with the framework. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +API Key +------- + +The first step requires you to obtain a valid Telegram API key (api_id and api_hash pair): + +#. Visit https://my.telegram.org/apps and log in with your Telegram account. +#. Fill out the form with your details and register a new Telegram application. +#. Done. The API key consists of two parts: **api_id** and **api_hash**. Keep it secret. + +.. note:: + + The API key defines a token for a Telegram *application* you are going to build. + This means that you are able to authorize multiple users or bots with a single API key. + +Configuration +------------- + +Having the API key from the previous step in handy, we can now begin to configure a Pyrogram project: pass your API key to Pyrogram by using the *api_id* and *api_hash* parameters of the Client class: + +.. code-block:: python + + from pyrogram import Client + + api_id = 12345 + api_hash = "0123456789abcdef0123456789abcdef" + + app = Client("my_account", api_id=api_id, api_hash=api_hash) \ No newline at end of file diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst new file mode 100644 index 00000000..685128c2 --- /dev/null +++ b/docs/source/start/updates.rst @@ -0,0 +1,78 @@ +Handling Updates +================ + +:doc:`Invoking API methods ` sequentially is one way to use Pyrogram. This page deals with Telegram updates +and how to handle new incoming messages or other events in Pyrogram. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Defining Updates +---------------- + +Updates are events that happen in your Telegram account (incoming messages, new members join, +bot button presses, etc.), which are meant to notify you about a new specific state that has changed. These updates are +handled by registering one or more callback functions in your app using :doc:`Handlers <../api/handlers>`. + +Each handler deals with a specific event and once a matching update arrives from Telegram, your registered callback +function will be called back by the framework and its body executed. + +Registering a Handler +--------------------- + +To explain how handlers work let's examine the one which will be in charge for handling :class:`~pyrogram.types.Message` +updates coming from all around your chats. Every other kind of handler shares the same setup logic and you should not +have troubles settings them up once you learn from this section. + +Using Decorators +^^^^^^^^^^^^^^^^ + +The most elegant way to register a message handler is by using the :meth:`~pyrogram.Client.on_message` decorator: + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + + @app.on_message() + async def my_handler(client, message): + await message.forward("me") + + + app.run() + +The defined function ``my_handler``, which accepts the two arguments *(client, message)*, will be the function that gets +executed every time a new message arrives. + +In the last line we see again the :meth:`~pyrogram.Client.run` method, this time used without any argument. +Its purpose here is simply to automatically :meth:`~pyrogram.Client.start`, keep the Client online so that it can listen +for updates and :meth:`~pyrogram.Client.stop` it once you hit ``CTRL+C``. + +Using add_handler() +^^^^^^^^^^^^^^^^^^^ + +The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback +function and registers it in your Client. It is useful in case you want to programmatically add handlers. + +.. code-block:: python + + from pyrogram import Client + from pyrogram.handlers import MessageHandler + + + async def my_function(client, message): + await message.forward("me") + + + app = Client("my_account") + + my_handler = MessageHandler(my_function) + app.add_handler(my_handler) + + app.run() diff --git a/docs/source/support.rst b/docs/source/support.rst new file mode 100644 index 00000000..8efa4bc1 --- /dev/null +++ b/docs/source/support.rst @@ -0,0 +1,63 @@ +Support Pyrogram +================ + +.. raw:: html + + + +
+ Star + + Fork +
+ +
+ +Pyrogram is a free and open source project. +If you enjoy Pyrogram and would like to show your appreciation, consider donating or becoming +a sponsor of the project. You can support Pyrogram via the ways shown below: + +----- + +GitHub Sponsor +-------------- + +`Become a GitHub sponsor `_. + +.. raw:: html + + Sponsor + +----- + +LiberaPay Patron +---------------- + +`Become a LiberaPay patron `_. + +.. raw:: html + + + +----- + +OpenCollective Backer +--------------------- + +`Become an OpenCollective backer `_ + +.. raw:: html + + \ No newline at end of file diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst new file mode 100644 index 00000000..df99042d --- /dev/null +++ b/docs/source/topics/advanced-usage.rst @@ -0,0 +1,124 @@ +Advanced Usage +============== + +Pyrogram's API -- which consists of well documented :doc:`methods <../api/methods/index>` and +:doc:`types <../api/types/index>` -- exists to provide an easier interface to the more complex Telegram API. + +In this section, you'll be shown the alternative way of communicating with Telegram using Pyrogram: the main "raw" +Telegram API with its functions and types. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Telegram Raw API +---------------- + +If you can't find a high-level method for your needs or if you want complete, low-level access to the whole +Telegram API, you have to use the raw :mod:`~pyrogram.raw.functions` and :mod:`~pyrogram.raw.types`. + +As already hinted, raw functions and types can be less convenient. This section will therefore explain some pitfalls to +take into consideration when working with the raw API. + +.. tip:: + + Every available high-level method in Pyrogram is built on top of these raw functions. + +Invoking Functions +------------------ + +Unlike the :doc:`methods <../api/methods/index>` found in Pyrogram's API, which can be called in the usual simple way, +functions to be invoked from the raw Telegram API have a different way of usage. + +First of all, both :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>` +live in their respective packages (and sub-packages): ``pyrogram.raw.functions``, ``pyrogram.raw.types``. They all exist +as Python classes, meaning you need to create an instance of each every time you need them and fill them in with the +correct values using named arguments. + +Next, to actually invoke the raw function you have to use the :meth:`~pyrogram.Client.invoke` method provided by the +Client class and pass the function object you created. + +Here's some examples: + +- Update first name, last name and bio: + + .. code-block:: python + + from pyrogram import Client + from pyrogram.raw import functions + + async with Client("my_account") as app: + await app.invoke( + functions.account.UpdateProfile( + first_name="First Name", last_name="Last Name", + about="New bio text" + ) + ) + +- Set online/offline status: + + .. code-block:: python + + from pyrogram import Client + from pyrogram.raw import functions, types + + async with Client("my_account") as app: + # Set online status + await app.invoke(functions.account.UpdateStatus(offline=False)) + + # Set offline status + await app.invoke(functions.account.UpdateStatus(offline=True)) + +- Get chat info: + + .. code-block:: python + + from pyrogram import Client + from pyrogram.raw import functions, types + + async with Client("my_account") as app: + r = await app.invoke( + functions.channels.GetFullChannel( + channel=app.resolve_peer("username") + ) + ) + + print(r) + +Chat IDs +-------- + +The way Telegram works makes it not possible to directly send a message to a user or a chat by using their IDs only. +Instead, a pair of ``id`` and ``access_hash`` wrapped in a so called ``InputPeer`` is always needed. Pyrogram allows +sending messages with IDs only thanks to cached access hashes. + +There are three different InputPeer types, one for each kind of Telegram entity. +Whenever an InputPeer is needed you must pass one of these: + +- :class:`~pyrogram.raw.types.InputPeerUser` - Users +- :class:`~pyrogram.raw.types.InputPeerChat` - Basic Chats +- :class:`~pyrogram.raw.types.InputPeerChannel` - Channels & Supergroups + +But you don't necessarily have to manually instantiate each object because Pyrogram already provides +:meth:`~pyrogram.Client.resolve_peer` as a convenience utility method that returns the correct InputPeer +by accepting a peer ID only. + +Another thing to take into consideration about chat IDs is the way they are represented: they are all integers and +all positive within their respective raw types. + +Things are different when working with Pyrogram's API because having them in the same space could lead to +collisions, and that's why Pyrogram uses a slightly different representation for each kind of ID. + +For example, given the ID *123456789*, here's how Pyrogram can tell entities apart: + +- ``+ID`` User: *123456789* +- ``-ID`` Chat: *-123456789* +- ``-100ID`` Channel or Supergroup: *-100123456789* + +So, every time you take a raw ID, make sure to translate it into the correct ID when you want to use it with an +high-level method. + +.. _Community: https://t.me/Pyrogram \ No newline at end of file diff --git a/docs/source/topics/client-settings.rst b/docs/source/topics/client-settings.rst new file mode 100644 index 00000000..02dce713 --- /dev/null +++ b/docs/source/topics/client-settings.rst @@ -0,0 +1,46 @@ +Client Settings +=============== + +You can control the way your client appears in the Active Sessions menu of an official client by changing some client +settings. By default you will see something like the following: + +- Device Model: ``CPython x.y.z`` +- Application: ``Pyrogram x.y.z`` +- System Version: ``Linux x.y.z`` + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Set Custom Values +----------------- + +To set custom values, you can pass the arguments directly in the Client's constructor. + +.. code-block:: python + + app = Client( + "my_account", + app_version="1.2.3", + device_model="PC", + system_version="Linux" + ) + +Set Custom Languages +-------------------- + +To tell Telegram in which language should speak to you (terms of service, bots, service messages, ...) you can +set ``lang_code`` in `ISO 639-1 `_ standard (defaults to "en", +English). + +With the following code we make Telegram know we want it to speak in Italian (it): + +.. code-block:: python + + app = Client( + "my_account", + lang_code="it", + ) \ No newline at end of file diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst new file mode 100644 index 00000000..f8c05af6 --- /dev/null +++ b/docs/source/topics/create-filters.rst @@ -0,0 +1,109 @@ +Creating Filters +================ + +Pyrogram already provides lots of built-in :class:`~pyrogram.filters` to work with, but in case you can't find a +specific one for your needs or want to build a custom filter by yourself you can use +:meth:`filters.create() `. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Custom Filters +-------------- + +An example to demonstrate how custom filters work is to show how to create and use one for the +:class:`~pyrogram.handlers.CallbackQueryHandler`. Note that callback queries updates are only received by bots as result +of a user pressing an inline button attached to the bot's message; create and :doc:`authorize your bot <../start/auth>`, +then send a message with an inline keyboard to yourself. This allows you to test your filter by pressing the inline +button: + +.. code-block:: python + + from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton + + await app.send_message( + "username", # Change this to your username or id + "Pyrogram custom filter test", + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton("Press me", "pyrogram")]] + ) + ) + +Basic Filters +------------- + +For this basic filter we will be using only the first parameter of :meth:`~pyrogram.filters.create`. + +The heart of a filter is its callback function, which accepts three arguments *(self, client, update)* and returns +either ``True``, in case you want the update to pass the filter or ``False`` otherwise. + +In this example we are matching the query data to "pyrogram", which means that the filter will only allow callback +queries containing "pyrogram" as data: + +.. code-block:: python + + from pyrogram import filters + + async def func(_, __, query): + return query.data == "pyrogram" + + static_data_filter = filters.create(func) + + +The first two arguments of the callback function are unused here and because of this we named them using underscores. + +Finally, the filter usage remains the same: + +.. code-block:: python + + @app.on_callback_query(static_data_filter) + async def pyrogram_data(_, query): + query.answer("it works!") + +Filters with Arguments +---------------------- + +A more flexible filter would be one that accepts "pyrogram" or any other string as argument at usage time. +A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.filters.create` method and the +first argument of the callback function, which is a reference to the filter object itself holding the extra data passed +via named arguments. + +This is how a dynamic custom filter looks like: + +.. code-block:: python + + from pyrogram import filters + + def dynamic_data_filter(data): + async def func(flt, _, query): + return flt.data == query.data + + # "data" kwarg is accessed with "flt.data" above + return filters.create(func, data=data) + +And finally its usage: + +.. code-block:: python + + @app.on_callback_query(dynamic_data_filter("pyrogram")) + async def pyrogram_data(_, query): + query.answer("it works!") + + +Method Calls Inside Filters +--------------------------- + +The missing piece we haven't covered yet is the second argument of a filter callback function, namely, the ``client`` +argument. This is a reference to the :obj:`~pyrogram.Client` instance that is running the filter and it is useful in +case you would like to make some API calls before deciding whether the filter should allow the update or not: + +.. code-block:: python + + async def func(_, client, query): + # r = await client.some_api_method() + # check response "r" and decide to return True or False + ... \ No newline at end of file diff --git a/docs/source/topics/debugging.rst b/docs/source/topics/debugging.rst new file mode 100644 index 00000000..1c0ac069 --- /dev/null +++ b/docs/source/topics/debugging.rst @@ -0,0 +1,122 @@ +Debugging +========= + +When working with the API, chances are you'll stumble upon bugs, get stuck and start wondering how to continue. Nothing +to actually worry about since Pyrogram provides some commodities to help you in this. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Caveman Debugging +----------------- + + *The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.* + + -- Brian Kernighan, "Unix for Beginners" (1979) + +Adding ``print()`` statements in crucial parts of your code is by far the most ancient, yet efficient technique for +debugging programs, especially considering the concurrent nature of the framework itself. Pyrogram goodness in this +respect comes with the fact that any object can be nicely printed just by calling ``print(obj)``, thus giving to you +an insight of all its inner details. + +Consider the following code: + +.. code-block:: python + + me = await app.get_users("me") + print(me) # User + +This will show a JSON representation of the object returned by :meth:`~pyrogram.Client.get_users`, which is a +:class:`~pyrogram.types.User` instance, in this case. The output on your terminal will be something similar to this: + +.. code-block:: json + + { + "_": "User", + "id": 123456789, + "is_self": true, + "is_contact": false, + "is_mutual_contact": false, + "is_deleted": false, + "is_bot": false, + "is_verified": false, + "is_restricted": false, + "is_support": false, + "first_name": "Pyrogram", + "photo": { + "_": "ChatPhoto", + "small_file_id": "AbCdE...EdCbA", + "small_photo_unique_id": "VwXyZ...ZyXwV", + "big_file_id": "AbCdE...EdCbA", + "big_photo_unique_id": "VwXyZ...ZyXwV" + } + } + +As you've probably guessed already, Pyrogram objects can be nested. That's how compound data are built, and nesting +keeps going until we are left with base data types only, such as ``str``, ``int``, ``bool``, etc. + +Accessing Attributes +-------------------- + +Even though you see a JSON output, it doesn't mean we are dealing with dictionaries; in fact, all Pyrogram types are +fully-fledged Python objects and the correct way to access any attribute of them is by using the dot notation ``.``: + +.. code-block:: python + + photo = me.photo + print(photo) # ChatPhoto + +.. code-block:: json + + { + "_": "ChatPhoto", + "small_file_id": "AbCdE...EdCbA", + "small_photo_unique_id": "VwXyZ...ZyXwV", + "big_file_id": "AbCdE...EdCbA", + "big_photo_unique_id": "VwXyZ...ZyXwV" + } + +Checking an Object's Type +------------------------- + +Another thing worth talking about is how to tell and check for an object's type. + +As you noticed already, when printing an object you'll see the special attribute ``"_"``. This is just a visual thing +useful to show humans the object type, but doesn't really exist anywhere; any attempt in accessing it will lead to an +error. The correct way to get the object type is by using the built-in function ``type()``: + +.. code-block:: python + + status = me.status + print(type(status)) + +.. code-block:: text + + + +And to check if an object is an instance of a given class, you use the built-in function ``isinstance()``: + +.. code-block:: python + :name: this-py + + from pyrogram.types import UserStatus + + status = me.status + print(isinstance(status, UserStatus)) + +.. code-block:: text + + True + +.. raw:: html + + \ No newline at end of file diff --git a/docs/source/topics/more-on-updates.rst b/docs/source/topics/more-on-updates.rst new file mode 100644 index 00000000..18c1a68a --- /dev/null +++ b/docs/source/topics/more-on-updates.rst @@ -0,0 +1,226 @@ +More on Updates +=============== + +Here we'll show some advanced usages when working with :doc:`update handlers <../start/updates>` and +:doc:`filters `. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Handler Groups +-------------- + +If you register handlers with overlapping (conflicting) filters, only the first one is executed and any other handler +will be ignored. This is intended by design. + +In order to handle the very same update more than once, you have to register your handler in a different dispatching +group. Dispatching groups hold one or more handlers and are processed sequentially, they are identified by a number +(number 0 being the default) and sorted, that is, a lower group number has a higher priority: + +For example, take these two handlers: + +.. code-block:: python + + @app.on_message(filters.text | filters.sticker) + async def text_or_sticker(client, message): + print("Text or Sticker") + + + @app.on_message(filters.text) + async def just_text(client, message): + print("Just Text") + +Here, ``just_text`` is never executed because ``text_or_sticker``, which has been registered first, already handles +texts (``filters.text`` is shared and conflicting). To enable it, register the handler using a different group: + +.. code-block:: python + + @app.on_message(filters.text, group=1) + async def just_text(client, message): + print("Just Text") + +Or, if you want ``just_text`` to be executed *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``): + +.. code-block:: python + + @app.on_message(filters.text, group=-1) + async def just_text(client, message): + print("Just Text") + +With :meth:`~pyrogram.Client.add_handler` (without decorators) the same can be achieved with: + +.. code-block:: python + + app.add_handler(MessageHandler(just_text, filters.text), -1) + +Update propagation +------------------ + +Registering multiple handlers, each in a different group, becomes useful when you want to handle the same update more +than once. Any incoming update will be sequentially processed by all of your registered functions by respecting the +groups priority policy described above. Even in case any handler raises an unhandled exception, Pyrogram will still +continue to propagate the same update to the next groups until all the handlers are done. Example: + +.. code-block:: python + + @app.on_message(filters.private) + async def _(client, message): + print(0) + + + @app.on_message(filters.private, group=1) + async def _(client, message): + raise Exception("Unhandled exception!") # Simulate an unhandled exception + + + @app.on_message(filters.private, group=2) + async def _(client, message): + print(2) + +All these handlers will handle the same kind of messages, that are, messages sent or received in private chats. +The output for each incoming update will therefore be: + +.. code-block:: text + + 0 + Exception: Unhandled exception! + 2 + +Stop Propagation +^^^^^^^^^^^^^^^^ + +In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following: + +- Call the update's bound-method ``.stop_propagation()`` (preferred way). +- Manually ``raise StopPropagation`` exception (more suitable for raw updates only). + +.. note:: + + Internally, the propagation is stopped by handling a custom exception. ``.stop_propagation()`` is just an elegant + and intuitive way to ``raise StopPropagation``; this also means that any code coming *after* calling the method + won't be executed as your function just raised an exception to signal the dispatcher not to propagate the + update anymore. + +Example with ``stop_propagation()``: + +.. code-block:: python + + @app.on_message(filters.private) + async def _(client, message): + print(0) + + + @app.on_message(filters.private, group=1) + async def _(client, message): + print(1) + message.stop_propagation() + + + @app.on_message(filters.private, group=2) + async def _(client, message): + print(2) + +Example with ``raise StopPropagation``: + +.. code-block:: python + + from pyrogram import StopPropagation + + @app.on_message(filters.private) + async def _(client, message): + print(0) + + + @app.on_message(filters.private, group=1) + async ef _(client, message): + print(1) + raise StopPropagation + + + @app.on_message(filters.private, group=2) + async def _(client, message): + print(2) + +Each handler is registered in a different group, but the handler in group number 2 will never be executed because the +propagation was stopped earlier. The output of both (equivalent) examples will be: + +.. code-block:: text + + 0 + 1 + +Continue Propagation +^^^^^^^^^^^^^^^^^^^^ + +As opposed to `stopping the update propagation <#stop-propagation>`_ and also as an alternative to the +`handler groups <#handler-groups>`_, you can signal the internal dispatcher to continue the update propagation within +**the same group** despite having conflicting filters in the next registered handler. This allows you to register +multiple handlers with overlapping filters in the same group; to let the dispatcher process the next handler you can do +*one* of the following in each handler you want to grant permission to continue: + +- Call the update's bound-method ``.continue_propagation()`` (preferred way). +- Manually ``raise ContinuePropagation`` exception (more suitable for raw updates only). + +.. note:: + + Internally, the propagation is continued by handling a custom exception. ``.continue_propagation()`` is just an + elegant and intuitive way to ``raise ContinuePropagation``; this also means that any code coming *after* calling the + method won't be executed as your function just raised an exception to signal the dispatcher to continue with the + next available handler. + + +Example with ``continue_propagation()``: + +.. code-block:: python + + @app.on_message(filters.private) + async def _(client, message): + print(0) + message.continue_propagation() + + + @app.on_message(filters.private) + async def _(client, message): + print(1) + message.continue_propagation() + + + @app.on_message(filters.private) + async def _(client, message): + print(2) + +Example with ``raise ContinuePropagation``: + +.. code-block:: python + + from pyrogram import ContinuePropagation + + @app.on_message(filters.private) + async def _(client, message): + print(0) + raise ContinuePropagation + + + @app.on_message(filters.private) + async def _(client, message): + print(1) + raise ContinuePropagation + + + @app.on_message(filters.private) + async def _(client, message): + print(2) + +Three handlers are registered in the same group, and all of them will be executed because the propagation was continued +in each handler (except in the last one, where is useless to do so since there is no more handlers after). +The output of both (equivalent) examples will be: + +.. code-block:: text + + 0 + 1 + 2 diff --git a/docs/source/topics/mtproto-vs-botapi.rst b/docs/source/topics/mtproto-vs-botapi.rst new file mode 100644 index 00000000..9681c1eb --- /dev/null +++ b/docs/source/topics/mtproto-vs-botapi.rst @@ -0,0 +1,112 @@ +MTProto vs. Bot API +=================== + +Pyrogram is a framework written from the ground up that acts as a fully-fledged Telegram client based on the MTProto +API. This means that Pyrogram is able to execute any official client and bot API action and more. This page will +therefore show you why Pyrogram might be a better choice for your project by comparing the two APIs, but first, let's +make it clear what actually is the MTProto and the Bot API. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +What is the MTProto API? +------------------------ + +`MTProto`_, took alone, is the name of the custom-made, open and encrypted communication protocol created by Telegram +itself --- it's the only protocol used to exchange information between a client and the actual Telegram servers. + +The MTProto API on the other hand, is what people for convenience call the main Telegram API in order to distinguish it +from the Bot API. The main Telegram API is able to authorize both users and bots and is built on top of the MTProto +encryption protocol by means of `binary data serialized`_ in a specific way, as described by the `TL language`_, and +delivered using UDP, TCP or even HTTP as transport-layer protocol. Clients that make use of Telegram's main API, such as +Pyrogram, implement all these details. + +.. _MTProto: https://core.telegram.org/mtproto +.. _binary data serialized: https://core.telegram.org/mtproto/serialize +.. _TL language: https://core.telegram.org/mtproto/TL + +What is the Bot API? +-------------------- + +The `Bot API`_ is an HTTP(S) interface for building normal bots using a sub-set of the main Telegram API. Bots are +special accounts that are authorized via tokens instead of phone numbers. The Bot API is built yet again on top of the +main Telegram API, but runs on an intermediate server application that in turn communicates with the actual Telegram +servers using MTProto. + +.. figure:: //_static/img/mtproto-vs-bot-api.png + :align: center + +.. _Bot API: https://core.telegram.org/bots/api + +Advantages of the MTProto API +----------------------------- + +Here is a non-exhaustive list of all the advantages in using MTProto-based libraries -- such as Pyrogram -- instead of +the official HTTP Bot API. Using Pyrogram you can: + +.. hlist:: + :columns: 1 + + - :guilabel:`+` **Authorize both user and bot identities** + - :guilabel:`--` The Bot API only allows bot accounts + +.. hlist:: + :columns: 1 + + - :guilabel:`+` **Upload & download any file, up to 2000 MiB each (~2 GB)** + - :guilabel:`--` The Bot API allows uploads and downloads of files only up to 50 MB / 20 MB in size (respectively). + +.. hlist:: + :columns: 1 + + - :guilabel:`+` **Has less overhead due to direct connections to Telegram** + - :guilabel:`--` The Bot API uses an intermediate server to handle HTTP requests before they are sent to the actual + Telegram servers. + +.. hlist:: + :columns: 1 + + - :guilabel:`+` **Run multiple sessions at once (for both user and bot identities)** + - :guilabel:`--` The Bot API intermediate server will terminate any other session in case you try to use the same + bot again in a parallel connection. + +.. hlist:: + :columns: 1 + + - :guilabel:`+` **Has much more detailed types and powerful methods** + - :guilabel:`--` The Bot API types often miss some useful information about Telegram entities and some of the + methods are limited as well. + +.. hlist:: + :columns: 1 + + - :guilabel:`+` **Obtain information about any message existing in a chat using their ids** + - :guilabel:`--` The Bot API simply doesn't support this + +.. hlist:: + :columns: 1 + + - :guilabel:`+` **Retrieve the whole chat members list of either public or private chats** + - :guilabel:`--` The Bot API simply doesn't support this + +.. hlist:: + :columns: 1 + + - :guilabel:`+` **Receive extra updates, such as the one about a user name change** + - :guilabel:`--` The Bot API simply doesn't support this + +.. hlist:: + :columns: 1 + + - :guilabel:`+` **Has more meaningful errors in case something went wrong** + - :guilabel:`--` The Bot API reports less detailed errors + +.. hlist:: + :columns: 1 + + - :guilabel:`+` **Get API version updates, and thus new features, sooner** + - :guilabel:`--` The Bot API is simply slower in implementing new features diff --git a/docs/source/topics/proxy.rst b/docs/source/topics/proxy.rst new file mode 100644 index 00000000..28674349 --- /dev/null +++ b/docs/source/topics/proxy.rst @@ -0,0 +1,34 @@ +Proxy Settings +============== + +Pyrogram supports proxies with and without authentication. This feature allows Pyrogram to exchange data with Telegram +through an intermediate SOCKS 4/5 or HTTP (CONNECT) proxy server. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Usage +----- + +To use Pyrogram with a proxy, use the *proxy* parameter in the Client class. If your proxy doesn't require authorization +you can omit ``username`` and ``password``. + +.. code-block:: python + + from pyrogram import Client + + proxy = { + "scheme": "socks5", # "socks4", "socks5" and "http" are supported + "hostname": "11.22.33.44", + "port": 1234, + "username": "username", + "password": "password" + } + + app = Client("my_account", proxy=proxy) + + app.run() diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst new file mode 100644 index 00000000..a67a9254 --- /dev/null +++ b/docs/source/topics/scheduling.rst @@ -0,0 +1,65 @@ +Scheduling Tasks +================ + +Scheduling tasks means executing one or more functions periodically at pre-defined intervals or after a delay. This is +useful, for example, to send recurring messages to specific chats or users. + +This page will show examples on how to integrate Pyrogram with ``apscheduler`` in both asynchronous and +non-asynchronous contexts. For more detailed information, you can visit and learn from the library documentation. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Using apscheduler +----------------- + +- Install with ``pip3 install apscheduler`` +- Documentation: https://apscheduler.readthedocs.io + +Asynchronously +^^^^^^^^^^^^^^ + +.. code-block:: python + + from apscheduler.schedulers.asyncio import AsyncIOScheduler + + from pyrogram import Client + + app = Client("my_account") + + + async def job(): + await app.send_message("me", "Hi!") + + + scheduler = AsyncIOScheduler() + scheduler.add_job(job, "interval", seconds=3) + + scheduler.start() + app.run() + +Non-Asynchronously +^^^^^^^^^^^^^^^^^^ + +.. code-block:: python + + from apscheduler.schedulers.background import BackgroundScheduler + + from pyrogram import Client + + app = Client("my_account") + + + def job(): + app.send_message("me", "Hi!") + + + scheduler = BackgroundScheduler() + scheduler.add_job(job, "interval", seconds=3) + + scheduler.start() + app.run() diff --git a/docs/source/topics/serializing.rst b/docs/source/topics/serializing.rst new file mode 100644 index 00000000..3dc644f8 --- /dev/null +++ b/docs/source/topics/serializing.rst @@ -0,0 +1,56 @@ +Object Serialization +==================== + +Serializing means converting a Pyrogram object, which exists as Python class instance, to a text string that can be +easily shared and stored anywhere. Pyrogram provides two formats for serializing its objects: one good looking for +humans and another more compact for machines that is able to recover the original structures. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +For Humans - str(obj) +--------------------- + +If you want a nicely formatted, human readable JSON representation of any object in the API you can use ``str(obj)``. + +.. code-block:: python + + ... + + async with app: + r = await app.get_chat("me") + print(str(r)) + +.. tip:: + + When using ``print()`` you don't actually need to use ``str()`` on the object because it is called automatically, we + have done that above just to show you how to explicitly convert a Pyrogram object to JSON. + +For Machines - repr(obj) +------------------------ + +If you want to share or store objects for future references in a more compact way, you can use ``repr(obj)``. While +still pretty much readable, this format is not intended for humans. The advantage of this format is that once you +serialize your object, you can use ``eval()`` to get back the original structure; just make sure to ``import pyrogram``, +as the process requires the package to be in scope. + +.. code-block:: python + + import pyrogram + + ... + + async with app: + r = await app.get_chat("me") + + print(repr(r)) + print(eval(repr(r)) == r) # True + +.. note:: + + Type definitions are subject to changes between versions. You should make sure to store and load objects using the + same Pyrogram version. \ No newline at end of file diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst new file mode 100644 index 00000000..c378c9d8 --- /dev/null +++ b/docs/source/topics/smart-plugins.rst @@ -0,0 +1,306 @@ +Smart Plugins +============= + +Pyrogram embeds a smart, lightweight yet powerful plugin system that is meant to further simplify the organization +of large projects and to provide a way for creating pluggable (modular) components that can be easily shared across +different Pyrogram applications with minimal boilerplate code. + +.. tip:: + + Smart Plugins are completely optional and disabled by default. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Introduction +------------ + +Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize +your applications, you had to put your function definitions in separate files and register them inside your main script +after importing your modules, like this: + +.. note:: + + This is an example application that replies in private chats with two messages: one containing the same + text message you sent and the other containing the reversed text message. + + Example: *"Pyrogram"* replies with *"Pyrogram"* and *"margoryP"* + +.. code-block:: text + + myproject/ + handlers.py + main.py + +- ``handlers.py`` + + .. code-block:: python + + async def echo(client, message): + await message.reply(message.text) + + + async def echo_reversed(client, message): + await message.reply(message.text[::-1]) + +- ``main.py`` + + .. code-block:: python + + from pyrogram import Client, filters + from pyrogram.handlers import MessageHandler + + from handlers import echo, echo_reversed + + app = Client("my_account") + + app.add_handler( + MessageHandler( + echo, + filters.text & filters.private)) + + app.add_handler( + MessageHandler( + echo_reversed, + filters.text & filters.private), + group=1) + + app.run() + +This is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to +manually ``import``, manually :meth:`~pyrogram.Client.add_handler` and manually instantiate each +:class:`~pyrogram.handlers.MessageHandler` object because you can't use decorators for your functions. +So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically. + +Using Smart Plugins +------------------- + +Setting up your Pyrogram project to accommodate Smart Plugins is pretty straightforward: + +#. Create a new folder to store all the plugins (e.g.: "plugins", "handlers", ...). +#. Put your python files full of plugins inside. Organize them as you wish. +#. Enable plugins in your Client. + +.. note:: + + This is the same example application as shown above, written using the Smart Plugin system. + +.. code-block:: text + + myproject/ + plugins/ + handlers.py + main.py + +- ``plugins/handlers.py`` + + .. code-block:: python + + from pyrogram import Client, filters + + + @Client.on_message(filters.text & filters.private) + async def echo(client, message): + await message.reply(message.text) + + + @Client.on_message(filters.text & filters.private, group=1) + async def echo_reversed(client, message): + await message.reply(message.text[::-1]) + +- ``main.py`` + + .. code-block:: python + + from pyrogram import Client + + plugins = dict(root="plugins") + + Client("my_account", plugins=plugins).run() + + +The first important thing to note is the new ``plugins`` folder. You can put *any python file* in *any subfolder* and +each file can contain *any decorated function* (handlers) with one limitation: within a single module (file) you must +use different names for each decorated function. + +The second thing is telling Pyrogram where to look for your plugins: you can use the Client parameter "plugins"; +the *root* value must match the name of your plugins root folder. Your Pyrogram Client instance will **automatically** +scan the folder upon starting to search for valid handlers and register them for you. + +Then you'll notice you can now use decorators. That's right, you can apply the usual decorators to your callback +functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class) +instead of the usual ``@app`` (Client instance) and things will work just the same. + +Specifying the Plugins to include +--------------------------------- + +By default, if you don't explicitly supply a list of plugins, every valid one found inside your plugins root folder will +be included by following the alphabetical order of the directory structure (files and subfolders); the single handlers +found inside each module will be, instead, loaded in the order they are defined, from top to bottom. + +.. note:: + + Remember: there can be at most one handler, within a group, dealing with a specific update. Plugins with overlapping + filters included a second time will not work, by design. Learn more at :doc:`More on Updates `. + +This default loading behaviour is usually enough, but sometimes you want to have more control on what to include (or +exclude) and in which exact order to load plugins. The way to do this is to make use of ``include`` and ``exclude`` +directives in the dictionary passed as Client argument. Here's how they work: + +- If both ``include`` and ``exclude`` are omitted, all plugins are loaded as described above. +- If ``include`` is given, only the specified plugins will be loaded, in the order they are passed. +- If ``exclude`` is given, the plugins specified here will be unloaded. + +The ``include`` and ``exclude`` value is a **list of strings**. Each string containing the path of the module relative +to the plugins root folder, in Python notation (dots instead of slashes). + + E.g.: ``subfolder.module`` refers to ``plugins/subfolder/module.py``, with ``root="plugins"``. + +You can also choose the order in which the single handlers inside a module are loaded, thus overriding the default +top-to-bottom loading policy. You can do this by appending the name of the functions to the module path, each one +separated by a blank space. + + E.g.: ``subfolder.module fn2 fn1 fn3`` will load *fn2*, *fn1* and *fn3* from *subfolder.module*, in this order. + +Examples +^^^^^^^^ + +Given this plugins folder structure with three modules, each containing their own handlers (fn1, fn2, etc...), which are +also organized in subfolders: + +.. code-block:: text + + myproject/ + plugins/ + subfolder1/ + plugins1.py + - fn1 + - fn2 + - fn3 + subfolder2/ + plugins2.py + ... + plugins0.py + ... + ... + +- Load every handler from every module, namely *plugins0.py*, *plugins1.py* and *plugins2.py* in alphabetical order + (files) and definition order (handlers inside files): + + .. code-block:: python + + plugins = dict(root="plugins") + + Client("my_account", plugins=plugins).run() + +- Load only handlers defined inside *plugins2.py* and *plugins0.py*, in this order: + + .. code-block:: python + + plugins = dict( + root="plugins", + include=[ + "subfolder2.plugins2", + "plugins0" + ] + ) + + Client("my_account", plugins=plugins).run() + +- Load everything except the handlers inside *plugins2.py*: + + .. code-block:: python + + plugins = dict( + root="plugins", + exclude=["subfolder2.plugins2"] + ) + + Client("my_account", plugins=plugins).run() + +- Load only *fn3*, *fn1* and *fn2* (in this order) from *plugins1.py*: + + .. code-block:: python + + plugins = dict( + root="plugins", + include=["subfolder1.plugins1 fn3 fn1 fn2"] + ) + + Client("my_account", plugins=plugins).run() + +Load/Unload Plugins at Runtime +------------------------------ + +In the previous section we've explained how to specify which plugins to load and which to ignore before your Client +starts. Here we'll show, instead, how to unload and load again a previously registered plugin at runtime. + +Each function decorated with the usual ``on_message`` decorator (or any other decorator that deals with Telegram +updates) will be modified in such a way that a special ``handlers`` attribute pointing to a list of tuples of +*(handler: Handler, group: int)* is attached to the function object itself. + +- ``plugins/handlers.py`` + + .. code-block:: python + + @Client.on_message(filters.text & filters.private) + async def echo(client, message): + await message.reply(message.text) + + print(echo) + print(echo.handlers) + +- Printing ``echo`` will show something like ````. + +- Printing ``echo.handlers`` will reveal the handlers, that is, a list of tuples containing the actual handlers and + the groups they were registered on ``[(, 0)]``. + +Unloading +^^^^^^^^^ + +In order to unload a plugin, all you need to do is obtain a reference to it by importing the relevant module and call +:meth:`~pyrogram.Client.remove_handler` Client's method with your function's *handler* instance: + +- ``main.py`` + + .. code-block:: python + + from plugins.handlers import echo + + handlers = echo.handlers + + for h in handlers: + app.remove_handler(*h) + +The star ``*`` operator is used to unpack the tuple into positional arguments so that *remove_handler* will receive +exactly what is needed. The same could have been achieved with: + +.. code-block:: python + + handlers = echo.handlers + handler, group = handlers[0] + + app.remove_handler(handler, group) + +Loading +^^^^^^^ + +Similarly to the unloading process, in order to load again a previously unloaded plugin you do the same, but this time +using :meth:`~pyrogram.Client.add_handler` instead. Example: + +- ``main.py`` + + .. code-block:: python + + from plugins.handlers import echo + + ... + + handlers = echo.handlers + + for h in handlers: + app.add_handler(*h) \ No newline at end of file diff --git a/docs/source/topics/speedups.rst b/docs/source/topics/speedups.rst new file mode 100644 index 00000000..821b26f4 --- /dev/null +++ b/docs/source/topics/speedups.rst @@ -0,0 +1,88 @@ +Speedups +======== + +Pyrogram's speed can be boosted up by using TgCrypto and uvloop. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +TgCrypto +-------- + +TgCrypto_ is a high-performance, easy-to-install cryptography library specifically written in C for Pyrogram as a Python +extension. It is a replacement for a slower Python-only alternative and implements the cryptographic algorithms Telegram +requires, namely: AES-256-IGE, AES-256-CTR and AES-256-CBC. + +Installation +^^^^^^^^^^^^ + +.. code-block:: bash + + $ pip3 install -U tgcrypto + +Usage +^^^^^ + +Pyrogram will automatically make use of TgCrypto when detected, all you need to do is to install it. + +uvloop +------ + +uvloop_ is a fast, drop-in replacement of the built-in asyncio event loop. uvloop is implemented in Cython and uses +libuv under the hood. It makes asyncio 2-4x faster. + +Installation +^^^^^^^^^^^^ + +.. code-block:: bash + + $ pip3 install -U uvloop + +Usage +^^^^^ + +Call ``uvloop.install()`` before calling ``asyncio.run()`` or ``app.run()``. + +.. code-block:: python + + import asyncio + import uvloop + + from pyrogram import Client + + + async def main(): + app = Client("my_account") + + async with app: + print(await app.get_me()) + + + uvloop.install() + asyncio.run(main()) + +The ``uvloop.install()`` call also needs to be placed before creating a Client instance. + +.. code-block:: python + + import uvloop + from pyrogram import Client + + uvloop.install() + + app = Client("my_account") + + + @app.on_message() + async def hello(client, message): + print(await client.get_me()) + + + app.run() + +.. _TgCrypto: https://github.com/pyrogram/tgcrypto +.. _uvloop: https://github.com/MagicStack/uvloop diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst new file mode 100644 index 00000000..34147917 --- /dev/null +++ b/docs/source/topics/storage-engines.rst @@ -0,0 +1,90 @@ +Storage Engines +=============== + +Every time you login to Telegram, some personal piece of data are created and held by both parties (the client, Pyrogram +and the server, Telegram). This session data is uniquely bound to your own account, indefinitely (until you logout or +decide to manually terminate it) and is used to authorize a client to execute API calls on behalf of your identity. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Persisting Sessions +------------------- + +In order to make a client reconnect successfully between restarts, that is, without having to start a new +authorization process from scratch each time, Pyrogram needs to store the generated session data somewhere. + +Different Storage Engines +------------------------- + +Pyrogram offers two different types of storage engines: a **File Storage** and a **Memory Storage**. +These engines are well integrated in the framework and require a minimal effort to set up. Here's how they work: + +File Storage +^^^^^^^^^^^^ + +This is the most common storage engine. It is implemented by using **SQLite**, which will store the session details. +The database will be saved to disk as a single portable file and is designed to efficiently store and retrieve +data whenever they are needed. + +To use this type of engine, simply pass any name of your choice to the ``name`` parameter of the +:obj:`~pyrogram.Client` constructor, as usual: + +.. code-block:: python + + from pyrogram import Client + + async with Client("my_account") as app: + print(await app.get_me()) + +Once you successfully log in (either with a user or a bot identity), a session file will be created and saved to disk as +``my_account.session``. Any subsequent client restart will make Pyrogram search for a file named that way and the +session database will be automatically loaded. + +Memory Storage +^^^^^^^^^^^^^^ + +In case you don't want to have any session file saved to disk, you can use an in-memory storage by passing True to the +``in_memory`` parameter of the :obj:`~pyrogram.Client` constructor: + +.. code-block:: python + + from pyrogram import Client + + async with Client("my_account", in_memory=True) as app: + print(await app.get_me()) + +This storage engine is still backed by SQLite, but the database exists purely in memory. This means that, once you stop +a client, the entire database is discarded and the session details used for logging in again will be lost forever. + +Session Strings +--------------- + +In case you want to use an in-memory storage, but also want to keep access to the session you created, call +:meth:`~pyrogram.Client.export_session_string` anytime before stopping the client... + +.. code-block:: python + + from pyrogram import Client + + async with Client("my_account", in_memory=True) as app: + print(await app.export_session_string()) + +...and save the resulting string. You can use this string by passing it as Client argument the next time you want to +login using the same session; the storage used will still be in-memory: + +.. code-block:: python + + from pyrogram import Client + + session_string = "...ZnUIFD8jsjXTb8g_vpxx48k1zkov9sapD-tzjz-S4WZv70M..." + + async with Client("my_account", session_string=session_string) as app: + print(await app.get_me()) + +Session strings are useful when you want to run authorized Pyrogram clients on platforms where their ephemeral +filesystems makes it harder for a file-based storage engine to properly work as intended. diff --git a/docs/source/topics/synchronous.rst b/docs/source/topics/synchronous.rst new file mode 100644 index 00000000..0a677b0e --- /dev/null +++ b/docs/source/topics/synchronous.rst @@ -0,0 +1,88 @@ +Synchronous Usage +================= + +Pyrogram is an asynchronous framework and as such is subject to the asynchronous rules. It can, however, run in +synchronous mode (also known as non-asynchronous or sync/non-async for short). This mode exists mainly as a convenience +way for invoking methods without the need of ``async``/``await`` keywords and the extra boilerplate, but **it's not the +intended way to use the framework**. + +You can use Pyrogram in this synchronous mode when you want to write something short and contained without the +async boilerplate or in case you want to combine Pyrogram with other libraries that are not async. + +.. warning:: + + You have to be very careful when using the framework in its synchronous, non-native form, especially when combined + with other non-async libraries because thread blocking operations that clog the asynchronous event loop underneath + will make the program run erratically. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Synchronous Invocations +----------------------- + +The following is a standard example of running asynchronous functions with Python's asyncio. +Pyrogram is being used inside the main function with its asynchronous interface. + +.. code-block:: python + + import asyncio + from pyrogram import Client + + + async def main(): + app = Client("my_account") + + async with app: + await app.send_message("me", "Hi!") + + + asyncio.run(main()) + +To run Pyrogram synchronously, use the non-async context manager as shown in the following example. +As you can see, the non-async example becomes less cluttered. + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + with app: + app.send_message("me", "Hi!") + +Synchronous handlers +-------------------- + +You can also have synchronous handlers; you only need to define the callback function without using ``async def`` and +invoke API methods by not placing ``await`` in front of them. Mixing ``def`` and ``async def`` handlers together is also +possible. + +.. code-block:: python + + @app.on_message() + async def handler1(client, message): + await message.forward("me") + + @app.on_edited_message() + def handler2(client, message): + message.forward("me") + +uvloop usage +------------ + +When using Pyrogram in its synchronous mode combined with uvloop, you need to call ``uvloop.install()`` before importing +Pyrogram. + +.. code-block:: python + + import uvloop + uvloop.install() + + from pyrogram import Client + + ... \ No newline at end of file diff --git a/docs/source/topics/test-servers.rst b/docs/source/topics/test-servers.rst new file mode 100644 index 00000000..1ccfe286 --- /dev/null +++ b/docs/source/topics/test-servers.rst @@ -0,0 +1,41 @@ +Test Servers +============ + +If you wish to test your application in a separate environment, Pyrogram is able to authorize your account into +Telegram's test servers without hassle. All you need to do is start a new session (e.g.: "my_account_test") using +``test_mode=True``: + +.. code-block:: python + + from pyrogram import Client + + async with Client("my_account_test", test_mode=True) as app: + print(await app.get_me()) + +.. note:: + + If this is the first time you login into test servers, you will be asked to register your account first. + Accounts registered on test servers reside in a different, parallel instance of a Telegram server. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Test Mode in Official Apps +-------------------------- + +You can also login yourself into test servers using official desktop apps, such as Telegram Web and Telegram Desktop: + +- **Telegram Web**: Login here: https://web.telegram.org/?test=1 +- **Telegram Desktop**: Hold ``Alt+Shift`` and right click on "Add account", then choose "Test server". + +Test Numbers +------------ + +Beside normal numbers, the test environment allows you to login with reserved test numbers. +Valid phone numbers follow the pattern ``99966XYYYY``, where ``X`` is the DC number (1 to 3) and ``YYYY`` are random +numbers. Users with such numbers always get ``XXXXX`` or ``XXXXXX`` as the confirmation code (the DC number, repeated +five or six times). \ No newline at end of file diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst new file mode 100644 index 00000000..00aa0cf8 --- /dev/null +++ b/docs/source/topics/text-formatting.rst @@ -0,0 +1,243 @@ +Text Formatting +=============== + +.. role:: strike + :class: strike + +.. role:: underline + :class: underline + +.. role:: bold-underline + :class: bold-underline + +.. role:: strike-italic + :class: strike-italic + +Pyrogram uses a custom Markdown dialect for text formatting which adds some unique features that make writing styled +texts easier in both Markdown and HTML. You can send sophisticated text messages and media captions using a +variety of decorations that can also be nested in order to combine multiple styles together. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Basic Styles +------------ + +When formatting your messages, you can choose between Markdown-style, HTML-style or both (default). The following is a +list of the basic styles currently supported by Pyrogram. + +- **bold** +- *italic* +- :strike:`strike` +- :underline:`underline` +- spoiler +- `text URL `_ +- `user text mention `_ +- ``inline fixed-width code`` +- .. code-block:: text + + pre-formatted + fixed-width + code block + +Markdown Style +-------------- + +To strictly use this mode, pass :obj:`~pyrogram.enums.ParseMode.MARKDOWN` to the *parse_mode* parameter when using +:meth:`~pyrogram.Client.send_message`. Use the following syntax in your message: + +.. code-block:: text + + **bold** + + __italic__ + + --underline-- + + ~~strike~~ + + ||spoiler|| + + [text URL](https://pyrogram.org/) + + [text user mention](tg://user?id=123456789) + + `inline fixed-width code` + + ``` + pre-formatted + fixed-width + code block + ``` + +**Example**: + +.. code-block:: python + + from pyrogram import enums + + await app.send_message( + "me", + ( + "**bold**, " + "__italic__, " + "--underline--, " + "~~strike~~, " + "||spoiler||, " + "[URL](https://pyrogram.org), " + "`code`, " + "```" + "for i in range(10):\n" + " print(i)" + "```" + ), + parse_mode=enums.ParseMode.MARKDOWN + ) + +HTML Style +---------- + +To strictly use this mode, pass :obj:`~pyrogram.enums.HTML` to the *parse_mode* parameter when using +:meth:`~pyrogram.Client.send_message`. The following tags are currently supported: + +.. code-block:: text + + bold, bold + + italic, italic + + underline + + strike, strike, strike + + spoiler + + text URL + + inline mention + + inline fixed-width code + + 🔥 + +
+    pre-formatted
+      fixed-width
+        code block
+    
+ +**Example**: + +.. code-block:: python + + from pyrogram import enums + + await app.send_message( + "me", + ( + "bold, " + "italic, " + "underline, " + "strike, " + "spoiler, " + "URL, " + "code\n\n" + "
"
+            "for i in range(10):\n"
+            "    print(i)"
+            "
" + ), + parse_mode=enums.ParseMode.HTML + ) + +.. note:: + + All ``<``, ``>`` and ``&`` symbols that are not a part of a tag or an HTML entity must be replaced with the + corresponding HTML entities (``<`` with ``<``, ``>`` with ``>`` and ``&`` with ``&``). You can use this + snippet to quickly escape those characters: + + .. code-block:: python + + import html + + text = "" + text = html.escape(text) + + print(text) + + .. code-block:: text + + <my text> + +Different Styles +---------------- + +By default, when ignoring the *parse_mode* parameter, both Markdown and HTML styles are enabled together. +This means you can combine together both syntaxes in the same text: + +.. code-block:: python + + await app.send_message("me", "**bold**, italic") + +Result: + + **bold**, *italic* + +If you don't like this behaviour you can always choose to only enable either Markdown or HTML in strict mode by passing +:obj:`~pyrogram.enums.MARKDOWN` or :obj:`~pyrogram.enums.HTML` as argument to the *parse_mode* parameter. + +.. code-block:: python + + from pyrogram import enums + + await app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.MARKDOWN) + await app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.HTML) + +Result: + + **bold**, italic + + \*\*bold**, *italic* + +In case you want to completely turn off the style parser, simply pass :obj:`~pyrogram.enums.DISABLED` to *parse_mode*. +The text will be sent as-is. + +.. code-block:: python + + from pyrogram import enums + + await app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.DISABLED) + +Result: + + \*\*bold**, italic + +Nested and Overlapping Entities +------------------------------- + +You can also style texts with more than one decoration at once by nesting entities together. For example, you can send +a text message with both :bold-underline:`bold and underline` styles, or a text that has both :strike-italic:`italic and +strike` styles, and you can still combine both Markdown and HTML together. + +Here there are some example texts you can try sending: + +**Markdown**: + +- ``**bold, --underline--**`` +- ``**bold __italic --underline ~~strike~~--__**`` +- ``**bold __and** italic__`` + +**HTML**: + +- ``bold, underline`` +- ``bold italic underline strike`` +- ``bold and italic`` + +**Combined**: + +- ``--you can combine HTML with **Markdown**--`` +- ``**and also overlap** --entities this way--`` diff --git a/docs/source/topics/use-filters.rst b/docs/source/topics/use-filters.rst new file mode 100644 index 00000000..ab7296af --- /dev/null +++ b/docs/source/topics/use-filters.rst @@ -0,0 +1,114 @@ +Using Filters +============= + +So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time an update comes +from the server, but there's much more than that to come. + +Here we'll discuss about :obj:`~pyrogram.filters`. Filters enable a fine-grain control over what kind of +updates are allowed or not to be passed in your callback functions, based on their inner details. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +Single Filters +-------------- + +Let's start right away with a simple example: + +- This example will show you how to **only** handle messages containing a :class:`~pyrogram.types.Sticker` object and + ignore any other message. Filters are passed as the first argument of the decorator: + + .. code-block:: python + + from pyrogram import filters + + + @app.on_message(filters.sticker) + async def my_handler(client, message): + print(message) + +- or, without decorators. Here filters are passed as the second argument of the handler constructor; the first is the + callback function itself: + + .. code-block:: python + + from pyrogram import filters + from pyrogram.handlers import MessageHandler + + + async def my_handler(client, message): + print(message) + + + app.add_handler(MessageHandler(my_handler, filters.sticker)) + +Combining Filters +----------------- + +Filters can be used in a more advanced way by inverting and combining more filters together using bitwise +operators ``~``, ``&`` and ``|``: + +- Use ``~`` to invert a filter (behaves like the ``not`` operator). +- Use ``&`` and ``|`` to merge two filters (behave like ``and``, ``or`` operators respectively). + +Here are some examples: + +- Message is a **text** message **or** a **photo**. + + .. code-block:: python + + @app.on_message(filters.text | filters.photo) + async def my_handler(client, message): + print(message) + +- Message is a **sticker** **and** is coming from a **channel or** a **private** chat. + + .. code-block:: python + + @app.on_message(filters.sticker & (filters.channel | filters.private)) + async def my_handler(client, message): + print(message) + +Advanced Filters +---------------- + +Some filters, like :meth:`~pyrogram.filters.command` or :meth:`~pyrogram.filters.regex` +can also accept arguments: + +- Message is either a */start* or */help* **command**. + + .. code-block:: python + + @app.on_message(filters.command(["start", "help"])) + async def my_handler(client, message): + print(message) + +- Message is a **text** message or a media **caption** matching the given **regex** pattern. + + .. code-block:: python + + @app.on_message(filters.regex("pyrogram")) + async def my_handler(client, message): + print(message) + +More handlers using different filters can also live together. + +.. code-block:: python + + @app.on_message(filters.command("start")) + async def start_command(client, message): + print("This is the /start command") + + + @app.on_message(filters.command("help")) + async def help_command(client, message): + print("This is the /help command") + + + @app.on_message(filters.chat("PyrogramChat")) + async def from_pyrogramchat(client, message): + print("New message in @PyrogramChat") diff --git a/docs/source/topics/voice-calls.rst b/docs/source/topics/voice-calls.rst new file mode 100644 index 00000000..aef4030c --- /dev/null +++ b/docs/source/topics/voice-calls.rst @@ -0,0 +1,19 @@ +Voice Calls +=========== + +Both private voice calls and group voice calls are currently supported by third-party, external libraries that integrate +with Pyrogram. + +Libraries +--------- + +There are currently two main libraries (with very similar names) you can use: + +1. https://github.com/pytgcalls/pytgcalls +2. https://github.com/MarshalX/tgcalls + +Older implementations +--------------------- + +An older implementation of Telegram voice calls can be found at https://github.com/bakatrouble/pylibtgvoip (currently +outdated due to the deprecation of the Telegram VoIP library used underneath). \ No newline at end of file