From 8c8288412fc80552f7a001d77d9afac905d81fc7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 7 Jan 2022 10:18:51 +0100 Subject: [PATCH] Various improvements --- .github/FUNDING.yml | 3 +- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/config.yml | 6 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- README.md | 60 ++- compiler/docs/compiler.py | 8 + compiler/docs/template/bound-methods.rst | 7 +- compiler/docs/template/methods.rst | 7 +- compiler/docs/template/types.rst | 1 - compiler/errors/source/400_BAD_REQUEST.tsv | 3 +- compiler/errors/source/401_UNAUTHORIZED.tsv | 2 +- docs/README.md | 8 - docs/requirements.txt | 4 +- docs/robots.txt | 7 - docs/scripts/releases.py | 84 ---- docs/scripts/sitemap.py | 81 ---- docs/source/_images/favicon.ico | Bin 16958 -> 0 bytes docs/source/_images/pyrogram.png | Bin 48928 -> 0 bytes docs/source/api/client.rst | 1 - docs/source/api/decorators.rst | 4 - docs/source/api/handlers.rst | 1 - docs/source/conf.py | 28 +- docs/source/faq.rst | 407 ------------------ .../client-started-but-nothing-happens.rst | 11 + ...alling-stop-restart-add-remove-handler.rst | 12 + docs/source/faq/how-to-avoid-flood-waits.rst | 23 + docs/source/faq/how-to-use-webhooks.rst | 9 + docs/source/faq/index.rst | 47 ++ ...ing-the-account-to-another-data-center.rst | 10 + docs/source/faq/peer-id-invalid-error.rst | 14 + ...-raised-exception-oserror-timeouterror.rst | 10 + ...interfaceerror-error-binding-parameter.rst | 13 + ...e3-operationalerror-database-is-locked.rst | 17 + ...e-account-has-been-limited-deactivated.rst | 16 + .../unicodeencodeerror-codec-cant-encode.rst | 7 + ...h-urls-gives-error-webpage-curl-failed.rst | 7 + ...le-clients-at-once-on-the-same-account.rst | 7 + ...same-file-id-across-different-accounts.rst | 6 + ...-ip-addresses-of-telegram-data-centers.rst | 30 ++ .../why-is-the-api-key-needed-for-bots.rst | 12 + ...eacting-slowly-in-supergroups-channels.rst | 18 + ...-event-handler-triggered-twice-or-more.rst | 28 ++ docs/source/glossary.rst | 86 ---- docs/source/index.rst | 65 +-- docs/source/intro/install.rst | 18 +- docs/source/intro/quickstart.rst | 35 +- docs/source/intro/setup.rst | 24 +- docs/source/license.rst | 16 - docs/source/start/auth.rst | 21 +- docs/source/start/errors.rst | 13 +- docs/source/start/examples/bot_keyboards.rst | 4 +- docs/source/start/examples/echobot.rst | 2 +- docs/source/start/examples/hello_world.rst | 6 - docs/source/start/examples/inline_queries.rst | 2 - .../source/start/examples/use_inline_bots.rst | 4 +- docs/source/start/examples/welcomebot.rst | 2 +- docs/source/start/invoking.rst | 105 ++--- docs/source/start/updates.rst | 52 +-- docs/source/support.rst | 78 ++-- docs/source/topics/advanced-usage.rst | 69 ++- docs/source/topics/bots-interaction.rst | 50 --- ...ssion-settings.rst => client-settings.rst} | 26 +- docs/source/topics/config-file.rst | 4 +- docs/source/topics/create-filters.rst | 2 +- docs/source/topics/debugging.rst | 68 ++- docs/source/topics/more-on-updates.rst | 1 - docs/source/topics/mtproto-vs-botapi.rst | 33 +- docs/source/topics/scheduling.rst | 4 +- docs/source/topics/serializing.rst | 5 +- docs/source/topics/smart-plugins.rst | 40 +- docs/source/topics/storage-engines.rst | 38 +- docs/source/topics/test-servers.rst | 17 +- docs/source/topics/text-formatting.rst | 26 +- docs/source/topics/tgcrypto.rst | 19 +- docs/source/topics/use-filters.rst | 10 +- docs/source/topics/voice-calls.rst | 4 +- pyrogram/client.py | 20 +- .../connection/transport/tcp/tcp_abridged.py | 1 - pyrogram/connection/transport/tcp/tcp_full.py | 2 +- .../transport/tcp/tcp_intermediate.py | 2 +- .../transport/tcp/tcp_intermediate_o.py | 2 +- pyrogram/errors/rpc_error.py | 2 +- pyrogram/filters.py | 9 - pyrogram/methods/bots/__init__.py | 2 +- pyrogram/methods/chats/get_chat_member.py | 4 +- pyrogram/methods/chats/get_chat_members.py | 18 +- .../methods/chats/get_chat_members_count.py | 2 +- pyrogram/methods/chats/iter_chat_members.py | 72 ++-- .../methods/chats/set_administrator_title.py | 2 +- pyrogram/methods/chats/set_slow_mode.py | 4 +- pyrogram/methods/contacts/add_contact.py | 2 +- pyrogram/methods/contacts/import_contacts.py | 6 +- pyrogram/methods/messages/copy_media_group.py | 5 +- pyrogram/methods/messages/download_media.py | 2 +- pyrogram/methods/messages/forward_messages.py | 1 - pyrogram/methods/messages/get_history.py | 6 +- .../methods/messages/get_history_count.py | 2 +- pyrogram/methods/messages/get_media_group.py | 4 +- pyrogram/methods/messages/get_messages.py | 4 +- pyrogram/methods/messages/read_history.py | 4 +- pyrogram/methods/messages/search_global.py | 10 +- pyrogram/methods/messages/search_messages.py | 16 +- pyrogram/methods/messages/send_animation.py | 2 +- pyrogram/methods/messages/send_audio.py | 5 +- .../methods/messages/send_cached_media.py | 2 +- pyrogram/methods/messages/send_contact.py | 2 +- pyrogram/methods/messages/send_dice.py | 6 +- pyrogram/methods/messages/send_document.py | 2 +- pyrogram/methods/messages/send_media_group.py | 2 +- pyrogram/methods/messages/send_message.py | 5 +- pyrogram/methods/messages/send_sticker.py | 2 +- pyrogram/methods/messages/send_video.py | 2 +- pyrogram/methods/messages/send_voice.py | 2 +- pyrogram/methods/users/get_common_chats.py | 2 +- pyrogram/methods/users/get_profile_photos.py | 6 +- .../methods/users/get_profile_photos_count.py | 2 +- pyrogram/methods/users/get_users.py | 2 +- pyrogram/methods/users/iter_profile_photos.py | 2 +- pyrogram/methods/utilities/add_handler.py | 1 - .../utilities/export_session_string.py | 1 - pyrogram/methods/utilities/idle.py | 1 - pyrogram/methods/utilities/remove_handler.py | 1 - pyrogram/methods/utilities/restart.py | 1 - pyrogram/methods/utilities/run.py | 5 +- pyrogram/methods/utilities/start.py | 1 - pyrogram/methods/utilities/stop.py | 1 - .../methods/utilities/stop_transmission.py | 3 +- pyrogram/raw/core/primitives/vector.py | 2 +- pyrogram/session/session.py | 8 - pyrogram/types/bots_and_keyboards/__init__.py | 2 +- pyrogram/types/inline_mode/__init__.py | 2 +- pyrogram/types/messages_and_media/message.py | 2 +- .../messages_and_media/message_entity.py | 2 +- pyrogram/types/user_and_chats/chat.py | 4 +- setup.py | 5 +- 135 files changed, 842 insertions(+), 1422 deletions(-) delete mode 100644 docs/README.md delete mode 100644 docs/robots.txt delete mode 100644 docs/scripts/releases.py delete mode 100644 docs/scripts/sitemap.py delete mode 100644 docs/source/_images/favicon.ico delete mode 100644 docs/source/_images/pyrogram.png delete mode 100644 docs/source/faq.rst create mode 100644 docs/source/faq/client-started-but-nothing-happens.rst create mode 100644 docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst create mode 100644 docs/source/faq/how-to-avoid-flood-waits.rst create mode 100644 docs/source/faq/how-to-use-webhooks.rst create mode 100644 docs/source/faq/index.rst create mode 100644 docs/source/faq/migrating-the-account-to-another-data-center.rst create mode 100644 docs/source/faq/peer-id-invalid-error.rst create mode 100644 docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst create mode 100644 docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst create mode 100644 docs/source/faq/sqlite3-operationalerror-database-is-locked.rst create mode 100644 docs/source/faq/the-account-has-been-limited-deactivated.rst create mode 100644 docs/source/faq/unicodeencodeerror-codec-cant-encode.rst create mode 100644 docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst create mode 100644 docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst create mode 100644 docs/source/faq/using-the-same-file-id-across-different-accounts.rst create mode 100644 docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst create mode 100644 docs/source/faq/why-is-the-api-key-needed-for-bots.rst create mode 100644 docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst create mode 100644 docs/source/faq/why-is-the-event-handler-triggered-twice-or-more.rst delete mode 100644 docs/source/glossary.rst delete mode 100644 docs/source/license.rst delete mode 100644 docs/source/topics/bots-interaction.rst rename docs/source/topics/{session-settings.rst => client-settings.rst} (54%) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 13e0071e..19d0cd78 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,3 @@ github: delivrance -custom: https://docs.pyrogram.org/support +liberapay: delivrance +open_collective: pyrogram diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7efd4ca1..e3bc5909 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug Report -about: Create a bug report affecting the library or the documentation +about: Create a bug report affecting the framework or the documentation --- diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 37e0cd33..453151d8 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: Ask Pyrogram related questions url: https://stackoverflow.com/questions/tagged/pyrogram - about: This place is for issues about Pyrogram. If you'd like to ask a question, please do so at StackOverflow. - - name: Join the Telegram community + about: This place is only for reporting issues about Pyrogram. You can ask questions at StackOverflow. + - name: Join the Telegram channel url: https://t.me/pyrogram - about: Join the official channel to stay tuned for news and updates. \ No newline at end of file + about: Join the official channel and stay tuned for news, updates and announcements. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 854db44d..bf5e6a21 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,7 +7,7 @@ labels: "enhancement" ## Checklist -- [ ] I believe the idea is awesome and would benefit the library. +- [ ] I believe the idea is awesome and would benefit the framework. - [ ] I have searched in the issue tracker for similar requests, including closed ones. ## Description diff --git a/README.md b/README.md index f8c4432e..e51ee0eb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Pyrogram + Pyrogram
Telegram MTProto API Framework for Python @@ -9,17 +9,19 @@ Documentation • - + Releases • - - Community + + News

## Pyrogram +> Elegant, modern and asynchronous Telegram MTProto API framework in Python for users and bots + ``` python from pyrogram import Client, filters @@ -28,34 +30,33 @@ app = Client("my_account") @app.on_message(filters.private) async def hello(client, message): - await message.reply_text(f"Hello {message.from_user.mention}") + await message.reply("Hello from Pyrogram!") app.run() ``` -**Pyrogram** is a modern, elegant and easy-to-use [Telegram](https://telegram.org/) client library framework written -from the ground up in Python and C. It enables you to easily create custom Telegram client applications for both user -and bot identities (bot API alternative) via the [MTProto API](https://docs.pyrogram.org/topics/mtproto-vs-botapi). +**Pyrogram** is a modern, elegant and asynchronous [MTProto API](https://docs.pyrogram.org/topics/mtproto-vs-botapi) +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. -### Features +### Support -- **Easy**: You can install Pyrogram with pip and start building your applications right away. -- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way. -- **Fast**: Crypto parts are boosted up by [TgCrypto](https://github.com/pyrogram/tgcrypto), a high-performance library - written in pure C. -- **Asynchronous**: Allows both synchronous and asynchronous models to fit all usage needs. -- **Documented**: API methods, types and public interfaces are all [well documented](https://docs.pyrogram.org). +If you'd like to support Pyrogram, you can consider: + +- [Become a GitHub sponsor](https://github.com/sponsors/delivrance). +- [Become a LiberaPay patron](https://liberapay.com/delivrance). +- [Become an OpenCollective backer](https://opencollective.com/pyrogram>). + +### Key Features + +- **Ready**: Install Pyrogram with pip and start building your applications right away. +- **Easy**: Makes the Telegram API simple and intuitive, while still allowing advanced usages. +- **Elegant**: Low-level details are abstracted and re-presented in a more convenient way. +- **Fast**: Boosted up by [TgCrypto](https://github.com/pyrogram/tgcrypto), a high-performance crypto library written in pure C. - **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. -- **Updated**, to make use of the latest Telegram API version and features. -- **Bot API-like**: Similar to the Bot API in its simplicity, but much more powerful and detailed. -- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code. -- **Comprehensive**: Execute any advanced action an official client is able to do, and even more. - -### Requirements - -- Python 3.6 or higher. -- A [Telegram API key](https://docs.pyrogram.org/intro/setup#api-keys). +- **Async**: Fully asynchronous (also usable synchronously if wanted, for convenience). +- **Powerful**: Full access to Telegram's API to execute any official client action and more. ### Installing @@ -65,11 +66,6 @@ pip3 install pyrogram ### Resources -- The docs contain lots of resources to help you get started with Pyrogram: https://docs.pyrogram.org. -- Seeking extra help? Come join and ask our community: https://t.me/pyrogram. -- For other kind of inquiries, you can send a [message](https://t.me/haskell) or an [e-mail](mailto:dan@pyrogram.org). - -### Copyright & License - -- Copyright (C) 2017-2021 Dan <> -- Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser) +- Check out the docs at https://docs.pyrogram.org to learn more about Pyrogram, get started right +away and discover more in-depth material for building your client applications. +- Join the official channel at https://t.me/pyrogram and stay tuned for news, updates and announcements. diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 6dd8adf4..65f67436 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -158,6 +158,7 @@ def pyrogram_api(): send_venue send_contact send_cached_media + send_reaction edit_message_text edit_message_caption edit_message_media @@ -180,7 +181,9 @@ def pyrogram_api(): retract_vote send_dice search_messages + search_messages_count search_global + search_global_count download_media get_discussion_message """, @@ -284,6 +287,7 @@ def pyrogram_api(): send_game set_game_score get_game_high_scores + set_bot_commands """, authorization=""" Authorization @@ -360,6 +364,7 @@ def pyrogram_api(): ChatEvent ChatEventFilter ChatMemberUpdated + ChatJoinRequest Dialog Restriction """, @@ -384,6 +389,7 @@ def pyrogram_api(): Poll PollOption Dice + Reaction VoiceChatScheduled VoiceChatStarted VoiceChatEnded @@ -420,6 +426,8 @@ def pyrogram_api(): InlineQueryResultArticle InlineQueryResultPhoto InlineQueryResultAnimation + InlineQueryResultAudio + InlineQueryResultVideo ChosenInlineResult """, input_message_content=""" diff --git a/compiler/docs/template/bound-methods.rst b/compiler/docs/template/bound-methods.rst index 13a51b04..ebaa2ab8 100644 --- a/compiler/docs/template/bound-methods.rst +++ b/compiler/docs/template/bound-methods.rst @@ -1,12 +1,11 @@ Bound Methods ============= -Some Pyrogram types define what are called bound methods. Bound methods are functions attached to a class which are -accessed via an instance of that class. They make it even easier to call specific methods by automatically inferring +Some Pyrogram types define what are called bound methods. Bound methods are functions attached to a type which are +accessed via an instance of that type. They make it even easier to call specific methods by automatically inferring some of the required arguments. .. code-block:: python - :emphasize-lines: 8 from pyrogram import Client @@ -15,7 +14,7 @@ some of the required arguments. @app.on_message() def hello(client, message) - message.reply_text("hi") + message.reply("hi") app.run() diff --git a/compiler/docs/template/methods.rst b/compiler/docs/template/methods.rst index dc2950dc..794e657e 100644 --- a/compiler/docs/template/methods.rst +++ b/compiler/docs/template/methods.rst @@ -1,18 +1,17 @@ Available Methods ================= -This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance, -except for :meth:`~pyrogram.idle()`, which is a special function that can be found in the main package directly. +This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance. +Some other utility functions can instead be found in the main package directly. .. code-block:: python - :emphasize-lines: 6 from pyrogram import Client app = Client("my_account") with app: - app.send_message("haskell", "hi") + app.send_message("me", "hi") .. contents:: Contents :backlinks: none diff --git a/compiler/docs/template/types.rst b/compiler/docs/template/types.rst index 8b340006..2651c355 100644 --- a/compiler/docs/template/types.rst +++ b/compiler/docs/template/types.rst @@ -6,7 +6,6 @@ Unless required as argument to a client method, most of the types don't need to are only returned by other methods. You also don't need to import them, unless you want to type-hint your variables. .. code-block:: python - :emphasize-lines: 1 from pyrogram.types import User, Message, ... diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 1b12472c..d6754027 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -62,6 +62,7 @@ CHAT_NOT_MODIFIED The chat settings (title, permissions, photo, etc..) were not CHAT_RESTRICTED The chat is restricted and cannot be used CHAT_SEND_INLINE_FORBIDDEN You cannot use inline bots to send messages in this chat CHAT_TITLE_EMPTY The chat title is empty +CHAT_TOO_BIG The chat is too big for this action CODE_EMPTY The provided code is empty CODE_HASH_INVALID The provided code hash invalid CODE_INVALID The provided code is invalid (i.e. from email) @@ -347,4 +348,4 @@ WEBDOCUMENT_URL_EMPTY The web document URL is empty WEBDOCUMENT_URL_INVALID The web document URL is invalid WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media -YOU_BLOCKED_USER You blocked this user +YOU_BLOCKED_USER You blocked this user \ No newline at end of file diff --git a/compiler/errors/source/401_UNAUTHORIZED.tsv b/compiler/errors/source/401_UNAUTHORIZED.tsv index 1bb07b89..32e0265f 100644 --- a/compiler/errors/source/401_UNAUTHORIZED.tsv +++ b/compiler/errors/source/401_UNAUTHORIZED.tsv @@ -2,7 +2,7 @@ id message ACTIVE_USER_REQUIRED The method is only available to already activated users AUTH_KEY_INVALID The key is invalid AUTH_KEY_PERM_EMPTY The method is unavailable for temporary authorization key, not bound to permanent -AUTH_KEY_UNREGISTERED The key is not registered in the system +AUTH_KEY_UNREGISTERED The key is not registered in the system. Delete your session file and login again SESSION_EXPIRED The authorization has expired SESSION_PASSWORD_NEEDED The two-step verification is enabled and a password is required SESSION_REVOKED The authorization has been invalidated, because of the user terminating all sessions diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 680e2cf4..00000000 --- a/docs/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Pyrogram Docs - -- Install requirements. -- Install `pandoc` and `latexmk`. -- HTML: `make html` -- PDF: `make latexpdf` - -TODO: Explain better \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt index 2cfd5679..f83e673d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,6 @@ sphinx -sphinx_rtd_theme +sphinx_rtd_theme==1.0.0 sphinx_copybutton pypandoc requests -sphinx-autobuild \ No newline at end of file +sphinx-autobuild diff --git a/docs/robots.txt b/docs/robots.txt deleted file mode 100644 index 3e416f24..00000000 --- a/docs/robots.txt +++ /dev/null @@ -1,7 +0,0 @@ -User-agent: * - -Allow: / - -Disallow: /old* - -Sitemap: https://docs.pyrogram.org/sitemap.xml \ No newline at end of file diff --git a/docs/scripts/releases.py b/docs/scripts/releases.py deleted file mode 100644 index 4a32dd29..00000000 --- a/docs/scripts/releases.py +++ /dev/null @@ -1,84 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 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 shutil -from datetime import datetime -from pathlib import Path - -import pypandoc -import requests - -URL = "https://api.github.com/repos/pyrogram/pyrogram/releases" -DEST = Path("../source/releases") -INTRO = """ -Release Notes -============= - -Release notes for Pyrogram releases will describe what's new in each version, and will also make you aware of any -backwards-incompatible changes made in that version. - -When upgrading to a new version of Pyrogram, you will need to check all the breaking changes in order to find -incompatible code in your application, but also to take advantage of new features and improvements. - -**Contents** - -""".lstrip("\n") - -shutil.rmtree(DEST, ignore_errors=True) -DEST.mkdir(parents=True) - -releases = requests.get(URL).json() - -with open(DEST / "index.rst", "w") as index: - index.write(INTRO) - - tags = [] - - for release in releases: - tag = release["tag_name"] - title = release["name"] - name = title.split(" - ")[1] - - date = datetime.strptime( - release["published_at"], - "%Y-%m-%dT%H:%M:%SZ" - ).strftime("%b %d, %Y") - - body = pypandoc.convert_text( - release["body"].replace(r"\r\n", "\n"), - "rst", - format="markdown_github", - extra_args=["--wrap=none"] - ) - - tarball_url = release["tarball_url"] - zipball_url = release["zipball_url"] - - index.write("- :doc:`{} <{}>`\n".format(title, tag)) - tags.append(tag) - - with open(DEST / "{}.rst".format(tag), "w") as page: - page.write("Pyrogram " + tag + "\n" + "=" * (len(tag) + 9) + "\n\n") - page.write("\t\tReleased on " + str(date) + "\n\n") - page.write("- :download:`Source Code (zip) <{}>`\n".format(zipball_url)) - page.write("- :download:`Source Code (tar.gz) <{}>`\n\n".format(tarball_url)) - page.write(name + "\n" + "-" * len(name) + "\n\n") - page.write(body + "\n\n") - - index.write("\n.. toctree::\n :hidden:\n\n") - index.write("\n".join(" {}".format(tag) for tag in tags)) diff --git a/docs/scripts/sitemap.py b/docs/scripts/sitemap.py deleted file mode 100644 index 9c28bd1c..00000000 --- a/docs/scripts/sitemap.py +++ /dev/null @@ -1,81 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 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 datetime -import os - -canonical = "https://docs.pyrogram.org/" - -dirs = { - ".": ("weekly", 1.0), - "intro": ("weekly", 0.9), - "start": ("weekly", 0.9), - "api": ("weekly", 0.8), - "topics": ("weekly", 0.8), - "releases": ("weekly", 0.8), - "telegram": ("weekly", 0.6) -} - - -def now(): - return datetime.datetime.today().strftime("%Y-%m-%d") - - -with open("sitemap.xml", "w") as f: - f.write('\n') - f.write('\n') - - urls = [] - - - def search(path): - try: - for j in os.listdir(path): - search(f"{path}/{j}") - except NotADirectoryError: - if not path.endswith(".rst"): - return - - path = path.split("/")[2:] - - if path[0].endswith(".rst"): - folder = "." - else: - folder = path[0] - - path = f"{canonical}{'/'.join(path)}"[:-len(".rst")] - - if path.endswith("index"): - path = path[:-len("index")] - - urls.append((path, now(), *dirs[folder])) - - - search("../source") - - urls.sort(key=lambda x: x[3], reverse=True) - - for i in urls: - f.write(f" \n") - f.write(f" {i[0]}\n") - f.write(f" {i[1]}\n") - f.write(f" {i[2]}\n") - f.write(f" {i[3]}\n") - f.write(f" \n") - - f.write("") diff --git a/docs/source/_images/favicon.ico b/docs/source/_images/favicon.ico deleted file mode 100644 index 4165f9edce821093f62506253dc379390f17cb64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16958 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ξ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 JR*4%0{XfJV4qE^K diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst index 4190af5b..3db28693 100644 --- a/docs/source/api/client.rst +++ b/docs/source/api/client.rst @@ -8,7 +8,6 @@ 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 - :emphasize-lines: 1-3 from pyrogram import Client diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst index c859063a..fa0e7ad3 100644 --- a/docs/source/api/decorators.rst +++ b/docs/source/api/decorators.rst @@ -1,16 +1,12 @@ Decorators ========== -While still being methods bound to the :class:`~pyrogram.Client` class, decorators are of a special kind and thus -deserve a dedicated page. - 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 - :emphasize-lines: 6 from pyrogram import Client diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index 2d83a05d..96941429 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -5,7 +5,6 @@ Handlers are used to instruct Pyrogram about which kind of updates you'd like to For a much more convenient way of registering callback functions have a look at :doc:`Decorators ` instead. .. code-block:: python - :emphasize-lines: 2, 11 from pyrogram import Client from pyrogram.handlers import MessageHandler diff --git a/docs/source/conf.py b/docs/source/conf.py index a9fcef52..ae84c5b8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,35 +29,37 @@ from pygments.styles.friendly import FriendlyStyle FriendlyStyle.background_color = "#f3f2f1" project = "Pyrogram" -copyright = f"2017-{datetime.now().year}, Dan" +copyright = f"2017-present, Dan" author = "Dan" +version = ".".join(__version__.split(".")[:-1]) + extensions = [ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.autosummary", - "sphinx_copybutton", - "sphinx_tabs.tabs" + "sphinx_copybutton" ] master_doc = "index" source_suffix = ".rst" autodoc_member_order = "bysource" -version = __version__ -release = version - -templates_path = ["_templates"] +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 = ["_static"] +html_static_path = ["_resources/static"] html_show_sourcelink = True html_show_copyright = False html_theme_options = { @@ -65,17 +67,15 @@ html_theme_options = { "collapse_navigation": True, "sticky_navigation": False, "logo_only": True, - "display_version": True, + "display_version": False, "style_external_links": True } -napoleon_use_param = False - -html_logo = "_images/pyrogram.png" -html_favicon = "_images/favicon.ico" +html_logo = "_resources/static/img/pyrogram.png" +html_favicon = "_resources/static/img/favicon.ico" latex_engine = "xelatex" -latex_logo = "_images/pyrogram.png" +latex_logo = "_resources/static/img/pyrogram.png" latex_elements = { "pointsize": "12pt", diff --git a/docs/source/faq.rst b/docs/source/faq.rst deleted file mode 100644 index 4bfc7135..00000000 --- a/docs/source/faq.rst +++ /dev/null @@ -1,407 +0,0 @@ -Pyrogram FAQ -============ - -.. role:: strike - :class: strike - -This FAQ page provides answers to common questions about Pyrogram and, to some extent, Telegram in general. - -.. tip:: - - If you think something interesting could be added here, feel free to propose it by opening a `Feature Request`_. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -What is Pyrogram? ------------------ - -**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and -C. It enables you to easily create custom applications for both user and bot identities (bot API alternative) via the -:doc:`MTProto API ` with the Python programming language. - -.. _Telegram: https://telegram.org - -Where does the name come from? ------------------------------- - -The name "Pyrogram" is composed by **pyro**, which comes from the Greek word *πῦρ (pyr)*, meaning fire, and **gram**, -from *Telegram*. The word *pyro* itself is built from *Python*, **py** for short, and the suffix **ro** to come up with -the word *fire*, which also inspired the project logo. - -How old is Pyrogram? --------------------- - -Pyrogram was first released on December 12, 2017. The actual work on the framework began roughly three months prior to the -initial public release on `GitHub`_. - -.. _GitHub: https://github.com/pyrogram/pyrogram - -Why Pyrogram? -------------- - -- **Easy**: You can install Pyrogram with pip and start building your applications right away. -- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way. -- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. -- **Asynchronous**: Allows both synchronous and asynchronous models to fit all usage needs. -- **Documented**: API methods, types and public interfaces are all well documented. -- **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. -- **Updated**, to make use of the latest Telegram API version and features. -- **Bot API-like**: Similar to the Bot API in its simplicity, but much more powerful and detailed. -- **Pluggable**: The :doc:`Smart Plugin ` system allows to write components with minimal - boilerplate code. -- **Comprehensive**: Execute any :doc:`advanced action ` an official client is able to do, and - even more. - -.. _TgCrypto: https://github.com/pyrogram/tgcrypto - -Why is Pyrogram defined as both Client Library and Framework? -------------------------------------------------------------- - -Simply because it falls in both categories, depending on how you use it. - -Pyrogram as a client library makes it easy and intuitive accessing the Telegram API by offering idiomatic Python code -that is generated or hand-written. Low-level details and client-server communication protocols are handled under the -hood. Pyrogram acts as a client library when *you call* its methods and use its types in a batch application that -executes a set of instructions. - -Pyrogram as a framework makes it easy to handle live events by allowing you to register event handlers that will be -executed as soon as they arrive from the server side. Pyrogram acts as a framework when it's Pyrogram itself that -*calls your code*, that is, your registered event handlers. Such applications are usually started and left online -indefinitely, until you decide to stop them. - -What can MTProto do more than the Bot API? ------------------------------------------- - -For a detailed answer, please refer to the :doc:`MTProto vs. Bot API ` page. - -Why do I need an API key for bots? ----------------------------------- - -Requests against the official bot API endpoint are made via JSON/HTTP, but are handled by an intermediate server -application that implements the MTProto protocol -- just like Pyrogram -- and uses its own API key, which is always -required, but hidden to the public. - -.. figure:: https://i.imgur.com/WvwBoZo.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. - -Can I use Webhooks? -------------------- - -Lots of people ask this question because they are used to the bot API, but things are different in Pyrogram! - -There is no webhook in Pyrogram, simply because there is no HTTP involved, by default. 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 simply sit down and wait for the server to send updates by itself -the very moment they are available (server push). - -Can I use the same file_id across different accounts? ------------------------------------------------------ - -No, Telegram doesn't allow this. - -File ids are personal and bound to a specific account; 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, like this -one: ``CAADBAADyg4AAvLQYAEYD4F7vcZ43AI``. - -Can I use Bot API's file_id values in Pyrogram? ------------------------------------------------ - -Yes! All file ids you take or might have taken from the Bot API are 100% compatible and re-usable in Pyrogram. -The opposite is also valid, you can take any file id generated by Pyrogram and re-use in the Bot API. - -Can I use multiple clients at once on the same account? -------------------------------------------------------- - -Yes, you can. Both user and bot accounts are able to run multiple sessions in parallel (up to 10 per account). However, -you must pay attention and not use the *same* exact session in more than one client at the same time. In other words: - -- Avoid copying your session file: even if you rename the file, the copied sessions will still point to a specific one - stored in the server. - -- Make sure that only one instance of your script runs, using your session file. - -If you -- even accidentally -- fail to do so, all the previous session copies will immediately stop receiving updates -and eventually the server will start throwing the error ``[406 AUTH_KEY_DUPLICATED]``, inviting you to login again. - -Why is that so? Because the server has recognized two identical sessions are running in two different locations, and -concludes it could possibly be due to a cloned/stolen device. Having the session terminated in such occasions will -protect the user's privacy. - -So, the only correct way to run multiple clients on the same account is authorizing your account (either user or bot) -from the beginning every time, and use one separate session for each parallel client you are going to use. - -I started a client and nothing happens! ---------------------------------------- - -If you are connecting from Russia, China or Iran :doc:`you need a proxy `, because Telegram could be -partially or totally blocked in those countries. More information about this block can be found at -`Wikipedia `_. - -Another possible cause might be network issues, either yours or Telegram's. To confirm this, add the following code on -the top of your script and run it again. You should see some error mentioning a socket timeout or an unreachable network -in a bunch of seconds: - -.. code-block:: python - - import logging - logging.basicConfig(level=logging.INFO) - -Another way to confirm you aren't able to connect to Telegram is by pinging the IP addresses below and see whether ping -fails or not. - -What are the IP addresses of Telegram Data Centers? ---------------------------------------------------- - -The Telegram cloud is currently composed by a decentralized, multi-DC infrastructure (currently 5 DCs, each of which can -work independently) spread in 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 `. - -***** Alias DC - -Thanks to `@FrayxRulez `_ for telling about alias DCs. - -I want to migrate my account from DCX to DCY. ---------------------------------------------- - -This question is often asked by people who find their account(s) always being connected to DC1 - USA (for example), but -are connecting from a place far away (e.g DC4 - Europe), thus resulting in slower interactions when using the API -because of the great physical distance between the user and its 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, based on the phone number origin. - -Even though Telegram `documentations `_ state the server might -decide to automatically migrate a user in case of prolonged usages from a distant, unusual location and albeit this -mechanism is also `confirmed `_ to exist by Telegram itself, -it's currently not possible to have your account migrated, in any way, simply because the feature was once planned but -not yet implemented. - -Thanks to `@gabriel `_ for confirming the feature was not implemented yet. - -Why is my client reacting slowly in supergroups? ------------------------------------------------- - -This issue affects only some supergroups or only some members within the same supergroup. Mostly, it affects supergroups -whose creator's account (and thus the supergroup itself) lives inside a **different DC**, far away from yours, but could -also depend on where a member is connecting from. - -Because of how Telegram works internally, every single message you receive from and send to other members 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 its DC to the creator's DC and finally to your DC. This process -will inevitably take its time. - - To confirm this theory and see it by yourself, you can test in a supergroup where you are sure all parties live - inside the same DC. In this case the responses will be faster. - -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. - -Thanks to `@Manuel15 `_ for the priority list. - -I keep getting PEER_ID_INVALID error! -------------------------------------- - -The error in question is ``[400 PEER_ID_INVALID]``, and could mean several things: - -- The chat id you tried to use is simply wrong, double check it. -- 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 -(either a forward or a mention in the message text) or obtaining the dialogs list. - -Code hangs when I 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 safely continue. In other words, 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. - -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 a -better terminal altogether. - -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 exceeds 20 MB in size. In such cases, your only option is to download the media yourself and upload from your -local machine. - -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. - -It could also 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 do 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. - -sqlite3.OperationalError: unable to open database file ------------------------------------------------------- - -Stackoverflow to the rescue: https://stackoverflow.com/questions/4636970 - -sqlite3.InterfaceError: Error binding parameter 0 -------------------------------------------------- - -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") - -My verification code expires immediately! ------------------------------------------ - -That is because you likely shared it across any of your Telegram chats. Yes, that's right: the server keeps scanning the -messages you send and if an active verification code is found it will immediately expire, automatically. - -The reason behind this is to protect unaware users from giving their account access to any potential scammer, but if you -legitimately want to share your account(s) verification codes, consider scrambling them, e.g. ``12345`` → ``1-2-3-4-5``. - -How can avoid Flood Waits? --------------------------- - -Long story short: make less requests, and remember that the API is designed to be used by official apps, by real people; -anything above normal usage could be limited. - -This question is being asked quite a lot of times, but the bottom line is that nobody knows the exact limits and it's -unlikely that such information will be ever disclosed, because otherwise people could easily circumvent them and defeat -their whole purpose. - -Do also note that Telegram wants to be a safe and reliable place and that limits exist to protect itself from abuses. -Having said that, here's some insights about limits: - -- They are tuned by Telegram based on real people usage and can change anytime. -- Some limits are be applied to single sessions, some others apply to the whole account. -- Limits vary based on methods and the arguments passed to methods. For example: log-ins are expensive and thus have - stricter limits; replying to a user command could cause a flood wait in case the user starts flooding, but - such limit will only be applied to that particular chat (i.e.: other users are not affected). -- You can catch Flood Wait exceptions in your code and wait the required seconds before continuing, this way: - - .. code-block:: python - - import time - from pyrogram.errors import FloodWait - - try: - ... # Your code - except FloodWait as e: - time.sleep(e.x) # Wait "x" seconds before continuing - - - More info about error handling can be found `here `_. - -My account has been deactivated/limited! ----------------------------------------- - -First of all, you should understand that Telegram wants to be a safe place for people to stay in, and to pursue this -goal there are automatic protection systems running to prevent flood and spam, as well as a moderation team of humans -who review reports. - -.. centered:: Pyrogram is a tool at your commands; it only does what you tell it to do, the rest is up to you. - -Having said that, here's a list of what Telegram definitely doesn't like: - -- Flood, abusing the API. -- Spam, sending unsolicited messages or adding people to unwanted groups and channels. -- Virtual/VoIP and cheap real numbers, because they are relatively easy to get and likely used for spam/flood. - -And thanks to `@koteeq `_, here's a good explanation of how, probably, the system works: - -.. raw:: html - - -

- -However, you might be right, and your account was deactivated/limited without any good reason. This could happen because -of mistakes by either the automatic systems or a moderator. In such cases you can kindly email Telegram at -recover@telegram.org, contact `@smstelegram`_ on Twitter or use `this form`_. - -Are there any secret easter eggs? ---------------------------------- - -Yes. If you found one, `let me know`_! - -.. _let me know: https://t.me/pyrogram - -.. _@smstelegram: https://twitter.com/smstelegram -.. _this form: https://telegram.org/support - -.. _Bug Report: https://github.com/pyrogram/pyrogram/issues/new?labels=bug&template=bug_report.md -.. _Feature Request: https://github.com/pyrogram/pyrogram/issues/new?labels=enhancement&template=feature_request.md 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..0736e576 --- /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 time + from pyrogram.errors import FloodWait + + ... + try: + ... # Your code + except FloodWait as e: + await asyncio.sleep(e.x) # Wait "x" seconds before continuing + ... + + +More info about error handling can be found :doc:`here <../start/errors>`. \ No newline at end of file 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..0b165349 --- /dev/null +++ b/docs/source/faq/index.rst @@ -0,0 +1,47 @@ +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:`why-is-the-event-handler-triggered-twice-or-more` +- :doc:`sqlite3-operationalerror-database-is-locked` +- :doc:`sqlite3-interfaceerror-error-binding-parameter` +- :doc:`socket-send-raised-exception-oserror-timeouterror` +- :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 + why-is-the-event-handler-triggered-twice-or-more + sqlite3-operationalerror-database-is-locked + sqlite3-interfaceerror-error-binding-parameter + socket-send-raised-exception-oserror-timeouterror + 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-raised-exception-oserror-timeouterror.rst b/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst new file mode 100644 index 00000000..e6a934d2 --- /dev/null +++ b/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst @@ -0,0 +1,10 @@ +socket.send() raised exception, OSError(), TimeoutError() +========================================================= + +If you get this error chances are you are blocking the event loop for too long. +In general, it means you are executing thread-blocking code that prevents the event loop from +running properly. For example: + +- You are using ``time.sleep()`` instead of ``asyncio.sleep()``. +- You are running processing loops that take too much time to complete. +- You are reading/writing files to disk that take too much time to complete. \ No newline at end of file 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/faq/why-is-the-event-handler-triggered-twice-or-more.rst b/docs/source/faq/why-is-the-event-handler-triggered-twice-or-more.rst new file mode 100644 index 00000000..ada12f1d --- /dev/null +++ b/docs/source/faq/why-is-the-event-handler-triggered-twice-or-more.rst @@ -0,0 +1,28 @@ +Why is the event handler called twice or more? +============================================== + +The event handler is being called twice or more because one or more message edit events have arrived. +By default, Pyrogram listens to both new and edit message events inside ``on_message`` handlers. To prevent edit events +from calling the handler, use the "not edited" filter ``~filters.edited``. + +For example: + +.. code-block:: python + + ... + + @app.on_message(... & ~filters.edited) + async def handler(client, message): + ... + +Or, avoid handling any edited message altogether this way: + +.. code-block:: python + + ... + + @app.on_message(filters.edited) + async def edited(client, message): + pass + + ... # other handlers \ No newline at end of file diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst deleted file mode 100644 index fa6086ce..00000000 --- a/docs/source/glossary.rst +++ /dev/null @@ -1,86 +0,0 @@ -Pyrogram Glossary -================= - -This page contains a list of common words with brief explanations related to Pyrogram and, to some extent, Telegram in -general. Some words may as well link to dedicated articles in case the topic is covered in a more detailed fashion. - -.. tip:: - - If you think something interesting could be added here, feel free to propose it by opening a `Feature Request`_. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Terms ------ - -.. glossary:: - :sorted: - - API - Application Programming Interface: a set of methods, protocols and tools that make it easier to develop programs - by providing useful building blocks to the developer. - - API key - A secret code used to authenticate and/or authorize a specific application to Telegram in order for it to - control how the API is being used, for example, to prevent abuses of the API. - :doc:`More on API keys `. - - DC - Also known as *data center*, is a place where lots of computer systems are housed and used together in order to - achieve high quality and availability for services. - - RPC - Acronym for Remote Procedure Call, that is, a function which gets executed at some remote place (i.e. Telegram - server) and not in your local machine. - - RPCError - An error caused by an RPC which must be returned in place of the successful result in order to let the caller - know something went wrong. :doc:`More on RPCError `. - - MTProto - The name of the custom-made, open and encrypted protocol by Telegram, implemented in Pyrogram. - :doc:`More on MTProto `. - - MTProto API - The Telegram main API Pyrogram makes use of, which is able to connect both users and normal bots to Telegram - using MTProto as application layer protocol and execute any method Telegram provides from its public TL-schema. - :doc:`More on MTProto API `. - - Bot API - The Telegram Bot API that is able to only connect normal bots only to Telegram using HTTP as application layer - protocol and allows to execute a sub-set of the main Telegram API. - :doc:`More on Bot API `. - - Pyrogrammer - A developer that uses Pyrogram to build Telegram applications. - - Userbot - Also known as *user bot* or *ubot* for short, is a user logged in by third-party Telegram libraries --- such as - Pyrogram --- to automate some behaviours, like sending messages or reacting to text commands or any other event. - Not to be confused with *bot*, that is, a normal Telegram bot created by `@BotFather `_. - - Session - Also known as *login session*, is a strictly personal piece of data created and held by both parties - (client and server) which is used to grant permission into a single account without having to start a new - authorization process from scratch. - - Callback - Also known as *callback function*, is a user-defined generic function that *can be* registered to and then - called-back by the framework when specific events occurs. - - Handler - An object that wraps around a callback function that is *actually meant* to be registered into the framework, - which will then be able to handle a specific kind of events, such as a new incoming message, for example. - :doc:`More on Handlers `. - - Decorator - Also known as *function decorator*, in Python, is a callable object that is used to modify another function. - Decorators in Pyrogram are used to automatically register callback functions for handling updates. - :doc:`More on Decorators `. - -.. _Feature Request: https://github.com/pyrogram/pyrogram/issues/new?labels=enhancement&template=feature_request.md diff --git a/docs/source/index.rst b/docs/source/index.rst index 38348534..8838c3db 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -5,7 +5,8 @@ Welcome to Pyrogram @@ -14,15 +15,15 @@ Welcome to Pyrogram
- Source Code + Development • - + Releases • - - Community + + News

@@ -35,16 +36,34 @@ Welcome to Pyrogram @app.on_message(filters.private) async def hello(client, message): - await message.reply_text(f"Hello {message.from_user.mention}") + await message.reply("Hello from Pyrogram!") app.run() -**Pyrogram** is a modern, elegant and easy-to-use Telegram_ framework written from the ground up in Python and C. -It enables you to easily create custom apps for both user and bot identities (bot API alternative) via the -:doc:`MTProto API `. +**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. -.. _Telegram: https://telegram.org +Support +------- + +If you'd like to support Pyrogram, you can consider: + +- `Become a GitHub sponsor `_. +- `Become a LiberaPay patron `_. +- `Become an OpenCollective backer `_. + +Key Features +------------ + +- **Ready**: Install Pyrogram with pip and start building your applications right away. +- **Easy**: Makes the Telegram API simple and intuitive, while still allowing advanced usages. +- **Elegant**: Low-level details are abstracted and re-presented in a more convenient way. +- **Fast**: Boosted up by :doc:`TgCrypto `, a high-performance crypto library written in pure C. +- **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. +- **Async**: Fully asynchronous (also usable synchronously if wanted, for convenience). +- **Powerful**: Full access to Telegram's API to execute any official client action and more. How the Documentation is Organized ---------------------------------- @@ -53,18 +72,11 @@ Contents are organized into sections composed of self-contained topics which can following them in order using the :guilabel:`Next` button at the end of each page. Here below you can, instead, find a list of the most relevant pages for a quick access. -.. admonition :: Cloud Credits - :class: tip - - If you need a cloud server to host your applications, we recommend using **Hetzner Cloud**. Sign up with - `this link `_ to get €20 in cloud credits and help support Pyrogram as - well. - First Steps ^^^^^^^^^^^ .. hlist:: - :columns: 2 + :columns: 1 - :doc:`Quick Start `: Overview to get you started quickly. - :doc:`Calling Methods `: How to call Pyrogram's methods. @@ -75,7 +87,7 @@ API Reference ^^^^^^^^^^^^^ .. hlist:: - :columns: 2 + :columns: 1 - :doc:`Pyrogram Client `: Reference details about the Client class. - :doc:`Available Methods `: List of available high-level methods. @@ -86,16 +98,12 @@ Meta ^^^^ .. hlist:: - :columns: 2 + :columns: 1 - - :doc:`Pyrogram FAQ `: Answers to common Pyrogram questions. - - :doc:`Pyrogram Glossary `: List of words with brief explanations. + - :doc:`Pyrogram FAQ `: Answers to common Pyrogram questions. - :doc:`Support Pyrogram `: Ways to show your appreciation. - - :doc:`About the License `: Information about the Project license. - :doc:`Release Notes `: Release notes for Pyrogram releases. -Last updated on |today| - .. toctree:: :hidden: :caption: Introduction @@ -136,14 +144,13 @@ Last updated on |today| topics/more-on-updates topics/config-file topics/smart-plugins - topics/session-settings + topics/client-settings topics/tgcrypto topics/storage-engines topics/text-formatting topics/serializing topics/proxy topics/scheduling - topics/bots-interaction topics/mtproto-vs-botapi topics/debugging topics/test-servers @@ -154,10 +161,8 @@ Last updated on |today| :hidden: :caption: Meta - faq - glossary + faq/index support - license releases/index .. toctree:: diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst index 813961dc..67a6b943 100644 --- a/docs/source/intro/install.rst +++ b/docs/source/intro/install.rst @@ -1,16 +1,9 @@ Install Guide ============= -Being a modern Python library, **Pyrogram** requires Python 3.6+ to be installed in your system. +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. -- Get **Python 3** from https://www.python.org/downloads/ (or with your package manager). -- Get **pip** by following the instructions at https://pip.pypa.io/en/latest/installing/. - -.. important:: - - Pyrogram supports **Python 3** only, starting from version 3.6. **PyPy** is supported too. - .. contents:: Contents :backlinks: none :depth: 1 @@ -36,12 +29,7 @@ Install Pyrogram Bleeding Edge ------------- -Pyrogram is always evolving, although new releases on PyPI are published only when enough changes are added, but this -doesn't mean you can't try new features right now! - -In case you'd like to try out the latest Pyrogram features, the `GitHub repo`_ is always kept updated with new changes; -you can install the development version straight from the ``master`` branch using this command (note "master.zip" in -the link): +You can install the development version from the git ``master`` branch using this command: .. code-block:: text @@ -57,6 +45,6 @@ If no error shows up you are good to go. >>> import pyrogram >>> pyrogram.__version__ - '|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 index eeb98482..0981d147 100644 --- a/docs/source/intro/quickstart.rst +++ b/docs/source/intro/quickstart.rst @@ -1,49 +1,54 @@ Quick Start =========== -The next few steps serve as a quick start for all new :term:`Pyrogrammers ` that want to see Pyrogram in -action as fast as possible. Let's go! +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 your best text editor and paste the following: +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" - with Client("my_account", api_id, api_hash) as app: - app.send_message("me", "Greetings from **Pyrogram**!") + 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 ``pyro.py``. +5. Save the file as ``hello.py``. -6. Run the script with ``python3 pyro.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. -9. Join our `community`_. - -10. Say, "hi!". - Enjoy the API ------------- -That was just a quick overview that barely scratched the surface! -In the next few pages of the introduction, we'll take a much more in-depth look of what we have just done above. +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. -Feeling eager to continue? You can take a shortcut to :doc:`Calling Methods <../start/invoking>` and come back later to -learn some more details. +If you are feeling eager to continue you can take a shortcut to :doc:`Calling Methods <../start/invoking>` and come back +later to learn some more details. .. _community: https://t.me/Pyrogram diff --git a/docs/source/intro/setup.rst b/docs/source/intro/setup.rst index e4c6e640..8bffcd86 100644 --- a/docs/source/intro/setup.rst +++ b/docs/source/intro/setup.rst @@ -2,7 +2,7 @@ Project Setup ============= We have just :doc:`installed Pyrogram `. In this page we'll discuss what you need to do in order to set up a -project with the library. Let's see how it's done. +project with the framework. .. contents:: Contents :backlinks: none @@ -17,18 +17,13 @@ API Keys The very first step requires you to obtain a valid Telegram API key (API id/hash pair): #. Visit https://my.telegram.org/apps and log in with your Telegram Account. -#. Fill out the form to register a new Telegram application. -#. Done! The API key consists of two parts: **api_id** and **api_hash**. - -.. important:: - - The API key is personal and must be kept secret. +#. 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 is unique for each user, but defines a token for a Telegram *application* you are going to build. This - means that you are able to authorize multiple users (and bots too) to access the Telegram database through the - MTProto API by a single API key. + 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 ------------- @@ -36,9 +31,9 @@ Configuration Having the API key from the previous step in handy, we can now begin to configure a Pyrogram project. There are two ways to do so, and you can choose what fits better for you: -- First option (recommended): create a new ``config.ini`` file next to your main script, copy-paste the following and - replace the **api_id** and **api_hash** values with your own. This is the preferred method because allows you to - keep your credentials out of your code without having to deal with how to load them: +- First option: create a new ``config.ini`` file next to your main script, copy-paste the following and + replace the *api_id* and *api_hash* values with your own. This method allows you to keep your credentials out of + your code without having to deal with how to load them. .. code-block:: ini @@ -47,8 +42,7 @@ There are two ways to do so, and you can choose what fits better for you: api_hash = 0123456789abcdef0123456789abcdef - Alternatively, you can pass your API key to Pyrogram by simply using the *api_id* and *api_hash* parameters of the - Client class. This way you can have full control on how to store and load your credentials (e.g., you can load the - credentials from the environment variables and directly pass the values into Pyrogram): + Client class. This way you can have full control on how to store and load your credentials: .. code-block:: python diff --git a/docs/source/license.rst b/docs/source/license.rst deleted file mode 100644 index 5f1d25ee..00000000 --- a/docs/source/license.rst +++ /dev/null @@ -1,16 +0,0 @@ -About the License -================= - -.. image:: https://www.gnu.org/graphics/lgplv3-with-text-154x68.png - :align: left - -Pyrogram is free software and is currently licensed under the terms of the -`GNU Lesser General Public License v3 or later (LGPLv3+)`_. In short: you may use, redistribute and/or modify it -provided that modifications are described and licensed for free under LGPLv3+. - -In other words: you can use and integrate Pyrogram into your code (either open source, under the same or a different -license, or even proprietary) for any purpose whatsoever without being required to release the source code of your -applications. Derivative works, including modifications to the library itself can only be redistributed under the same -LGPLv3+ license. - -.. _GNU Lesser General Public License v3 or later (LGPLv3+): https://github.com/pyrogram/pyrogram/blob/develop/COPYING.lesser diff --git a/docs/source/start/auth.rst b/docs/source/start/auth.rst index 23ff9fe2..0e61e59d 100644 --- a/docs/source/start/auth.rst +++ b/docs/source/start/auth.rst @@ -26,23 +26,20 @@ the :meth:`~pyrogram.Client.run` method: app = Client("my_account") app.run() -This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_) and the -**phone code** you will receive in your devices that are already authorized or via SMS: +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: +39********** - Is "+39**********" correct? (y/n): y - Enter phone code: 32768 - Logged in successfully as Dan + 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 will be loaded again when you restart your app, and as long as you -keep the session alive, Pyrogram won't ask you again to enter your phone number. - -.. important:: - - Your ``*.session`` file is personal and must be kept secret. +execute API calls with your identity. This file is personal and will be loaded again when you restart your app, and as +long as you keep the session alive, Pyrogram won't ask you again to enter your phone number. .. note:: diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst index 8e592f18..5f59deef 100644 --- a/docs/source/start/errors.rst +++ b/docs/source/start/errors.rst @@ -1,8 +1,8 @@ Error Handling ============== -Errors are inevitable when working with the API, and they 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: +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 @@ -25,10 +25,10 @@ This error is raised every time a method call against Telegram's API was unsucce from pyrogram.errors import RPCError -.. warning:: +.. note:: - It must be noted that catching this error is bad practice, 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. + 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 ---------------- @@ -84,9 +84,6 @@ whole category of errors and be sure to also handle these unknown errors. In case a whole class of errors is unknown (that is, an error code that is unknown), Pyrogram will raise a special ``520 UnknownError`` exception. -In both cases, Pyrogram will log them in the ``unknown_errors.txt`` file. Users are invited to report -these unknown errors in the `discussion group `_. - Errors with Values ------------------ diff --git a/docs/source/start/examples/bot_keyboards.rst b/docs/source/start/examples/bot_keyboards.rst index 6e288c43..a3a549f4 100644 --- a/docs/source/start/examples/bot_keyboards.rst +++ b/docs/source/start/examples/bot_keyboards.rst @@ -19,7 +19,7 @@ like send_audio(), send_document(), send_location(), etc... with app: app.send_message( - "haskell", # Edit this + "me", # Edit this "This is a ReplyKeyboardMarkup example", reply_markup=ReplyKeyboardMarkup( [ @@ -33,7 +33,7 @@ like send_audio(), send_document(), send_location(), etc... ) app.send_message( - "haskell", # Edit this + "me", # Edit this "This is a InlineKeyboardMarkup example", reply_markup=InlineKeyboardMarkup( [ diff --git a/docs/source/start/examples/echobot.rst b/docs/source/start/examples/echobot.rst index 61dc9929..2ff578e9 100644 --- a/docs/source/start/examples/echobot.rst +++ b/docs/source/start/examples/echobot.rst @@ -15,7 +15,7 @@ It uses the ``@on_message`` decorator to register a ``MessageHandler`` and appli @app.on_message(filters.text & filters.private) def echo(client, message): - message.reply_text(message.text) + message.reply(message.text) app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/hello_world.rst b/docs/source/start/examples/hello_world.rst index e6884779..997659e2 100644 --- a/docs/source/start/examples/hello_world.rst +++ b/docs/source/start/examples/hello_world.rst @@ -13,9 +13,3 @@ This example demonstrates a basic API usage with app: # Send a message, Markdown is enabled by default app.send_message("me", "Hi there! I'm using **Pyrogram**") - - # Send a location - app.send_location("me", 51.500729, -0.124583) - - # Send a sticker - app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") \ No newline at end of file diff --git a/docs/source/start/examples/inline_queries.rst b/docs/source/start/examples/inline_queries.rst index 023b9c6e..09d226ef 100644 --- a/docs/source/start/examples/inline_queries.rst +++ b/docs/source/start/examples/inline_queries.rst @@ -26,7 +26,6 @@ It uses the @on_inline_query decorator to register an InlineQueryHandler. ), url="https://docs.pyrogram.org/intro/install", description="How to install Pyrogram", - thumb_url="https://i.imgur.com/JyxrStE.png", reply_markup=InlineKeyboardMarkup( [ [InlineKeyboardButton( @@ -43,7 +42,6 @@ It uses the @on_inline_query decorator to register an InlineQueryHandler. ), url="https://docs.pyrogram.org/start/invoking", description="How to use Pyrogram", - thumb_url="https://i.imgur.com/JyxrStE.png", reply_markup=InlineKeyboardMarkup( [ [InlineKeyboardButton( diff --git a/docs/source/start/examples/use_inline_bots.rst b/docs/source/start/examples/use_inline_bots.rst index 284432d8..63e46985 100644 --- a/docs/source/start/examples/use_inline_bots.rst +++ b/docs/source/start/examples/use_inline_bots.rst @@ -11,8 +11,8 @@ This example shows how to query an inline bot (as user). app = Client("my_account") with app: - # Get bot results for "Fuzz Universe" from the inline bot @vid - bot_results = app.get_inline_bot_results("vid", "Fuzz Universe") + # Get bot results for "hello" from the inline bot @vid + bot_results = app.get_inline_bot_results("vid", "hello") # Send the first result (bot_results.results[0]) to your own chat (Saved Messages) app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id) \ No newline at end of file diff --git a/docs/source/start/examples/welcomebot.rst b/docs/source/start/examples/welcomebot.rst index cdd7f022..a7420590 100644 --- a/docs/source/start/examples/welcomebot.rst +++ b/docs/source/start/examples/welcomebot.rst @@ -8,7 +8,7 @@ to make it only work for specific messages in a specific chat. from pyrogram import Client, emoji, filters - TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames + TARGET = -100123456789 # Target chat. Can also be a list of multiple chat ids/usernames MENTION = "[{}](tg://user?id={})" # User mention markup MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.org/)'s group chat {}!" # Welcome message diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst index b8eddb9e..de1e321e 100644 --- a/docs/source/start/invoking.rst +++ b/docs/source/start/invoking.rst @@ -2,7 +2,7 @@ Calling 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 library. It's time to start playing with the API! +account; we are now aiming towards the core of the framework. .. contents:: Contents :backlinks: none @@ -22,60 +22,53 @@ Making API method calls with Pyrogram is very simple. Here's a basic example we app = Client("my_account") - with app: - app.send_message("me", "Hi!") + async def main(): + async with app: + await app.send_message("me", "Hi!") -Basic step-by-step -^^^^^^^^^^^^^^^^^^ + app.run(main()) -#. Let's begin by importing the Client class: +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: +#. Now instantiate a new Client object, "my_account" is a session name of your choice. .. code-block:: python app = Client("my_account") -#. The ``with`` context manager is a shortcut for starting, executing and stopping the Client: +#. Async methods can't be executed at the top level, because they must be inside 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 - with app: + async def main(): + async with app: + await app.send_message("me", "Hi!") -#. Now, you can call any method you like: +#. Finally, we tell Python to schedule our ``main()`` async function by using Pyrogram's :meth:`~pyrogram.Client.run` + method. .. code-block:: python - app.send_message("me", "Hi!") + app.run(main()) Context Manager --------------- -The ``with`` statement starts a context manager used as a shortcut to automatically call :meth:`~pyrogram.Client.start` -and :meth:`~pyrogram.Client.stop`, which are methods required for Pyrogram to work properly. The context manager does -also gracefully stop the client, even in case of unhandled exceptions in your code. +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. -This is how Pyrogram looks without the context manager: - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - app.start() - app.send_message("me", "Hi!") - app.stop() - -Asynchronous Calls ------------------- - -In case you want Pyrogram to run asynchronously (e.g.: if you are using third party libraries that require you to call -them with ``await``), use the asynchronous context manager: +Below there's the same example as above, but without the use of the context manager: .. code-block:: python @@ -84,36 +77,48 @@ them with ``await``), use the asynchronous context manager: app = Client("my_account") async def main(): - async with app: - await app.send_message("me", "Hi!") + await app.start() + await app.send_message("me", "Hi!") + await app.stop() app.run(main()) -Asynchronous step-by-step -^^^^^^^^^^^^^^^^^^^^^^^^^ +Using asyncio.run() +------------------- -#. Import the Client class and create an instance: +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 +.. code-block:: python - from pyrogram import Client + import asyncio + from pyrogram import Client + async def main(): app = Client("my_account") -#. Async methods can't normally be executed at the top level, because they must be inside an async-defined function; - here we define one and put our code inside; the context manager is also being used differently in asyncio and - method calls require the await keyword: + async with app: + await app.send_message("me", "Hi!") - .. code-block:: python + asyncio.run(main()) - async def main(): - async with app: - await app.send_message("me", "Hi!") +Synchronous Calls +------------------ -#. Finally, we tell Python to schedule our ``main()`` async function, which in turn will execute Pyrogram's methods. - Using :meth:`~pyrogram.Client.run` this way is a friendly alternative for the much more verbose - ``asyncio.get_event_loop().run_until_complete(main())``: +Pyrogram is an asynchronous framework, but it also provides a convenience way for calling methods without the need +of async/await keywords and the extra boilerplate. In case you want Pyrogram to run synchronously, simply use the +synchronous context manager: - .. code-block:: python +.. code-block:: python - app.run(main()) + from pyrogram import Client + + app = Client("my_account") + + with app: + app.send_message("me", "Hi!") + +As you can see, the non-async example becomes less cluttered. Use Pyrogram in this non-asynchronous way only when you +want to write something without the boilerplate or in case you want to combine Pyrogram with other libraries that are +not async. \ No newline at end of file diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst index 27dd0316..dee5a115 100644 --- a/docs/source/start/updates.rst +++ b/docs/source/start/updates.rst @@ -1,8 +1,8 @@ Handling Updates ================ -Calling :doc:`API methods ` sequentially is cool, but how to react when, for example, a new message arrives? -This page deals with updates and how to handle such events in Pyrogram. Let's have a look at how they work. +Calling :doc:`API methods ` sequentially is one way to use Pyrogram, but how to react when, for example, a +new message arrives? This page deals with updates and how to handle such events in Pyrogram. .. contents:: Contents :backlinks: none @@ -14,10 +14,9 @@ This page deals with updates and how to handle such events in Pyrogram. Let's ha Defining Updates ---------------- -First, let's define what are these updates. As hinted already, updates are simply 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>`. +As hinted already, updates are simply 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. @@ -40,50 +39,51 @@ The most elegant way to register a message handler is by using the :meth:`~pyrog app = Client("my_account") - @app.on_message() - def my_handler(client, message): - message.forward("me") - + 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. -Asynchronous handlers +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``. + +Synchronous handlers ^^^^^^^^^^^^^^^^^^^^^ -You can also have asynchronous handlers; you only need to define the callback function using ``async def`` and call API -methods by placing ``await`` in front of them: +You can also have synchronous handlers; you only need to define the callback function without using ``async def`` and +call API methods by not placing ``await`` in front of them: .. code-block:: python @app.on_message() - async def my_handler(client, message): - await message.forward("me") + def my_handler(client, message): + message.forward("me") .. note:: - You can mix ``def`` and ``async def`` handlers as much as you need, Pyrogram will still work concurrently and - efficiently regardless of what you choose. + You can mix ``def`` and ``async def`` handlers as much as you like, Pyrogram will still work concurrently and + efficiently regardless of what you choose. However, it is recommended to use Pyrogram in its native, asynchronous + form at all times, unless you want to write something without the boilerplate or in case you want to combine + Pyrogram with other libraries that are not async. 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 (or in case, -for some reason, you don't like to use decorators). +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 - - def my_function(client, message): - message.forward("me") - + async def my_function(client, message): + await message.forward("me") app = Client("my_account") @@ -92,12 +92,12 @@ for some reason, you don't like to use decorators). app.run() -The same about asynchronous handlers applies for :meth:`~pyrogram.Client.add_handler`: +The same about synchronous handlers applies for :meth:`~pyrogram.Client.add_handler`: .. code-block:: python - async def my_function(client, message): - await message.forward("me") + def my_function(client, message): + message.forward("me") .. note:: diff --git a/docs/source/support.rst b/docs/source/support.rst index 029eeec0..32dfdb6b 100644 --- a/docs/source/support.rst +++ b/docs/source/support.rst @@ -1,66 +1,62 @@ Support Pyrogram ================ -As a developer, you probably understand that "open source" doesn't mean "free work". If you wish to tip me for Pyrogram --- or any of my `other works`_ -- you can do so by the ways shown below. Your appreciation means a lot and helps -staying motivated! +.. raw:: html ---- `Dan`_ + + +
+ Fork + + Star +
+ +
+ +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: ----- -Star ----- +GitHub Sponsor +-------------- -Pyrogram is free and open source software, and thus powered by your love and support! If you like the project and have -found it to be useful, give Pyrogram a `Star on GitHub`_. +`Become a GitHub sponsor `_. .. raw:: html - Star -

+ + Sponsor ----- -Sponsor -------- +LiberaPay Patron +---------------- -You can become a GitHub sponsor: +`Become a LiberaPay patron `_. .. raw:: html - + ----- -Donate ------- +OpenCollective Backer +--------------------- -You can donate via PayPal using the button below: +`Become an OpenCollective backer `_ .. raw:: html -
- - - -
- ------ - -Cloud Credits -------------- - -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 and help support Pyrogram and -my `other projects`_. - -.. _Star on GitHub: https://github.com/pyrogram/pyrogram -.. _other projects: https://github.com/delivrance -.. _other works: https://github.com/delivrance -.. _Dan: https://t.me/haskell + \ No newline at end of file diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst index 9df028ad..0b0f0683 100644 --- a/docs/source/topics/advanced-usage.rst +++ b/docs/source/topics/advanced-usage.rst @@ -1,9 +1,8 @@ Advanced Usage ============== -Pyrogram's API, which consists of well documented convenience :doc:`methods <../api/methods/index>` and facade -:doc:`types <../api/types/index>`, exists to provide a much easier interface to the undocumented and often confusing -Telegram API. +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. @@ -21,25 +20,18 @@ 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 really confusing, mainly because people don't realize soon enough they -accept *only* the right types and that all required parameters must be filled in. This section will therefore explain -some pitfalls to take into consideration when working with the raw API. +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. -.. hint:: +.. tip:: - Every available high-level methods in Pyrogram is built on top of these raw functions. - - Nothing stops you from using the raw functions only, but they are rather complex and - :doc:`plenty of them <../api/methods/index>` are already re-implemented by providing a much simpler and cleaner - interface which is very similar to the Bot API (yet much more powerful). - - If you think a raw function should be wrapped and added as a high-level method, feel free to ask in our Community_! + 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 and are more complex. +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 @@ -61,12 +53,12 @@ Here's some examples: with Client("my_account") as app: app.send( functions.account.UpdateProfile( - first_name="Dan", last_name="Tès", - about="Bio written from Pyrogram" + first_name="First Name", last_name="Last Name", + about="New bio text" ) ) -- Disable links to your account when someone forwards your messages: +- Set online/offline status: .. code-block:: python @@ -74,14 +66,13 @@ Here's some examples: from pyrogram.raw import functions, types with Client("my_account") as app: - app.send( - functions.account.SetPrivacy( - key=types.PrivacyKeyForwards(), - rules=[types.InputPrivacyValueDisallowAll()] - ) - ) + # Set online status + app.send(functions.account.UpdateStatus(offline=False)) -- Invite users to your channel/supergroup: + # Set offline status + app.send(functions.account.UpdateStatus(offline=True)) + +- Get chat info: .. code-block:: python @@ -89,21 +80,18 @@ Here's some examples: from pyrogram.raw import functions, types with Client("my_account") as app: - app.send( - functions.channels.InviteToChannel( - channel=app.resolve_peer(123456789), # ID or Username - users=[ # The users you want to invite - app.resolve_peer(23456789), # By ID - app.resolve_peer("username"), # By username - app.resolve_peer("+393281234567"), # By phone number - ] + r = app.send( + functions.channels.GetFullChannel( + channel=app.resolve_peer("username") ) ) + print(r) + Chat IDs -^^^^^^^^ +-------- -The way Telegram works makes it impossible to directly send a message to a user or a chat by using their IDs only. +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. @@ -112,18 +100,17 @@ 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` - Either Channels or Supergroups +- :class:`~pyrogram.raw.types.InputPeerChannel` - Channels & Supergroups -But you don't necessarily have to manually instantiate each object because, luckily for you, Pyrogram already provides +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 can theoretically lead to -collisions, and that's why Pyrogram (as well as the official Bot API) uses a slightly different representation for each -kind of ID. +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: diff --git a/docs/source/topics/bots-interaction.rst b/docs/source/topics/bots-interaction.rst deleted file mode 100644 index 1fad2069..00000000 --- a/docs/source/topics/bots-interaction.rst +++ /dev/null @@ -1,50 +0,0 @@ -Bots Interaction -================ - -Users can interact with other bots via plain text messages as well as inline queries. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Inline Bots ------------ - -- If a bot accepts inline queries, you can call it by using - :meth:`~pyrogram.Client.get_inline_bot_results` to get the list of its inline results for a query: - - .. code-block:: python - - # Get bot results for "Fuzz Universe" from the inline bot @vid - bot_results = app.get_inline_bot_results("vid", "Fuzz Universe") - - .. figure:: https://i.imgur.com/IAqLs54.png - :width: 90% - :align: center - :figwidth: 60% - - ``get_inline_bot_results()`` is the equivalent action of writing ``@vid Fuzz Universe`` and getting the - results list. - -- After you retrieved the bot results, you can use - :meth:`~pyrogram.Client.send_inline_bot_result` to send a chosen result to any chat: - - .. code-block:: python - - # Send the first result to your own chat - app.send_inline_bot_result( - "me", - bot_results.query_id, - bot_results.results[0].id - ) - - .. figure:: https://i.imgur.com/wwxr7B7.png - :width: 90% - :align: center - :figwidth: 60% - - ``send_inline_bot_result()`` is the equivalent action of choosing a result from the list and sending it - to a chat. diff --git a/docs/source/topics/session-settings.rst b/docs/source/topics/client-settings.rst similarity index 54% rename from docs/source/topics/session-settings.rst rename to docs/source/topics/client-settings.rst index 847427cf..1b93d99d 100644 --- a/docs/source/topics/session-settings.rst +++ b/docs/source/topics/client-settings.rst @@ -1,24 +1,12 @@ -Session Settings -================ +Client Settings +=============== -As you may probably know, Telegram allows users (and bots) having more than one session (authorizations) registered -in the system at the same time. +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: -Briefly explaining, sessions are simply new logins in your account. They can be reviewed in the settings of an official -app (or by invoking :class:`~pyrogram.api.functions.account.GetAuthorizations` with Pyrogram). They -store some useful information such as the client who's using them and from which country and IP address. - -.. figure:: https://i.imgur.com/YaqtMLO.png - :width: 600 - :align: center - - **A Pyrogram session running on Linux, Python 3.7.** - -That's how a session looks like on the Android app, showing the three main pieces of information. - -- ``app_version``: **Pyrogram 0.13.0** -- ``device_model``: **CPython 3.7.2** -- ``system_version``: **Linux 4.15.0-23-generic** +- Device Model: ``CPython x.y.z`` +- Application: ``Pyrogram x.y.z`` +- System Version: ``Linux x.y.z`` .. contents:: Contents :backlinks: none diff --git a/docs/source/topics/config-file.rst b/docs/source/topics/config-file.rst index 38bb9e33..1657625a 100644 --- a/docs/source/topics/config-file.rst +++ b/docs/source/topics/config-file.rst @@ -15,7 +15,7 @@ Introduction ------------ The idea behind using a configuration file is to help keeping your code free of private settings information such as -the API Key and Proxy, without having you to even deal with how to load such settings. The configuration file, usually +the API Key and Proxy, without having you to deal with how to load such settings. The configuration file, usually referred as ``config.ini`` file, is automatically loaded from the root of your working directory; all you need to do is fill in the necessary parts. @@ -23,7 +23,7 @@ fill in the necessary parts. The configuration file is optional, but recommended. If, for any reason, you prefer not to use it, there's always an alternative way to configure Pyrogram via Client's parameters. Doing so, you can have full control on how to store - and load your settings (e.g.: from environment variables). + and load your settings. Settings specified via Client's parameter have higher priority and will override any setting stored in the configuration file. diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst index 191eeb05..ae7bd5ec 100644 --- a/docs/source/topics/create-filters.rst +++ b/docs/source/topics/create-filters.rst @@ -87,7 +87,7 @@ Finally, the filter usage remains the same: Filters with Arguments ---------------------- -A much cooler filter would be one that accepts "pyrogram" or any other string as argument at usage time. +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. diff --git a/docs/source/topics/debugging.rst b/docs/source/topics/debugging.rst index ac0b396f..6e6e6d5e 100644 --- a/docs/source/topics/debugging.rst +++ b/docs/source/topics/debugging.rst @@ -2,7 +2,7 @@ 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 -- that's normal -- and luckily for you, Pyrogram provides some commodities to help you in this. +to actually worry about since Pyrogram provides some commodities to help you in this. .. contents:: Contents :backlinks: none @@ -27,8 +27,8 @@ Consider the following code: .. code-block:: python - dan = app.get_users("haskell") - print(dan) # User + me = 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: @@ -36,9 +36,9 @@ This will show a JSON representation of the object returned by :meth:`~pyrogram. .. code-block:: json { - "_": "pyrogram.User", - "id": 23122162, - "is_self": false, + "_": "User", + "id": 123456789, + "is_self": true, "is_contact": false, "is_mutual_contact": false, "is_deleted": false, @@ -46,19 +46,13 @@ This will show a JSON representation of the object returned by :meth:`~pyrogram. "is_verified": false, "is_restricted": false, "is_support": false, - "is_scam": false, - "first_name": "Dan", - "status": { - "_": "pyrogram.UserStatus", - "user_id": 23122162, - "recently": true - }, - "username": "haskell", - "language_code": "en", + "first_name": "Pyrogram", "photo": { - "_": "pyrogram.ChatPhoto", - "small_file_id": "AQADBAAD8tBgAQAEJjCxGgAEo5IBAAIC", - "big_file_id": "AQADBAAD8tBgAQAEJjCxGgAEpZIBAAEBAg" + "_": "ChatPhoto", + "small_file_id": "AbCdE...EdCbA", + "small_photo_unique_id": "VwXyZ...ZyXwV", + "big_file_id": "AbCdE...EdCbA", + "big_photo_unique_id": "VwXyZ...ZyXwV" } } @@ -69,37 +63,23 @@ Accessing Attributes -------------------- Even though you see a JSON output, it doesn't mean we are dealing with dictionaries; in fact, all Pyrogram types are -full-fledged Python objects and the correct way to access any attribute of them is by using the dot notation ``.``: +fully-fledged Python objects and the correct way to access any attribute of them is by using the dot notation ``.``: .. code-block:: python - dan_photo = dan.photo - print(dan_photo) # ChatPhoto + photo = me.photo + print(photo) # ChatPhoto .. code-block:: json { - "_": "pyrogram.ChatPhoto", - "small_file_id": "AQADBAAD8tBgAQAEJjCxGgAEo5IBAAIC", - "big_file_id": "AQADBAAD8tBgAQAEJjCxGgAEpZIBAAEBAg" + "_": "ChatPhoto", + "small_file_id": "AbCdE...EdCbA", + "small_photo_unique_id": "VwXyZ...ZyXwV", + "big_file_id": "AbCdE...EdCbA", + "big_photo_unique_id": "VwXyZ...ZyXwV" } -However, the bracket notation ``[]`` is also supported, but its usage is discouraged: - -.. warning:: - - Bracket notation in Python is not commonly used for getting/setting object attributes. While it works for Pyrogram - objects, it might not work for anything else and you should not rely on this. - -.. code-block:: python - - dan_photo_big = dan["photo"]["big_file_id"] - print(dan_photo_big) # str - -.. code-block:: text - - AQADBAAD8tBgAQAEJjCxGgAEpZIBAAEBAg - Checking an Object's Type ------------------------- @@ -111,8 +91,8 @@ error. The correct way to get the object type is by using the built-in function .. code-block:: python - dan_status = dan.status - print(type(dan_status)) + status = me.status + print(type(status)) .. code-block:: text @@ -125,8 +105,8 @@ And to check if an object is an instance of a given class, you use the built-in from pyrogram.types import UserStatus - dan_status = dan.status - print(isinstance(dan_status, UserStatus)) + status = me.status + print(isinstance(status, UserStatus)) .. code-block:: text diff --git a/docs/source/topics/more-on-updates.rst b/docs/source/topics/more-on-updates.rst index a636008a..81a46f3b 100644 --- a/docs/source/topics/more-on-updates.rst +++ b/docs/source/topics/more-on-updates.rst @@ -24,7 +24,6 @@ group. Dispatching groups hold one or more handlers and are processed sequential For example, take these two handlers: .. code-block:: python - :emphasize-lines: 1, 6 @app.on_message(filters.text | filters.sticker) def text_or_sticker(client, message): diff --git a/docs/source/topics/mtproto-vs-botapi.rst b/docs/source/topics/mtproto-vs-botapi.rst index c73d6692..9681c1eb 100644 --- a/docs/source/topics/mtproto-vs-botapi.rst +++ b/docs/source/topics/mtproto-vs-botapi.rst @@ -1,10 +1,10 @@ MTProto vs. Bot API =================== -Pyrogram is a framework that acts as a fully-fledged Telegram client based on MTProto, and this very feature makes it -already superior to, what is usually called, the official Bot API, in many respects. 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. +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 @@ -19,10 +19,11 @@ 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 as a whole. This 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. +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 @@ -31,12 +32,12 @@ HTTP as transport-layer protocol. What is the Bot API? -------------------- -The `Bot API`_ is an HTTP(S) interface for building normal bots using a sub-set of the main MTProto 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. +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:: https://i.imgur.com/WvwBoZo.png +.. figure:: //_static/img/mtproto-vs-bot-api.png :align: center .. _Bot API: https://core.telegram.org/bots/api @@ -44,8 +45,8 @@ using MTProto. Advantages of the MTProto API ----------------------------- -Here is a list of all the advantages in using MTProto-based libraries -- such as Pyrogram -- instead of the official -HTTP Bot API. Using Pyrogram you can: +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 @@ -69,7 +70,7 @@ HTTP Bot API. Using Pyrogram you can: .. hlist:: :columns: 1 - - :guilabel:`+` **Run multiple sessions at once, up to 10 per account (either bot or user)** + - :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. diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst index f96e7b07..a67a9254 100644 --- a/docs/source/topics/scheduling.rst +++ b/docs/source/topics/scheduling.rst @@ -14,8 +14,8 @@ non-asynchronous contexts. For more detailed information, you can visit and lear ----- -Using ``apscheduler`` ---------------------- +Using apscheduler +----------------- - Install with ``pip3 install apscheduler`` - Documentation: https://apscheduler.readthedocs.io diff --git a/docs/source/topics/serializing.rst b/docs/source/topics/serializing.rst index 7e013573..e12e3f05 100644 --- a/docs/source/topics/serializing.rst +++ b/docs/source/topics/serializing.rst @@ -24,8 +24,7 @@ If you want a nicely formatted, human readable JSON representation of any object ... with app: - r = app.get_chat("haskell") - + r = app.get_chat("me") print(str(r)) .. tip:: @@ -48,7 +47,7 @@ as the process requires the package to be in scope. ... with app: - r = app.get_chat("haskell") + r = app.get_chat("me") print(repr(r)) print(eval(repr(r)) == r) # True diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst index fece6366..a94e3212 100644 --- a/docs/source/topics/smart-plugins.rst +++ b/docs/source/topics/smart-plugins.rst @@ -1,9 +1,9 @@ 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**. +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:: @@ -74,8 +74,8 @@ after importing your modules, like this: 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 those cool decorators** for your -functions. So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically. +: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 ------------------- @@ -91,7 +91,6 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight This is the same example application as shown above, written using the Smart Plugin system. .. code-block:: text - :emphasize-lines: 2, 3 myproject/ plugins/ @@ -102,7 +101,6 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight - ``plugins/handlers.py`` .. code-block:: python - :emphasize-lines: 4, 9 from pyrogram import Client, filters @@ -164,7 +162,7 @@ found inside each module will be, instead, loaded in the order they are defined, .. 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. Learn more at :doc:`More on Updates `. + 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`` @@ -300,32 +298,30 @@ In the previous section we've explained how to specify which plugins to load and 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 ``handler`` attribute pointing to a tuple of +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 - :emphasize-lines: 5, 6 @Client.on_message(filters.text & filters.private) def echo(client, message): message.reply(message.text) print(echo) - print(echo.handler) + print(echo.handlers) - Printing ``echo`` will show something like ````. -- Printing ``echo.handler`` will reveal the handler, that is, a tuple containing the actual handler and the group it - was registered on ``(, 0)``. +- 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* special attribute preceded by the -star ``*`` operator as argument. Example: +:meth:`~pyrogram.Client.remove_handler` Client's method with your function's *handler* instance: - ``main.py`` @@ -333,16 +329,19 @@ star ``*`` operator as argument. Example: from plugins.handlers import echo - ... + handlers = echo.handlers - app.remove_handler(*echo.handler) + 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 - handler, group = echo.handler + handlers = echo.handlers + handler, group = handlers[0] + app.remove_handler(handler, group) Loading @@ -359,4 +358,7 @@ using :meth:`~pyrogram.Client.add_handler` instead. Example: ... - app.add_handler(*echo.handler) \ No newline at end of file + handlers = echo.handlers + + for h in handlers: + app.add_handler(*h) \ No newline at end of file diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst index 1a86cad8..8d693647 100644 --- a/docs/source/topics/storage-engines.rst +++ b/docs/source/topics/storage-engines.rst @@ -18,28 +18,18 @@ 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. -Other useful data being stored is peers' cache. In short, peers are all those entities you can chat with, such as users -or bots, basic groups, but also channels and supergroups. Because of how Telegram works, a unique pair of **id** and -**access_hash** is needed to contact a peer. This, plus other useful info such as the peer type, is what is stored -inside a session storage. - -So, if you ever wondered how is Pyrogram able to contact peers just by asking for their ids, it's because of this very -reason: the peer *id* is looked up in the internal database and the available *access_hash* is retrieved, which is then -used to correctly invoke API methods. - Different Storage Engines ------------------------- -Let's now talk about how Pyrogram actually stores all the relevant data. Pyrogram offers two different types of storage -engines: a **File Storage** and a **Memory Storage**. These engines are well integrated in the library and require a -minimal effort to set up. Here's how they work: +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 and peers -details. The database will be saved to disk as a single portable file and is designed to efficiently store and retrieve -peers whenever they are needed. +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 ``session_name`` parameter of the :obj:`~pyrogram.Client` constructor, as usual: @@ -68,8 +58,8 @@ session name "**:memory:**" to the ``session_name`` parameter of the :obj:`~pyro with Client(":memory:") as app: print(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. +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 --------------- @@ -84,8 +74,8 @@ In case you want to use an in-memory storage, but also want to keep access to th with Client(":memory:") as app: print(app.export_session_string()) -...and save the resulting (quite long) string somewhere. You can use this string as session name the next time you want -to login using the same session; the storage used will still be completely in-memory: +...and save the resulting string. You can use this string as session name the next time you want to login +using the same session; the storage used will still be in-memory: .. code-block:: python @@ -96,11 +86,5 @@ to login using the same session; the storage used will still be completely in-me with Client(session_string) as app: print(app.get_me()) -Session strings are useful when you want to run authorized Pyrogram clients on platforms like -`Heroku `_, where their ephemeral filesystems makes it much harder for a file-based storage -engine to properly work as intended. - -But, why is the session string so long? Can't it be shorter? No, it can't. The session string already packs the bare -minimum data Pyrogram needs to successfully reconnect to an authorized session, and the 2048-bits auth key is the major -contributor to the overall length. Needless to say that this string, as well as any other session storage, represent -strictly personal data. Keep them safe. +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/test-servers.rst b/docs/source/topics/test-servers.rst index 3ed996ee..cba5e709 100644 --- a/docs/source/topics/test-servers.rst +++ b/docs/source/topics/test-servers.rst @@ -15,8 +15,7 @@ Telegram's test servers without hassle. All you need to do is start a new sessio .. note:: If this is the first time you login into test servers, you will be asked to register your account first. - Don't worry about your contacts and chats, they will be kept untouched inside the production environment; - accounts authorized on test servers reside in a different, parallel instance of a Telegram database. + Accounts registered on test servers reside in a different, parallel instance of a Telegram server. .. contents:: Contents :backlinks: none @@ -28,19 +27,15 @@ Telegram's test servers without hassle. All you need to do is start a new sessio Test Mode in Official Apps -------------------------- -You can also login yourself into test servers using official desktop apps, such as Webogram and TDesktop: +You can also login yourself into test servers using official desktop apps, such as Telegram Web and Telegram Desktop: -- **Webogram**: Login here: https://web.telegram.org/?test=1 -- **TDesktop**: Hold ``Alt+Shift`` and right click on "Add account", then choose "Test server". +- **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`` as the confirmation code (the DC number, repeated five times). - -.. important:: - - Do not store any important or private information in such test users' accounts; anyone can make use of the - simplified authorization mechanism and login at any time. +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 index e3b730e5..df3589a9 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -14,7 +14,7 @@ Text Formatting :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 great +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 @@ -36,7 +36,7 @@ list of the basic styles currently supported by Pyrogram. - :underline:`underline` - spoiler - `text URL `_ -- `user text mention `_ +- `user text mention `_ - ``inline fixed-width code`` - .. code-block:: text @@ -66,9 +66,9 @@ To strictly use this mode, pass "markdown" to the *parse_mode* parameter when us ||spoiler|| - [text URL](https://docs.pyrogram.org/) + [text URL](https://pyrogram.org/) - [text user mention](tg://user?id=23122162) + [text user mention](tg://user?id=123456789) `inline fixed-width code` @@ -83,14 +83,13 @@ To strictly use this mode, pass "markdown" to the *parse_mode* parameter when us .. code-block:: python app.send_message( - "haskell", + "me", ( "**bold**, " "__italic__, " "--underline--, " "~~strike~~, " "||spoiler||, " - "[mention](tg://user?id=23122162), " "[URL](https://pyrogram.org), " "`code`, " "```" @@ -119,9 +118,9 @@ The following tags are currently supported: spoiler - text URL + text URL - inline mention + inline mention inline fixed-width code @@ -136,14 +135,13 @@ The following tags are currently supported: .. code-block:: python app.send_message( - "haskell", + "me", ( "bold, " "italic, " "underline, " "strike, " "spoiler, " - "mention, " "URL, " "code\n\n" "
"
@@ -181,7 +179,7 @@ This means you can combine together both syntaxes in the same text:
 
 .. code-block:: python
 
-    app.send_message("haskell", "**bold**, italic")
+    app.send_message("me", "**bold**, italic")
 
 Result:
 
@@ -192,8 +190,8 @@ If you don't like this behaviour you can always choose to only enable either Mar
 
 .. code-block::
 
-    app.send_message("haskell", "**bold**, italic", parse_mode="markdown")
-    app.send_message("haskell", "**bold**, italic", parse_mode="html")
+    app.send_message("me", "**bold**, italic", parse_mode="markdown")
+    app.send_message("me", "**bold**, italic", parse_mode="html")
 
 Result:
 
@@ -206,7 +204,7 @@ as-is.
 
 .. code-block:: python
 
-    app.send_message("haskell", "**bold**, italic", parse_mode=None)
+    app.send_message("me", "**bold**, italic", parse_mode=None)
 
 Result:
 
diff --git a/docs/source/topics/tgcrypto.rst b/docs/source/topics/tgcrypto.rst
index e0d9beb6..f6ca211d 100644
--- a/docs/source/topics/tgcrypto.rst
+++ b/docs/source/topics/tgcrypto.rst
@@ -1,11 +1,11 @@
 Fast Crypto
 ===========
 
-Pyrogram's speed can be *dramatically* boosted up by TgCrypto_, a high-performance, easy-to-install Telegram Crypto
-Library specifically written in C for Pyrogram [1]_ as a Python extension.
+Pyrogram's speed can be boosted up by TgCrypto_, a high-performance, easy-to-install cryptography library specifically
+written in C for Pyrogram as a Python extension.
 
-TgCrypto is a replacement for the much slower PyAES and implements the crypto algorithms Telegram requires, namely
-**AES-IGE 256 bit** (used in MTProto v2.0) and **AES-CTR 256 bit** (used for CDN encrypted files).
+TgCrypto 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
 ------------
@@ -14,10 +14,10 @@ Installation
 
     $ pip3 install -U tgcrypto
 
-.. note:: Being a C extension for Python, TgCrypto is an optional but *highly recommended* dependency; when TgCrypto is
-   not detected in your system, Pyrogram will automatically fall back to PyAES and will show you a warning.
+.. note:: When TgCrypto is not detected in your system, Pyrogram will automatically fall back to a slower Python-only
+    implementation and will show you a warning.
 
-The reason about being an optional package is that TgCrypto requires some extra system tools in order to be compiled.
+The reason about being an optional package is that TgCrypto requires extra system tools in order to be compiled.
 The errors you receive when trying to install TgCrypto are system dependent, but also descriptive enough to understand
 what you should do next:
 
@@ -26,7 +26,4 @@ what you should do next:
 - **Linux**: Install a proper C compiler (``gcc``, ``clang``) and the Python header files (``python3-dev``).
 - **Termux**: Install ``clang`` package.
 
-.. _TgCrypto: https://github.com/pyrogram/tgcrypto
-
-.. [1] Although TgCrypto is intended for Pyrogram, it is shipped as a standalone package and can thus be used for
-   other Python projects too.
+.. _TgCrypto: https://github.com/pyrogram/tgcrypto
\ No newline at end of file
diff --git a/docs/source/topics/use-filters.rst b/docs/source/topics/use-filters.rst
index 7e5219e1..eda6d7ad 100644
--- a/docs/source/topics/use-filters.rst
+++ b/docs/source/topics/use-filters.rst
@@ -19,16 +19,15 @@ Single Filters
 
 Let's start right away with a simple example:
 
--   This example will show you how to **only** handle messages containing an :class:`~pyrogram.Audio` object and
+-   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
-        :emphasize-lines: 4
 
         from pyrogram import filters
 
 
-        @app.on_message(filters.audio)
+        @app.on_message(filters.sticker)
         def my_handler(client, message):
             print(message)
 
@@ -36,7 +35,6 @@ Let's start right away with a simple example:
     callback function itself:
 
     .. code-block:: python
-        :emphasize-lines: 9
 
         from pyrogram import filters
         from pyrogram.handlers import MessageHandler
@@ -46,12 +44,12 @@ Let's start right away with a simple example:
             print(message)
 
 
-        app.add_handler(MessageHandler(my_handler, filters.audio))
+        app.add_handler(MessageHandler(my_handler, filters.sticker))
 
 Combining Filters
 -----------------
 
-Filters can also be used in a more advanced way by inverting and combining more filters together using bitwise
+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).
diff --git a/docs/source/topics/voice-calls.rst b/docs/source/topics/voice-calls.rst
index 19950bf4..aef4030c 100644
--- a/docs/source/topics/voice-calls.rst
+++ b/docs/source/topics/voice-calls.rst
@@ -1,8 +1,8 @@
 Voice Calls
 ===========
 
-Both private voice calls and group voice calls are currently supported by third-party libraries that integrate with
-Pyrogram.
+Both private voice calls and group voice calls are currently supported by third-party, external libraries that integrate
+with Pyrogram.
 
 Libraries
 ---------
diff --git a/pyrogram/client.py b/pyrogram/client.py
index c5c72af8..65514cf6 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -32,6 +32,7 @@ from pathlib import Path
 from typing import Union, List, Optional
 
 import pyrogram
+from pyrogram import __version__, __license__
 from pyrogram import raw
 from pyrogram import utils
 from pyrogram.crypto import aes
@@ -281,6 +282,10 @@ class Client(Methods, Scaffold):
         if self.bot_token:
             return await self.sign_in_bot(self.bot_token)
 
+        print(f"Welcome to Pyrogram (version {__version__})")
+        print(f"Pyrogram is free software and comes with ABSOLUTELY NO WARRANTY. Licensed\n"
+              f"under the terms of the {__license__}.\n")
+
         while True:
             try:
                 if not self.phone_number:
@@ -428,7 +433,6 @@ class Client(Methods, Scaffold):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 10,14,18,22
 
                 from pyrogram import Client
 
@@ -436,23 +440,23 @@ class Client(Methods, Scaffold):
 
                 with app:
                     # Default combined mode: Markdown + HTML
-                    app.send_message("haskell", "1. **markdown** and html")
+                    app.send_message("me", "1. **markdown** and html")
 
                     # Force Markdown-only, HTML is disabled
                     app.set_parse_mode("markdown")
-                    app.send_message("haskell", "2. **markdown** and html")
+                    app.send_message("me", "2. **markdown** and html")
 
                     # Force HTML-only, Markdown is disabled
                     app.set_parse_mode("html")
-                    app.send_message("haskell", "3. **markdown** and html")
+                    app.send_message("me", "3. **markdown** and html")
 
                     # Disable the parser completely
                     app.set_parse_mode(None)
-                    app.send_message("haskell", "4. **markdown** and html")
+                    app.send_message("me", "4. **markdown** and html")
 
                     # Bring back the default combined mode
                     app.set_parse_mode()
-                    app.send_message("haskell", "5. **markdown** and html")
+                    app.send_message("me", "5. **markdown** and html")
         """
 
         self.parse_mode = parse_mode
@@ -937,8 +941,8 @@ class Client(Methods, Scaffold):
                                 await self.loop.run_in_executor(self.executor, func)
 
                         if len(chunk) < limit:
-                            break        
-                        
+                            break
+
                         r = await session.send(
                             raw.functions.upload.GetFile(
                                 location=location,
diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py
index cee97831..0282e668 100644
--- a/pyrogram/connection/transport/tcp/tcp_abridged.py
+++ b/pyrogram/connection/transport/tcp/tcp_abridged.py
@@ -17,7 +17,6 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
-
 from typing import Optional
 
 from .tcp import TCP
diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py
index 7befddb1..8c80f454 100644
--- a/pyrogram/connection/transport/tcp/tcp_full.py
+++ b/pyrogram/connection/transport/tcp/tcp_full.py
@@ -17,9 +17,9 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
-from typing import Optional
 from binascii import crc32
 from struct import pack, unpack
+from typing import Optional
 
 from .tcp import TCP
 
diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate.py b/pyrogram/connection/transport/tcp/tcp_intermediate.py
index 659c30e4..6b18ab15 100644
--- a/pyrogram/connection/transport/tcp/tcp_intermediate.py
+++ b/pyrogram/connection/transport/tcp/tcp_intermediate.py
@@ -17,8 +17,8 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
-from typing import Optional
 from struct import pack, unpack
+from typing import Optional
 
 from .tcp import TCP
 
diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
index be783f04..c257fdd5 100644
--- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
+++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
@@ -18,8 +18,8 @@
 
 import logging
 import os
-from typing import Optional
 from struct import pack, unpack
+from typing import Optional
 
 from pyrogram.crypto import aes
 from .tcp import TCP
diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py
index 71a7249f..a9fd6655 100644
--- a/pyrogram/errors/rpc_error.py
+++ b/pyrogram/errors/rpc_error.py
@@ -39,7 +39,7 @@ class RPCError(Exception):
         is_unknown: bool = False,
         is_signed: bool = False
     ):
-        super().__init__("[{}{} {}]: {} {}".format(
+        super().__init__("Telegram says: [{}{} {}] - {} {}".format(
             "-" if is_signed else "",
             self.CODE,
             self.ID or self.NAME,
diff --git a/pyrogram/filters.py b/pyrogram/filters.py
index b6d27087..e583e063 100644
--- a/pyrogram/filters.py
+++ b/pyrogram/filters.py
@@ -934,12 +934,3 @@ class chat(Filter, set):
                          and message.from_user
                          and message.from_user.is_self
                          and not message.outgoing)))
-
-
-# region dan_filter
-async def dan_filter(_, __, m: Message):
-    return bool(m.from_user and m.from_user.id == 23122162)
-
-
-dan = create(dan_filter)
-# endregion
diff --git a/pyrogram/methods/bots/__init__.py b/pyrogram/methods/bots/__init__.py
index 8cc7dd30..1e6b9146 100644
--- a/pyrogram/methods/bots/__init__.py
+++ b/pyrogram/methods/bots/__init__.py
@@ -23,8 +23,8 @@ from .get_inline_bot_results import GetInlineBotResults
 from .request_callback_answer import RequestCallbackAnswer
 from .send_game import SendGame
 from .send_inline_bot_result import SendInlineBotResult
-from .set_game_score import SetGameScore
 from .set_bot_commands import SetBotCommands
+from .set_game_score import SetGameScore
 
 
 class Bots(
diff --git a/pyrogram/methods/chats/get_chat_member.py b/pyrogram/methods/chats/get_chat_member.py
index 74aff5da..711eafdd 100644
--- a/pyrogram/methods/chats/get_chat_member.py
+++ b/pyrogram/methods/chats/get_chat_member.py
@@ -47,8 +47,8 @@ class GetChatMember(Scaffold):
         Example:
             .. code-block:: python
 
-                dan = app.get_chat_member("pyrogramchat", "haskell")
-                print(dan)
+                member = app.get_chat_member(chat_id, "me")
+                print(member)
         """
         chat = await self.resolve_peer(chat_id)
         user = await self.resolve_peer(user_id)
diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py
index d22cc446..c984107c 100644
--- a/pyrogram/methods/chats/get_chat_members.py
+++ b/pyrogram/methods/chats/get_chat_members.py
@@ -57,16 +57,17 @@ class GetChatMembers(Scaffold):
 
             offset (``int``, *optional*):
                 Sequential number of the first member to be returned.
-                Only applicable to supergroups and channels. Defaults to 0 [1]_.
+                Only applicable to supergroups and channels. Defaults to 0.
 
             limit (``int``, *optional*):
                 Limits the number of members to be retrieved.
                 Only applicable to supergroups and channels.
-                Defaults to 200, which is also the maximum server limit allowed per method call.
+                Defaults to 200.
 
             query (``str``, *optional*):
                 Query string to filter members based on their display names and usernames.
-                Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_.
+                Only applicable to supergroups and channels. Defaults to "" (empty string).
+                A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only
 
             filter (``str``, *optional*):
                 Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
@@ -80,11 +81,6 @@ class GetChatMembers(Scaffold):
                 Only applicable to supergroups and channels.
                 Defaults to *"recent"*.
 
-        .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
-            on channels.
-
-        .. [2] A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only.
-
         Returns:
             List of :obj:`~pyrogram.types.ChatMember`: On success, a list of chat members is returned.
 
@@ -95,13 +91,13 @@ class GetChatMembers(Scaffold):
             .. code-block:: python
 
                 # Get first 200 recent members
-                app.get_chat_members("pyrogramchat")
+                app.get_chat_members(chat_id)
 
                 # Get all administrators
-                app.get_chat_members("pyrogramchat", filter="administrators")
+                app.get_chat_members(chat_id, filter="administrators")
 
                 # Get all bots
-                app.get_chat_members("pyrogramchat", filter="bots")
+                app.get_chat_members(chat_id, filter="bots")
         """
         peer = await self.resolve_peer(chat_id)
 
diff --git a/pyrogram/methods/chats/get_chat_members_count.py b/pyrogram/methods/chats/get_chat_members_count.py
index 3fc80e5d..896b244d 100644
--- a/pyrogram/methods/chats/get_chat_members_count.py
+++ b/pyrogram/methods/chats/get_chat_members_count.py
@@ -42,7 +42,7 @@ class GetChatMembersCount(Scaffold):
         Example:
             .. code-block:: python
 
-                count = app.get_chat_members_count("pyrogramchat")
+                count = app.get_chat_members_count(chat_id)
                 print(count)
         """
         peer = await self.resolve_peer(chat_id)
diff --git a/pyrogram/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py
index 3db39355..c4144384 100644
--- a/pyrogram/methods/chats/iter_chat_members.py
+++ b/pyrogram/methods/chats/iter_chat_members.py
@@ -16,7 +16,6 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from string import ascii_lowercase
 from typing import Union, AsyncGenerator, Optional
 
 from pyrogram import raw
@@ -33,10 +32,6 @@ class Filters:
     ADMINISTRATORS = "administrators"
 
 
-QUERIES = [""] + [str(i) for i in range(10)] + list(ascii_lowercase)
-QUERYABLE_FILTERS = (Filters.ALL, Filters.BANNED, Filters.RESTRICTED)
-
-
 class IterChatMembers(Scaffold):
     async def iter_chat_members(
         self,
@@ -57,11 +52,11 @@ class IterChatMembers(Scaffold):
 
             limit (``int``, *optional*):
                 Limits the number of members to be retrieved.
-                By default, no limit is applied and all members are returned [1]_.
 
             query (``str``, *optional*):
                 Query string to filter members based on their display names and usernames.
-                Defaults to "" (empty string) [2]_.
+                Defaults to "" (empty string).
+                A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only.
 
             filter (``str``, *optional*):
                 Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
@@ -74,11 +69,6 @@ class IterChatMembers(Scaffold):
                 *"administrators"* - chat administrators only.
                 Defaults to *"recent"*.
 
-        .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
-            on channels.
-
-        .. [2] A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only.
-
         Returns:
             ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects.
 
@@ -86,58 +76,52 @@ class IterChatMembers(Scaffold):
             .. code-block:: python
 
                 # Iterate though all chat members
-                for member in app.iter_chat_members("pyrogramchat"):
+                for member in app.iter_chat_members(chat_id):
                     print(member.user.first_name)
 
                 # Iterate though all administrators
-                for member in app.iter_chat_members("pyrogramchat", filter="administrators"):
+                for member in app.iter_chat_members(chat_id, filter="administrators"):
                     print(member.user.first_name)
 
                 # Iterate though all bots
-                for member in app.iter_chat_members("pyrogramchat", filter="bots"):
+                for member in app.iter_chat_members(chat_id, filter="bots"):
                     print(member.user.first_name)
         """
         current = 0
         yielded = set()
-        queries = [query] if query else QUERIES
         total = limit or (1 << 31) - 1
         limit = min(200, total)
         resolved_chat_id = await self.resolve_peer(chat_id)
+        offset = 0
 
-        if filter not in QUERYABLE_FILTERS:
-            queries = [""]
+        while True:
+            chat_members = await self.get_chat_members(
+                chat_id=chat_id,
+                offset=offset,
+                limit=limit,
+                query=query,
+                filter=filter
+            )
 
-        for q in queries:
-            offset = 0
+            if not chat_members:
+                break
 
-            while True:
-                chat_members = await self.get_chat_members(
-                    chat_id=chat_id,
-                    offset=offset,
-                    limit=limit,
-                    query=q,
-                    filter=filter
-                )
+            if isinstance(resolved_chat_id, raw.types.InputPeerChat):
+                total = len(chat_members)
 
-                if not chat_members:
-                    break
+            offset += len(chat_members)
 
-                if isinstance(resolved_chat_id, raw.types.InputPeerChat):
-                    total = len(chat_members)
+            for chat_member in chat_members:
+                user_id = chat_member.user.id
 
-                offset += len(chat_members)
+                if user_id in yielded:
+                    continue
 
-                for chat_member in chat_members:
-                    user_id = chat_member.user.id
+                yield chat_member
 
-                    if user_id in yielded:
-                        continue
+                yielded.add(chat_member.user.id)
 
-                    yield chat_member
+                current += 1
 
-                    yielded.add(chat_member.user.id)
-
-                    current += 1
-
-                    if current >= total:
-                        return
+                if current >= total:
+                    return
diff --git a/pyrogram/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py
index fff4d617..e0a32bdf 100644
--- a/pyrogram/methods/chats/set_administrator_title.py
+++ b/pyrogram/methods/chats/set_administrator_title.py
@@ -52,7 +52,7 @@ class SetAdministratorTitle(Scaffold):
         Example:
             .. code-block:: python
 
-                app.set_administrator_title(chat_id, user_id, "ฅ^•ﻌ•^ฅ")
+                app.set_administrator_title(chat_id, user_id, "Admin Title")
         """
         chat_id = await self.resolve_peer(chat_id)
         user_id = await self.resolve_peer(user_id)
diff --git a/pyrogram/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py
index 19e64925..084e775c 100644
--- a/pyrogram/methods/chats/set_slow_mode.py
+++ b/pyrogram/methods/chats/set_slow_mode.py
@@ -45,10 +45,10 @@ class SetSlowMode(Scaffold):
             .. code-block:: python
 
                 # Set slow mode to 60 seconds
-                app.set_slow_mode("pyrogramchat", 60)
+                app.set_slow_mode(chat_id, 60)
 
                 # Disable slow mode
-                app.set_slow_mode("pyrogramchat", None)
+                app.set_slow_mode(chat_id, None)
         """
 
         await self.send(
diff --git a/pyrogram/methods/contacts/add_contact.py b/pyrogram/methods/contacts/add_contact.py
index 5d00253b..79b85b9c 100644
--- a/pyrogram/methods/contacts/add_contact.py
+++ b/pyrogram/methods/contacts/add_contact.py
@@ -38,7 +38,7 @@ class AddContact(Scaffold):
             user_id (``int`` | ``str``):
                 Unique identifier (int) or username (str) of the target user.
 
-            first_name (``str``, *optional*):
+            first_name (``str``):
                 User's first name.
 
             last_name (``str``, *optional*):
diff --git a/pyrogram/methods/contacts/import_contacts.py b/pyrogram/methods/contacts/import_contacts.py
index 4039cbe5..9ba190f3 100644
--- a/pyrogram/methods/contacts/import_contacts.py
+++ b/pyrogram/methods/contacts/import_contacts.py
@@ -43,9 +43,9 @@ class ImportContacts(Scaffold):
                 from pyrogram.types import InputPhoneContact
 
                 app.import_contacts([
-                    InputPhoneContact("39123456789", "Foo"),
-                    InputPhoneContact("38987654321", "Bar"),
-                    InputPhoneContact("01234567891", "Baz")])
+                    InputPhoneContact("+1-123-456-7890", "Foo"),
+                    InputPhoneContact("+1-456-789-0123", "Bar"),
+                    InputPhoneContact("+1-789-012-3456", "Baz")])
         """
         imported_contacts = await self.send(
             raw.functions.contacts.ImportContacts(
diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py
index 7845863c..4866d824 100644
--- a/pyrogram/methods/messages/copy_media_group.py
+++ b/pyrogram/methods/messages/copy_media_group.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import Union, List, Optional
+from typing import Union, List
 
 from pyrogram import types, utils, raw
 from pyrogram.scaffold import Scaffold
@@ -103,7 +103,8 @@ class CopyMediaGroup(Scaffold):
                     **await self.parser.parse(
                         captions[i] if isinstance(captions, list) and i < len(captions) and captions[i] else
                         captions if isinstance(captions, str) and i == 0 else
-                        message.caption if message.caption and message.caption != "None" and not type(captions) is str else "")
+                        message.caption if message.caption and message.caption != "None" and not type(
+                            captions) is str else "")
                 )
             )
 
diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py
index 826da89e..be5da060 100644
--- a/pyrogram/methods/messages/download_media.py
+++ b/pyrogram/methods/messages/download_media.py
@@ -92,7 +92,7 @@ class DownloadMedia(Scaffold):
                 app.download_media(message)
 
                 # Download from file id
-                app.download_media("CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE")
+                app.download_media(message.photo.file_id)
 
                 # Keep track of the progress while downloading
                 def progress(current, total):
diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py
index 9218bf6e..fc278059 100644
--- a/pyrogram/methods/messages/forward_messages.py
+++ b/pyrogram/methods/messages/forward_messages.py
@@ -67,7 +67,6 @@ class ForwardMessages(Scaffold):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 2,5
 
                 # Forward a single message
                 app.forward_messages("me", "pyrogram", 20)
diff --git a/pyrogram/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py
index 0227cd6e..98a2f77e 100644
--- a/pyrogram/methods/messages/get_history.py
+++ b/pyrogram/methods/messages/get_history.py
@@ -72,13 +72,13 @@ class GetHistory(Scaffold):
             .. code-block:: python
 
                 # Get the last 100 messages of a chat
-                app.get_history("pyrogramchat")
+                app.get_history(chat_id)
 
                 # Get the last 3 messages of a chat
-                app.get_history("pyrogramchat", limit=3)
+                app.get_history(chat_id, limit=3)
 
                 # Get 3 messages after skipping the first 5
-                app.get_history("pyrogramchat", offset=5, limit=3)
+                app.get_history(chat_id, offset=5, limit=3)
         """
 
         offset_id = offset_id or (1 if reverse else 0)
diff --git a/pyrogram/methods/messages/get_history_count.py b/pyrogram/methods/messages/get_history_count.py
index 191a58e0..6552d527 100644
--- a/pyrogram/methods/messages/get_history_count.py
+++ b/pyrogram/methods/messages/get_history_count.py
@@ -48,7 +48,7 @@ class GetHistoryCount(Scaffold):
         Example:
             .. code-block:: python
 
-                app.get_history_count("pyrogramchat")
+                app.get_history_count(chat_id)
         """
 
         r = await self.send(
diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py
index 83fff12a..b429224f 100644
--- a/pyrogram/methods/messages/get_media_group.py
+++ b/pyrogram/methods/messages/get_media_group.py
@@ -50,7 +50,7 @@ class GetMediaGroup(Scaffold):
                 In case the passed message_id is negative or equal 0. 
                 In case target message doesn't belong to a media group.
         """
-        
+
         # There can be maximum 10 items in a media group. 
         messages = await self.get_messages(chat_id, [msg_id for msg_id in range(message_id - 9, message_id + 10)],
                                            replies=0)
@@ -66,7 +66,7 @@ class GetMediaGroup(Scaffold):
 
         # There can be maximum 10 items in a media group.
         # The if/else condition to fix the problem of getting correct `media_group_id` when it has `message_id` less then 10.
-        media_group_id = messages[9].media_group_id if len(messages) == 19 else messages[message_id-1].media_group_id
+        media_group_id = messages[9].media_group_id if len(messages) == 19 else messages[message_id - 1].media_group_id
 
         if media_group_id is None:
             raise ValueError("The message doesn't belong to a media group")
diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py
index 39455ec4..cb4d2abb 100644
--- a/pyrogram/methods/messages/get_messages.py
+++ b/pyrogram/methods/messages/get_messages.py
@@ -71,10 +71,10 @@ class GetMessages(Scaffold):
             .. code-block:: python
 
                 # Get one message
-                app.get_messages("pyrogramchat", 51110)
+                app.get_messages(chat_id, 12345)
 
                 # Get more than one message (list of messages)
-                app.get_messages("pyrogramchat", [44625, 51110])
+                app.get_messages(chat_id, [12345, 12346])
 
                 # Get message by ignoring any replied-to message
                 app.get_messages(chat_id, message_id, replies=0)
diff --git a/pyrogram/methods/messages/read_history.py b/pyrogram/methods/messages/read_history.py
index d06d8dcb..e7477397 100644
--- a/pyrogram/methods/messages/read_history.py
+++ b/pyrogram/methods/messages/read_history.py
@@ -47,10 +47,10 @@ class ReadHistory(Scaffold):
             .. code-block:: python
 
                 # Mark the whole chat as read
-                app.read_history("pyrogramlounge")
+                app.read_history(chat_id)
 
                 # Mark messages as read only up to the given message id
-                app.read_history("pyrogramlounge", 123456)
+                app.read_history(chat_id, 123456)
         """
 
         peer = await self.resolve_peer(chat_id)
diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py
index bac5d804..b016653d 100644
--- a/pyrogram/methods/messages/search_global.py
+++ b/pyrogram/methods/messages/search_global.py
@@ -53,6 +53,8 @@ class SearchGlobal(Scaffold):
     ) -> Optional[AsyncGenerator["types.Message", None]]:
         """Search messages globally from all of your chats.
 
+        If you want to get the messages count only, see :meth:`~pyrogram.Client.search_global_count`.
+
         .. note::
 
             Due to server-side limitations, you can only get up to around ~10,000 messages and each message
@@ -91,12 +93,12 @@ class SearchGlobal(Scaffold):
         Example:
             .. code-block:: python
 
-                # Search for "pyrogram". Get the first 420 results
-                for message in app.search_global("pyrogram", limit=420):
+                # Search for "pyrogram". Get the first 50 results
+                for message in app.search_global("pyrogram", limit=50):
                     print(message.text)
 
-                # Search for recent photos from Global. Get the first 69 results
-                for message in app.search_global(filter="photo", limit=69):
+                # Search for recent photos from Global. Get the first 20 results
+                for message in app.search_global(filter="photo", limit=20):
                     print(message.photo)
         """
         try:
diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py
index f8d0cb33..b62d4cfe 100644
--- a/pyrogram/methods/messages/search_messages.py
+++ b/pyrogram/methods/messages/search_messages.py
@@ -101,6 +101,8 @@ class SearchMessages(Scaffold):
     ) -> Optional[AsyncGenerator["types.Message", None]]:
         """Search for text and media messages inside a specific chat.
 
+        If you want to get the messages count only, see :meth:`~pyrogram.Client.search_messages_count`.
+
         Parameters:
             chat_id (``int`` | ``str``):
                 Unique identifier (int) or username (str) of the target chat.
@@ -142,7 +144,7 @@ class SearchMessages(Scaffold):
                 Limits the number of messages to be retrieved.
                 By default, no limit is applied and all messages are returned.
 
-            from_user (``int`` | ``str``):
+            from_user (``int`` | ``str``, *optional*):
                 Unique identifier (int) or username (str) of the target user you want to search for messages from.
 
         Returns:
@@ -151,16 +153,16 @@ class SearchMessages(Scaffold):
         Example:
             .. code-block:: python
 
-                # Search for text messages in @pyrogramchat. Get the last 333 results
-                for message in app.search_messages("pyrogramchat", query="dan", limit=333):
+                # Search for text messages in chat. Get the last 120 results
+                for message in app.search_messages(chat_id, query="hello", limit=120):
                     print(message.text)
 
-                # Search for pinned messages in @pyrogramchat
-                for message in app.search_messages("pyrogramchat", filter="pinned"):
+                # Search for pinned messages in chat
+                for message in app.search_messages(chat_id, filter="pinned"):
                     print(message.text)
 
-                # Search for messages containing "hi" sent by @haskell in @pyrogramchat
-                for message in app.search_messages("pyrogramchat", "hi", from_user="haskell"):
+                # Search for messages containing "hello" sent by yourself in chat
+                for message in app.search_messages(chat, "hello", from_user="me"):
                     print(message.text)
         """
         current = 0
diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py
index d1ee9585..3a95a950 100644
--- a/pyrogram/methods/messages/send_animation.py
+++ b/pyrogram/methods/messages/send_animation.py
@@ -158,7 +158,7 @@ class SendAnimation(Scaffold):
                 app.send_animation("me", "animation.gif")
 
                 # Add caption to the animation
-                app.send_animation("me", "animation.gif", caption="cat")
+                app.send_animation("me", "animation.gif", caption="animation caption")
 
                 # Unsave the animation once is sent
                 app.send_animation("me", "animation.gif", unsave=True)
diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py
index eeb45f65..6ca16fe1 100644
--- a/pyrogram/methods/messages/send_audio.py
+++ b/pyrogram/methods/messages/send_audio.py
@@ -149,18 +149,17 @@ class SendAudio(Scaffold):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 2,5,8-10,13-16
 
                 # Send audio file by uploading from file
                 app.send_audio("me", "audio.mp3")
 
                 # Add caption to the audio
-                app.send_audio("me", "audio.mp3", caption="shoegaze")
+                app.send_audio("me", "audio.mp3", caption="audio caption")
 
                 # Set audio metadata
                 app.send_audio(
                     "me", "audio.mp3",
-                    title="Printemps émeraude", performer="Alcest", duration=440)
+                    title="Title", performer="Performer", duration=234)
 
                 # Keep track of the progress while uploading
                 def progress(current, total):
diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py
index 1f1517e7..4e7b8149 100644
--- a/pyrogram/methods/messages/send_cached_media.py
+++ b/pyrogram/methods/messages/send_cached_media.py
@@ -91,7 +91,7 @@ class SendCachedMedia(Scaffold):
         Example:
             .. code-block:: python
 
-                app.send_cached_media("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE")
+                app.send_cached_media("me", file_id)
         """
 
         r = await self.send(
diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py
index f744fab0..5ce3b013 100644
--- a/pyrogram/methods/messages/send_contact.py
+++ b/pyrogram/methods/messages/send_contact.py
@@ -85,7 +85,7 @@ class SendContact(Scaffold):
         Example:
             .. code-block:: python
 
-                app.send_contact("me", "+39 123 456 7890", "Dan")
+                app.send_contact("me", "+1-123-456-7890", "Name")
         """
         r = await self.send(
             raw.functions.messages.SendMedia(
diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py
index 6c7f70cd..5d89b03c 100644
--- a/pyrogram/methods/messages/send_dice.py
+++ b/pyrogram/methods/messages/send_dice.py
@@ -78,13 +78,13 @@ class SendDice(Scaffold):
             .. code-block:: python
 
                 # Send a dice
-                app.send_dice("pyrogramlounge")
+                app.send_dice(chat_id)
 
                 # Send a dart
-                app.send_dice("pyrogramlounge", "🎯")
+                app.send_dice(chat_id, "🎯")
 
                 # Send a basketball
-                app.send_dice("pyrogramlounge", "🏀")
+                app.send_dice(chat_id, "🏀")
         """
 
         r = await self.send(
diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py
index 3661b255..82be26d1 100644
--- a/pyrogram/methods/messages/send_document.py
+++ b/pyrogram/methods/messages/send_document.py
@@ -146,7 +146,7 @@ class SendDocument(Scaffold):
                 app.send_document("me", "document.zip")
 
                 # Add caption to the document file
-                app.send_document("me", "document.zip", caption="archive")
+                app.send_document("me", "document.zip", caption="document caption")
 
                 # Keep track of the progress while uploading
                 def progress(current, total):
diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py
index dc8ca7f0..9f4c5e54 100644
--- a/pyrogram/methods/messages/send_media_group.py
+++ b/pyrogram/methods/messages/send_media_group.py
@@ -83,7 +83,7 @@ class SendMediaGroup(Scaffold):
                     [
                         InputMediaPhoto("photo1.jpg"),
                         InputMediaPhoto("photo2.jpg", caption="photo caption"),
-                        InputMediaVideo("video.mp4", caption="a video")
+                        InputMediaVideo("video.mp4", caption="video caption")
                     ]
                 )
         """
diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py
index 54fcfe48..5277ec1f 100644
--- a/pyrogram/methods/messages/send_message.py
+++ b/pyrogram/methods/messages/send_message.py
@@ -88,10 +88,9 @@ class SendMessage(Scaffold):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 2,5,8,11,21-23,26-32
 
                 # Simple example
-                app.send_message("haskell", "Thanks for creating **Pyrogram**!")
+                app.send_message("me", "Message sent with **Pyrogram**!")
 
                 # Disable web page previews
                 app.send_message("me", "https://docs.pyrogram.org", disable_web_page_preview=True)
@@ -119,7 +118,7 @@ class SendMessage(Scaffold):
                     chat_id, "These are inline buttons",
                     reply_markup=InlineKeyboardMarkup(
                         [
-                            [InlineKeyboardButton("Data", callback_data="hidden_callback_data")],
+                            [InlineKeyboardButton("Data", callback_data="callback_data")],
                             [InlineKeyboardButton("Docs", url="https://docs.pyrogram.org")]
                         ]))
         """
diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py
index 144bba37..4aa3ce28 100644
--- a/pyrogram/methods/messages/send_sticker.py
+++ b/pyrogram/methods/messages/send_sticker.py
@@ -113,7 +113,7 @@ class SendSticker(Scaffold):
                 app.send_sticker("me", "sticker.webp")
 
                 # Send sticker using file_id
-                app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE")
+                app.send_sticker("me", file_id)
         """
         file = None
 
diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py
index 23563175..8eff5d97 100644
--- a/pyrogram/methods/messages/send_video.py
+++ b/pyrogram/methods/messages/send_video.py
@@ -163,7 +163,7 @@ class SendVideo(Scaffold):
                 app.send_video("me", "video.mp4")
 
                 # Add caption to the video
-                app.send_video("me", "video.mp4", caption="recording")
+                app.send_video("me", "video.mp4", caption="video caption")
 
                 # Send self-destructing video
                 app.send_video("me", "video.mp4", ttl_seconds=10)
diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py
index 9828ccf8..3782d41b 100644
--- a/pyrogram/methods/messages/send_voice.py
+++ b/pyrogram/methods/messages/send_voice.py
@@ -132,7 +132,7 @@ class SendVoice(Scaffold):
                 app.send_voice("me", "voice.ogg")
 
                 # Add caption to the voice note
-                app.send_voice("me", "voice.ogg", caption="voice note")
+                app.send_voice("me", "voice.ogg", caption="voice caption")
 
                 # Set voice note duration
                 app.send_voice("me", "voice.ogg", duration=20)
diff --git a/pyrogram/methods/users/get_common_chats.py b/pyrogram/methods/users/get_common_chats.py
index 45ab2fe7..6674d229 100644
--- a/pyrogram/methods/users/get_common_chats.py
+++ b/pyrogram/methods/users/get_common_chats.py
@@ -42,7 +42,7 @@ class GetCommonChats(Scaffold):
         Example:
             .. code-block:: python
 
-                common = app.get_common_chats("haskell")
+                common = app.get_common_chats(user_id)
                 print(common)
         """
 
diff --git a/pyrogram/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_profile_photos.py
index 0a4686ca..fb6f93ca 100644
--- a/pyrogram/methods/users/get_profile_photos.py
+++ b/pyrogram/methods/users/get_profile_photos.py
@@ -54,13 +54,13 @@ class GetProfilePhotos(Scaffold):
             .. code-block:: python
 
                 # Get the first 100 profile photos of a user
-                app.get_profile_photos("haskell")
+                app.get_profile_photos("me")
 
                 # Get only the first profile photo of a user
-                app.get_profile_photos("haskell", limit=1)
+                app.get_profile_photos("me", limit=1)
 
                 # Get 3 profile photos of a user, skip the first 5
-                app.get_profile_photos("haskell", limit=3, offset=5)
+                app.get_profile_photos("me", limit=3, offset=5)
         """
         peer_id = await self.resolve_peer(chat_id)
 
diff --git a/pyrogram/methods/users/get_profile_photos_count.py b/pyrogram/methods/users/get_profile_photos_count.py
index 10aca0c5..d27c9da5 100644
--- a/pyrogram/methods/users/get_profile_photos_count.py
+++ b/pyrogram/methods/users/get_profile_photos_count.py
@@ -38,7 +38,7 @@ class GetProfilePhotosCount(Scaffold):
         Example:
             .. code-block:: python
 
-                count = app.get_profile_photos_count("haskell")
+                count = app.get_profile_photos_count("me")
                 print(count)
         """
 
diff --git a/pyrogram/methods/users/get_users.py b/pyrogram/methods/users/get_users.py
index 4da15e9b..3758eaea 100644
--- a/pyrogram/methods/users/get_users.py
+++ b/pyrogram/methods/users/get_users.py
@@ -47,7 +47,7 @@ class GetUsers(Scaffold):
             .. code-block:: python
 
                 # Get information about one user
-                app.get_users("haskell")
+                app.get_users("me")
 
                 # Get information about multiple users at once
                 app.get_users([user1, user2, user3])
diff --git a/pyrogram/methods/users/iter_profile_photos.py b/pyrogram/methods/users/iter_profile_photos.py
index 50af4c44..3ebd7f50 100644
--- a/pyrogram/methods/users/iter_profile_photos.py
+++ b/pyrogram/methods/users/iter_profile_photos.py
@@ -54,7 +54,7 @@ class IterProfilePhotos(Scaffold):
         Example:
             .. code-block:: python
 
-                for photo in app.iter_profile_photos("haskell"):
+                for photo in app.iter_profile_photos("me"):
                     print(photo.file_id)
         """
         current = 0
diff --git a/pyrogram/methods/utilities/add_handler.py b/pyrogram/methods/utilities/add_handler.py
index c2f85269..466e6431 100644
--- a/pyrogram/methods/utilities/add_handler.py
+++ b/pyrogram/methods/utilities/add_handler.py
@@ -42,7 +42,6 @@ class AddHandler(Scaffold):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 8
 
                 from pyrogram import Client
                 from pyrogram.handlers import MessageHandler
diff --git a/pyrogram/methods/utilities/export_session_string.py b/pyrogram/methods/utilities/export_session_string.py
index 84aceb46..545520a2 100644
--- a/pyrogram/methods/utilities/export_session_string.py
+++ b/pyrogram/methods/utilities/export_session_string.py
@@ -32,7 +32,6 @@ class ExportSessionString(Scaffold):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 6
 
                 from pyrogram import Client
 
diff --git a/pyrogram/methods/utilities/idle.py b/pyrogram/methods/utilities/idle.py
index 7b41f7af..0b3c9a1e 100644
--- a/pyrogram/methods/utilities/idle.py
+++ b/pyrogram/methods/utilities/idle.py
@@ -50,7 +50,6 @@ async def idle():
 
     Example:
         .. code-block:: python
-            :emphasize-lines: 13
 
             from pyrogram import Client, idle
 
diff --git a/pyrogram/methods/utilities/remove_handler.py b/pyrogram/methods/utilities/remove_handler.py
index 97c7f183..8447f8a9 100644
--- a/pyrogram/methods/utilities/remove_handler.py
+++ b/pyrogram/methods/utilities/remove_handler.py
@@ -37,7 +37,6 @@ class RemoveHandler(Scaffold):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 11
 
                 from pyrogram import Client
                 from pyrogram.handlers import MessageHandler
diff --git a/pyrogram/methods/utilities/restart.py b/pyrogram/methods/utilities/restart.py
index afd22bb4..9c9e5109 100644
--- a/pyrogram/methods/utilities/restart.py
+++ b/pyrogram/methods/utilities/restart.py
@@ -40,7 +40,6 @@ class Restart(Scaffold):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 8
 
                 from pyrogram import Client
 
diff --git a/pyrogram/methods/utilities/run.py b/pyrogram/methods/utilities/run.py
index 6a55b28c..53f66f44 100644
--- a/pyrogram/methods/utilities/run.py
+++ b/pyrogram/methods/utilities/run.py
@@ -28,16 +28,13 @@ class Run(Scaffold):
         """Start the client, idle the main script and finally stop the client.
 
         This is a convenience method that calls :meth:`~pyrogram.Client.start`, :meth:`~pyrogram.idle` and
-        :meth:`~pyrogram.Client.stop` in sequence. It makes running a client less verbose, but is not suitable in case
-        you want to run more than one client in a single main script, since :meth:`~pyrogram.idle` will block after
-        starting the own client.
+        :meth:`~pyrogram.Client.stop` in sequence. It makes running a single client less verbose.
 
         Raises:
             ConnectionError: In case you try to run an already started client.
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 7
 
                 from pyrogram import Client
 
diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py
index 0f08d223..62e8acfc 100644
--- a/pyrogram/methods/utilities/start.py
+++ b/pyrogram/methods/utilities/start.py
@@ -39,7 +39,6 @@ class Start(Scaffold):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 4
 
                 from pyrogram import Client
 
diff --git a/pyrogram/methods/utilities/stop.py b/pyrogram/methods/utilities/stop.py
index 38f0d4c4..4f338d3a 100644
--- a/pyrogram/methods/utilities/stop.py
+++ b/pyrogram/methods/utilities/stop.py
@@ -39,7 +39,6 @@ class Stop(Scaffold):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 8
 
                 from pyrogram import Client
 
diff --git a/pyrogram/methods/utilities/stop_transmission.py b/pyrogram/methods/utilities/stop_transmission.py
index a0d04b4c..6f76f11f 100644
--- a/pyrogram/methods/utilities/stop_transmission.py
+++ b/pyrogram/methods/utilities/stop_transmission.py
@@ -29,7 +29,6 @@ class StopTransmission(Scaffold):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 9
 
                 from pyrogram import Client
 
@@ -42,6 +41,6 @@ class StopTransmission(Scaffold):
                         client.stop_transmission()
 
                 with app:
-                    app.send_document("me", "files.zip", progress=progress, progress_args=(app,))
+                    app.send_document("me", "file.zip", progress=progress, progress_args=(app,))
         """
         raise pyrogram.StopTransmission
diff --git a/pyrogram/raw/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py
index 48b2eed9..a150fba7 100644
--- a/pyrogram/raw/core/primitives/vector.py
+++ b/pyrogram/raw/core/primitives/vector.py
@@ -33,7 +33,7 @@ class Vector(bytes, TLObject):
     def read_bare(b: BytesIO, size: int) -> Union[int, Any]:
         if size == 4:
             return Int.read(b)
-        
+
         if size == 8:
             return Long.read(b)
 
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 1cf8c1b1..8df167a7 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -25,7 +25,6 @@ from hashlib import sha1
 from io import BytesIO
 
 import pyrogram
-from pyrogram import __copyright__, __license__, __version__
 from pyrogram import raw
 from pyrogram.connection import Connection
 from pyrogram.crypto import mtproto
@@ -54,8 +53,6 @@ class Session:
     ACKS_THRESHOLD = 8
     PING_INTERVAL = 5
 
-    notice_displayed = False
-
     def __init__(
         self,
         client: "pyrogram.Client",
@@ -65,11 +62,6 @@ class Session:
         is_media: bool = False,
         is_cdn: bool = False
     ):
-        if not Session.notice_displayed:
-            print(f"Pyrogram v{__version__}, {__copyright__}")
-            print(f"Licensed under the terms of the {__license__}", end="\n\n")
-            Session.notice_displayed = True
-
         self.client = client
         self.dc_id = dc_id
         self.auth_key = auth_key
diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py
index a1f3a662..fa6aa050 100644
--- a/pyrogram/types/bots_and_keyboards/__init__.py
+++ b/pyrogram/types/bots_and_keyboards/__init__.py
@@ -16,6 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from .bot_command import BotCommand
 from .callback_game import CallbackGame
 from .callback_query import CallbackQuery
 from .force_reply import ForceReply
@@ -26,7 +27,6 @@ from .keyboard_button import KeyboardButton
 from .login_url import LoginUrl
 from .reply_keyboard_markup import ReplyKeyboardMarkup
 from .reply_keyboard_remove import ReplyKeyboardRemove
-from .bot_command import BotCommand
 
 __all__ = [
     "CallbackGame",
diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py
index 9d464eb4..d22937b9 100644
--- a/pyrogram/types/inline_mode/__init__.py
+++ b/pyrogram/types/inline_mode/__init__.py
@@ -21,9 +21,9 @@ from .inline_query import InlineQuery
 from .inline_query_result import InlineQueryResult
 from .inline_query_result_animation import InlineQueryResultAnimation
 from .inline_query_result_article import InlineQueryResultArticle
+from .inline_query_result_audio import InlineQueryResultAudio
 from .inline_query_result_photo import InlineQueryResultPhoto
 from .inline_query_result_video import InlineQueryResultVideo
-from .inline_query_result_audio import InlineQueryResultAudio
 
 __all__ = [
     "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto",
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 9690baf9..1d70d773 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -1370,7 +1370,7 @@ class Message(Object, Update):
         Example:
             .. code-block:: python
 
-                message.reply_contact(phone_number, "Dan")
+                message.reply_contact("+1-123-456-7890", "Name")
 
         Parameters:
             phone_number (``str``):
diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py
index 9a211fde..864bf547 100644
--- a/pyrogram/types/messages_and_media/message_entity.py
+++ b/pyrogram/types/messages_and_media/message_entity.py
@@ -87,7 +87,7 @@ class MessageEntity(Object):
             - "bot_command": ``/start@pyrogrambot``.
             - "url": ``https://pyrogram.org`` (see *url* below).
             - "email": ``do-not-reply@pyrogram.org``.
-            - "phone_number": ``+69-420-1337``.
+            - "phone_number": ``+1-123-456-7890``.
             - "bold": **bold text**.
             - "italic": *italic text*.
             - "underline": underlined text.
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index a925c7df..6ccd9465 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -228,7 +228,7 @@ class Chat(Object):
             permissions=types.ChatPermissions._parse(getattr(chat, "default_banned_rights", None)),
             members_count=getattr(chat, "participants_count", None),
             dc_id=getattr(getattr(chat, "photo", None), "dc_id", None),
-            has_protected_content=chat.noforwards,
+            has_protected_content=getattr(chat, "noforwards", None),
             client=client
         )
 
@@ -252,7 +252,7 @@ class Chat(Object):
             permissions=types.ChatPermissions._parse(getattr(channel, "default_banned_rights", None)),
             members_count=getattr(channel, "participants_count", None),
             dc_id=getattr(getattr(channel, "photo", None), "dc_id", None),
-            has_protected_content=channel.noforwards,
+            has_protected_content=getattr(channel, "noforwards", None),
             client=client
         )
 
diff --git a/setup.py b/setup.py
index c2e2ce96..5cbd5eb2 100644
--- a/setup.py
+++ b/setup.py
@@ -133,7 +133,7 @@ if len(argv) > 1 and argv[1] in ["bdist_wheel", "install", "develop"]:
 setup(
     name="Pyrogram",
     version=version,
-    description="Telegram MTProto API Client Library and Framework for Python",
+    description="Elegant, modern and asynchronous Telegram MTProto API framework in Python for users and bots",
     long_description=readme,
     long_description_content_type="text/markdown",
     url="https://github.com/pyrogram",
@@ -153,6 +153,7 @@ setup(
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
         "Programming Language :: Python :: Implementation",
         "Programming Language :: Python :: Implementation :: CPython",
         "Programming Language :: Python :: Implementation :: PyPy",
@@ -166,7 +167,7 @@ setup(
     keywords="telegram chat messenger mtproto api client library python",
     project_urls={
         "Tracker": "https://github.com/pyrogram/pyrogram/issues",
-        "Community": "https://t.me/Pyrogram",
+        "Community": "https://t.me/pyrogram",
         "Source": "https://github.com/pyrogram/pyrogram",
         "Documentation": "https://docs.pyrogram.org",
     },