From 17d3a5b4cd446e6018c4da08bb1934a787e0c0de Mon Sep 17 00:00:00 2001 From: Ningmua Bruno Date: Mon, 1 Apr 2019 02:35:04 +0100 Subject: [PATCH 1/3] Add files via upload From 8023aa5d280fe664ea325dc066a1d558297dfc11 Mon Sep 17 00:00:00 2001 From: Ningmua Bruno Date: Mon, 1 Apr 2019 02:36:54 +0100 Subject: [PATCH 2/3] Attempting solution to why pyrogram breaks --- pyrogram/__init__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index bbb6557b..ab0c7d12 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -18,6 +18,13 @@ import sys +if sys.version_info[:3] in [(3, 5, 0), (3, 5, 1), (3, 5, 2)]: + from .vendor import typing + + # Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one. + sys.modules["typing"] = typing + + __version__ = "0.12.0.develop" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2019 Dan Tès ".replace( @@ -28,9 +35,3 @@ from .errors import RPCError from .client import * from .client.handlers import * from .client.types import * - -if sys.version_info[:3] in [(3, 5, 0), (3, 5, 1), (3, 5, 2)]: - from .vendor import typing - - # Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one. - sys.modules["typing"] = typing From 05aed5e0e104102e3ae5278f011695c1a485dc87 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 12 Apr 2019 15:52:06 +0200 Subject: [PATCH 3/3] Clean up documentation pages --- docs/source/conf.py | 8 +- docs/source/pyrogram/Handlers.rst | 4 + docs/source/resources/AdvancedUsage.rst | 153 ++++++++++-------- docs/source/resources/ConfigurationFile.rst | 6 +- docs/source/resources/CustomizeSessions.rst | 8 +- docs/source/resources/ErrorHandling.rst | 2 +- docs/source/resources/MoreOnUpdates.rst | 37 ++--- docs/source/resources/SmartPlugins.rst | 29 ++-- docs/source/resources/UpdateHandling.rst | 61 ++++--- docs/source/resources/UsingFilters.rst | 60 +++---- docs/source/start/Installation.rst | 20 ++- docs/source/start/Setup.rst | 50 +++--- docs/source/start/Usage.rst | 36 +++-- .../methods/decorators/on_callback_query.py | 7 - .../methods/decorators/on_deleted_messages.py | 7 - .../client/methods/decorators/on_message.py | 7 - .../methods/decorators/on_raw_update.py | 7 - .../methods/decorators/on_user_status.py | 7 - 18 files changed, 263 insertions(+), 246 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index cecf047f..8acfde42 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -25,6 +25,10 @@ sys.path.insert(0, os.path.abspath('../..')) # Import after sys.path.insert() to avoid issues from pyrogram import __version__ +from pygments.styles.friendly import FriendlyStyle + +FriendlyStyle.background_color = "#f3f2f1" + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -60,7 +64,7 @@ master_doc = 'index' # General information about the project. project = 'Pyrogram' -copyright = '2017-2018, Dan Tès' +copyright = '2017-2019, Dan Tès' author = 'Dan Tès' # The version info for the project you're documenting, acts as replacement for @@ -85,7 +89,7 @@ language = None exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'tango' +pygments_style = 'friendly' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False diff --git a/docs/source/pyrogram/Handlers.rst b/docs/source/pyrogram/Handlers.rst index 3b748e9d..1bb16ece 100644 --- a/docs/source/pyrogram/Handlers.rst +++ b/docs/source/pyrogram/Handlers.rst @@ -9,6 +9,7 @@ Handlers MessageHandler DeletedMessagesHandler CallbackQueryHandler + InlineQueryHandler UserStatusHandler DisconnectHandler RawUpdateHandler @@ -22,6 +23,9 @@ Handlers .. autoclass:: CallbackQueryHandler :members: +.. autoclass:: InlineQueryHandler + :members: + .. autoclass:: UserStatusHandler :members: diff --git a/docs/source/resources/AdvancedUsage.rst b/docs/source/resources/AdvancedUsage.rst index 02395f26..8b722b2a 100644 --- a/docs/source/resources/AdvancedUsage.rst +++ b/docs/source/resources/AdvancedUsage.rst @@ -1,40 +1,102 @@ Advanced Usage ============== -In this section, you'll be shown the alternative way of communicating with Telegram using Pyrogram: the main Telegram -API with its raw functions and types. +Pyrogram's API, which consists of well documented convenience methods_ and facade types_, exists to provide a much +easier interface to the undocumented and often confusing 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. 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:`functions ` and :mod:`types ` -exposed by the ``pyrogram.api`` package and call any Telegram API method you wish using the -:meth:`send() ` method provided by the Client class. +Telegram API, you have to use the raw :mod:`functions ` and :mod:`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. .. hint:: - Every available high-level method mentioned in the previous page is built on top of these raw functions. + 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 `plenty of them`_ are already - re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API. + 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_! -Caveats -------- +Invoking Functions +^^^^^^^^^^^^^^^^^^ -As hinted before, raw functions and types can be confusing, mainly because people don't realize they must accept -*exactly* the right values, but also because most of them don't have enough Python experience to fully grasp how things -work. +Unlike the methods_ 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. -This section will therefore explain some pitfalls to take into consideration when working with the raw API. +First of all, both `raw functions`_ and `raw types`_ live in their respective packages (and sub-packages): +``pyrogram.api.functions``, ``pyrogram.api.types``. They all exist as Python classes, meaning you need to create an +instance of each every time you need them and fill them in with the correct values using named arguments. + +Next, to actually invoke the raw function you have to use the :meth:`send() ` method provided by +the Client class and pass the function object you created. + +Here's some examples: + +- Update first name, last name and bio: + + .. code-block:: python + + from pyrogram import Client + from pyrogram.api import functions + + with Client("my_account") as app: + app.send( + functions.account.UpdateProfile( + first_name="Dan", last_name="Tès", + about="Bio written from Pyrogram" + ) + ) + +- Disable links to your account when someone forwards your messages: + + .. code-block:: python + + from pyrogram import Client + from pyrogram.api import functions, types + + with Client("my_account") as app: + app.send( + functions.account.SetPrivacy( + key=types.PrivacyKeyForwards(), + rules=[types.InputPrivacyValueDisallowAll()] + ) + ) + +- Invite users to your channel/supergroup: + + .. code-block:: python + + from pyrogram import Client + from pyrogram.api 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 + ] + ) + ) 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. -Instead, a pair of ``id`` and ``access_hash`` wrapped in a so called ``InputPeer`` is always needed. +Instead, a pair of ``id`` and ``access_hash`` wrapped in a so called ``InputPeer`` is always needed. Pyrogram allows +sending messages with IDs only thanks to cached access hashes. There are three different InputPeer types, one for each kind of Telegram entity. Whenever an InputPeer is needed you must pass one of these: @@ -56,66 +118,19 @@ kind of ID. For example, given the ID *123456789*, here's how Pyrogram can tell entities apart: - - ``+ID`` - User: *123456789* - - ``-ID`` - Chat: *-123456789* - - ``-100ID`` - Channel (and Supergroup): *-100123456789* + - ``+ID`` User: *123456789* + - ``-ID`` Chat: *-123456789* + - ``-100ID`` Channel (and Supergroup): *-100123456789* So, every time you take a raw ID, make sure to translate it into the correct ID when you want to use it with an high-level method. -Examples --------- - -- Update first name, last name and bio: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.api import functions - - with Client("my_account") as app: - app.send( - functions.account.UpdateProfile( - first_name="Dan", last_name="Tès", - about="Bio written from Pyrogram" - ) - ) - -- Share your Last Seen time only with your contacts: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.api import functions, types - - with Client("my_account") as app: - app.send( - functions.account.SetPrivacy( - key=types.InputPrivacyKeyStatusTimestamp(), - rules=[types.InputPrivacyValueAllowContacts()] - ) - ) - -- Invite users to your channel/supergroup: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.api 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 - ] - ) - ) + +.. _methods: ../pyrogram/Client.html#messages +.. _types: ../pyrogram/Types.html .. _plenty of them: ../pyrogram/Client.html#messages -.. _Raw Functions: Usage.html#using-raw-functions +.. _raw functions: ../pyrogram/functions +.. _raw types: ../pyrogram/types .. _Community: https://t.me/PyrogramChat \ No newline at end of file diff --git a/docs/source/resources/ConfigurationFile.rst b/docs/source/resources/ConfigurationFile.rst index 759bfd9f..2a50277f 100644 --- a/docs/source/resources/ConfigurationFile.rst +++ b/docs/source/resources/ConfigurationFile.rst @@ -1,13 +1,13 @@ Configuration File ================== -As already mentioned in previous sections, Pyrogram can also be configured by the use of an INI file. +As already mentioned in previous sections, Pyrogram can be configured by the use of an INI file. This page explains how this file is structured in Pyrogram, how to use it and why. Introduction ------------ -The idea behind using a configuration file is to help keeping your code free of settings (private) information such as +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 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. @@ -46,7 +46,7 @@ These are all the sections Pyrogram uses in its configuration file: Pyrogram ^^^^^^^^ -The ``[pyrogram]`` section contains your Telegram API credentials *api_id* and *api_hash*. +The ``[pyrogram]`` section contains your Telegram API credentials: *api_id* and *api_hash*. .. code-block:: ini diff --git a/docs/source/resources/CustomizeSessions.rst b/docs/source/resources/CustomizeSessions.rst index e98792b7..77765287 100644 --- a/docs/source/resources/CustomizeSessions.rst +++ b/docs/source/resources/CustomizeSessions.rst @@ -1,19 +1,19 @@ Customize Sessions ================== -As you may probably know, Telegram allows Users (and Bots) having more than one session (authorizations) registered +As you may probably know, Telegram allows users (and bots) having more than one session (authorizations) registered in the system at the same time. Briefly explaining, sessions are simply new logins in your account. They can be reviewed in the settings of an official -app (or by invoking `GetAuthorizations <../functions/account/GetAuthorizations.html>`_ with Pyrogram) and store some useful -information about the client who generated them. +app (or by invoking `GetAuthorizations <../functions/account/GetAuthorizations.html>`_ 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/lzGPCdZ.png :width: 70% :align: center - A Pyrogram session running on Linux, Python 3.6. + **A Pyrogram session running on Linux, Python 3.6.** That's how a session looks like on the Android app, showing the three main pieces of information. diff --git a/docs/source/resources/ErrorHandling.rst b/docs/source/resources/ErrorHandling.rst index c6459a70..7e87b94a 100644 --- a/docs/source/resources/ErrorHandling.rst +++ b/docs/source/resources/ErrorHandling.rst @@ -4,7 +4,7 @@ Error Handling Errors are inevitable when working with the API, and they must be correctly handled with ``try..except`` blocks. There are many errors that Telegram could return, but they all fall in one of these categories -(which are in turn children of the :obj:`RPCError ` superclass) +(which are in turn children of the :obj:`RPCError ` superclass): - :obj:`303 - See Other ` - :obj:`400 - Bad Request ` diff --git a/docs/source/resources/MoreOnUpdates.rst b/docs/source/resources/MoreOnUpdates.rst index 9712a5d2..f3658c6e 100644 --- a/docs/source/resources/MoreOnUpdates.rst +++ b/docs/source/resources/MoreOnUpdates.rst @@ -1,24 +1,22 @@ More on Updates =============== -Here we'll show some advanced usages when working with updates. - -.. note:: - This page makes use of Handlers and Filters to show you how to handle updates. - Learn more at `Update Handling `_ and `Using Filters `_. +Here we'll show some advanced usages when working with `update handlers`_ and `filters`_. Handler Groups -------------- -If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored. +If you register handlers with overlapping (conflicting) filters, only the first one is executed and any other handler +will be ignored. This is intended by design. -In order to process the same update more than once, you can register your handler in a different group. -Groups are identified by a number (number 0 being the default) and are sorted, that is, a lower group number has a -higher priority. +In order to handle the very same update more than once, you have to register your handler in a different dispatching +group. Dispatching groups hold one or more handlers and are processed sequentially, they are identified by a number +(number 0 being the default) and sorted, that is, a lower group number has a higher priority: -For example, in: +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): @@ -29,8 +27,8 @@ For example, in: def just_text(client, message): print("Just Text") -``just_text`` is never executed because ``text_or_sticker`` already handles texts. To enable it, simply register the -function using a different group: +Here, ``just_text`` is never executed because ``text_or_sticker``, which has been registered first, already handles +texts (``Filters.text`` is shared and conflicting). To enable it, register the function using a different group: .. code-block:: python @@ -69,7 +67,7 @@ continue to propagate the same update to the next groups until all the handlers @app.on_message(Filters.private, group=1) def _(client, message): - print(1 / 0) # Unhandled exception: ZeroDivisionError + raise Exception("Unhandled exception!") # Simulate an unhandled exception @app.on_message(Filters.private, group=2) @@ -82,7 +80,7 @@ The output for each incoming update will therefore be: .. code-block:: text 0 - ZeroDivisionError: division by zero + Exception: Unhandled exception! 2 Stop Propagation @@ -153,9 +151,9 @@ Continue Propagation As opposed to `stopping the update propagation <#stop-propagation>`_ and also as an alternative to the `handler groups <#handler-groups>`_, you can signal the internal dispatcher to continue the update propagation within -the group regardless of the next handler's filters. This allows you to register multiple handlers with overlapping -filters in the same group; to let the dispatcher process the next handler you can do *one* of the following in each -handler you want to grant permission to continue: +**the same group** regardless of the next handler's filters. This allows you to register multiple handlers with +overlapping filters in the same group; to let the dispatcher process the next handler you can do *one* of the following +in each handler you want to grant permission to continue: - Call the update's bound-method ``.continue_propagation()`` (preferred way). - Manually ``raise ContinuePropagation`` exception (more suitable for raw updates only). @@ -218,4 +216,7 @@ The output of both (equivalent) examples will be: 0 1 - 2 \ No newline at end of file + 2 + +.. _`update handlers`: UpdateHandling.html +.. _`filters`: UsingFilters.html \ No newline at end of file diff --git a/docs/source/resources/SmartPlugins.rst b/docs/source/resources/SmartPlugins.rst index 972efdd8..6f266590 100644 --- a/docs/source/resources/SmartPlugins.rst +++ b/docs/source/resources/SmartPlugins.rst @@ -13,8 +13,8 @@ Introduction ------------ Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize -your applications, you had to put your function definitions in separate files and register them inside your main script, -like this: +your applications, you had to put your function definitions in separate files and register them inside your main script +after importing your modules, like this: .. note:: @@ -72,7 +72,7 @@ functions. So, what if you could? Smart Plugins solve this issue by taking care Using Smart Plugins ------------------- -Setting up your Pyrogram project to accommodate Smart Plugins is straightforward: +Setting up your Pyrogram project to accommodate Smart Plugins is pretty straightforward: #. Create a new folder to store all the plugins (e.g.: "plugins", "handlers", ...). #. Put your python files full of plugins inside. Organize them as you wish. @@ -129,18 +129,17 @@ Setting up your Pyrogram project to accommodate Smart Plugins is straightforward from pyrogram import Client - plugins = dict( - root="plugins" - ) + plugins = dict(root="plugins") Client("my_account", plugins=plugins).run() + The first important thing to note is the new ``plugins`` folder. You can put *any python file* in *any subfolder* and each file can contain *any decorated function* (handlers) with one limitation: within a single module (file) you must use different names for each decorated function. The second thing is telling Pyrogram where to look for your plugins: you can either use the *config.ini* file or -the Client parameter "plugins"; the *root* value must match the name of your plugins folder. Your Pyrogram Client +the Client parameter "plugins"; the *root* value must match the name of your plugins root folder. Your Pyrogram Client instance will **automatically** scan the folder upon starting to search for valid handlers and register them for you. Then you'll notice you can now use decorators. That's right, you can apply the usual decorators to your callback @@ -161,7 +160,7 @@ found inside each module will be, instead, loaded in the order they are defined, 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`` -keys, either in the *config.ini* file or in the dictionary passed as Client argument. Here's how they work: +directives, either in the *config.ini* file or in the dictionary passed as Client argument. Here's how they work: - If both ``include`` and ``exclude`` are omitted, all plugins are loaded as described above. - If ``include`` is given, only the specified plugins will be loaded, in the order they are passed. @@ -214,9 +213,7 @@ also organized in subfolders: .. code-block:: python - plugins = dict( - root="plugins" - ) + plugins = dict(root="plugins") Client("my_account", plugins=plugins).run() @@ -293,7 +290,7 @@ Load/Unload Plugins at Runtime In the `previous section <#specifying-the-plugins-to-include>`_ we've explained how to specify which plugins to load and which to ignore before your Client starts. Here we'll show, instead, how to unload and load again a previously -registered plugins at runtime. +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, when you reference them later on, they will be actually pointing to a tuple of @@ -314,14 +311,14 @@ attribute. Here's an example: - Printing ``echo`` will show something like ``(, 0)``. -- Printing ``echo[0].callback``, that is, the *callback* attribute of the first eleent of the tuple, which is an +- Printing ``echo[0].callback``, that is, the *callback* attribute of the first element of the tuple, which is an Handler, will reveal the actual callback ````. Unloading ^^^^^^^^^ -In order to unload a plugin, or any other handler, all you need to do is obtain a reference to it (by importing the -relevant module) and call :meth:`remove_handler ` Client's method with your function +In order to unload a plugin, or any other handler, all you need to do is obtain a reference to it by importing the +relevant module and call :meth:`remove_handler() ` Client's method with your function name preceded by the star ``*`` operator as argument. Example: - ``main.py`` @@ -346,7 +343,7 @@ Loading ^^^^^^^ Similarly to the unloading process, in order to load again a previously unloaded plugin you do the same, but this time -using :meth:`add_handler ` instead. Example: +using :meth:`add_handler() ` instead. Example: - ``main.py`` diff --git a/docs/source/resources/UpdateHandling.rst b/docs/source/resources/UpdateHandling.rst index 12afe324..ed0ad909 100644 --- a/docs/source/resources/UpdateHandling.rst +++ b/docs/source/resources/UpdateHandling.rst @@ -1,11 +1,13 @@ Update Handling =============== -Updates are events that happen in your Telegram account (incoming messages, new channel posts, new members join, ...) -and can be handled by registering one or more callback functions in your app by using `Handlers <../pyrogram/Handlers.html>`_. +Let's now dive right into the core of the framework. -To put it simply, whenever an update is received from Telegram it will be dispatched and your previously defined callback -function(s) matching it will be called back with the update itself as argument. +Updates are events that happen in your Telegram account (incoming messages, new channel posts, new members join, ...) +and are handled by registering one or more callback functions in your app using `Handlers <../pyrogram/Handlers.html>`_. + +Each handler deals with a specific event and once a matching update arrives from Telegram, your registered callback +function will be called. Registering an Handler ---------------------- @@ -15,13 +17,34 @@ To explain how handlers work let's have a look at the most used one, the updates coming from all around your chats. Every other handler shares the same setup logic; you should not have troubles settings them up once you learn from this section. +Using add_handler() +------------------- + +The :meth:`add_handler() ` method takes any handler instance that wraps around your defined +callback function and registers it in your Client. Here's a full example that prints out the content of a message as +soon as it arrives: + +.. code-block:: python + + from pyrogram import Client, MessageHandler + + + def my_function(client, message): + print(message) + + + app = Client("my_account") + + my_handler = MessageHandler(my_function) + app.add_handler(my_handler) + + app.run() Using Decorators ---------------- -The easiest and nicest way to register a MessageHandler is by decorating your function with the -:meth:`on_message() ` decorator. Here's a full example that prints out the content -of a message as soon as it arrives. +A much nicer way to register a MessageHandler is by decorating your callback function with the +:meth:`on_message() ` decorator, which will still make use of add_handler() under the hood. .. code-block:: python @@ -37,23 +60,13 @@ of a message as soon as it arrives. app.run() -Using add_handler() -------------------- -If you prefer not to use decorators for any reason, there is an alternative way for registering Handlers. -This is useful, for example, when you want to keep your callback functions in separate files. +.. note:: -.. code-block:: python + Due to how these decorators work in Pyrogram, they will wrap your defined callback function in a tuple consisting of + ``(handler, group)``; this will be the value held by your function identifier (e.g.: *my_function* from the example + above). - from pyrogram import Client, MessageHandler - - - def my_handler(client, message): - print(message) - - - app = Client("my_account") - - app.add_handler(MessageHandler(my_handler)) - - app.run() + In case, for some reason, you want to get your own function back after it has been decorated, you need to access + ``my_function[0].callback``, that is, the *callback* field of the *handler* object which is the first element in the + tuple. \ No newline at end of file diff --git a/docs/source/resources/UsingFilters.rst b/docs/source/resources/UsingFilters.rst index 3fe87b8a..ec3e2e10 100644 --- a/docs/source/resources/UsingFilters.rst +++ b/docs/source/resources/UsingFilters.rst @@ -1,17 +1,19 @@ Using Filters ============= -For a finer grained control over what kind of messages will be allowed or not in your callback functions, you can use -:class:`Filters `. +So far we've seen how to register a callback function that executes every time a specific update comes from the server, +but there's much more than that to come. -.. note:: - This page makes use of Handlers to show you how to handle updates. - Learn more at `Update Handling `_. +Here we'll discuss about :class:`Filters `. Filters enable a fine-grain control over what kind of +updates are allowed or not to be passed in your callback functions, based on their inner details. + +Let's start right away with a simple example: - This example will show you how to **only** handle messages containing an :obj:`Audio ` object and - ignore any other message: + ignore any other message. Filters are passed as the first argument of the decorator: .. code-block:: python + :emphasize-lines: 4 from pyrogram import Filters @@ -20,9 +22,10 @@ For a finer grained control over what kind of messages will be allowed or not in def my_handler(client, message): print(message) -- or, without decorators: +- or, without decorators. Here filters are passed as the second argument of the handler constructor: .. code-block:: python + :emphasize-lines: 8 from pyrogram import Filters, MessageHandler @@ -37,7 +40,7 @@ Combining Filters ----------------- Filters can also be used in a more advanced way by inverting and combining more filters together using bitwise -operators: +operators ``~``, ``&`` and ``|``: - Use ``~`` to invert a filter (behaves like the ``not`` operator). - Use ``&`` and ``|`` to merge two filters (behave like ``and``, ``or`` operators respectively). @@ -74,7 +77,7 @@ can also accept arguments: def my_handler(client, message): print(message) -- Message is a **text** message matching the given **regex** pattern. +- Message is a **text** message or a media **caption** matching the given **regex** pattern. .. code-block:: python @@ -104,17 +107,17 @@ Custom Filters -------------- Pyrogram already provides lots of built-in :class:`Filters ` to work with, but in case you can't find -a specific one for your needs or want to build a custom filter by yourself (to be used in a different handler, for -example) you can use :meth:`Filters.create() `. +a specific one for your needs or want to build a custom filter by yourself (to be used in a different kind of handler, +for example) you can use :meth:`Filters.create() `. .. note:: At the moment, the built-in filters are intended to be used with the :obj:`MessageHandler ` only. An example to demonstrate how custom filters work is to show how to create and use one for the -:obj:`CallbackQueryHandler `. Note that callback queries updates are only received by Bots; -create and `authorize your bot <../start/Setup.html#bot-authorization>`_, then send a message with an inline keyboard to -yourself. This allows you to test your filter by pressing the inline button: +:obj:`CallbackQueryHandler `. Note that callback queries updates are only received by +bots; create and `authorize your bot <../start/Setup.html#bot-authorization>`_, then send a message with an inline +keyboard to yourself. This allows you to test your filter by pressing the inline button: .. code-block:: python @@ -133,26 +136,27 @@ Basic Filters For this basic filter we will be using only the first two parameters of :meth:`Filters.create() `. -The code below creates a simple filter for hardcoded callback data. This filter will only allow callback queries -containing "pyrogram" as data: +The code below creates a simple filter for hardcoded, static callback data. This filter will only allow callback queries +containing "Pyrogram" as data, that is, the function *func* you pass returns True in case the callback query data +equals to ``b"Pyrogram"``. .. code-block:: python - hardcoded_data = Filters.create( - name="HardcodedData", - func=lambda filter, callback_query: callback_query.data == b"pyrogram" + static_data = Filters.create( + name="StaticdData", + func=lambda flt, callback_query: callback_query.data == b"Pyrogram" ) The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example, the same -could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter itself: +could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter's scope: .. code-block:: python - def func(filter, callback_query): - return callback_query.data == b"pyrogram" + def func(flt, callback_query): + return callback_query.data == b"Pyrogram" - hardcoded_data = Filters.create( - name="HardcodedData", + static_data = Filters.create( + name="StaticData", func=func ) @@ -160,14 +164,14 @@ The filter usage remains the same: .. code-block:: python - @app.on_callback_query(hardcoded_data) + @app.on_callback_query(static_data) def pyrogram_data(client, callback_query): client.answer_callback_query(callback_query.id, "it works!") Filters with Arguments ^^^^^^^^^^^^^^^^^^^^^^ -A much cooler filter would be one that accepts "pyrogram" or any other data as argument at usage time. +A much cooler filter would be one that accepts "Pyrogram" or any other data as argument at usage time. A dynamic filter like this will make use of the third parameter of :meth:`Filters.create() `. This is how a dynamic custom filter looks like: @@ -177,7 +181,7 @@ This is how a dynamic custom filter looks like: def dynamic_data(data): return Filters.create( name="DynamicData", - func=lambda filter, callback_query: filter.data == callback_query.data, + func=lambda flt, callback_query: flt.data == callback_query.data, data=data # "data" kwarg is accessed with "filter.data" ) @@ -185,6 +189,6 @@ And its usage: .. code-block:: python - @app.on_callback_query(dynamic_data(b"pyrogram")) + @app.on_callback_query(dynamic_data(b"Pyrogram")) def pyrogram_data(client, callback_query): client.answer_callback_query(callback_query.id, "it works!") \ No newline at end of file diff --git a/docs/source/start/Installation.rst b/docs/source/start/Installation.rst index 6a6ceef8..079e1b1f 100644 --- a/docs/source/start/Installation.rst +++ b/docs/source/start/Installation.rst @@ -1,11 +1,11 @@ Installation ============ -Being a Python library, Pyrogram requires Python to be installed in your system. +Being a Python library, **Pyrogram** requires Python to be installed in your system. We recommend using the latest version of Python 3 and pip. -Get Python 3 from https://www.python.org/downloads/ (or with your package manager) and pip -by following the instructions at https://pip.pypa.io/en/latest/installing/. +- 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:: @@ -29,8 +29,12 @@ Install Pyrogram Bleeding Edge ------------- -If you want the latest development version of Pyrogram, you can install it straight from the develop_ -branch using this command (note "develop.zip" in the link): +Things are constantly evolving in Pyrogram, although new releases are published only when enough changes are added, +but this doesn't mean you can't try new features right now! + +In case you would like to try out the latest Pyrogram features and additions, the `GitHub repo`_ is always kept updated +with new changes; you can install the development version straight from the ``develop`` branch using this command +(note "develop.zip" in the link): .. code-block:: text @@ -39,10 +43,10 @@ branch using this command (note "develop.zip" in the link): Asynchronous ------------ -Pyrogram heavily depends on IO-bound network code (it's a cloud-based messaging client library after all), and here's +Pyrogram heavily depends on IO-bound network code (it's a cloud-based messaging framework after all), and here's where asyncio shines the most by providing extra performance while running on a single OS-level thread only. -**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5+ required). +**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5.3+ required). Use this command to install (note "asyncio.zip" in the link): .. code-block:: text @@ -85,4 +89,4 @@ If no error shows up you are good to go. '0.12.0' .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto -.. _develop: http://github.com/pyrogram/pyrogram +.. _`Github repo`: http://github.com/pyrogram/pyrogram diff --git a/docs/source/start/Setup.rst b/docs/source/start/Setup.rst index e004aaf8..45a40d16 100644 --- a/docs/source/start/Setup.rst +++ b/docs/source/start/Setup.rst @@ -15,23 +15,22 @@ If you already have one you can skip this step, otherwise: #. Fill out the form to register a new Telegram application. #. Done. The API key consists of two parts: **App api_id** and **App api_hash**. - .. important:: - This API key is personal and should be kept secret. + This API key is personal and must be kept secret. Configuration ------------- The API key obtained in the `previous step <#api-keys>`_ defines a token for your application allowing you to access -the Telegram database using the MTProto API — **it is therefore required for all authorizations of both Users and Bots**. +the Telegram database using the MTProto API — **it is therefore required for all authorizations of both users and bots**. Having it handy, it's time to configure your Pyrogram project. There are two ways to do so, and you can choose what fits better for you: - Create a new ``config.ini`` file at the root of your working directory, 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: + **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: .. code-block:: ini @@ -39,8 +38,8 @@ fits better for you: api_id = 12345 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: +- 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: .. code-block:: python @@ -54,16 +53,16 @@ fits better for you: .. note:: - The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id* - and *api_hash* parameters usage. + From now on, the code snippets assume you are using the ``config.ini`` file, thus they won't show the *api_id* and + *api_hash* parameters usage to keep them as clean as possible. User Authorization ------------------ -In order to use the API, Telegram requires that Users be authorized via their phone numbers. -Pyrogram automatically manages this access, all you need to do is create an instance of -the :class:`Client ` class by passing to it a ``session_name`` of your choice -(e.g.: "my_account") and call the :meth:`run() ` method: +In order to use the API, Telegram requires that users be authorized via their phone numbers. +Pyrogram automatically manages this access, all you need to do is create an instance of the +:class:`Client ` class by passing to it a ``session_name`` of your choice (e.g.: "my_account") and call +the :meth:`run() ` method: .. code-block:: python @@ -80,31 +79,40 @@ and the **phone code** you will receive: Enter phone number: +39********** Is "+39**********" correct? (y/n): y Enter phone code: 32768 + Logged in successfully as Dan -After successfully authorizing yourself, a new file called ``my_account.session`` will be created allowing -Pyrogram executing 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. +After successfully authorizing yourself, a new file called ``my_account.session`` will be created allowing Pyrogram +executing 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`` files are personal and must be kept secret. +.. note:: + + The code above does nothing except asking for credentials and keeping the client online, hit ``CTRL+C`` now to stop + your application and keep reading. + Bot Authorization ----------------- Bots are a special kind of users that are authorized via their tokens (instead of phone numbers), which are created by -BotFather_. Bot tokens replace the Users' phone numbers only — you still need to -`configure a Telegram API key <#configuration>`_ with Pyrogram, even when using Bots. +BotFather_. Bot tokens replace the users' phone numbers only — you still need to +`configure a Telegram API key <#configuration>`_ with Pyrogram, even when using bots. The authorization process is automatically managed. All you need to do is choose a ``session_name`` (can be anything, -usually your bot username) and pass your bot token using the ``bot_token`` parameter. -The session file will be named after the session name, which will be ``pyrogrambot.session`` for the example below. +usually your bot username) and pass your bot token using the ``bot_token`` parameter. The session file will be named +after the session name, which will be ``pyrogrambot.session`` for the example below. .. code-block:: python from pyrogram import Client - app = Client("pyrogrambot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") + app = Client( + "pyrogrambot", + bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" + ) app.run() .. _installed Pyrogram: Installation.html diff --git a/docs/source/start/Usage.rst b/docs/source/start/Usage.rst index 8bb197ab..35ae79a0 100644 --- a/docs/source/start/Usage.rst +++ b/docs/source/start/Usage.rst @@ -1,7 +1,7 @@ Usage ===== -Having your `project set up`_ and your account authorized_, it's time to play with the API. Let's start! +Having your `project set up`_ and your account authorized_, it's time to start playing with the API. Let's start! High-level API -------------- @@ -11,34 +11,36 @@ named after the `Telegram Bot API`_. Here's a simple example: - .. code-block:: python +.. code-block:: python - from pyrogram import Client + from pyrogram import Client - app = Client("my_account") + app = Client("my_account") - app.start() + app.start() - print(app.get_me()) - app.send_message("me", "Hi there! I'm using **Pyrogram**") - app.send_location("me", 51.500729, -0.124583) + print(app.get_me()) + app.send_message("me", "Hi there! I'm using **Pyrogram**") + app.send_location("me", 51.500729, -0.124583) + app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") - app.stop() + app.stop() You can also use Pyrogram in a context manager with the ``with`` statement. The Client will automatically :meth:`start ` and :meth:`stop ` gracefully, even in case of unhandled exceptions in your code: - .. code-block:: python +.. code-block:: python - from pyrogram import Client + from pyrogram import Client - app = Client("my_account") + app = Client("my_account") - with app: - print(app.get_me()) - app.send_message("me", "Hi there! I'm using **Pyrogram**") - app.send_location("me", 51.500729, -0.124583) + with app: + print(app.get_me()) + app.send_message("me", "Hi there! I'm using **Pyrogram**") + app.send_location("me", 51.500729, -0.124583) + app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") More examples on `GitHub `_. @@ -46,4 +48,4 @@ More examples on `GitHub `): Pass one or more filters to allow only a subset of callback queries to be passed diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index b32889ef..cf8f9cf2 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.py @@ -33,13 +33,6 @@ class OnDeletedMessages(BaseClient): """Use this decorator to automatically register a function for handling deleted messages. This does the same thing as :meth:`add_handler` using the :class:`DeletedMessagesHandler`. - .. note:: - This decorator will wrap your defined function in a tuple consisting of *(Handler, group)*. - - To reference your own function after it has been decorated, you need to access - *my_function[0].callback*, that is, the *callback* field of Handler object which is the the - first element in the tuple. - Args: filters (:obj:`Filters `): Pass one or more filters to allow only a subset of messages to be passed diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index 9bbd96c1..e6563893 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.py @@ -33,13 +33,6 @@ class OnMessage(BaseClient): """Use this decorator to automatically register a function for handling messages. This does the same thing as :meth:`add_handler` using the :class:`MessageHandler`. - .. note:: - This decorator will wrap your defined function in a tuple consisting of *(Handler, group)*. - - To reference your own function after it has been decorated, you need to access - *my_function[0].callback*, that is, the *callback* field of Handler object which is the the - first element in the tuple. - Args: filters (:obj:`Filters `): Pass one or more filters to allow only a subset of messages to be passed diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py index cf1da94d..1494a319 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -31,13 +31,6 @@ class OnRawUpdate(BaseClient): """Use this decorator to automatically register a function for handling raw updates. This does the same thing as :meth:`add_handler` using the :class:`RawUpdateHandler`. - .. note:: - This decorator will wrap your defined function in a tuple consisting of *(Handler, group)*. - - To reference your own function after it has been decorated, you need to access - *my_function[0].callback*, that is, the *callback* field of Handler object which is the the - first element in the tuple. - Args: group (``int``, *optional*): The group identifier, defaults to 0. diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py index 5f962270..4d8185b1 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/client/methods/decorators/on_user_status.py @@ -33,13 +33,6 @@ class OnUserStatus(BaseClient): """Use this decorator to automatically register a function for handling user status updates. This does the same thing as :meth:`add_handler` using the :class:`UserStatusHandler`. - .. note:: - This decorator will wrap your defined function in a tuple consisting of *(Handler, group)*. - - To reference your own function after it has been decorated, you need to access - *my_function[0].callback*, that is, the *callback* field of Handler object which is the the - first element in the tuple. - Args: filters (:obj:`Filters `): Pass one or more filters to allow only a subset of UserStatus updated to be passed in your function.