Merge branch 'develop' into layer-95

# Conflicts:
#	pyrogram/__init__.py
This commit is contained in:
Dan 2019-03-01 18:38:27 +01:00
commit ffa0da5800
48 changed files with 3440 additions and 279 deletions

View File

@ -17,18 +17,24 @@ Pyrogram
app.run() app.run()
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for **Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C.
building custom Telegram applications that interact with the MTProto API as both User and Bot. It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the `MTProto API`_.
`Pyrogram in fully-asynchronous mode is also available » <https://github.com/pyrogram/pyrogram/issues/181>`_
`Working PoC of Telegram voice calls using Pyrogram » <https://github.com/bakatrouble/pytgvoip>`_
Features Features
-------- --------
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away. - **Easy**: You can install Pyrogram with pip and start building your applications right away.
- **High-level**: The low-level details of MTProto are abstracted and automatically handled. - **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. - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0. - **Documented**: Pyrogram API methods, types and public interfaces are well documented.
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API. - **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
- **Full API**, allowing to execute any advanced action an official client is able to do, and more. - **Updated**, to the latest Telegram API version, currently Layer 91 on top of `MTProto 2.0`_.
- **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 Requirements
------------ ------------
@ -43,11 +49,11 @@ Installing
pip3 install pyrogram pip3 install pyrogram
Getting Started Resources
--------------- ---------
- The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.ml. - The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.ml.
- Reading Examples_ in this repository is also a good way for learning how things work. - Reading `Examples in this repository`_ is also a good way for learning how Pyrogram works.
- Seeking extra help? Don't be shy, come join and ask our Community_! - Seeking extra help? Don't be shy, come join and ask our Community_!
- For other requests you can send an Email_ or a Message_. - For other requests you can send an Email_ or a Message_.
@ -65,13 +71,15 @@ Copyright & License
- Licensed under the terms of the `GNU Lesser General Public License v3 or later (LGPLv3+)`_ - Licensed under the terms of the `GNU Lesser General Public License v3 or later (LGPLv3+)`_
.. _`Telegram`: https://telegram.org/ .. _`Telegram`: https://telegram.org/
.. _`MTProto API`: https://core.telegram.org/api#telegram-api
.. _`Telegram API key`: https://docs.pyrogram.ml/start/ProjectSetup#api-keys .. _`Telegram API key`: https://docs.pyrogram.ml/start/ProjectSetup#api-keys
.. _`Community`: https://t.me/PyrogramChat .. _`Community`: https://t.me/PyrogramChat
.. _`Examples`: https://github.com/pyrogram/pyrogram/tree/master/examples .. _`Examples in this repository`: https://github.com/pyrogram/pyrogram/tree/master/examples
.. _`GitHub`: https://github.com/pyrogram/pyrogram/issues .. _`GitHub`: https://github.com/pyrogram/pyrogram/issues
.. _`Email`: admin@pyrogram.ml .. _`Email`: admin@pyrogram.ml
.. _`Message`: https://t.me/haskell .. _`Message`: https://t.me/haskell
.. _TgCrypto: https://github.com/pyrogram/tgcrypto .. _TgCrypto: https://github.com/pyrogram/tgcrypto
.. _`MTProto 2.0`: https://core.telegram.org/mtproto
.. _`GNU Lesser General Public License v3 or later (LGPLv3+)`: COPYING.lesser .. _`GNU Lesser General Public License v3 or later (LGPLv3+)`: COPYING.lesser
.. |header| raw:: html .. |header| raw:: html
@ -83,17 +91,17 @@ Copyright & License
</h1> </h1>
<p align="center"> <p align="center">
<b>Telegram MTProto API Client Library for Python</b> <b>Telegram MTProto API Framework for Python</b>
<br> <br>
<a href="https://github.com/pyrogram/pyrogram/releases/latest">
Download
</a>
<a href="https://docs.pyrogram.ml"> <a href="https://docs.pyrogram.ml">
Documentation Documentation
</a> </a>
<a href="https://github.com/pyrogram/pyrogram/releases">
Changelog
</a>
<a href="https://t.me/PyrogramChat"> <a href="https://t.me/PyrogramChat">
Community Community
</a> </a>
@ -104,7 +112,7 @@ Copyright & License
</a> </a>
<a href="https://github.com/pyrogram/tgcrypto"> <a href="https://github.com/pyrogram/tgcrypto">
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30" <img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
alt="TgCrypto"> alt="TgCrypto Version">
</a> </a>
</p> </p>
@ -112,12 +120,12 @@ Copyright & License
:target: https://pyrogram.ml :target: https://pyrogram.ml
:alt: Pyrogram :alt: Pyrogram
.. |description| replace:: **Telegram MTProto API Client Library for Python** .. |description| replace:: **Telegram MTProto API Framework for Python**
.. |scheme| image:: "https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30" .. |schema| image:: https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30
:target: compiler/api/source/main_api.tl :target: compiler/api/source/main_api.tl
:alt: Scheme Layer :alt: Schema Layer
.. |tgcrypto| image:: "https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30" .. |tgcrypto| image:: https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30
:target: https://github.com/pyrogram/tgcrypto :target: https://github.com/pyrogram/tgcrypto
:alt: TgCrypto :alt: TgCrypto Version

View File

@ -87,3 +87,7 @@ MESSAGE_POLL_CLOSED You can't interact with a closed poll
MEDIA_INVALID The media is invalid MEDIA_INVALID The media is invalid
BOT_SCORE_NOT_MODIFIED The bot score was not modified BOT_SCORE_NOT_MODIFIED The bot score was not modified
USER_BOT_REQUIRED The method can be used by bots only USER_BOT_REQUIRED The method can be used by bots only
IMAGE_PROCESS_FAILED The server failed to process your image
USERNAME_NOT_MODIFIED The username was not modified
CALL_ALREADY_ACCEPTED The call is already accepted
CALL_ALREADY_DECLINED The call is already declined

1 id message
87 MEDIA_INVALID The media is invalid
88 BOT_SCORE_NOT_MODIFIED The bot score was not modified
89 USER_BOT_REQUIRED The method can be used by bots only
90 IMAGE_PROCESS_FAILED The server failed to process your image
91 USERNAME_NOT_MODIFIED The username was not modified
92 CALL_ALREADY_ACCEPTED The call is already accepted
93 CALL_ALREADY_DECLINED The call is already declined

View File

@ -1,2 +1,3 @@
id message id message
FLOOD_WAIT_X A wait of {x} seconds is required FLOOD_WAIT_X A wait of {x} seconds is required
TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {x} seconds

1 id message
2 FLOOD_WAIT_X A wait of {x} seconds is required
3 TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {x} seconds

View File

@ -10,27 +10,28 @@ Welcome to Pyrogram
</div> </div>
<p align="center"> <p align="center">
<b>Telegram MTProto API Client Library for Python</b> <b>Telegram MTProto API Framework for Python</b>
<br> <br>
<a href="https://github.com/pyrogram/pyrogram/releases/latest"> <a href="https://docs.pyrogram.ml">
Download Documentation
</a> </a>
<a href="https://github.com/pyrogram/pyrogram"> <a href="https://github.com/pyrogram/pyrogram/releases">
Source code Changelog
</a> </a>
<a href="https://t.me/PyrogramChat"> <a href="https://t.me/PyrogramChat">
Community Community
</a> </a>
<br> <br>
<a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl"> <a href="compiler/api/source/main_api.tl">
<img src="https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30" <img src="https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30"
alt="Scheme Layer"> alt="Schema Layer">
</a> </a>
<a href="https://github.com/pyrogram/tgcrypto"> <a href="https://github.com/pyrogram/tgcrypto">
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30" <img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
alt="TgCrypto"> alt="TgCrypto Version">
</a> </a>
</p> </p>
@ -48,25 +49,27 @@ Welcome to Pyrogram
app.run() app.run()
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library. Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the framework.
Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order
using the Next button at the end of each page. But first, here's a brief overview of what is this all about. using the Next button at the end of each page. But first, here's a brief overview of what is this all about.
About About
----- -----
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for **Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C.
building custom Telegram applications that interact with the MTProto API as both User and Bot. It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the `MTProto API`_.
Features Features
-------- --------
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away. - **Easy**: You can install Pyrogram with pip and start building your applications right away.
- **High-level**: The low-level details of MTProto are abstracted and automatically handled. - **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. - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0. - **Documented**: Pyrogram API methods, types and public interfaces are well documented.
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API. - **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
- **Full API**, allowing to execute any advanced action an official client is able to do, and more. - **Updated**, to the latest Telegram API version, currently Layer 91 on top of `MTProto 2.0`_.
- **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.
To get started, press the Next button. To get started, press the Next button.
@ -85,6 +88,7 @@ To get started, press the Next button.
resources/UpdateHandling resources/UpdateHandling
resources/UsingFilters resources/UsingFilters
resources/MoreOnUpdates resources/MoreOnUpdates
resources/ConfigurationFile
resources/SmartPlugins resources/SmartPlugins
resources/AutoAuthorization resources/AutoAuthorization
resources/CustomizeSessions resources/CustomizeSessions
@ -95,6 +99,7 @@ To get started, press the Next button.
resources/ErrorHandling resources/ErrorHandling
resources/TestServers resources/TestServers
resources/AdvancedUsage resources/AdvancedUsage
resources/VoiceCalls
resources/Changelog resources/Changelog
.. toctree:: .. toctree::
@ -112,3 +117,5 @@ To get started, press the Next button.
.. _`Telegram`: https://telegram.org/ .. _`Telegram`: https://telegram.org/
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto/ .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto/
.. _`MTProto API`: https://core.telegram.org/api#telegram-api
.. _`MTProto 2.0`: https://core.telegram.org/mtproto

View File

@ -0,0 +1,90 @@
Configuration File
==================
As already mentioned in previous sections, Pyrogram can also 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 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.
.. note::
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).
Settings specified via Client's parameter have higher priority and will override any setting stored in the
configuration file.
The config.ini File
-------------------
By default, Pyrogram will look for a file named ``config.ini`` placed at the root of your working directory, that is,
the same folder of your running script. You can change the name or location of your configuration file by specifying it
in your Client's parameter *config_file*.
- Replace the default *config.ini* file with *my_configuration.ini*:
.. code-block:: python
from pyrogram import Client
app = Client("my_account", config_file="my_configuration.ini")
Configuration Sections
----------------------
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*.
.. code-block:: ini
[pyrogram]
api_id = 12345
api_hash = 0123456789abcdef0123456789abcdef
`More info about API Key. <../start/Setup.html#configuration>`_
Proxy
^^^^^
The ``[proxy]`` section contains settings about your SOCKS5 proxy.
.. code-block:: ini
[proxy]
enabled = True
hostname = 11.22.33.44
port = 1080
username = <your_username>
password = <your_password>
`More info about SOCKS5 Proxy. <SOCKS5Proxy.html>`_
Plugins
^^^^^^^
The ``[plugins]`` section contains settings about Smart Plugins.
.. code-block:: ini
[plugins]
root = plugins
include =
module
folder.module
exclude =
module fn2
`More info about Smart Plugins. <SmartPlugins.html>`_

View File

@ -91,13 +91,14 @@ Stop Propagation
In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following: In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following:
- Call the update's bound-method ``.stop_propagation()`` (preferred way). - Call the update's bound-method ``.stop_propagation()`` (preferred way).
- Manually ``raise StopPropagation`` error (more suitable for raw updates only). - Manually ``raise StopPropagation`` exception (more suitable for raw updates only).
.. note:: .. note::
Note that ``.stop_propagation()`` is just an elegant and intuitive way to raise a ``StopPropagation`` error; Internally, the propagation is stopped by handling a custom exception. ``.stop_propagation()`` is just an elegant
this means that any code coming *after* calling it won't be executed as your function just raised a custom exception and intuitive way to ``raise StopPropagation``; this also means that any code coming *after* calling the method
to signal the dispatcher not to propagate the update anymore. won't be executed as your function just raised an exception to signal the dispatcher not to propagate the
update anymore.
Example with ``stop_propagation()``: Example with ``stop_propagation()``:
@ -139,10 +140,82 @@ Example with ``raise StopPropagation``:
def _(client, message): def _(client, message):
print(2) print(2)
The handler in group number 2 will never be executed because the propagation was stopped before. The output of both Each handler is registered in a different group, but the handler in group number 2 will never be executed because the
examples will be: propagation was stopped earlier. The output of both (equivalent) examples will be:
.. code-block:: text .. code-block:: text
0 0
1 1
Continue Propagation
^^^^^^^^^^^^^^^^^^^^
As opposed to `stopping the update propagation <#stop-propagation>`_ and also as an alternative to the
`handler groups <#handler-groups>`_, you can signal the internal dispatcher to continue the update propagation within
the 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).
.. note::
Internally, the propagation is continued by handling a custom exception. ``.continue_propagation()`` is just an
elegant and intuitive way to ``raise ContinuePropagation``; this also means that any code coming *after* calling the
method won't be executed as your function just raised an exception to signal the dispatcher to continue with the
next available handler.
Example with ``continue_propagation()``:
.. code-block:: python
@app.on_message(Filters.private)
def _(client, message):
print(0)
message.continue_propagation()
@app.on_message(Filters.private)
def _(client, message):
print(1)
message.continue_propagation()
@app.on_message(Filters.private)
def _(client, message):
print(2)
Example with ``raise ContinuePropagation``:
.. code-block:: python
from pyrogram import ContinuePropagation
@app.on_message(Filters.private)
def _(client, message):
print(0)
raise ContinuePropagation
@app.on_message(Filters.private)
def _(client, message):
print(1)
raise ContinuePropagation
@app.on_message(Filters.private)
def _(client, message):
print(2)
Three handlers are registered in the same group, and all of them will be executed because the propagation was continued
in each handler (except in the last one, where is useless to do so since there is no more handlers after).
The output of both (equivalent) examples will be:
.. code-block:: text
0
1
2

View File

@ -1,9 +1,9 @@
Smart Plugins Smart Plugins
============= =============
Pyrogram embeds a **smart** (automatic) and lightweight plugin system that is meant to further simplify the organization 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 components that can be **easily shared** across different of large projects and to provide a way for creating pluggable (modular) components that can be **easily shared** across
Pyrogram applications with **minimal boilerplate code**. different Pyrogram applications with **minimal boilerplate code**.
.. tip:: .. tip::
@ -13,7 +13,8 @@ Introduction
------------ ------------
Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize
your applications, you had to do something like this... your applications, you had to put your function definitions in separate files and register them inside your main script,
like this:
.. note:: .. note::
@ -63,19 +64,19 @@ your applications, you had to do something like this...
app.run() app.run()
...which is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to 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:`add_handler <pyrogram.Client.add_handler>` and manually instantiate each manually ``import``, manually :meth:`add_handler <pyrogram.Client.add_handler>` and manually instantiate each
:obj:`MessageHandler <pyrogram.MessageHandler>` object because **you can't use those cool decorators** for your :obj:`MessageHandler <pyrogram.MessageHandler>` object because **you can't use those cool decorators** for your
functions. So... What if you could? functions. So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically.
Using Smart Plugins Using Smart Plugins
------------------- -------------------
Setting up your Pyrogram project to accommodate Smart Plugins is pretty straightforward: Setting up your Pyrogram project to accommodate Smart Plugins is straightforward:
#. Create a new folder to store all the plugins (e.g.: "plugins"). #. Create a new folder to store all the plugins (e.g.: "plugins", "handlers", ...).
#. Put your files full of plugins inside. #. Put your python files full of plugins inside. Organize them as you wish.
#. Enable plugins in your Client. #. Enable plugins in your Client or via the *config.ini* file.
.. note:: .. note::
@ -107,20 +108,252 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight
def echo_reversed(client, message): def echo_reversed(client, message):
message.reply(message.text[::-1]) message.reply(message.text[::-1])
- ``config.ini``
.. code-block:: ini
[plugins]
root = plugins
- ``main.py`` - ``main.py``
.. code-block:: python .. code-block:: python
from pyrogram import Client from pyrogram import Client
Client("my_account", plugins_dir="plugins").run() Client("my_account").run()
The first important thing to note is the new ``plugins`` folder, whose name is passed to the the ``plugins_dir`` Alternatively, without using the *config.ini* file:
parameter when creating a :obj:`Client <pyrogram.Client>` in the ``main.py`` file — you can put *any python file* in
there and each file can contain *any decorated function* (handlers) with only one limitation: within a single plugin .. code-block:: python
file you must use different names for each decorated function. Your Pyrogram Client instance will **automatically**
scan the folder upon creation to search for valid handlers and register them for you. from pyrogram import Client
plugins = dict(
root="plugins"
)
Client("my_account", plugins=plugins).run()
The first important thing to note is the new ``plugins`` folder. You can put *any python file* in *any subfolder* and
each file can contain *any decorated function* (handlers) with one limitation: within a single module (file) you must
use different names for each decorated function.
The second thing is telling Pyrogram where to look for your plugins: you can 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
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 Then you'll notice you can now use decorators. That's right, you can apply the usual decorators to your callback
functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class) functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class)
instead of the usual ``@app`` (Client instance) namespace and things will work just the same. instead of the usual ``@app`` (Client instance) and things will work just the same.
Specifying the Plugins to include
---------------------------------
By default, if you don't explicitly supply a list of plugins, every valid one found inside your plugins root folder will
be included by following the alphabetical order of the directory structure (files and subfolders); the single handlers
found inside each module will be, instead, loaded in the order they are defined, from top to bottom.
.. note::
Remember: there can be at most one handler, within a group, dealing with a specific update. Plugins with overlapping
filters included a second time will not work. Learn more at `More on Updates <MoreOnUpdates.html>`_.
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:
- If both ``include`` and ``exclude`` are omitted, all plugins are loaded as described above.
- If ``include`` is given, only the specified plugins will be loaded, in the order they are passed.
- If ``exclude`` is given, the plugins specified here will be unloaded.
The ``include`` and ``exclude`` value is a **list of strings**. Each string containing the path of the module relative
to the plugins root folder, in Python notation (dots instead of slashes).
E.g.: ``subfolder.module`` refers to ``plugins/subfolder/module.py``, with ``root="plugins"``.
You can also choose the order in which the single handlers inside a module are loaded, thus overriding the default
top-to-bottom loading policy. You can do this by appending the name of the functions to the module path, each one
separated by a blank space.
E.g.: ``subfolder.module fn2 fn1 fn3`` will load *fn2*, *fn1* and *fn3* from *subfolder.module*, in this order.
Examples
^^^^^^^^
Given this plugins folder structure with three modules, each containing their own handlers (fn1, fn2, etc...), which are
also organized in subfolders:
.. code-block:: text
myproject/
plugins/
subfolder1/
plugins1.py
- fn1
- fn2
- fn3
subfolder2/
plugins2.py
...
plugins0.py
...
...
- Load every handler from every module, namely *plugins0.py*, *plugins1.py* and *plugins2.py* in alphabetical order
(files) and definition order (handlers inside files):
Using *config.ini* file:
.. code-block:: ini
[plugins]
root = plugins
Using *Client*'s parameter:
.. code-block:: python
plugins = dict(
root="plugins"
)
Client("my_account", plugins=plugins).run()
- Load only handlers defined inside *plugins2.py* and *plugins0.py*, in this order:
Using *config.ini* file:
.. code-block:: ini
[plugins]
root = plugins
include =
subfolder2.plugins2
plugins0
Using *Client*'s parameter:
.. code-block:: python
plugins = dict(
root="plugins",
include=[
"subfolder2.plugins2",
"plugins0"
]
)
Client("my_account", plugins=plugins).run()
- Load everything except the handlers inside *plugins2.py*:
Using *config.ini* file:
.. code-block:: ini
[plugins]
root = plugins
exclude = subfolder2.plugins2
Using *Client*'s parameter:
.. code-block:: python
plugins = dict(
root="plugins",
exclude=["subfolder2.plugins2"]
)
Client("my_account", plugins=plugins).run()
- Load only *fn3*, *fn1* and *fn2* (in this order) from *plugins1.py*:
Using *config.ini* file:
.. code-block:: ini
[plugins]
root = plugins
include = subfolder1.plugins1 fn3 fn1 fn2
Using *Client*'s parameter:
.. code-block:: python
plugins = dict(
root="plugins",
include=["subfolder1.plugins1 fn3 fn1 fn2"]
)
Client("my_account", plugins=plugins).run()
Load/Unload Plugins at Runtime
------------------------------
In the `previous section <#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.
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
*(handler: Handler, group: int)*. The actual callback function is therefore stored inside the handler's *callback*
attribute. Here's an example:
- ``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[0].callback)
- Printing ``echo`` will show something like ``(<MessageHandler object at 0x10e3abc50>, 0)``.
- Printing ``echo[0].callback``, that is, the *callback* attribute of the first eleent of the tuple, which is an
Handler, will reveal the actual callback ``<function echo at 0x10e3b6598>``.
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 <pyrogram.Client.remove_handler>` Client's method with your function
name preceded by the star ``*`` operator as argument. Example:
- ``main.py``
.. code-block:: python
from plugins.handlers import echo
...
app.remove_handler(*echo)
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
app.remove_handler(handler, group)
Loading
^^^^^^^
Similarly to the unloading process, in order to load again a previously unloaded plugin you do the same, but this time
using :meth:`add_handler <pyrogram.Client.add_handler>` instead. Example:
- ``main.py``
.. code-block:: python
from plugins.handlers import echo
...
app.add_handler(*echo)

View File

@ -0,0 +1,10 @@
Voice Calls
===========
A working proof-of-concept of Telegram voice calls using Pyrogram can be found here:
https://github.com/bakatrouble/pylibtgvoip. Thanks to `@bakatrouble <https://t.me/bakatrouble>`_.
.. note::
This page will be updated with more information once voice calls become eventually more usable and more integrated
in Pyrogram itself.

View File

@ -82,7 +82,7 @@ If no error shows up you are good to go.
>>> import pyrogram >>> import pyrogram
>>> pyrogram.__version__ >>> pyrogram.__version__
'0.10.3' '0.11.0'
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto
.. _develop: http://github.com/pyrogram/pyrogram .. _develop: http://github.com/pyrogram/pyrogram

View File

@ -13,4 +13,4 @@ with app:
app.send_location("me", 51.500729, -0.124583) app.send_location("me", 51.500729, -0.124583)
# Send a sticker # Send a sticker
app.send_sticker("me", "CAADBAADhw4AAvLQYAHICbZ5SUs_jwI") app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI")

View File

@ -6,13 +6,15 @@ to make it only work for specific messages in a specific chat.
from pyrogram import Client, Emoji, Filters from pyrogram import Client, Emoji, Filters
MENTION = "[{}](tg://user?id={})" TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames
MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!" MENTION = "[{}](tg://user?id={})" # User mention markup
MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!" # Welcome message
app = Client("my_account") app = Client("my_account")
@app.on_message(Filters.chat("PyrogramChat") & Filters.new_chat_members) # Filter in only new_chat_members updates generated in TARGET chat
@app.on_message(Filters.chat(TARGET) & Filters.new_chat_members)
def welcome(client, message): def welcome(client, message):
# Build the new members list (with mentions) by using their first_name # Build the new members list (with mentions) by using their first_name
new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members] new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members]

View File

@ -18,12 +18,18 @@
import sys 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
__copyright__ = "Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>".replace( __copyright__ = "Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>".replace(
"\xe8", "\xe8",
"e" if sys.getfilesystemencoding() != "utf-8" else "\xe8" "e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
) )
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
__version__ = "0.10.4.develop" __version__ = "0.11.1.develop"
from .api.errors import Error from .api.errors import Error
from .client.types import ( from .client.types import (
@ -32,8 +38,8 @@ from .client.types import (
Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus,
UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply,
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove,
Poll, PollOption, ChatPreview, StopPropagation, Game, CallbackGame, GameHighScore, GameHighScores, Poll, PollOption, ChatPreview, StopPropagation, ContinuePropagation, Game, CallbackGame, GameHighScore,
ChatPermissions GameHighScores, ChatPermissions
) )
from .client import ( from .client import (
Client, ChatAction, ParseMode, Emoji, Client, ChatAction, ParseMode, Emoji,

View File

@ -29,6 +29,7 @@ import struct
import tempfile import tempfile
import threading import threading
import time import time
import warnings
from configparser import ConfigParser from configparser import ConfigParser
from datetime import datetime from datetime import datetime
from hashlib import sha256, md5 from hashlib import sha256, md5
@ -67,10 +68,10 @@ class Client(Methods, BaseClient):
Args: Args:
session_name (``str``): session_name (``str``):
Name to uniquely identify a session of either a User or a Bot. Name to uniquely identify a session of either a User or a Bot, e.g.: "my_account". This name will be used
For Users: pass a string of your choice, e.g.: "my_main_account". to save a file to disk that stores details needed for reconnecting without asking again for credentials.
For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" Note for bots: You can pass a bot token here, but this usage will be deprecated in next releases.
Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number. Use *bot_token* instead.
api_id (``int``, *optional*): api_id (``int``, *optional*):
The *api_id* part of your Telegram API Key, as integer. E.g.: 12345 The *api_id* part of your Telegram API Key, as integer. E.g.: 12345
@ -139,8 +140,13 @@ class Client(Methods, BaseClient):
Only applicable for new sessions. Only applicable for new sessions.
first_name (``str``, *optional*): first_name (``str``, *optional*):
Pass a First Name to avoid entering it manually. It will be used to automatically Pass a First Name as string to avoid entering it manually. Or pass a callback function which accepts no
create a new Telegram account in case the phone number you passed is not registered yet. arguments and must return the correct name as string (e.g., "Dan"). It will be used to automatically create
a new Telegram account in case the phone number you passed is not registered yet.
Only applicable for new sessions.
bot_token (``str``, *optional*):
Pass your Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
Only applicable for new sessions. Only applicable for new sessions.
last_name (``str``, *optional*): last_name (``str``, *optional*):
@ -157,10 +163,9 @@ class Client(Methods, BaseClient):
config_file (``str``, *optional*): config_file (``str``, *optional*):
Path of the configuration file. Defaults to ./config.ini Path of the configuration file. Defaults to ./config.ini
plugins_dir (``str``, *optional*): plugins (``dict``, *optional*):
Define a custom directory for your plugins. The plugins directory is the location in your Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*.
filesystem where Pyrogram will automatically load your update handlers. This is an alternative way to setup plugins if you don't want to use the *config.ini* file.
Defaults to None (plugins disabled).
no_updates (``bool``, *optional*): no_updates (``bool``, *optional*):
Pass True to completely disable incoming updates for the current session. Pass True to completely disable incoming updates for the current session.
@ -192,12 +197,13 @@ class Client(Methods, BaseClient):
password: str = None, password: str = None,
recovery_code: callable = None, recovery_code: callable = None,
force_sms: bool = False, force_sms: bool = False,
bot_token: str = None,
first_name: str = None, first_name: str = None,
last_name: str = None, last_name: str = None,
workers: int = BaseClient.WORKERS, workers: int = BaseClient.WORKERS,
workdir: str = BaseClient.WORKDIR, workdir: str = BaseClient.WORKDIR,
config_file: str = BaseClient.CONFIG_FILE, config_file: str = BaseClient.CONFIG_FILE,
plugins_dir: str = None, plugins: dict = None,
no_updates: bool = None, no_updates: bool = None,
takeout: bool = None): takeout: bool = None):
super().__init__() super().__init__()
@ -218,12 +224,13 @@ class Client(Methods, BaseClient):
self.password = password self.password = password
self.recovery_code = recovery_code self.recovery_code = recovery_code
self.force_sms = force_sms self.force_sms = force_sms
self.bot_token = bot_token
self.first_name = first_name self.first_name = first_name
self.last_name = last_name self.last_name = last_name
self.workers = workers self.workers = workers
self.workdir = workdir self.workdir = workdir
self.config_file = config_file self.config_file = config_file
self.plugins_dir = plugins_dir self.plugins = plugins
self.no_updates = no_updates self.no_updates = no_updates
self.takeout = takeout self.takeout = takeout
@ -263,8 +270,13 @@ class Client(Methods, BaseClient):
raise ConnectionError("Client has already been started") raise ConnectionError("Client has already been started")
if self.BOT_TOKEN_RE.match(self.session_name): if self.BOT_TOKEN_RE.match(self.session_name):
self.is_bot = True
self.bot_token = self.session_name self.bot_token = self.session_name
self.session_name = self.session_name.split(":")[0] self.session_name = self.session_name.split(":")[0]
warnings.warn('\nYou are using a bot token as session name.\n'
'It will be deprecated in next update, please use session file name to load '
'existing sessions and bot_token argument to create new sessions.',
DeprecationWarning, stacklevel=2)
self.load_config() self.load_config()
self.load_session() self.load_session()
@ -282,13 +294,15 @@ class Client(Methods, BaseClient):
try: try:
if self.user_id is None: if self.user_id is None:
if self.bot_token is None: if self.bot_token is None:
self.is_bot = False
self.authorize_user() self.authorize_user()
else: else:
self.is_bot = True
self.authorize_bot() self.authorize_bot()
self.save_session() self.save_session()
if self.bot_token is None: if not self.is_bot:
if self.takeout: if self.takeout:
self.takeout_id = self.send(functions.account.InitTakeoutSession()).id self.takeout_id = self.send(functions.account.InitTakeoutSession()).id
log.warning("Takeout session {} initiated".format(self.takeout_id)) log.warning("Takeout session {} initiated".format(self.takeout_id))
@ -1075,6 +1089,31 @@ class Client(Methods, BaseClient):
self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None
self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None
if self.plugins:
self.plugins["enabled"] = bool(self.plugins.get("enabled", True))
self.plugins["include"] = "\n".join(self.plugins.get("include", [])) or None
self.plugins["exclude"] = "\n".join(self.plugins.get("exclude", [])) or None
else:
try:
section = parser["plugins"]
self.plugins = {
"enabled": section.getboolean("enabled", True),
"root": section.get("root"),
"include": section.get("include") or None,
"exclude": section.get("exclude") or None
}
except KeyError:
self.plugins = {}
if self.plugins:
for option in ["include", "exclude"]:
if self.plugins[option] is not None:
self.plugins[option] = [
(i.split()[0], i.split()[1:] or None)
for i in self.plugins[option].strip().split("\n")
]
def load_session(self): def load_session(self):
try: try:
with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f: with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f:
@ -1089,6 +1128,8 @@ class Client(Methods, BaseClient):
self.auth_key = base64.b64decode("".join(s["auth_key"])) self.auth_key = base64.b64decode("".join(s["auth_key"]))
self.user_id = s["user_id"] self.user_id = s["user_id"]
self.date = s.get("date", 0) self.date = s.get("date", 0)
# TODO: replace default with False once token session name will be deprecated
self.is_bot = s.get("is_bot", self.is_bot)
for k, v in s.get("peers_by_id", {}).items(): for k, v in s.get("peers_by_id", {}).items():
self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v)
@ -1106,21 +1147,19 @@ class Client(Methods, BaseClient):
self.peers_by_phone[k] = peer self.peers_by_phone[k] = peer
def load_plugins(self): def load_plugins(self):
if self.plugins_dir is not None: if self.plugins.get("enabled", False):
plugins_count = 0 root = self.plugins["root"]
include = self.plugins["include"]
exclude = self.plugins["exclude"]
for path in Path(self.plugins_dir).rglob("*.py"): count = 0
file_path = os.path.splitext(str(path))[0]
import_path = []
while file_path: if include is None:
file_path, tail = os.path.split(file_path) for path in sorted(Path(root).rglob("*.py")):
import_path.insert(0, tail) module_path = '.'.join(path.parent.parts + (path.stem,))
module = import_module(module_path)
import_path = ".".join(import_path) for name in vars(module).keys():
module = import_module(import_path)
for name in dir(module):
# noinspection PyBroadException # noinspection PyBroadException
try: try:
handler, group = getattr(module, name) handler, group = getattr(module, name)
@ -1128,21 +1167,88 @@ class Client(Methods, BaseClient):
if isinstance(handler, Handler) and isinstance(group, int): if isinstance(handler, Handler) and isinstance(group, int):
self.add_handler(handler, group) self.add_handler(handler, group)
log.info('{}("{}") from "{}" loaded in group {}'.format( log.info('[LOAD] {}("{}") in group {} from "{}"'.format(
type(handler).__name__, name, import_path, group)) type(handler).__name__, name, group, module_path))
plugins_count += 1 count += 1
except Exception: except Exception:
pass pass
if plugins_count > 0:
log.warning('Successfully loaded {} plugin{} from "{}"'.format(
plugins_count,
"s" if plugins_count > 1 else "",
self.plugins_dir
))
else: else:
log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(self.plugins_dir)) for path, handlers in include:
module_path = root + "." + path
warn_non_existent_functions = True
try:
module = import_module(module_path)
except ModuleNotFoundError:
log.warning('[LOAD] Ignoring non-existent module "{}"'.format(module_path))
continue
if "__path__" in dir(module):
log.warning('[LOAD] Ignoring namespace "{}"'.format(module_path))
continue
if handlers is None:
handlers = vars(module).keys()
warn_non_existent_functions = False
for name in handlers:
# noinspection PyBroadException
try:
handler, group = getattr(module, name)
if isinstance(handler, Handler) and isinstance(group, int):
self.add_handler(handler, group)
log.info('[LOAD] {}("{}") in group {} from "{}"'.format(
type(handler).__name__, name, group, module_path))
count += 1
except Exception:
if warn_non_existent_functions:
log.warning('[LOAD] Ignoring non-existent function "{}" from "{}"'.format(
name, module_path))
if exclude is not None:
for path, handlers in exclude:
module_path = root + "." + path
warn_non_existent_functions = True
try:
module = import_module(module_path)
except ModuleNotFoundError:
log.warning('[UNLOAD] Ignoring non-existent module "{}"'.format(module_path))
continue
if "__path__" in dir(module):
log.warning('[UNLOAD] Ignoring namespace "{}"'.format(module_path))
continue
if handlers is None:
handlers = vars(module).keys()
warn_non_existent_functions = False
for name in handlers:
# noinspection PyBroadException
try:
handler, group = getattr(module, name)
if isinstance(handler, Handler) and isinstance(group, int):
self.remove_handler(handler, group)
log.info('[UNLOAD] {}("{}") from group {} in "{}"'.format(
type(handler).__name__, name, group, module_path))
count -= 1
except Exception:
if warn_non_existent_functions:
log.warning('[UNLOAD] Ignoring non-existent function "{}" from "{}"'.format(
name, module_path))
if count > 0:
log.warning('Successfully loaded {} plugin{} from "{}"'.format(count, "s" if count > 1 else "", root))
else:
log.warning('No plugin loaded from "{}"'.format(root))
def save_session(self): def save_session(self):
auth_key = base64.b64encode(self.auth_key).decode() auth_key = base64.b64encode(self.auth_key).decode()
@ -1157,7 +1263,8 @@ class Client(Methods, BaseClient):
test_mode=self.test_mode, test_mode=self.test_mode,
auth_key=auth_key, auth_key=auth_key,
user_id=self.user_id, user_id=self.user_id,
date=self.date date=self.date,
is_bot=self.is_bot,
), ),
f, f,
indent=4 indent=4
@ -1350,6 +1457,7 @@ class Client(Methods, BaseClient):
md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()])
break break
for _ in range(3):
if is_big: if is_big:
rpc = functions.upload.SaveBigFilePart( rpc = functions.upload.SaveBigFilePart(
file_id=file_id, file_id=file_id,
@ -1364,7 +1472,10 @@ class Client(Methods, BaseClient):
bytes=chunk bytes=chunk
) )
assert session.send(rpc), "Couldn't upload file" if session.send(rpc):
break
else:
raise AssertionError("Telegram didn't accept chunk #{} of {}".format(file_part, path))
if is_missing_part: if is_missing_part:
return return

View File

@ -128,35 +128,37 @@ class Dispatcher:
parser = self.update_parsers.get(type(update), None) parser = self.update_parsers.get(type(update), None)
if parser is None: parsed_update, handler_type = (
continue parser(update, users, chats)
if parser is not None
parsed_update, handler_type = parser(update, users, chats) else (None, type(None))
)
for group in self.groups.values(): for group in self.groups.values():
try:
for handler in group: for handler in group:
args = None args = None
if isinstance(handler, RawUpdateHandler): if isinstance(handler, handler_type):
args = (update, users, chats)
elif isinstance(handler, handler_type):
if handler.check(parsed_update): if handler.check(parsed_update):
args = (parsed_update,) args = (parsed_update,)
elif isinstance(handler, RawUpdateHandler):
args = (update, users, chats)
if args is None: if args is None:
continue continue
try: try:
handler.callback(self.client, *args) handler.callback(self.client, *args)
except StopIteration: except pyrogram.StopPropagation:
raise raise
except pyrogram.ContinuePropagation:
continue
except Exception as e: except Exception as e:
log.error(e, exc_info=True) log.error(e, exc_info=True)
break break
except StopIteration: except pyrogram.StopPropagation:
break pass
except Exception as e: except Exception as e:
log.error(e, exc_info=True) log.error(e, exc_info=True)

View File

@ -68,7 +68,7 @@ class BaseClient:
} }
def __init__(self): def __init__(self):
self.bot_token = None self.is_bot = None
self.dc_id = None self.dc_id = None
self.auth_key = None self.auth_key = None
self.user_id = None self.user_id = None

View File

@ -94,6 +94,7 @@ class Syncer:
auth_key=auth_key, auth_key=auth_key,
user_id=client.user_id, user_id=client.user_id,
date=int(time.time()), date=int(time.time()),
is_bot=client.is_bot,
peers_by_id={ peers_by_id={
k: getattr(v, "access_hash", None) k: getattr(v, "access_hash", None)
for k, v in client.peers_by_id.copy().items() for k, v in client.peers_by_id.copy().items()

View File

@ -62,16 +62,16 @@ class Filters:
create = create create = create
me = create("Me", lambda _, m: bool(m.from_user and m.from_user.is_self)) me = create("Me", lambda _, m: bool(m.from_user and m.from_user.is_self))
"""Filter messages coming from you yourself""" """Filter messages generated by you yourself."""
bot = create("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot)) bot = create("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot))
"""Filter messages coming from bots""" """Filter messages coming from bots."""
incoming = create("Incoming", lambda _, m: not m.outgoing) incoming = create("Incoming", lambda _, m: not m.outgoing)
"""Filter incoming messages.""" """Filter incoming messages. Messages sent to your own chat (Saved Messages) are also recognised as incoming."""
outgoing = create("Outgoing", lambda _, m: m.outgoing) outgoing = create("Outgoing", lambda _, m: m.outgoing)
"""Filter outgoing messages.""" """Filter outgoing messages. Messages sent to your own chat (Saved Messages) are not recognized as outgoing."""
text = create("Text", lambda _, m: bool(m.text)) text = create("Text", lambda _, m: bool(m.text))
"""Filter text messages.""" """Filter text messages."""

View File

@ -45,10 +45,3 @@ class CallbackQueryHandler(Handler):
def __init__(self, callback: callable, filters=None): def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters) super().__init__(callback, filters)
def check(self, callback_query):
return (
self.filters(callback_query)
if callable(self.filters)
else True
)

View File

@ -48,8 +48,4 @@ class DeletedMessagesHandler(Handler):
super().__init__(callback, filters) super().__init__(callback, filters)
def check(self, messages): def check(self, messages):
return ( return super().check(messages.messages[0])
self.filters(messages.messages[0])
if callable(self.filters)
else True
)

View File

@ -21,3 +21,10 @@ class Handler:
def __init__(self, callback: callable, filters=None): def __init__(self, callback: callable, filters=None):
self.callback = callback self.callback = callback
self.filters = filters self.filters = filters
def check(self, update):
return (
self.filters(update)
if callable(self.filters)
else True
)

View File

@ -46,10 +46,3 @@ class MessageHandler(Handler):
def __init__(self, callback: callable, filters=None): def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters) super().__init__(callback, filters)
def check(self, message):
return (
self.filters(message)
if callable(self.filters)
else True
)

View File

@ -45,10 +45,3 @@ class UserStatusHandler(Handler):
def __init__(self, callback: callable, filters=None): def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters) super().__init__(callback, filters)
def check(self, user_status):
return (
self.filters(user_status)
if callable(self.filters)
else True
)

View File

@ -37,6 +37,7 @@ from .set_chat_photo import SetChatPhoto
from .set_chat_title import SetChatTitle from .set_chat_title import SetChatTitle
from .unban_chat_member import UnbanChatMember from .unban_chat_member import UnbanChatMember
from .unpin_chat_message import UnpinChatMessage from .unpin_chat_message import UnpinChatMessage
from .update_chat_username import UpdateChatUsername
class Chats( class Chats(
@ -60,6 +61,7 @@ class Chats(
GetChatMembersCount, GetChatMembersCount,
GetChatPreview, GetChatPreview,
IterDialogs, IterDialogs,
IterChatMembers IterChatMembers,
UpdateChatUsername
): ):
pass pass

View File

@ -16,6 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram.api import functions, types from pyrogram.api import functions, types
from ...ext import BaseClient from ...ext import BaseClient
@ -30,17 +31,24 @@ class JoinChat(BaseClient):
Unique identifier for the target chat in form of a *t.me/joinchat/* link or username of the target Unique identifier for the target chat in form of a *t.me/joinchat/* link or username of the target
channel/supergroup (in the format @username). channel/supergroup (in the format @username).
Returns:
On success, a :obj:`Chat <pyrogram.Chat>` object is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error. :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
match = self.INVITE_LINK_RE.match(chat_id) match = self.INVITE_LINK_RE.match(chat_id)
if match: if match:
return self.send( chat = self.send(
functions.messages.ImportChatInvite( functions.messages.ImportChatInvite(
hash=match.group(1) hash=match.group(1)
) )
) )
if isinstance(chat.chats[0], types.Chat):
return pyrogram.Chat._parse_chat_chat(self, chat.chats[0])
elif isinstance(chat.chats[0], types.Channel):
return pyrogram.Chat._parse_channel_chat(self, chat.chats[0])
else: else:
resolved_peer = self.send( resolved_peer = self.send(
functions.contacts.ResolveUsername( functions.contacts.ResolveUsername(
@ -53,8 +61,10 @@ class JoinChat(BaseClient):
access_hash=resolved_peer.chats[0].access_hash access_hash=resolved_peer.chats[0].access_hash
) )
return self.send( chat = self.send(
functions.channels.JoinChannel( functions.channels.JoinChannel(
channel=channel channel=channel
) )
) )
return pyrogram.Chat._parse_channel_chat(self, chat.chats[0])

View File

@ -0,0 +1,59 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Union
from pyrogram.api import functions, types
from ...ext import BaseClient
class UpdateChatUsername(BaseClient):
def update_chat_username(self,
chat_id: Union[int, str],
username: Union[str, None]) -> bool:
"""Use this method to update a channel or a supergroup username.
To update your own username (for users only, not bots) you can use :meth:`update_username`.
Args:
chat_id (``int`` | ``str``)
Unique identifier (int) or username (str) of the target chat.
username (``str`` | ``None``):
Username to set. Pass "" (empty string) or None to remove the username.
Returns:
True on success.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError`` if a chat_id belongs to a user or chat.
"""
peer = self.resolve_peer(chat_id)
if isinstance(peer, types.InputPeerChannel):
return bool(
self.send(
functions.channels.UpdateUsername(
channel=peer,
username=username or ""
)
)
)
else:
raise ValueError("The chat_id \"{}\" belongs to a user or chat".format(chat_id))

View File

@ -19,7 +19,8 @@
import logging import logging
import time import time
from pyrogram.api import functions, types import pyrogram
from pyrogram.api import functions
from pyrogram.api.errors import FloodWait from pyrogram.api.errors import FloodWait
from ...ext import BaseClient from ...ext import BaseClient
@ -28,12 +29,10 @@ log = logging.getLogger(__name__)
class GetContacts(BaseClient): class GetContacts(BaseClient):
def get_contacts(self): def get_contacts(self):
"""Use this method to get contacts from your Telegram address book """Use this method to get contacts from your Telegram address book.
Requires no parameters.
Returns: Returns:
On success, the user's contacts are returned On success, a list of :obj:`User` objects is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error. :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
@ -44,9 +43,6 @@ class GetContacts(BaseClient):
except FloodWait as e: except FloodWait as e:
log.warning("get_contacts flood: waiting {} seconds".format(e.x)) log.warning("get_contacts flood: waiting {} seconds".format(e.x))
time.sleep(e.x) time.sleep(e.x)
continue
else: else:
if isinstance(contacts, types.contacts.Contacts):
log.info("Total contacts: {}".format(len(self.peers_by_phone))) log.info("Total contacts: {}".format(len(self.peers_by_phone)))
return [pyrogram.User._parse(self, user) for user in contacts.users]
return contacts

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii import binascii
import mimetypes
import os import os
import struct import struct
from typing import Union from typing import Union
@ -122,7 +121,8 @@ class EditMessageMedia(BaseClient):
functions.messages.UploadMedia( functions.messages.UploadMedia(
peer=self.resolve_peer(chat_id), peer=self.resolve_peer(chat_id),
media=types.InputMediaUploadedDocument( media=types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map[".mp4"], mime_type="video/mp4",
thumb=None if media.thumb is None else self.save_file(media.thumb),
file=self.save_file(media.media), file=self.save_file(media.media),
attributes=[ attributes=[
types.DocumentAttributeVideo( types.DocumentAttributeVideo(
@ -178,7 +178,8 @@ class EditMessageMedia(BaseClient):
functions.messages.UploadMedia( functions.messages.UploadMedia(
peer=self.resolve_peer(chat_id), peer=self.resolve_peer(chat_id),
media=types.InputMediaUploadedDocument( media=types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + media.media.split(".")[-1], "audio/mpeg"), mime_type="audio/mpeg",
thumb=None if media.thumb is None else self.save_file(media.thumb),
file=self.save_file(media.media), file=self.save_file(media.media),
attributes=[ attributes=[
types.DocumentAttributeAudio( types.DocumentAttributeAudio(
@ -233,7 +234,8 @@ class EditMessageMedia(BaseClient):
functions.messages.UploadMedia( functions.messages.UploadMedia(
peer=self.resolve_peer(chat_id), peer=self.resolve_peer(chat_id),
media=types.InputMediaUploadedDocument( media=types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map[".mp4"], mime_type="video/mp4",
thumb=None if media.thumb is None else self.save_file(media.thumb),
file=self.save_file(media.media), file=self.save_file(media.media),
attributes=[ attributes=[
types.DocumentAttributeVideo( types.DocumentAttributeVideo(
@ -290,7 +292,8 @@ class EditMessageMedia(BaseClient):
functions.messages.UploadMedia( functions.messages.UploadMedia(
peer=self.resolve_peer(chat_id), peer=self.resolve_peer(chat_id),
media=types.InputMediaUploadedDocument( media=types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + media.media.split(".")[-1], "text/plain"), mime_type="application/zip",
thumb=None if media.thumb is None else self.save_file(media.thumb),
file=self.save_file(media.media), file=self.save_file(media.media),
attributes=[ attributes=[
types.DocumentAttributeFilename(os.path.basename(media.media)) types.DocumentAttributeFilename(os.path.basename(media.media))

View File

@ -16,12 +16,17 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import logging
import time
from typing import Union from typing import Union
import pyrogram import pyrogram
from pyrogram.api import functions from pyrogram.api import functions
from pyrogram.api.errors import FloodWait
from ...ext import BaseClient from ...ext import BaseClient
log = logging.getLogger(__name__)
class GetHistory(BaseClient): class GetHistory(BaseClient):
def get_history(self, def get_history(self,
@ -66,6 +71,8 @@ class GetHistory(BaseClient):
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error. :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
while True:
try:
messages = pyrogram.Messages._parse( messages = pyrogram.Messages._parse(
self, self,
self.send( self.send(
@ -81,6 +88,11 @@ class GetHistory(BaseClient):
) )
) )
) )
except FloodWait as e:
log.warning("Sleeping for {}s".format(e.x))
time.sleep(e.x)
else:
break
if reverse: if reverse:
messages.messages.reverse() messages.messages.reverse()

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii import binascii
import mimetypes
import os import os
import struct import struct
from typing import Union from typing import Union
@ -132,7 +131,7 @@ class SendAnimation(BaseClient):
thumb = None if thumb is None else self.save_file(thumb) thumb = None if thumb is None else self.save_file(thumb)
file = self.save_file(animation, progress=progress, progress_args=progress_args) file = self.save_file(animation, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument( media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map[".mp4"], mime_type="video/mp4",
file=file, file=file,
thumb=thumb, thumb=thumb,
attributes=[ attributes=[

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii import binascii
import mimetypes
import os import os
import struct import struct
from typing import Union from typing import Union
@ -134,7 +133,7 @@ class SendAudio(BaseClient):
thumb = None if thumb is None else self.save_file(thumb) thumb = None if thumb is None else self.save_file(thumb)
file = self.save_file(audio, progress=progress, progress_args=progress_args) file = self.save_file(audio, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument( media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"), mime_type="audio/mpeg",
file=file, file=file,
thumb=thumb, thumb=thumb,
attributes=[ attributes=[

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii import binascii
import mimetypes
import os import os
import struct import struct
from typing import Union from typing import Union
@ -120,7 +119,7 @@ class SendDocument(BaseClient):
thumb = None if thumb is None else self.save_file(thumb) thumb = None if thumb is None else self.save_file(thumb)
file = self.save_file(document, progress=progress, progress_args=progress_args) file = self.save_file(document, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument( media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"), mime_type="application/zip",
file=file, file=file,
thumb=thumb, thumb=thumb,
attributes=[ attributes=[

View File

@ -17,20 +17,22 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii import binascii
import mimetypes import logging
import os import os
import struct import struct
import time
from typing import Union, List from typing import Union, List
import pyrogram import pyrogram
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.api.errors import FileIdInvalid from pyrogram.api.errors import FileIdInvalid, FloodWait
from pyrogram.client.ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
log = logging.getLogger(__name__)
class SendMediaGroup(BaseClient): class SendMediaGroup(BaseClient):
# TODO: Add progress parameter # TODO: Add progress parameter
# TODO: Return new Message object
# TODO: Figure out how to send albums using URLs # TODO: Figure out how to send albums using URLs
def send_media_group(self, def send_media_group(self,
chat_id: Union[int, str], chat_id: Union[int, str],
@ -38,7 +40,6 @@ class SendMediaGroup(BaseClient):
disable_notification: bool = None, disable_notification: bool = None,
reply_to_message_id: int = None): reply_to_message_id: int = None):
"""Use this method to send a group of photos or videos as an album. """Use this method to send a group of photos or videos as an album.
On success, an Update containing the sent Messages is returned.
Args: Args:
chat_id (``int`` | ``str``): chat_id (``int`` | ``str``):
@ -57,6 +58,13 @@ class SendMediaGroup(BaseClient):
reply_to_message_id (``int``, *optional*): reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message. If the message is a reply, ID of the original message.
Returns:
On success, a :obj:`Messages <pyrogram.Messages>` object is returned containing all the
single messages sent.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
multi_media = [] multi_media = []
@ -65,6 +73,8 @@ class SendMediaGroup(BaseClient):
if isinstance(i, pyrogram.InputMediaPhoto): if isinstance(i, pyrogram.InputMediaPhoto):
if os.path.exists(i.media): if os.path.exists(i.media):
while True:
try:
media = self.send( media = self.send(
functions.messages.UploadMedia( functions.messages.UploadMedia(
peer=self.resolve_peer(chat_id), peer=self.resolve_peer(chat_id),
@ -73,6 +83,11 @@ class SendMediaGroup(BaseClient):
) )
) )
) )
except FloodWait as e:
log.warning("Sleeping for {}s".format(e.x))
time.sleep(e.x)
else:
break
media = types.InputMediaPhoto( media = types.InputMediaPhoto(
id=types.InputPhoto( id=types.InputPhoto(
@ -106,13 +121,15 @@ class SendMediaGroup(BaseClient):
) )
elif isinstance(i, pyrogram.InputMediaVideo): elif isinstance(i, pyrogram.InputMediaVideo):
if os.path.exists(i.media): if os.path.exists(i.media):
while True:
try:
media = self.send( media = self.send(
functions.messages.UploadMedia( functions.messages.UploadMedia(
peer=self.resolve_peer(chat_id), peer=self.resolve_peer(chat_id),
media=types.InputMediaUploadedDocument( media=types.InputMediaUploadedDocument(
file=self.save_file(i.media), file=self.save_file(i.media),
thumb=None if i.thumb is None else self.save_file(i.thumb), thumb=None if i.thumb is None else self.save_file(i.thumb),
mime_type=mimetypes.types_map[".mp4"], mime_type="video/mp4",
attributes=[ attributes=[
types.DocumentAttributeVideo( types.DocumentAttributeVideo(
supports_streaming=i.supports_streaming or None, supports_streaming=i.supports_streaming or None,
@ -125,6 +142,11 @@ class SendMediaGroup(BaseClient):
) )
) )
) )
except FloodWait as e:
log.warning("Sleeping for {}s".format(e.x))
time.sleep(e.x)
else:
break
media = types.InputMediaDocument( media = types.InputMediaDocument(
id=types.InputDocument( id=types.InputDocument(
@ -165,7 +187,9 @@ class SendMediaGroup(BaseClient):
) )
) )
return self.send( while True:
try:
r = self.send(
functions.messages.SendMultiMedia( functions.messages.SendMultiMedia(
peer=self.resolve_peer(chat_id), peer=self.resolve_peer(chat_id),
multi_media=multi_media, multi_media=multi_media,
@ -173,3 +197,20 @@ class SendMediaGroup(BaseClient):
reply_to_msg_id=reply_to_message_id reply_to_msg_id=reply_to_message_id
) )
) )
except FloodWait as e:
log.warning("Sleeping for {}s".format(e.x))
time.sleep(e.x)
else:
break
return pyrogram.Messages._parse(
self,
types.messages.Messages(
messages=[m.message for m in filter(
lambda u: isinstance(u, (types.UpdateNewMessage, types.UpdateNewChannelMessage)),
r.updates
)],
users=r.users,
chats=r.chats
)
)

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii import binascii
import mimetypes
import os import os
import struct import struct
from typing import Union from typing import Union
@ -136,7 +135,7 @@ class SendVideo(BaseClient):
thumb = None if thumb is None else self.save_file(thumb) thumb = None if thumb is None else self.save_file(thumb)
file = self.save_file(video, progress=progress, progress_args=progress_args) file = self.save_file(video, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument( media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map[".mp4"], mime_type="video/mp4",
file=file, file=file,
thumb=thumb, thumb=thumb,
attributes=[ attributes=[

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii import binascii
import mimetypes
import os import os
import struct import struct
from typing import Union from typing import Union
@ -117,7 +116,7 @@ class SendVideoNote(BaseClient):
thumb = None if thumb is None else self.save_file(thumb) thumb = None if thumb is None else self.save_file(thumb)
file = self.save_file(video_note, progress=progress, progress_args=progress_args) file = self.save_file(video_note, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument( media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map[".mp4"], mime_type="video/mp4",
file=file, file=file,
thumb=thumb, thumb=thumb,
attributes=[ attributes=[

View File

@ -17,7 +17,6 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import binascii import binascii
import mimetypes
import os import os
import struct import struct
from typing import Union from typing import Union
@ -116,7 +115,7 @@ class SendVoice(BaseClient):
if os.path.exists(voice): if os.path.exists(voice):
file = self.save_file(voice, progress=progress, progress_args=progress_args) file = self.save_file(voice, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument( media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + voice.split(".")[-1], "audio/mpeg"), mime_type="audio/mpeg",
file=file, file=file,
attributes=[ attributes=[
types.DocumentAttributeAudio( types.DocumentAttributeAudio(

View File

@ -21,6 +21,7 @@ from .get_me import GetMe
from .get_user_profile_photos import GetUserProfilePhotos from .get_user_profile_photos import GetUserProfilePhotos
from .get_users import GetUsers from .get_users import GetUsers
from .set_user_profile_photo import SetUserProfilePhoto from .set_user_profile_photo import SetUserProfilePhoto
from .update_username import UpdateUsername
class Users( class Users(
@ -28,6 +29,7 @@ class Users(
SetUserProfilePhoto, SetUserProfilePhoto,
DeleteUserProfilePhotos, DeleteUserProfilePhotos,
GetUsers, GetUsers,
GetMe GetMe,
UpdateUsername
): ):
pass pass

View File

@ -0,0 +1,51 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Union
from pyrogram.api import functions
from ...ext import BaseClient
class UpdateUsername(BaseClient):
def update_username(self,
username: Union[str, None]) -> bool:
"""Use this method to update your own username.
This method only works for users, not bots. Bot usernames must be changed via Bot Support or by recreating
them from scratch using BotFather. To update a channel or supergroup username you can use
:meth:`update_chat_username`.
Args:
username (``str`` | ``None``):
Username to set. "" (empty string) or None to remove the username.
Returns:
True on success.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
return bool(
self.send(
functions.account.UpdateUsername(
username=username or ""
)
)
)

View File

@ -30,7 +30,7 @@ from .messages_and_media import (
Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos, Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos,
Message, Messages, MessageEntity, Poll, PollOption, Game Message, Messages, MessageEntity, Poll, PollOption, Game
) )
from .update import StopPropagation from .update import StopPropagation, ContinuePropagation
from .user_and_chats import ( from .user_and_chats import (
Chat, ChatMember, ChatMembers, ChatPhoto, Chat, ChatMember, ChatMembers, ChatPhoto,
Dialog, Dialogs, User, UserStatus, ChatPreview, ChatPermissions Dialog, Dialogs, User, UserStatus, ChatPreview, ChatPermissions

View File

@ -40,15 +40,15 @@ class CallbackQuery(PyrogramType, Update):
Sender. Sender.
chat_instance (``str``, *optional*): chat_instance (``str``, *optional*):
Global identifier, uniquely corresponding to the chat to which the message with the callback button was
sent. Useful for high scores in games.
message (:obj:`Message <pyrogram.Message>`, *optional*):
Message with the callback button that originated the query. Note that message content and message date will Message with the callback button that originated the query. Note that message content and message date will
not be available if the message is too old. not be available if the message is too old.
message (:obj:`Message <pyrogram.Message>`, *optional*):
Identifier of the message sent via the bot in inline mode, that originated the query.
inline_message_id (``str``): inline_message_id (``str``):
Global identifier, uniquely corresponding to the chat to which the message with the callback button was Identifier of the message sent via the bot in inline mode, that originated the query.
sent. Useful for high scores in games.
data (``bytes``, *optional*): data (``bytes``, *optional*):
Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field. Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field.
@ -72,9 +72,9 @@ class CallbackQuery(PyrogramType, Update):
self.id = id self.id = id
self.from_user = from_user self.from_user = from_user
self.chat_instance = chat_instance
self.message = message self.message = message
self.inline_message_id = inline_message_id self.inline_message_id = inline_message_id
self.chat_instance = chat_instance
self.data = data self.data = data
self.game_short_name = game_short_name self.game_short_name = game_short_name

View File

@ -63,7 +63,7 @@ class InlineKeyboardButton(PyrogramType):
callback_game: CallbackGame = None): callback_game: CallbackGame = None):
super().__init__(None) super().__init__(None)
self.text = text self.text = str(text)
self.url = url self.url = url
self.callback_data = callback_data self.callback_data = callback_data
self.switch_inline_query = switch_inline_query self.switch_inline_query = switch_inline_query

View File

@ -46,7 +46,7 @@ class KeyboardButton(PyrogramType):
request_location: bool = None): request_location: bool = None):
super().__init__(None) super().__init__(None)
self.text = text self.text = str(text)
self.request_contact = request_contact self.request_contact = request_contact
self.request_location = request_location self.request_location = request_location

View File

@ -21,6 +21,13 @@ class StopPropagation(StopIteration):
pass pass
class ContinuePropagation(StopIteration):
pass
class Update: class Update:
def stop_propagation(self): def stop_propagation(self):
raise StopPropagation raise StopPropagation
def continue_propagation(self):
raise ContinuePropagation

View File

@ -209,3 +209,14 @@ class Chat(PyrogramType):
parsed_chat.invite_link = full_chat.exported_invite.link parsed_chat.invite_link = full_chat.exported_invite.link
return parsed_chat return parsed_chat
@staticmethod
def _parse_chat(client, chat):
# A wrapper around each entity parser: User, Chat and Channel.
# Currently unused, might become useful in future.
if isinstance(chat, types.Chat):
return Chat._parse_chat_chat(client, chat)
elif isinstance(chat, types.User):
return Chat._parse_user_chat(client, chat)
else:
return Chat._parse_channel_chat(client, chat)

19
pyrogram/vendor/__init__.py vendored Normal file
View File

@ -0,0 +1,19 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
from .typing import typing

17
pyrogram/vendor/typing/__init__.py vendored Normal file
View File

@ -0,0 +1,17 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.

2413
pyrogram/vendor/typing/typing.py vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
build:
image: latest
python:
version: 3.6
setup_py_install: true

View File

@ -39,10 +39,10 @@ def get_version():
def get_readme(): def get_readme():
# PyPI doesn"t like raw html # PyPI doesn't like raw html
with open("README.rst", encoding="utf-8") as f: with open("README.rst", encoding="utf-8") as f:
readme = re.sub(r"\.\. \|.+\| raw:: html(?:\s{4}.+)+\n\n", "", f.read()) readme = re.sub(r"\.\. \|.+\| raw:: html(?:\s{4}.+)+\n\n", "", f.read())
return re.sub(r"\|header\|", "|logo|\n\n|description|\n\n|scheme| |tgcrypto|", readme) return re.sub(r"\|header\|", "|logo|\n\n|description|\n\n|schema| |tgcrypto|", readme)
class Clean(Command): class Clean(Command):
@ -127,7 +127,7 @@ class Generate(Command):
docs_compiler.start() docs_compiler.start()
if len(argv) > 1 and argv[1] in ["bdist_wheel", "install"]: if len(argv) > 1 and argv[1] in ["bdist_wheel", "install", "develop"]:
error_compiler.start() error_compiler.start()
api_compiler.start() api_compiler.start()
docs_compiler.start() docs_compiler.start()