mirror of
https://github.com/TeamPGM/pyrogram.git
synced 2024-11-30 17:43:32 +00:00
Merge branch 'develop' into layer-95
# Conflicts: # pyrogram/__init__.py
This commit is contained in:
commit
ffa0da5800
52
README.rst
52
README.rst
@ -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
|
||||||
|
@ -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,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
|
||||||
|
|
@ -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::
|
||||||
@ -111,4 +116,6 @@ To get started, press the Next button.
|
|||||||
types/index
|
types/index
|
||||||
|
|
||||||
.. _`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
|
90
docs/source/resources/ConfigurationFile.rst
Normal file
90
docs/source/resources/ConfigurationFile.rst
Normal 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>`_
|
@ -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
|
@ -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)
|
10
docs/source/resources/VoiceCalls.rst
Normal file
10
docs/source/resources/VoiceCalls.rst
Normal 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.
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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]
|
||||||
|
@ -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,
|
||||||
|
@ -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,43 +1147,108 @@ 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)
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
|
handler, group = getattr(module, name)
|
||||||
|
|
||||||
for name in dir(module):
|
if isinstance(handler, Handler) and isinstance(group, int):
|
||||||
# noinspection PyBroadException
|
self.add_handler(handler, group)
|
||||||
try:
|
|
||||||
handler, group = getattr(module, name)
|
|
||||||
|
|
||||||
if isinstance(handler, Handler) and isinstance(group, int):
|
log.info('[LOAD] {}("{}") in group {} from "{}"'.format(
|
||||||
self.add_handler(handler, group)
|
type(handler).__name__, name, group, module_path))
|
||||||
|
|
||||||
log.info('{}("{}") from "{}" loaded in group {}'.format(
|
count += 1
|
||||||
type(handler).__name__, name, import_path, group))
|
except Exception:
|
||||||
|
pass
|
||||||
plugins_count += 1
|
|
||||||
except Exception:
|
|
||||||
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,21 +1457,25 @@ 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
|
||||||
|
|
||||||
if is_big:
|
for _ in range(3):
|
||||||
rpc = functions.upload.SaveBigFilePart(
|
if is_big:
|
||||||
file_id=file_id,
|
rpc = functions.upload.SaveBigFilePart(
|
||||||
file_part=file_part,
|
file_id=file_id,
|
||||||
file_total_parts=file_total_parts,
|
file_part=file_part,
|
||||||
bytes=chunk
|
file_total_parts=file_total_parts,
|
||||||
)
|
bytes=chunk
|
||||||
else:
|
)
|
||||||
rpc = functions.upload.SaveFilePart(
|
else:
|
||||||
file_id=file_id,
|
rpc = functions.upload.SaveFilePart(
|
||||||
file_part=file_part,
|
file_id=file_id,
|
||||||
bytes=chunk
|
file_part=file_part,
|
||||||
)
|
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
|
||||||
|
@ -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)
|
if handler.check(parsed_update):
|
||||||
elif isinstance(handler, handler_type):
|
args = (parsed_update,)
|
||||||
if handler.check(parsed_update):
|
elif isinstance(handler, RawUpdateHandler):
|
||||||
args = (parsed_update,)
|
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 Exception as e:
|
except pyrogram.ContinuePropagation:
|
||||||
log.error(e, exc_info=True)
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
log.error(e, exc_info=True)
|
||||||
|
|
||||||
break
|
|
||||||
except StopIteration:
|
|
||||||
break
|
break
|
||||||
|
except pyrogram.StopPropagation:
|
||||||
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(e, exc_info=True)
|
log.error(e, exc_info=True)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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."""
|
||||||
|
@ -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
|
|
||||||
)
|
|
||||||
|
@ -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
|
|
||||||
)
|
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
@ -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
|
|
||||||
)
|
|
||||||
|
@ -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
|
|
||||||
)
|
|
||||||
|
@ -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
|
||||||
|
@ -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])
|
||||||
|
59
pyrogram/client/methods/chats/update_chat_username.py
Normal file
59
pyrogram/client/methods/chats/update_chat_username.py
Normal 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))
|
@ -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
|
|
||||||
|
@ -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))
|
||||||
|
@ -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,21 +71,28 @@ 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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
messages = pyrogram.Messages._parse(
|
while True:
|
||||||
self,
|
try:
|
||||||
self.send(
|
messages = pyrogram.Messages._parse(
|
||||||
functions.messages.GetHistory(
|
self,
|
||||||
peer=self.resolve_peer(chat_id),
|
self.send(
|
||||||
offset_id=offset_id,
|
functions.messages.GetHistory(
|
||||||
offset_date=offset_date,
|
peer=self.resolve_peer(chat_id),
|
||||||
add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0),
|
offset_id=offset_id,
|
||||||
limit=limit,
|
offset_date=offset_date,
|
||||||
max_id=0,
|
add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0),
|
||||||
min_id=0,
|
limit=limit,
|
||||||
hash=0
|
max_id=0,
|
||||||
|
min_id=0,
|
||||||
|
hash=0
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
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()
|
||||||
|
@ -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=[
|
||||||
|
@ -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=[
|
||||||
|
@ -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=[
|
||||||
|
@ -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,14 +73,21 @@ 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):
|
||||||
media = self.send(
|
while True:
|
||||||
functions.messages.UploadMedia(
|
try:
|
||||||
peer=self.resolve_peer(chat_id),
|
media = self.send(
|
||||||
media=types.InputMediaUploadedPhoto(
|
functions.messages.UploadMedia(
|
||||||
file=self.save_file(i.media)
|
peer=self.resolve_peer(chat_id),
|
||||||
|
media=types.InputMediaUploadedPhoto(
|
||||||
|
file=self.save_file(i.media)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
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,25 +121,32 @@ 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):
|
||||||
media = self.send(
|
while True:
|
||||||
functions.messages.UploadMedia(
|
try:
|
||||||
peer=self.resolve_peer(chat_id),
|
media = self.send(
|
||||||
media=types.InputMediaUploadedDocument(
|
functions.messages.UploadMedia(
|
||||||
file=self.save_file(i.media),
|
peer=self.resolve_peer(chat_id),
|
||||||
thumb=None if i.thumb is None else self.save_file(i.thumb),
|
media=types.InputMediaUploadedDocument(
|
||||||
mime_type=mimetypes.types_map[".mp4"],
|
file=self.save_file(i.media),
|
||||||
attributes=[
|
thumb=None if i.thumb is None else self.save_file(i.thumb),
|
||||||
types.DocumentAttributeVideo(
|
mime_type="video/mp4",
|
||||||
supports_streaming=i.supports_streaming or None,
|
attributes=[
|
||||||
duration=i.duration,
|
types.DocumentAttributeVideo(
|
||||||
w=i.width,
|
supports_streaming=i.supports_streaming or None,
|
||||||
h=i.height
|
duration=i.duration,
|
||||||
),
|
w=i.width,
|
||||||
types.DocumentAttributeFilename(os.path.basename(i.media))
|
h=i.height
|
||||||
]
|
),
|
||||||
|
types.DocumentAttributeFilename(os.path.basename(i.media))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
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,11 +187,30 @@ class SendMediaGroup(BaseClient):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.send(
|
while True:
|
||||||
functions.messages.SendMultiMedia(
|
try:
|
||||||
peer=self.resolve_peer(chat_id),
|
r = self.send(
|
||||||
multi_media=multi_media,
|
functions.messages.SendMultiMedia(
|
||||||
silent=disable_notification or None,
|
peer=self.resolve_peer(chat_id),
|
||||||
reply_to_msg_id=reply_to_message_id
|
multi_media=multi_media,
|
||||||
|
silent=disable_notification or None,
|
||||||
|
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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -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=[
|
||||||
|
@ -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=[
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
51
pyrogram/client/methods/users/update_username.py
Normal file
51
pyrogram/client/methods/users/update_username.py
Normal 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 ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
19
pyrogram/vendor/__init__.py
vendored
Normal 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
17
pyrogram/vendor/typing/__init__.py
vendored
Normal 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
2413
pyrogram/vendor/typing/typing.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +0,0 @@
|
|||||||
build:
|
|
||||||
image: latest
|
|
||||||
|
|
||||||
python:
|
|
||||||
version: 3.6
|
|
||||||
setup_py_install: true
|
|
6
setup.py
6
setup.py
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user