mirror of
https://github.com/TeamPGM/pyrogram.git
synced 2024-11-16 20:59:29 +00:00
Merge branch 'master' into new-api
This commit is contained in:
commit
4132a1d266
48
README.rst
48
README.rst
@ -3,7 +3,7 @@
|
|||||||
Table of Contents
|
Table of Contents
|
||||||
=================
|
=================
|
||||||
|
|
||||||
- `Overview`_
|
- `About`_
|
||||||
|
|
||||||
- `Features`_
|
- `Features`_
|
||||||
|
|
||||||
@ -26,8 +26,8 @@ Table of Contents
|
|||||||
- `License`_
|
- `License`_
|
||||||
|
|
||||||
|
|
||||||
Overview
|
About
|
||||||
========
|
=====
|
||||||
|
|
||||||
**Pyrogram** is a fully functional Telegram Client Library written from the ground up in Python.
|
**Pyrogram** is a fully functional Telegram Client Library written from the ground up in Python.
|
||||||
It offers simple and complete access to the `Telegram Messenger API`_ and is designed for Python
|
It offers simple and complete access to the `Telegram Messenger API`_ and is designed for Python
|
||||||
@ -37,20 +37,23 @@ developers keen on building custom Telegram applications.
|
|||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
- **Easy to setup**: Pyrogram can be easily installed and upgraded using ``pip``, requires
|
- **Easy to setup**: Pyrogram can be easily installed and upgraded using **pip**, requires
|
||||||
a minimal set of dependencies (which are also automatically managed) and very few lines
|
a minimal set of dependencies (which are also automatically managed) and very few lines
|
||||||
of code to get started with.
|
of code to get started with.
|
||||||
|
|
||||||
- **Easy to use**: Pyrogram provides idiomatic, developer-friendly, clean and readable
|
- **Easy to use**: Pyrogram provides idiomatic, developer-friendly, clean and readable
|
||||||
Python code (either generated or hand-written) making the Telegram API simple to use.
|
Python code (either generated or hand-written) making the Telegram API simple to use.
|
||||||
|
|
||||||
- **High level**: Pyrogram automatically handles all the low-level details of
|
- **High level**: Pyrogram automatically handles all the low-level details of
|
||||||
communication with the Telegram servers by implementing the
|
communication with the Telegram servers by implementing the
|
||||||
`MTProto Mobile Protocol v2.0`_ and the mechanisms needed for establishing
|
`MTProto Mobile Protocol v2.0`_ and the mechanisms needed for establishing
|
||||||
a reliable connection.
|
a reliable connection.
|
||||||
|
|
||||||
|
- **Fast**: Pyrogram's speed is boosted up by `TgCrypto`_, a high-performance, easy-to-install
|
||||||
|
Telegram Crypto Library written in C as a Python extension.
|
||||||
|
|
||||||
- **Updated**: Pyrogram makes use of the latest Telegram API version, currently `Layer 75`_.
|
- **Updated**: Pyrogram makes use of the latest Telegram API version, currently `Layer 75`_.
|
||||||
|
|
||||||
- **Documented**: Pyrogram API public methods are documented and resemble the well
|
- **Documented**: Pyrogram API public methods are documented and resemble the well
|
||||||
established Telegram Bot API, thus offering a familiar look to Bot developers.
|
established Telegram Bot API, thus offering a familiar look to Bot developers.
|
||||||
|
|
||||||
@ -77,6 +80,12 @@ Installation
|
|||||||
.. code:: shell
|
.. code:: shell
|
||||||
|
|
||||||
$ pip install --upgrade pyrogram
|
$ pip install --upgrade pyrogram
|
||||||
|
|
||||||
|
- Or, with TgCrypto_:
|
||||||
|
|
||||||
|
.. code:: shell
|
||||||
|
|
||||||
|
$ pip install --upgrade pyrogram[tgcrypto]
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
@ -93,7 +102,7 @@ Configuration
|
|||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
- And here's how Pyrogram looks:
|
- And here's how Pyrogram looks like:
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
@ -130,6 +139,7 @@ Feedback
|
|||||||
|
|
||||||
Means for getting in touch:
|
Means for getting in touch:
|
||||||
|
|
||||||
|
- `Community`_
|
||||||
- `Telegram`_
|
- `Telegram`_
|
||||||
- `GitHub`_
|
- `GitHub`_
|
||||||
- `Email`_
|
- `Email`_
|
||||||
@ -156,12 +166,16 @@ License
|
|||||||
|
|
||||||
.. _`Telegram`: https://t.me/haskell
|
.. _`Telegram`: https://t.me/haskell
|
||||||
|
|
||||||
|
.. _`Community`: https://t.me/PyrogramChat
|
||||||
|
|
||||||
.. _`bot-like`: https://core.telegram.org/bots/api#available-methods
|
.. _`bot-like`: https://core.telegram.org/bots/api#available-methods
|
||||||
|
|
||||||
.. _`GitHub`: https://github.com/pyrogram/pyrogram/issues
|
.. _`GitHub`: https://github.com/pyrogram/pyrogram/issues
|
||||||
|
|
||||||
.. _`Email`: admin@pyrogram.ml
|
.. _`Email`: admin@pyrogram.ml
|
||||||
|
|
||||||
|
.. _TgCrypto: https://github.com/pyrogram/tgcrypto
|
||||||
|
|
||||||
.. _`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
|
||||||
@ -177,7 +191,7 @@ License
|
|||||||
<b>Telegram MTProto API Client Library for Python</b>
|
<b>Telegram MTProto API Client Library for Python</b>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<a href="https://pypi.python.org/pypi/Pyrogram">
|
<a href="https://github.com/pyrogram/pyrogram/releases/latest">
|
||||||
Download
|
Download
|
||||||
</a>
|
</a>
|
||||||
•
|
•
|
||||||
@ -187,15 +201,15 @@ License
|
|||||||
•
|
•
|
||||||
<a href="https://t.me/PyrogramChat">
|
<a href="https://t.me/PyrogramChat">
|
||||||
Community
|
Community
|
||||||
</a
|
</a>
|
||||||
<br><br><br>
|
<br><br>
|
||||||
<a href="compiler/api/source/main_api.tl">
|
<a href="compiler/api/source/main_api.tl">
|
||||||
<img src="https://www.pyrogram.ml/images/scheme.svg"
|
<img src="https://www.pyrogram.ml/images/scheme.svg"
|
||||||
alt="Scheme Layer 75">
|
alt="Scheme Layer 75">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://core.telegram.org/mtproto">
|
<a href="https://github.com/pyrogram/tgcrypto">
|
||||||
<img src="https://www.pyrogram.ml/images/mtproto.svg"
|
<img src="https://www.pyrogram.ml/images/tgcrypto.svg"
|
||||||
alt="MTProto v2.0">
|
alt="TgCrypto">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -209,6 +223,6 @@ License
|
|||||||
:target: compiler/api/source/main_api.tl
|
:target: compiler/api/source/main_api.tl
|
||||||
:alt: Scheme Layer 75
|
:alt: Scheme Layer 75
|
||||||
|
|
||||||
.. |mtproto| image:: https://www.pyrogram.ml/images/mtproto.svg
|
.. |tgcrypto| image:: https://www.pyrogram.ml/images/tgcrypto.svg
|
||||||
:target: https://core.telegram.org/mtproto
|
:target: https://github.com/pyrogram/tgcrypto
|
||||||
:alt: MTProto v2.0
|
:alt: TgCrypto
|
||||||
|
@ -29,6 +29,7 @@ destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
|
|||||||
---functions---
|
---functions---
|
||||||
|
|
||||||
req_pq#60469778 nonce:int128 = ResPQ;
|
req_pq#60469778 nonce:int128 = ResPQ;
|
||||||
|
req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
|
||||||
|
|
||||||
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:bytes q:bytes public_key_fingerprint:long encrypted_data:bytes = Server_DH_Params;
|
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:bytes q:bytes public_key_fingerprint:long encrypted_data:bytes = Server_DH_Params;
|
||||||
|
|
||||||
|
@ -42,7 +42,10 @@ CDN_METHOD_INVALID The method can't be used on CDN DCs
|
|||||||
VOLUME_LOC_NOT_FOUND The volume location can't be found
|
VOLUME_LOC_NOT_FOUND The volume location can't be found
|
||||||
FILE_ID_INVALID The file id is invalid
|
FILE_ID_INVALID The file id is invalid
|
||||||
LOCATION_INVALID The file location is invalid
|
LOCATION_INVALID The file location is invalid
|
||||||
CHAT_ADMIN_REQUIRED The method requires admin privileges
|
CHAT_ADMIN_REQUIRED The method requires chat admin privileges
|
||||||
PHONE_NUMBER_BANNED The phone number is banned
|
PHONE_NUMBER_BANNED The phone number is banned
|
||||||
ABOUT_TOO_LONG The about text is too long
|
ABOUT_TOO_LONG The about text is too long
|
||||||
MULTI_MEDIA_TOO_LONG The album contains more than 10 items
|
MULTI_MEDIA_TOO_LONG The album contains more than 10 items
|
||||||
|
USERNAME_OCCUPIED The username is already in use
|
||||||
|
BOT_INLINE_DISABLED The inline feature of the bot is disabled
|
||||||
|
INLINE_RESULT_EXPIRED The inline bot query expired
|
|
@ -3,16 +3,77 @@ Welcome to Pyrogram
|
|||||||
|
|
||||||
.. raw:: html
|
.. raw:: html
|
||||||
|
|
||||||
|
<p align="right">
|
||||||
|
<a class="github-button" href="https://github.com/pyrogram/pyrogram/subscription" data-icon="octicon-eye" data-size="large" data-show-count="true" aria-label="Watch pyrogram/pyrogram on GitHub">Watch</a>
|
||||||
|
<a class="github-button" href="https://github.com/pyrogram/pyrogram" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star pyrogram/pyrogram on GitHub">Star</a>
|
||||||
|
<a class="github-button" href="https://github.com/pyrogram/pyrogram/fork" data-icon="octicon-repo-forked" data-size="large" data-show-count="true" aria-label="Fork pyrogram/pyrogram on GitHub">Fork</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://pyrogram.ml">
|
<a href="https://pyrogram.ml">
|
||||||
<img src="https://pyrogram.ml/images/logo2.png" alt="Logo">
|
<div><img src="https://pyrogram.ml/images/icon.png" alt="Pyrogram Icon"></div>
|
||||||
|
<div><img src="https://pyrogram.ml/images/label.png" alt="Pyrogram Label"></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<b>Telegram MTProto API Client Library for Python</b>
|
<b>Telegram MTProto API Client Library for Python</b>
|
||||||
|
<br>
|
||||||
|
<a href="https://github.com/pyrogram/pyrogram/releases/latest">
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
|
•
|
||||||
|
<a href="https://github.com/pyrogram/pyrogram">
|
||||||
|
Source code
|
||||||
|
</a>
|
||||||
|
•
|
||||||
|
<a href="https://t.me/PyrogramChat">
|
||||||
|
Community
|
||||||
|
</a>
|
||||||
|
<br><br>
|
||||||
|
<a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl">
|
||||||
|
<img src="https://www.pyrogram.ml/images/scheme.svg"
|
||||||
|
alt="Scheme Layer 75">
|
||||||
|
</a>
|
||||||
|
<a href="https://core.telegram.org/mtproto">
|
||||||
|
<img src="https://www.pyrogram.ml/images/mtproto.svg"
|
||||||
|
alt="MTProto v2.0">
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
About
|
||||||
|
-----
|
||||||
|
|
||||||
|
Pyrogram is a fully functional Telegram Client Library written from the ground up in Python.
|
||||||
|
It offers **simple** and **complete** access to the Telegram Messenger API and is designed for Python developers
|
||||||
|
keen on building custom Telegram applications.
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- **Easy to setup**: Pyrogram can be easily installed and upgraded using **pip**, requires
|
||||||
|
a minimal set of dependencies (which are also automatically managed) and very few lines
|
||||||
|
of code to get started with.
|
||||||
|
|
||||||
|
- **Easy to use**: Pyrogram provides idiomatic, developer-friendly, clean and readable
|
||||||
|
Python code (either generated or hand-written) making the Telegram API simple to use.
|
||||||
|
|
||||||
|
- **High level**: Pyrogram automatically handles all the low-level details of
|
||||||
|
communication with the Telegram servers by implementing the
|
||||||
|
`MTProto Mobile Protocol v2.0`_ and the mechanisms needed for establishing
|
||||||
|
a reliable connection.
|
||||||
|
|
||||||
|
- **Fast**: Pyrogram's speed is boosted up by `TgCrypto`_, a high-performance, easy-to-install
|
||||||
|
crypto library written in C.
|
||||||
|
|
||||||
|
- **Updated**: Pyrogram makes use of the latest Telegram API version, currently `Layer 75`_.
|
||||||
|
|
||||||
|
- **Documented**: Pyrogram API public methods are documented and resemble the well
|
||||||
|
established Telegram Bot API, thus offering a familiar look to Bot developers.
|
||||||
|
|
||||||
|
- **Full API support**: Beside the simple, bot-like methods offered by the Pyrogram API,
|
||||||
|
the library also provides a complete, low-level access to every single Telegram API method.
|
||||||
|
|
||||||
Preview
|
Preview
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@ -28,12 +89,6 @@ Preview
|
|||||||
|
|
||||||
client.stop()
|
client.stop()
|
||||||
|
|
||||||
About
|
|
||||||
-----
|
|
||||||
|
|
||||||
Welcome to the Pyrogram's documentation! Here you can find resources for learning how to use the library.
|
|
||||||
Contents are organized by topic and are accessible from the sidebar.
|
|
||||||
|
|
||||||
To get started, press Next.
|
To get started, press Next.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
@ -51,6 +106,9 @@ To get started, press Next.
|
|||||||
resources/TextFormatting
|
resources/TextFormatting
|
||||||
resources/UpdateHandling
|
resources/UpdateHandling
|
||||||
resources/ErrorHandling
|
resources/ErrorHandling
|
||||||
|
resources/ProxyServer
|
||||||
|
resources/AutoAuthorization
|
||||||
|
resources/FastCrypto
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:hidden:
|
:hidden:
|
||||||
@ -64,3 +122,9 @@ To get started, press Next.
|
|||||||
|
|
||||||
functions/index
|
functions/index
|
||||||
types/index
|
types/index
|
||||||
|
|
||||||
|
.. _`MTProto Mobile Protocol v2.0`: https://core.telegram.org/mtproto
|
||||||
|
|
||||||
|
.. _TgCrypto: https://docs.pyrogram.ml/resources/FastCrypto/
|
||||||
|
|
||||||
|
.. _`Layer 75`: https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl
|
37
docs/source/resources/FastCrypto.rst
Normal file
37
docs/source/resources/FastCrypto.rst
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
Fast Crypto
|
||||||
|
===========
|
||||||
|
|
||||||
|
Pyrogram's speed can be *dramatically* boosted up by installing TgCrypto_, a high-performance, easy-to-install crypto
|
||||||
|
library specifically written in C for Pyrogram [#f1]_. TgCrypto is a replacement for the painfully slow PyAES and
|
||||||
|
implements the crypto algorithms MTProto requires, namely AES-IGE and AES-CTR 256 bit.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ pip install --upgrade tgcrypto
|
||||||
|
|
||||||
|
|
||||||
|
.. note:: Being a C extension for Python, TgCrypto is an optional but *highly recommended* dependency; when TgCrypto
|
||||||
|
is not detected on your system, Pyrogram will automatically fall back to PyAES and will show you a warning.
|
||||||
|
|
||||||
|
The reason about being an optional package is that TgCrypto requires some extra system tools in order to be compiled.
|
||||||
|
Usually the errors you receive when trying to install TgCrypto are enough to understand what you should do next.
|
||||||
|
|
||||||
|
- **Windows**: Install `Visual C++ 2015 Build Tools <http://landinghub.visualstudio.com/visual-cpp-build-tools>`_.
|
||||||
|
|
||||||
|
- **macOS**: A pop-up will automatically ask you to install the command line developer tools as soon as you issue the
|
||||||
|
installation command.
|
||||||
|
|
||||||
|
- **Linux**: Depending on your distro, install a proper C compiler (``gcc``, ``clang``) and the Python header files
|
||||||
|
(``python3-dev``).
|
||||||
|
|
||||||
|
- **Termux (Android)**: Install ``clang`` and ``python-dev`` packages.
|
||||||
|
|
||||||
|
More help on the `Pyrogram group chat <https://t.me/PyrogramChat>`_.
|
||||||
|
|
||||||
|
.. _TgCrypto: https://github.com/pyrogram/tgcrypto
|
||||||
|
|
||||||
|
.. [#f1] Although TgCrypto is intended for Pyrogram, it is shipped as a standalone package and can thus be used for
|
||||||
|
other projects too.
|
16
examples/README.md
Normal file
16
examples/README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Examples
|
||||||
|
|
||||||
|
This folder contains example scripts to show you how **Pyrogram** looks like.
|
||||||
|
You can start with [hello_world.py](https://github.com/pyrogram/pyrogram/blob/master/examples/hello_world.py) and continue
|
||||||
|
with the more advanced examples. Every script is working right away, meaning you can simply copy-paste and run, the only things
|
||||||
|
you have to change are the target chats (username, id) and file paths for sending media (photo, video, ...).
|
||||||
|
|
||||||
|
- [**hello_world.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/hello_world.py)
|
||||||
|
- [**get_history.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/get_history.py)
|
||||||
|
- [**get_participants.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/get_participants.py)
|
||||||
|
- [**inline_bots.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/inline_bots.py)
|
||||||
|
- [**updates.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/updates.py)
|
||||||
|
- [**simple_echo.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/simple_echo.py)
|
||||||
|
- [**advanced_echo.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/advanced_echo.py)
|
||||||
|
- [**advanced_echo2.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/advanced_echo2.py)
|
||||||
|
- [**welcome_bot.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/welcome_bot.py)
|
64
examples/advanced_echo.py
Normal file
64
examples/advanced_echo.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.api import types
|
||||||
|
|
||||||
|
"""This is a more advanced example bot that will reply to all private and basic groups text messages
|
||||||
|
by also mentioning the Users.
|
||||||
|
|
||||||
|
Beware! This script will make you reply to ALL new messages in private chats and in every basic group you are in.
|
||||||
|
Make sure you add an extra check to filter them:
|
||||||
|
|
||||||
|
# Filter Groups by ID
|
||||||
|
if message.to_id.chat_id == MY_GROUP_ID:
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def update_handler(client, update, users, chats):
|
||||||
|
if isinstance(update, types.UpdateNewMessage): # Filter by UpdateNewMessage (PM and Chats)
|
||||||
|
message = update.message
|
||||||
|
|
||||||
|
if isinstance(message, types.Message): # Filter by Message to exclude MessageService and MessageEmpty
|
||||||
|
if isinstance(message.to_id, types.PeerUser): # Private Messages
|
||||||
|
text = '[{}](tg://user?id={}) said "{}" to me ([{}](tg://user?id={}))'.format(
|
||||||
|
users[message.from_id].first_name,
|
||||||
|
users[message.from_id].id,
|
||||||
|
message.message,
|
||||||
|
users[message.to_id.user_id].first_name,
|
||||||
|
users[message.to_id.user_id].id
|
||||||
|
)
|
||||||
|
|
||||||
|
client.send_message(
|
||||||
|
message.from_id, # Send the message to the private chat (from_id)
|
||||||
|
text,
|
||||||
|
reply_to_message_id=message.id
|
||||||
|
)
|
||||||
|
else: # Group chats
|
||||||
|
text = '[{}](tg://user?id={}) said "{}" in **{}** group'.format(
|
||||||
|
users[message.from_id].first_name,
|
||||||
|
users[message.from_id].id,
|
||||||
|
message.message,
|
||||||
|
chats[message.to_id.chat_id].title
|
||||||
|
)
|
||||||
|
|
||||||
|
client.send_message(
|
||||||
|
message.to_id, # Send the message to the group chat (to_id)
|
||||||
|
text,
|
||||||
|
reply_to_message_id=message.id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Pyrogram setup
|
||||||
|
client = Client("example")
|
||||||
|
|
||||||
|
# Set the update_handler callback function
|
||||||
|
client.set_update_handler(update_handler)
|
||||||
|
client.start()
|
||||||
|
|
||||||
|
# Blocks the program execution until you press CTRL+C then
|
||||||
|
# automatically stops the Client by closing the underlying connection
|
||||||
|
client.idle()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
55
examples/advanced_echo2.py
Normal file
55
examples/advanced_echo2.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.api import types
|
||||||
|
|
||||||
|
"""This example is similar to advanced_echo.py, except for the fact that it will reply to Supergroup text messages only.
|
||||||
|
|
||||||
|
Beware! This script will make you reply to ALL new messages in every single supergroup you are in.
|
||||||
|
Make sure you add an extra check to filter them:
|
||||||
|
|
||||||
|
# Filter Supergroups by ID
|
||||||
|
if message.to_id.channel_id == MY_SUPERGROUP_ID:
|
||||||
|
...
|
||||||
|
|
||||||
|
# Filter Supergroups by Username
|
||||||
|
if chats[message.to_id.channel_id].username == MY_SUPERGROUP_USERNAME:
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def update_handler(client, update, users, chats):
|
||||||
|
# Channels and Supergroups share the same type (Channel). The .megagroup field is used to tell them apart, and is
|
||||||
|
# True for Supegroups, False for Channels.
|
||||||
|
if isinstance(update, types.UpdateNewChannelMessage): # Filter by UpdateNewChannelMessage (Channels/Supergroups)
|
||||||
|
message = update.message
|
||||||
|
|
||||||
|
if isinstance(message, types.Message): # Filter by Message to exclude MessageService and MessageEmpty
|
||||||
|
if chats[message.to_id.channel_id].megagroup: # Only handle messages from Supergroups not Channels
|
||||||
|
text = '[{}](tg://user?id={}) said "{}" in **{}** supergroup'.format(
|
||||||
|
users[message.from_id].first_name,
|
||||||
|
users[message.from_id].id,
|
||||||
|
message.message,
|
||||||
|
chats[message.to_id.channel_id].title
|
||||||
|
)
|
||||||
|
|
||||||
|
client.send_message(
|
||||||
|
message.to_id,
|
||||||
|
text,
|
||||||
|
reply_to_message_id=message.id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Pyrogram setup
|
||||||
|
client = Client("example")
|
||||||
|
|
||||||
|
# Set the update_handler callback function
|
||||||
|
client.set_update_handler(update_handler)
|
||||||
|
client.start()
|
||||||
|
|
||||||
|
# Blocks the program execution until you press CTRL+C then
|
||||||
|
# automatically stops the Client by closing the underlying connection
|
||||||
|
client.idle()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
BIN
examples/data/pyrogram.png
Normal file
BIN
examples/data/pyrogram.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
37
examples/get_history.py
Normal file
37
examples/get_history.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.api import functions
|
||||||
|
from pyrogram.api.errors import FloodWait
|
||||||
|
|
||||||
|
client = Client("example")
|
||||||
|
client.start()
|
||||||
|
|
||||||
|
target = "me" # "me" refers to your own chat (Saved Messages)
|
||||||
|
history = [] # List that will contain all the messages of the target chat
|
||||||
|
limit = 100 # Amount of messages to retrieve for each API call
|
||||||
|
offset = 0 # Offset starts at 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
messages = client.send(
|
||||||
|
functions.messages.GetHistory(
|
||||||
|
client.resolve_peer(target),
|
||||||
|
0, 0, offset, limit, 0, 0, 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except FloodWait as e:
|
||||||
|
# For very large chats the method call can raise a FloodWait
|
||||||
|
time.sleep(e.x) # Sleep X seconds before continuing
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not messages.messages:
|
||||||
|
break # No more messages left
|
||||||
|
|
||||||
|
history.extend(messages.messages)
|
||||||
|
offset += limit
|
||||||
|
|
||||||
|
client.stop()
|
||||||
|
|
||||||
|
# Now the "history" list contains all the messages sorted by date in
|
||||||
|
# descending order (from the most recent to the oldest one)
|
40
examples/get_participants.py
Normal file
40
examples/get_participants.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.api import functions, types
|
||||||
|
from pyrogram.api.errors import FloodWait
|
||||||
|
|
||||||
|
client = Client("example")
|
||||||
|
client.start()
|
||||||
|
|
||||||
|
target = "username" # Target channel/supergroup
|
||||||
|
users = [] # List that will contain all the users of the target chat
|
||||||
|
limit = 200 # Amount of users to retrieve for each API call
|
||||||
|
offset = 0 # Offset starts at 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
participants = client.send(
|
||||||
|
functions.channels.GetParticipants(
|
||||||
|
channel=client.resolve_peer(target),
|
||||||
|
filter=types.ChannelParticipantsSearch(""), # Filter by empty string (search for all)
|
||||||
|
offset=offset,
|
||||||
|
limit=limit,
|
||||||
|
hash=0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except FloodWait as e:
|
||||||
|
# Very large channels will trigger FloodWait.
|
||||||
|
# When happens, wait X seconds before continuing
|
||||||
|
time.sleep(e.x)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not participants.participants:
|
||||||
|
break # No more participants left
|
||||||
|
|
||||||
|
users.extend(participants.users)
|
||||||
|
offset += limit
|
||||||
|
|
||||||
|
client.stop()
|
||||||
|
|
||||||
|
# Now the "users" list contains all the members of the target chat
|
19
examples/hello_world.py
Normal file
19
examples/hello_world.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
# Create a new Client
|
||||||
|
client = Client("example")
|
||||||
|
|
||||||
|
# Start the Client
|
||||||
|
client.start()
|
||||||
|
|
||||||
|
# Send a message to yourself, Markdown is enabled by default
|
||||||
|
client.send_message("me", "Hi there! I'm using **Pyrogram**")
|
||||||
|
|
||||||
|
# Send a photo with a formatted caption to yourself
|
||||||
|
client.send_photo("me", "data/pyrogram.png", "__This is a formatted__ **caption**")
|
||||||
|
|
||||||
|
# Send a location to yourself
|
||||||
|
client.send_location("me", 51.500729, -0.124583)
|
||||||
|
|
||||||
|
# Stop the client
|
||||||
|
client.stop()
|
15
examples/inline_bots.py
Normal file
15
examples/inline_bots.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
# Create a new Client
|
||||||
|
client = Client("example")
|
||||||
|
|
||||||
|
# Start the Client
|
||||||
|
client.start()
|
||||||
|
|
||||||
|
# Get bot results for "Fuzz Universe" from the inline bot @vid
|
||||||
|
bot_results = client.get_inline_bot_results("vid", "Fuzz Universe")
|
||||||
|
# Send the first result (bot_results.results[0]) to your own chat (Saved Messages)
|
||||||
|
client.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id)
|
||||||
|
|
||||||
|
# Stop the client
|
||||||
|
client.stop()
|
34
examples/simple_echo.py
Normal file
34
examples/simple_echo.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
from pyrogram import Client
|
||||||
|
from pyrogram.api import types
|
||||||
|
|
||||||
|
"""This simple example bot will reply to all private text messages"""
|
||||||
|
|
||||||
|
|
||||||
|
def update_handler(client, update, users, chats):
|
||||||
|
if isinstance(update, types.UpdateNewMessage): # Filter by UpdateNewMessage (Private Messages)
|
||||||
|
message = update.message # type: types.Message
|
||||||
|
|
||||||
|
if isinstance(message, types.Message): # Filter by Message to exclude MessageService and MessageEmpty
|
||||||
|
if isinstance(message.to_id, types.PeerUser): # Private Messages (Message from user)
|
||||||
|
client.send_message(
|
||||||
|
chat_id=message.from_id,
|
||||||
|
text=message.message,
|
||||||
|
reply_to_message_id=message.id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Pyrogram setup
|
||||||
|
client = Client("example")
|
||||||
|
|
||||||
|
# Set the update_handler callback function
|
||||||
|
client.set_update_handler(update_handler)
|
||||||
|
client.start()
|
||||||
|
|
||||||
|
# Blocks the program execution until you press CTRL+C then
|
||||||
|
# automatically stops the Client by closing the underlying connection
|
||||||
|
client.idle()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
25
examples/updates.py
Normal file
25
examples/updates.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
|
||||||
|
# This function will be called every time a new Update is received from Telegram
|
||||||
|
def update_handler(client, update, users, chats):
|
||||||
|
# Send EVERY update that arrives to your own chat (Saved Messages)
|
||||||
|
# Use triple backticks to make the text look nicer.
|
||||||
|
client.send_message("me", "```\n" + str(update) + "```")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Pyrogram setup
|
||||||
|
client = Client("example")
|
||||||
|
|
||||||
|
# Set the update_handler callback function
|
||||||
|
client.set_update_handler(update_handler)
|
||||||
|
client.start()
|
||||||
|
|
||||||
|
# Blocks the program execution until you press CTRL+C then
|
||||||
|
# automatically stops the Client by closing the underlying connection
|
||||||
|
client.idle()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
52
examples/welcome_bot.py
Normal file
52
examples/welcome_bot.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from pyrogram import Client, Emoji
|
||||||
|
from pyrogram.api import types
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is the Welcome Bot in @PyrogramChat
|
||||||
|
The code is commented to help you understand each part
|
||||||
|
|
||||||
|
It also uses the Emoji module to easily add emojis in your text messages
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Your Supergroup ID
|
||||||
|
SUPERGROUP_ID = 1387666944
|
||||||
|
|
||||||
|
|
||||||
|
def update_handler(client, update, users, chats):
|
||||||
|
# Supergroup messages are contained in the "UpdateNewChannelMessage" update type
|
||||||
|
if isinstance(update, types.UpdateNewChannelMessage):
|
||||||
|
message = update.message
|
||||||
|
# When a user joins, a "MessageService" is received
|
||||||
|
if isinstance(message, types.MessageService):
|
||||||
|
# Check if the message is sent to your SUPERGROUP_ID
|
||||||
|
if message.to_id.channel_id == SUPERGROUP_ID:
|
||||||
|
# A "MessageService" contains the "action" field.
|
||||||
|
# The action for user joins is "MessageActionChatAddUser" if the user
|
||||||
|
# joined using the username, otherwise is "MessageActionChatJoinedByLink" if
|
||||||
|
# the user joined a private group by link
|
||||||
|
if isinstance(message.action, (types.MessageActionChatAddUser, types.MessageActionChatJoinedByLink)):
|
||||||
|
# Now send the welcome message. Extra info about a user (such as the first_name, username, ...)
|
||||||
|
# are contained in the users dictionary and can be accessed by the user ID
|
||||||
|
client.send_message(
|
||||||
|
SUPERGROUP_ID,
|
||||||
|
"{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s "
|
||||||
|
"group chat, [{}](tg://user?id={})!".format(
|
||||||
|
Emoji.SPARKLES, # Add an emoji
|
||||||
|
users[message.from_id].first_name,
|
||||||
|
users[message.from_id].id
|
||||||
|
),
|
||||||
|
reply_to_message_id=message.id,
|
||||||
|
disable_web_page_preview=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
client = Client("example")
|
||||||
|
client.set_update_handler(update_handler)
|
||||||
|
|
||||||
|
client.start()
|
||||||
|
client.idle()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -23,10 +23,12 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance
|
|||||||
"e" if sys.getfilesystemencoding() == "ascii" else "\xe8"
|
"e" if sys.getfilesystemencoding() == "ascii" else "\xe8"
|
||||||
)
|
)
|
||||||
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
|
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
|
||||||
__version__ = "0.5.0"
|
__version__ = "0.6.2"
|
||||||
|
|
||||||
from .api.errors import Error
|
from .api.errors import Error
|
||||||
from .client import ChatAction
|
from .client import ChatAction
|
||||||
from .client import Client
|
from .client import Client
|
||||||
from .client import ParseMode
|
from .client import ParseMode
|
||||||
from .client.input_media import InputMedia
|
from .client.input_media import InputMedia
|
||||||
|
from .client.input_phone_contact import InputPhoneContact
|
||||||
|
from .client import Emoji
|
||||||
|
@ -19,3 +19,4 @@
|
|||||||
from .chat_action import ChatAction
|
from .chat_action import ChatAction
|
||||||
from .client import Client
|
from .client import Client
|
||||||
from .parse_mode import ParseMode
|
from .parse_mode import ParseMode
|
||||||
|
from .emoji import Emoji
|
||||||
|
File diff suppressed because it is too large
Load Diff
7829
pyrogram/client/emoji.py
Normal file
7829
pyrogram/client/emoji.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -20,10 +20,11 @@
|
|||||||
class InputMedia:
|
class InputMedia:
|
||||||
class Photo:
|
class Photo:
|
||||||
"""This object represents a photo to be sent inside an album.
|
"""This object represents a photo to be sent inside an album.
|
||||||
|
It is intended to be used with :obj:`pyrogram.Client.send_media_group`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
media (:obj:`str`):
|
media (:obj:`str`):
|
||||||
File to send.
|
Photo file to send.
|
||||||
Pass a file path as string to send a photo that exists on your local machine.
|
Pass a file path as string to send a photo that exists on your local machine.
|
||||||
|
|
||||||
caption (:obj:`str`):
|
caption (:obj:`str`):
|
||||||
@ -45,19 +46,32 @@ class InputMedia:
|
|||||||
|
|
||||||
class Video:
|
class Video:
|
||||||
"""This object represents a video to be sent inside an album.
|
"""This object represents a video to be sent inside an album.
|
||||||
|
It is intended to be used with :obj:`pyrogram.Client.send_media_group`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
media (:obj:`str`):
|
media (:obj:`str`):
|
||||||
File to send.
|
Video file to send.
|
||||||
Pass a file path as string to send a video that exists on your local machine.
|
Pass a file path as string to send a video that exists on your local machine.
|
||||||
|
|
||||||
caption (:obj:`str`):
|
caption (:obj:`str`, optional):
|
||||||
Caption of the video to be sent, 0-200 characters
|
Caption of the video to be sent, 0-200 characters
|
||||||
|
|
||||||
parse_mode (:obj:`str`):
|
parse_mode (:obj:`str`, optional):
|
||||||
Use :obj:`pyrogram.ParseMode.MARKDOWN` or :obj:`pyrogram.ParseMode.HTML` if you want Telegram apps
|
Use :obj:`pyrogram.ParseMode.MARKDOWN` or :obj:`pyrogram.ParseMode.HTML` if you want Telegram apps
|
||||||
to show bold, italic, fixed-width text or inline URLs in your caption.
|
to show bold, italic, fixed-width text or inline URLs in your caption.
|
||||||
Defaults to Markdown.
|
Defaults to Markdown.
|
||||||
|
|
||||||
|
width (:obj:`int`, optional):
|
||||||
|
Video width.
|
||||||
|
|
||||||
|
height (:obj:`int`, optional):
|
||||||
|
Video height
|
||||||
|
|
||||||
|
duration (:obj:`int`, optional):
|
||||||
|
Video duration.
|
||||||
|
|
||||||
|
supports_streaming (:obj:`bool`, optional):
|
||||||
|
Pass True, if the uploaded video is suitable for streaming.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
@ -66,10 +80,12 @@ class InputMedia:
|
|||||||
parse_mode: str = "",
|
parse_mode: str = "",
|
||||||
width: int = 0,
|
width: int = 0,
|
||||||
height: int = 0,
|
height: int = 0,
|
||||||
duration: int = 0):
|
duration: int = 0,
|
||||||
|
supports_streaming: bool = None):
|
||||||
self.media = media
|
self.media = media
|
||||||
self.caption = caption
|
self.caption = caption
|
||||||
self.parse_mode = parse_mode
|
self.parse_mode = parse_mode
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
|
self.supports_streaming = supports_streaming
|
||||||
|
@ -16,20 +16,29 @@
|
|||||||
# 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/>.
|
||||||
|
|
||||||
try:
|
from pyrogram.api.types import InputPhoneContact as RawInputPhoneContact
|
||||||
from pyaes import AESModeOfOperationCTR
|
from pyrogram.session.internals import MsgId
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CTR:
|
class InputPhoneContact:
|
||||||
def __init__(self, key: bytes, iv: bytes):
|
"""This object represents a Phone Contact to be added in your Telegram address book.
|
||||||
self.ctr = AESModeOfOperationCTR(key)
|
It is intended to be used with :obj:`pyrogram.Client.add_contacts`
|
||||||
self.iv = iv
|
|
||||||
|
|
||||||
def decrypt(self, data: bytes, offset: int) -> bytes:
|
Args:
|
||||||
replace = int.to_bytes(offset // 16, byteorder="big", length=4)
|
phone (:obj:`str`):
|
||||||
iv = self.iv[:-4] + replace
|
Contact's phone number
|
||||||
self.ctr._counter._counter = list(iv)
|
|
||||||
|
|
||||||
return self.ctr.decrypt(data)
|
first_name (:obj:`str`):
|
||||||
|
Contact's first name
|
||||||
|
|
||||||
|
last_name (:obj:`str`, optional):
|
||||||
|
Contact's last name
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __new__(cls, phone: str, first_name: str, last_name: str = ""):
|
||||||
|
return RawInputPhoneContact(
|
||||||
|
client_id=MsgId(),
|
||||||
|
phone="+" + phone.strip("+"),
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name
|
||||||
|
)
|
@ -31,7 +31,7 @@ from . import utils
|
|||||||
|
|
||||||
|
|
||||||
class HTML:
|
class HTML:
|
||||||
HTML_RE = re.compile(r"<(\w+)(?: href=([\"'])(.*)\2)?>(.*)</\1>")
|
HTML_RE = re.compile(r"<(\w+)(?: href=([\"'])([^<]+)\2)?>([^>]+)</\1>")
|
||||||
MENTION_RE = re.compile(r"tg://user\?id=(\d+)")
|
MENTION_RE = re.compile(r"tg://user\?id=(\d+)")
|
||||||
|
|
||||||
def __init__(self, peers_by_id):
|
def __init__(self, peers_by_id):
|
||||||
@ -44,7 +44,7 @@ class HTML:
|
|||||||
|
|
||||||
for match in self.HTML_RE.finditer(text):
|
for match in self.HTML_RE.finditer(text):
|
||||||
start = match.start() - offset
|
start = match.start() - offset
|
||||||
style, url, body = match.groups()
|
style, url, body = match.group(1, 3, 4)
|
||||||
|
|
||||||
if url:
|
if url:
|
||||||
mention = self.MENTION_RE.match(url)
|
mention = self.MENTION_RE.match(url)
|
||||||
|
@ -24,95 +24,84 @@ from pyrogram.api.types import (
|
|||||||
MessageEntityCode as Code,
|
MessageEntityCode as Code,
|
||||||
MessageEntityTextUrl as Url,
|
MessageEntityTextUrl as Url,
|
||||||
MessageEntityPre as Pre,
|
MessageEntityPre as Pre,
|
||||||
|
MessageEntityMentionName as MentionInvalid,
|
||||||
InputMessageEntityMentionName as Mention
|
InputMessageEntityMentionName as Mention
|
||||||
)
|
)
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
class Markdown:
|
class Markdown:
|
||||||
INLINE_DELIMITERS = {
|
BOLD_DELIMITER = "**"
|
||||||
"**": Bold,
|
ITALIC_DELIMITER = "__"
|
||||||
"__": Italic,
|
CODE_DELIMITER = "`"
|
||||||
"`": Code
|
PRE_DELIMITER = "```"
|
||||||
}
|
|
||||||
|
|
||||||
# ``` python
|
MARKDOWN_RE = re.compile(r"```([\w ]*)\n([\w\W]*)(?:\n|)```|\[([^[(]+)\]\(([^])]+)\)|({d})(.+?)\5".format(
|
||||||
# for i in range(10):
|
|
||||||
# print(i)
|
|
||||||
# ```
|
|
||||||
PRE_RE = r"(?P<pre>```(?P<lang>.*)\n(?P<code>(.|\n)*)\n```)"
|
|
||||||
|
|
||||||
# [url](github.com)
|
|
||||||
URL_RE = r"(?P<url>(\[(?P<url_text>.+?)\]\((?P<url_path>.+?)\)))"
|
|
||||||
|
|
||||||
# [name](tg://user?id=123456789)
|
|
||||||
MENTION_RE = r"(?P<mention>(\[(?P<mention_text>.+?)\]\(tg:\/\/user\?id=(?P<user_id>\d+?)\)))"
|
|
||||||
|
|
||||||
# **bold**
|
|
||||||
# __italic__
|
|
||||||
# `code`
|
|
||||||
INLINE_RE = r"(?P<inline>(?P<start_delimiter>{d})(?P<body>.+?)(?P<end_delimiter>{d}))".format(
|
|
||||||
d="|".join(
|
d="|".join(
|
||||||
["".join(i) for i in [
|
["".join(i) for i in [
|
||||||
["\{}".format(j) for j in i]
|
["\{}".format(j) for j in i]
|
||||||
for i in sorted( # Sort delimiters by length
|
for i in [
|
||||||
INLINE_DELIMITERS.keys(),
|
PRE_DELIMITER,
|
||||||
key=lambda k: len(k), # Or: key=len
|
CODE_DELIMITER,
|
||||||
reverse=True
|
ITALIC_DELIMITER,
|
||||||
)
|
BOLD_DELIMITER
|
||||||
|
]
|
||||||
]]
|
]]
|
||||||
)
|
)
|
||||||
)
|
))
|
||||||
|
MENTION_RE = re.compile(r"tg://user\?id=(\d+)")
|
||||||
|
|
||||||
MARKDOWN_RE = re.compile("|".join([PRE_RE, MENTION_RE, URL_RE, INLINE_RE]))
|
def __init__(self, peers_by_id: dict):
|
||||||
|
|
||||||
def __init__(self, peers_by_id):
|
|
||||||
self.peers_by_id = peers_by_id
|
self.peers_by_id = peers_by_id
|
||||||
|
|
||||||
def parse(self, text):
|
def parse(self, message: str):
|
||||||
entities = []
|
entities = []
|
||||||
text = utils.add_surrogates(text)
|
message = utils.add_surrogates(message).strip()
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
||||||
for match in self.MARKDOWN_RE.finditer(text):
|
for match in self.MARKDOWN_RE.finditer(message):
|
||||||
start = match.start() - offset
|
start = match.start() - offset
|
||||||
|
lang, pre, text, url, style, body = match.groups()
|
||||||
|
|
||||||
if match.group("pre"):
|
if pre:
|
||||||
pattern = match.group("pre")
|
body = pre = pre.strip()
|
||||||
lang = match.group("lang")
|
entity = Pre(start, len(pre), lang.strip() or "")
|
||||||
replace = match.group("code")
|
offset += len(lang) + len(self.PRE_DELIMITER) * 2
|
||||||
entity = Pre(start, len(replace), lang.strip())
|
elif url:
|
||||||
offset += len(lang) + 8
|
mention = self.MENTION_RE.match(url)
|
||||||
elif match.group("url"):
|
|
||||||
pattern = match.group("url")
|
|
||||||
replace = match.group("url_text")
|
|
||||||
path = match.group("url_path")
|
|
||||||
entity = Url(start, len(replace), path)
|
|
||||||
offset += len(path) + 4
|
|
||||||
elif match.group("mention"):
|
|
||||||
pattern = match.group("mention")
|
|
||||||
replace = match.group("mention_text")
|
|
||||||
user_id = match.group("user_id")
|
|
||||||
entity = Mention(start, len(replace), self.peers_by_id[int(user_id)])
|
|
||||||
offset += len(user_id) + 17
|
|
||||||
elif match.group("inline"):
|
|
||||||
pattern = match.group("inline")
|
|
||||||
replace = match.group("body")
|
|
||||||
start_delimiter = match.group("start_delimiter")
|
|
||||||
end_delimiter = match.group("end_delimiter")
|
|
||||||
|
|
||||||
if start_delimiter != end_delimiter:
|
if mention:
|
||||||
|
user_id = int(mention.group(1))
|
||||||
|
input_user = self.peers_by_id.get(user_id, None)
|
||||||
|
|
||||||
|
entity = (
|
||||||
|
Mention(start, len(text), input_user)
|
||||||
|
if input_user
|
||||||
|
else MentionInvalid(start, len(text), user_id)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
entity = Url(start, len(text), url)
|
||||||
|
|
||||||
|
body = text
|
||||||
|
offset += len(url) + 4
|
||||||
|
else:
|
||||||
|
if style == self.BOLD_DELIMITER:
|
||||||
|
entity = Bold(start, len(body))
|
||||||
|
elif style == self.ITALIC_DELIMITER:
|
||||||
|
entity = Italic(start, len(body))
|
||||||
|
elif style == self.CODE_DELIMITER:
|
||||||
|
entity = Code(start, len(body))
|
||||||
|
elif style == self.PRE_DELIMITER:
|
||||||
|
entity = Pre(start, len(body), "")
|
||||||
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
entity = self.INLINE_DELIMITERS[start_delimiter](start, len(replace))
|
offset += len(style) * 2
|
||||||
offset += len(start_delimiter) * 2
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
entities.append(entity)
|
entities.append(entity)
|
||||||
text = text.replace(pattern, replace)
|
message = message.replace(match.group(), body)
|
||||||
|
|
||||||
return dict(
|
return dict(
|
||||||
message=utils.remove_surrogates(text),
|
message=utils.remove_surrogates(message),
|
||||||
entities=entities
|
entities=entities
|
||||||
)
|
)
|
||||||
|
@ -30,6 +30,7 @@ Proxy = namedtuple("Proxy", ["enabled", "hostname", "port", "username", "passwor
|
|||||||
class TCP(socks.socksocket):
|
class TCP(socks.socksocket):
|
||||||
def __init__(self, proxy: Proxy):
|
def __init__(self, proxy: Proxy):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.settimeout(10)
|
||||||
self.proxy_enabled = False
|
self.proxy_enabled = False
|
||||||
|
|
||||||
if proxy and proxy.enabled:
|
if proxy and proxy.enabled:
|
||||||
@ -43,6 +44,11 @@ class TCP(socks.socksocket):
|
|||||||
password=proxy.password
|
password=proxy.password
|
||||||
)
|
)
|
||||||
|
|
||||||
|
log.info("Using proxy {}:{}".format(
|
||||||
|
proxy.hostname,
|
||||||
|
proxy.port
|
||||||
|
))
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
try:
|
try:
|
||||||
self.shutdown(socket.SHUT_RDWR)
|
self.shutdown(socket.SHUT_RDWR)
|
||||||
|
@ -16,8 +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/>.
|
||||||
|
|
||||||
from .ctr import CTR
|
from .aes import AES
|
||||||
from .ige import IGE
|
|
||||||
from .kdf import KDF
|
from .kdf import KDF
|
||||||
from .prime import Prime
|
from .prime import Prime
|
||||||
from .rsa import RSA
|
from .rsa import RSA
|
||||||
|
@ -16,21 +16,52 @@
|
|||||||
# 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/>.
|
||||||
|
|
||||||
from pyaes import AES
|
import logging
|
||||||
|
|
||||||
BLOCK_SIZE = 16
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import tgcrypto
|
||||||
|
except ImportError:
|
||||||
|
log.warning(
|
||||||
|
"TgCrypto is missing! "
|
||||||
|
"Pyrogram will work the same, but at a much slower speed. "
|
||||||
|
"More info: https://docs.pyrogram.ml/resources/TgCrypto"
|
||||||
|
)
|
||||||
|
is_fast = False
|
||||||
|
import pyaes
|
||||||
|
else:
|
||||||
|
log.info("Using TgCrypto")
|
||||||
|
is_fast = True
|
||||||
|
|
||||||
|
|
||||||
# TODO: Performance optimization
|
# TODO: Ugly IFs
|
||||||
|
class AES:
|
||||||
class IGE:
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
def ige_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||||
return cls.ige(data, key, iv, True)
|
if is_fast:
|
||||||
|
return tgcrypto.ige_encrypt(data, key, iv)
|
||||||
|
else:
|
||||||
|
return cls.ige(data, key, iv, True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
def ige_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||||
return cls.ige(data, key, iv, False)
|
if is_fast:
|
||||||
|
return tgcrypto.ige_decrypt(data, key, iv)
|
||||||
|
else:
|
||||||
|
return cls.ige(data, key, iv, False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ctr_decrypt(data: bytes, key: bytes, iv: bytes, offset: int) -> bytes:
|
||||||
|
replace = int.to_bytes(offset // 16, byteorder="big", length=4)
|
||||||
|
iv = iv[:-4] + replace
|
||||||
|
|
||||||
|
if is_fast:
|
||||||
|
return tgcrypto.ctr_decrypt(data, key, iv)
|
||||||
|
else:
|
||||||
|
ctr = pyaes.AESModeOfOperationCTR(key)
|
||||||
|
ctr._counter._counter = list(iv)
|
||||||
|
return ctr.decrypt(data)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xor(a: bytes, b: bytes) -> bytes:
|
def xor(a: bytes, b: bytes) -> bytes:
|
||||||
@ -42,12 +73,12 @@ class IGE:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
|
def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
|
||||||
cipher = AES(key)
|
cipher = pyaes.AES(key)
|
||||||
|
|
||||||
iv_1 = iv[:BLOCK_SIZE]
|
iv_1 = iv[:16]
|
||||||
iv_2 = iv[BLOCK_SIZE:]
|
iv_2 = iv[16:]
|
||||||
|
|
||||||
data = [data[i: i + BLOCK_SIZE] for i in range(0, len(data), BLOCK_SIZE)]
|
data = [data[i: i + 16] for i in range(0, len(data), 16)]
|
||||||
|
|
||||||
if encrypt:
|
if encrypt:
|
||||||
for i, chunk in enumerate(data):
|
for i, chunk in enumerate(data):
|
@ -23,10 +23,16 @@ PublicKey = namedtuple("PublicKey", ["m", "e"])
|
|||||||
|
|
||||||
class RSA:
|
class RSA:
|
||||||
# To get modulus and exponent:
|
# To get modulus and exponent:
|
||||||
|
#
|
||||||
|
# [RSA PUBLIC KEY]:
|
||||||
# grep -v -- - public.key | tr -d \\n | base64 -d | openssl asn1parse -inform DER -i
|
# grep -v -- - public.key | tr -d \\n | base64 -d | openssl asn1parse -inform DER -i
|
||||||
|
#
|
||||||
|
# [PUBLIC KEY]:
|
||||||
|
# openssl rsa -pubin -in key -text -noout
|
||||||
|
|
||||||
server_public_keys = {
|
server_public_keys = {
|
||||||
0xc3b42b026ce86b21 - (1 << 64): PublicKey( # Telegram servers
|
# -4344800451088585951
|
||||||
|
0xc3b42b026ce86b21 - (1 << 64): PublicKey( # Telegram servers #1
|
||||||
# -----BEGIN RSA PUBLIC KEY-----
|
# -----BEGIN RSA PUBLIC KEY-----
|
||||||
# MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
|
# MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
|
||||||
# lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
|
# lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
|
||||||
@ -48,6 +54,108 @@ class RSA:
|
|||||||
), # Modulus
|
), # Modulus
|
||||||
int("010001", 16) # Exponent
|
int("010001", 16) # Exponent
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# 847625836280919973
|
||||||
|
0x10bc35f3509f7b7a5 - (1 << 64): PublicKey( # Telegram servers #2
|
||||||
|
# -----BEGIN PUBLIC KEY-----
|
||||||
|
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAruw2yP/BCcsJliRoW5eB
|
||||||
|
# VBVle9dtjJw+OYED160Wybum9SXtBBLXriwt4rROd9csv0t0OHCaTmRqBcQ0J8fx
|
||||||
|
# hN6/cpR1GWgOZRUAiQxoMnlt0R93LCX/j1dnVa/gVbCjdSxpbrfY2g2L4frzjJvd
|
||||||
|
# l84Kd9ORYjDEAyFnEA7dD556OptgLQQ2e2iVNq8NZLYTzLp5YpOdO1doK+ttrltg
|
||||||
|
# gTCy5SrKeLoCPPbOgGsdxJxyz5KKcZnSLj16yE5HvJQn0CNpRdENvRUXe6tBP78O
|
||||||
|
# 39oJ8BTHp9oIjd6XWXAsp2CvK45Ol8wFXGF710w9lwCGNbmNxNYhtIkdqfsEcwR5
|
||||||
|
# JwIDAQAB
|
||||||
|
# -----END PUBLIC KEY-----
|
||||||
|
int(
|
||||||
|
"AEEC36C8FFC109CB099624685B97815415657BD76D8C9C3E398103D7AD16C9BB"
|
||||||
|
"A6F525ED0412D7AE2C2DE2B44E77D72CBF4B7438709A4E646A05C43427C7F184"
|
||||||
|
"DEBF72947519680E651500890C6832796DD11F772C25FF8F576755AFE055B0A3"
|
||||||
|
"752C696EB7D8DA0D8BE1FAF38C9BDD97CE0A77D3916230C4032167100EDD0F9E"
|
||||||
|
"7A3A9B602D04367B689536AF0D64B613CCBA7962939D3B57682BEB6DAE5B6081"
|
||||||
|
"30B2E52ACA78BA023CF6CE806B1DC49C72CF928A7199D22E3D7AC84E47BC9427"
|
||||||
|
"D0236945D10DBD15177BAB413FBF0EDFDA09F014C7A7DA088DDE9759702CA760"
|
||||||
|
"AF2B8E4E97CC055C617BD74C3D97008635B98DC4D621B4891DA9FB0473047927",
|
||||||
|
16
|
||||||
|
), # Modulus
|
||||||
|
int("010001", 16) # Exponent
|
||||||
|
),
|
||||||
|
|
||||||
|
# 1562291298945373506
|
||||||
|
0x115ae5fa8b5529542 - (1 << 64): PublicKey( # Telegram servers #3
|
||||||
|
# -----BEGIN PUBLIC KEY-----
|
||||||
|
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvfLHfYH2r9R70w8prHbl
|
||||||
|
# Wt/nDkh+XkgpflqQVcnAfSuTtO05lNPspQmL8Y2XjVT4t8cT6xAkdgfmmvnvRPOO
|
||||||
|
# KPi0OfJXoRVylFzAQG/j83u5K3kRLbae7fLccVhKZhY46lvsueI1hQdLgNV9n1cQ
|
||||||
|
# 3TDS2pQOCtovG4eDl9wacrXOJTG2990VjgnIKNA0UMoP+KF03qzryqIt3oTvZq03
|
||||||
|
# DyWdGK+AZjgBLaDKSnC6qD2cFY81UryRWOab8zKkWAnhw2kFpcqhI0jdV5QaSCEx
|
||||||
|
# vnsjVaX0Y1N0870931/5Jb9ICe4nweZ9kSDF/gip3kWLG0o8XQpChDfyvsqB9OLV
|
||||||
|
# /wIDAQAB
|
||||||
|
# -----END PUBLIC KEY-----
|
||||||
|
int(
|
||||||
|
"BDF2C77D81F6AFD47BD30F29AC76E55ADFE70E487E5E48297E5A9055C9C07D2B"
|
||||||
|
"93B4ED3994D3ECA5098BF18D978D54F8B7C713EB10247607E69AF9EF44F38E28"
|
||||||
|
"F8B439F257A11572945CC0406FE3F37BB92B79112DB69EEDF2DC71584A661638"
|
||||||
|
"EA5BECB9E23585074B80D57D9F5710DD30D2DA940E0ADA2F1B878397DC1A72B5"
|
||||||
|
"CE2531B6F7DD158E09C828D03450CA0FF8A174DEACEBCAA22DDE84EF66AD370F"
|
||||||
|
"259D18AF806638012DA0CA4A70BAA83D9C158F3552BC9158E69BF332A45809E1"
|
||||||
|
"C36905A5CAA12348DD57941A482131BE7B2355A5F4635374F3BD3DDF5FF925BF"
|
||||||
|
"4809EE27C1E67D9120C5FE08A9DE458B1B4A3C5D0A428437F2BECA81F4E2D5FF",
|
||||||
|
16
|
||||||
|
), # Modulus
|
||||||
|
int("010001", 16) # Exponent
|
||||||
|
),
|
||||||
|
|
||||||
|
# -5859577972006586033
|
||||||
|
0xaeae98e13cd7f94f - (1 << 64): PublicKey( # Telegram servers #4
|
||||||
|
# -----BEGIN PUBLIC KEY-----
|
||||||
|
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs/ditzm+mPND6xkhzwFI
|
||||||
|
# z6J/968CtkcSE/7Z2qAJiXbmZ3UDJPGrzqTDHkO30R8VeRM/Kz2f4nR05GIFiITl
|
||||||
|
# 4bEjvpy7xqRDspJcCFIOcyXm8abVDhF+th6knSU0yLtNKuQVP6voMrnt9MV1X92L
|
||||||
|
# GZQLgdHZbPQz0Z5qIpaKhdyA8DEvWWvSUwwc+yi1/gGaybwlzZwqXYoPOhwMebzK
|
||||||
|
# Uk0xW14htcJrRrq+PXXQbRzTMynseCoPIoke0dtCodbA3qQxQovE16q9zz4Otv2k
|
||||||
|
# 4j63cz53J+mhkVWAeWxVGI0lltJmWtEYK6er8VqqWot3nqmWMXogrgRLggv/Nbbo
|
||||||
|
# oQIDAQAB
|
||||||
|
# -----END PUBLIC KEY-----
|
||||||
|
int(
|
||||||
|
"B3F762B739BE98F343EB1921CF0148CFA27FF7AF02B6471213FED9DAA0098976"
|
||||||
|
"E667750324F1ABCEA4C31E43B7D11F1579133F2B3D9FE27474E462058884E5E1"
|
||||||
|
"B123BE9CBBC6A443B2925C08520E7325E6F1A6D50E117EB61EA49D2534C8BB4D"
|
||||||
|
"2AE4153FABE832B9EDF4C5755FDD8B19940B81D1D96CF433D19E6A22968A85DC"
|
||||||
|
"80F0312F596BD2530C1CFB28B5FE019AC9BC25CD9C2A5D8A0F3A1C0C79BCCA52"
|
||||||
|
"4D315B5E21B5C26B46BABE3D75D06D1CD33329EC782A0F22891ED1DB42A1D6C0"
|
||||||
|
"DEA431428BC4D7AABDCF3E0EB6FDA4E23EB7733E7727E9A1915580796C55188D"
|
||||||
|
"2596D2665AD1182BA7ABF15AAA5A8B779EA996317A20AE044B820BFF35B6E8A1",
|
||||||
|
16
|
||||||
|
), # Modulus
|
||||||
|
int("010001", 16) # Exponent
|
||||||
|
),
|
||||||
|
|
||||||
|
# 6491968696586960280
|
||||||
|
0x15a181b2235057d98 - (1 << 64): PublicKey( # Telegram servers #5
|
||||||
|
# -----BEGIN PUBLIC KEY-----
|
||||||
|
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmpxVY7ld/8DAjz6F6q0
|
||||||
|
# 5shjg8/4p6047bn6/m8yPy1RBsvIyvuDuGnP/RzPEhzXQ9UJ5Ynmh2XJZgHoE9xb
|
||||||
|
# nfxL5BXHplJhMtADXKM9bWB11PU1Eioc3+AXBB8QiNFBn2XI5UkO5hPhbb9mJpjA
|
||||||
|
# 9Uhw8EdfqJP8QetVsI/xrCEbwEXe0xvifRLJbY08/Gp66KpQvy7g8w7VB8wlgePe
|
||||||
|
# xW3pT13Ap6vuC+mQuJPyiHvSxjEKHgqePji9NP3tJUFQjcECqcm0yV7/2d0t/pbC
|
||||||
|
# m+ZH1sadZspQCEPPrtbkQBlvHb4OLiIWPGHKSMeRFvp3IWcmdJqXahxLCUS1Eh6M
|
||||||
|
# AQIDAQAB
|
||||||
|
# -----END PUBLIC KEY-----
|
||||||
|
int(
|
||||||
|
"BE6A71558EE577FF03023CFA17AAB4E6C86383CFF8A7AD38EDB9FAFE6F323F2D"
|
||||||
|
"5106CBC8CAFB83B869CFFD1CCF121CD743D509E589E68765C96601E813DC5B9D"
|
||||||
|
"FC4BE415C7A6526132D0035CA33D6D6075D4F535122A1CDFE017041F1088D141"
|
||||||
|
"9F65C8E5490EE613E16DBF662698C0F54870F0475FA893FC41EB55B08FF1AC21"
|
||||||
|
"1BC045DED31BE27D12C96D8D3CFC6A7AE8AA50BF2EE0F30ED507CC2581E3DEC5"
|
||||||
|
"6DE94F5DC0A7ABEE0BE990B893F2887BD2C6310A1E0A9E3E38BD34FDED254150"
|
||||||
|
"8DC102A9C9B4C95EFFD9DD2DFE96C29BE647D6C69D66CA500843CFAED6E44019"
|
||||||
|
"6F1DBE0E2E22163C61CA48C79116FA77216726749A976A1C4B0944B5121E8C01",
|
||||||
|
16
|
||||||
|
), # Modulus
|
||||||
|
int("010001", 16) # Exponent
|
||||||
|
),
|
||||||
|
|
||||||
|
# 6427105915145367799
|
||||||
0x15931aac70e0d30f7 - (1 << 64): PublicKey( # CDN DC-121
|
0x15931aac70e0d30f7 - (1 << 64): PublicKey( # CDN DC-121
|
||||||
# -----BEGIN RSA PUBLIC KEY-----
|
# -----BEGIN RSA PUBLIC KEY-----
|
||||||
# MIIBCgKCAQEA+Lf3PvgE1yxbJUCMaEAkV0QySTVpnaDjiednB5RbtNWjCeqSVakY
|
# MIIBCgKCAQEA+Lf3PvgE1yxbJUCMaEAkV0QySTVpnaDjiednB5RbtNWjCeqSVakY
|
||||||
@ -70,6 +178,8 @@ class RSA:
|
|||||||
), # Modulus
|
), # Modulus
|
||||||
int("010001", 16) # Exponent
|
int("010001", 16) # Exponent
|
||||||
),
|
),
|
||||||
|
|
||||||
|
# 2685959930972952888
|
||||||
0x1254672538e935938 - (1 << 64): PublicKey( # CDN DC-140
|
0x1254672538e935938 - (1 << 64): PublicKey( # CDN DC-140
|
||||||
# -----BEGIN RSA PUBLIC KEY-----
|
# -----BEGIN RSA PUBLIC KEY-----
|
||||||
# MIIBCgKCAQEAzuHVC7sE50Kho/yDVZtWnlmA5Bf/aM8KZY3WzS16w6w1sBqipj8o
|
# MIIBCgKCAQEAzuHVC7sE50Kho/yDVZtWnlmA5Bf/aM8KZY3WzS16w6w1sBqipj8o
|
||||||
|
@ -25,7 +25,7 @@ from os import urandom
|
|||||||
from pyrogram.api import functions, types
|
from pyrogram.api import functions, types
|
||||||
from pyrogram.api.core import Object, Long, Int
|
from pyrogram.api.core import Object, Long, Int
|
||||||
from pyrogram.connection import Connection
|
from pyrogram.connection import Connection
|
||||||
from pyrogram.crypto import IGE, RSA, Prime
|
from pyrogram.crypto import AES, RSA, Prime
|
||||||
from .internals import MsgId, DataCenter
|
from .internals import MsgId, DataCenter
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -51,12 +51,12 @@ class Auth:
|
|||||||
self.test_mode = test_mode
|
self.test_mode = test_mode
|
||||||
|
|
||||||
self.connection = Connection(DataCenter(dc_id, test_mode), proxy)
|
self.connection = Connection(DataCenter(dc_id, test_mode), proxy)
|
||||||
self.msg_id = MsgId()
|
|
||||||
|
|
||||||
def pack(self, data: Object) -> bytes:
|
@staticmethod
|
||||||
|
def pack(data: Object) -> bytes:
|
||||||
return (
|
return (
|
||||||
bytes(8)
|
bytes(8)
|
||||||
+ Long(self.msg_id())
|
+ Long(MsgId())
|
||||||
+ Int(len(data.write()))
|
+ Int(len(data.write()))
|
||||||
+ data.write()
|
+ data.write()
|
||||||
)
|
)
|
||||||
@ -91,8 +91,19 @@ class Auth:
|
|||||||
# Step 1; Step 2
|
# Step 1; Step 2
|
||||||
nonce = int.from_bytes(urandom(16), "little", signed=True)
|
nonce = int.from_bytes(urandom(16), "little", signed=True)
|
||||||
log.debug("Send req_pq: {}".format(nonce))
|
log.debug("Send req_pq: {}".format(nonce))
|
||||||
res_pq = self.send(functions.ReqPq(nonce))
|
res_pq = self.send(functions.ReqPqMulti(nonce))
|
||||||
log.debug("Got ResPq: {}".format(res_pq.server_nonce))
|
log.debug("Got ResPq: {}".format(res_pq.server_nonce))
|
||||||
|
log.debug("Server public key fingerprints: {}".format(res_pq.server_public_key_fingerprints))
|
||||||
|
|
||||||
|
for i in res_pq.server_public_key_fingerprints:
|
||||||
|
if i in RSA.server_public_keys:
|
||||||
|
log.debug("Using fingerprint: {}".format(i))
|
||||||
|
public_key_fingerprint = i
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
log.debug("Fingerprint unknown: {}".format(i))
|
||||||
|
else:
|
||||||
|
raise Exception("Public key not found")
|
||||||
|
|
||||||
# Step 3
|
# Step 3
|
||||||
pq = int.from_bytes(res_pq.pq, "big")
|
pq = int.from_bytes(res_pq.pq, "big")
|
||||||
@ -118,7 +129,7 @@ class Auth:
|
|||||||
sha = sha1(data).digest()
|
sha = sha1(data).digest()
|
||||||
padding = urandom(- (len(data) + len(sha)) % 255)
|
padding = urandom(- (len(data) + len(sha)) % 255)
|
||||||
data_with_hash = sha + data + padding
|
data_with_hash = sha + data + padding
|
||||||
encrypted_data = RSA.encrypt(data_with_hash, res_pq.server_public_key_fingerprints[0])
|
encrypted_data = RSA.encrypt(data_with_hash, public_key_fingerprint)
|
||||||
|
|
||||||
log.debug("Done encrypt data with RSA")
|
log.debug("Done encrypt data with RSA")
|
||||||
|
|
||||||
@ -130,7 +141,7 @@ class Auth:
|
|||||||
server_nonce,
|
server_nonce,
|
||||||
int.to_bytes(p, 4, "big"),
|
int.to_bytes(p, 4, "big"),
|
||||||
int.to_bytes(q, 4, "big"),
|
int.to_bytes(q, 4, "big"),
|
||||||
res_pq.server_public_key_fingerprints[0],
|
public_key_fingerprint,
|
||||||
encrypted_data
|
encrypted_data
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -152,7 +163,7 @@ class Auth:
|
|||||||
|
|
||||||
server_nonce = int.from_bytes(server_nonce, "little", signed=True)
|
server_nonce = int.from_bytes(server_nonce, "little", signed=True)
|
||||||
|
|
||||||
answer_with_hash = IGE.decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
|
answer_with_hash = AES.ige_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
|
||||||
answer = answer_with_hash[20:]
|
answer = answer_with_hash[20:]
|
||||||
|
|
||||||
server_dh_inner_data = Object.read(BytesIO(answer))
|
server_dh_inner_data = Object.read(BytesIO(answer))
|
||||||
@ -181,7 +192,7 @@ class Auth:
|
|||||||
sha = sha1(data).digest()
|
sha = sha1(data).digest()
|
||||||
padding = urandom(- (len(data) + len(sha)) % 16)
|
padding = urandom(- (len(data) + len(sha)) % 16)
|
||||||
data_with_hash = sha + data + padding
|
data_with_hash = sha + data + padding
|
||||||
encrypted_data = IGE.encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv)
|
encrypted_data = AES.ige_encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv)
|
||||||
|
|
||||||
log.debug("Send set_client_DH_params")
|
log.debug("Send set_client_DH_params")
|
||||||
set_client_dh_params_answer = self.send(
|
set_client_dh_params_answer = self.send(
|
||||||
@ -236,7 +247,7 @@ class Auth:
|
|||||||
log.debug("Nonce fields check: OK")
|
log.debug("Nonce fields check: OK")
|
||||||
|
|
||||||
# Step 9
|
# Step 9
|
||||||
server_salt = IGE.xor(new_nonce[:8], server_nonce[:8])
|
server_salt = AES.xor(new_nonce[:8], server_nonce[:8])
|
||||||
|
|
||||||
log.debug("Server salt: {}".format(int.from_bytes(server_salt, "little")))
|
log.debug("Server salt: {}".format(int.from_bytes(server_salt, "little")))
|
||||||
|
|
||||||
|
@ -26,14 +26,13 @@ not_content_related = [Ping, HttpWait, MsgsAck, MsgContainer]
|
|||||||
|
|
||||||
|
|
||||||
class MsgFactory:
|
class MsgFactory:
|
||||||
def __init__(self, msg_id: MsgId):
|
def __init__(self):
|
||||||
self.msg_id = msg_id
|
|
||||||
self.seq_no = SeqNo()
|
self.seq_no = SeqNo()
|
||||||
|
|
||||||
def __call__(self, body: Object) -> Message:
|
def __call__(self, body: Object) -> Message:
|
||||||
return Message(
|
return Message(
|
||||||
body,
|
body,
|
||||||
self.msg_id(),
|
MsgId(),
|
||||||
self.seq_no(type(body) not in not_content_related),
|
self.seq_no(type(body) not in not_content_related),
|
||||||
len(body)
|
len(body)
|
||||||
)
|
)
|
||||||
|
@ -16,19 +16,20 @@
|
|||||||
# 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/>.
|
||||||
|
|
||||||
|
from threading import Lock
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
|
||||||
class MsgId:
|
class MsgId:
|
||||||
def __init__(self, delta_time: float = 0.0):
|
last_time = 0
|
||||||
self.delta_time = delta_time
|
offset = 0
|
||||||
self.last_time = 0
|
lock = Lock()
|
||||||
self.offset = 0
|
|
||||||
|
|
||||||
def __call__(self) -> int:
|
def __new__(cls) -> int:
|
||||||
now = time()
|
with cls.lock:
|
||||||
self.offset = self.offset + 4 if now == self.last_time else 0
|
now = time()
|
||||||
msg_id = int((now + self.delta_time) * 2 ** 32) + self.offset
|
cls.offset = cls.offset + 4 if now == cls.last_time else 0
|
||||||
self.last_time = now
|
msg_id = int(now * 2 ** 32) + cls.offset
|
||||||
|
cls.last_time = now
|
||||||
|
|
||||||
return msg_id
|
return msg_id
|
||||||
|
@ -16,15 +16,19 @@
|
|||||||
# 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/>.
|
||||||
|
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
|
||||||
class SeqNo:
|
class SeqNo:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.content_related_messages_sent = 0
|
self.content_related_messages_sent = 0
|
||||||
|
self.lock = Lock()
|
||||||
|
|
||||||
def __call__(self, is_content_related: bool) -> int:
|
def __call__(self, is_content_related: bool) -> int:
|
||||||
seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0)
|
with self.lock:
|
||||||
|
seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0)
|
||||||
|
|
||||||
if is_content_related:
|
if is_content_related:
|
||||||
self.content_related_messages_sent += 1
|
self.content_related_messages_sent += 1
|
||||||
|
|
||||||
return seq_no
|
return seq_no
|
||||||
|
@ -33,7 +33,7 @@ from pyrogram.api.all import layer
|
|||||||
from pyrogram.api.core import Message, Object, MsgContainer, Long, FutureSalt, Int
|
from pyrogram.api.core import Message, Object, MsgContainer, Long, FutureSalt, Int
|
||||||
from pyrogram.api.errors import Error
|
from pyrogram.api.errors import Error
|
||||||
from pyrogram.connection import Connection
|
from pyrogram.connection import Connection
|
||||||
from pyrogram.crypto import IGE, KDF
|
from pyrogram.crypto import AES, KDF
|
||||||
from .internals import MsgId, MsgFactory, DataCenter
|
from .internals import MsgId, MsgFactory, DataCenter
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -60,7 +60,7 @@ class Session:
|
|||||||
)
|
)
|
||||||
|
|
||||||
INITIAL_SALT = 0x616e67656c696361
|
INITIAL_SALT = 0x616e67656c696361
|
||||||
NET_WORKERS = 2
|
NET_WORKERS = 1
|
||||||
WAIT_TIMEOUT = 10
|
WAIT_TIMEOUT = 10
|
||||||
MAX_RETRIES = 5
|
MAX_RETRIES = 5
|
||||||
ACKS_THRESHOLD = 8
|
ACKS_THRESHOLD = 8
|
||||||
@ -68,6 +68,20 @@ class Session:
|
|||||||
|
|
||||||
notice_displayed = False
|
notice_displayed = False
|
||||||
|
|
||||||
|
BAD_MSG_DESCRIPTION = {
|
||||||
|
16: "[16] msg_id too low, the client time has to be synchronized",
|
||||||
|
17: "[17] msg_id too high, the client time has to be synchronized",
|
||||||
|
18: "[18] incorrect two lower order msg_id bits, the server expects client message msg_id to be divisible by 4",
|
||||||
|
19: "[19] container msg_id is the same as msg_id of a previously received message",
|
||||||
|
20: "[20] message too old, it cannot be verified by the server",
|
||||||
|
32: "[32] msg_seqno too low",
|
||||||
|
33: "[33] msg_seqno too high",
|
||||||
|
34: "[34] an even msg_seqno expected, but odd received",
|
||||||
|
35: "[35] odd msg_seqno expected, but even received",
|
||||||
|
48: "[48] incorrect server salt",
|
||||||
|
64: "[64] invalid container"
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
dc_id: int,
|
dc_id: int,
|
||||||
test_mode: bool,
|
test_mode: bool,
|
||||||
@ -89,9 +103,8 @@ class Session:
|
|||||||
self.auth_key = auth_key
|
self.auth_key = auth_key
|
||||||
self.auth_key_id = sha1(auth_key).digest()[-8:]
|
self.auth_key_id = sha1(auth_key).digest()[-8:]
|
||||||
|
|
||||||
self.msg_id = MsgId()
|
self.session_id = Long(MsgId())
|
||||||
self.session_id = Long(self.msg_id())
|
self.msg_factory = MsgFactory()
|
||||||
self.msg_factory = MsgFactory(self.msg_id)
|
|
||||||
|
|
||||||
self.current_salt = None
|
self.current_salt = None
|
||||||
|
|
||||||
@ -146,7 +159,7 @@ class Session:
|
|||||||
self.ping_thread.start()
|
self.ping_thread.start()
|
||||||
|
|
||||||
log.info("Connection inited: Layer {}".format(layer))
|
log.info("Connection inited: Layer {}".format(layer))
|
||||||
except (OSError, TimeoutError):
|
except (OSError, TimeoutError, Error):
|
||||||
self.stop()
|
self.stop()
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
@ -192,14 +205,14 @@ class Session:
|
|||||||
msg_key = msg_key_large[8:24]
|
msg_key = msg_key_large[8:24]
|
||||||
aes_key, aes_iv = KDF(self.auth_key, msg_key, True)
|
aes_key, aes_iv = KDF(self.auth_key, msg_key, True)
|
||||||
|
|
||||||
return self.auth_key_id + msg_key + IGE.encrypt(data + padding, aes_key, aes_iv)
|
return self.auth_key_id + msg_key + AES.ige_encrypt(data + padding, aes_key, aes_iv)
|
||||||
|
|
||||||
def unpack(self, b: BytesIO) -> Message:
|
def unpack(self, b: BytesIO) -> Message:
|
||||||
assert b.read(8) == self.auth_key_id, b.getvalue()
|
assert b.read(8) == self.auth_key_id, b.getvalue()
|
||||||
|
|
||||||
msg_key = b.read(16)
|
msg_key = b.read(16)
|
||||||
aes_key, aes_iv = KDF(self.auth_key, msg_key, False)
|
aes_key, aes_iv = KDF(self.auth_key, msg_key, False)
|
||||||
data = BytesIO(IGE.decrypt(b.read(), aes_key, aes_iv))
|
data = BytesIO(AES.ige_decrypt(b.read(), aes_key, aes_iv))
|
||||||
data.read(8)
|
data.read(8)
|
||||||
|
|
||||||
# https://core.telegram.org/mtproto/security_guidelines#checking-session-id
|
# https://core.telegram.org/mtproto/security_guidelines#checking-session-id
|
||||||
@ -270,7 +283,7 @@ class Session:
|
|||||||
msg_id = msg.body.msg_id
|
msg_id = msg.body.msg_id
|
||||||
else:
|
else:
|
||||||
if self.client is not None:
|
if self.client is not None:
|
||||||
self.client.update_queue.put(msg.body)
|
self.client.updates_queue.put(msg.body)
|
||||||
|
|
||||||
if msg_id in self.results:
|
if msg_id in self.results:
|
||||||
self.results[msg_id].value = getattr(msg.body, "result", msg.body)
|
self.results[msg_id].value = getattr(msg.body, "result", msg.body)
|
||||||
@ -296,7 +309,7 @@ class Session:
|
|||||||
break
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._send(functions.Ping(0), False)
|
self._send(functions.PingDelayDisconnect(0, self.PING_INTERVAL + 15), False)
|
||||||
except (OSError, TimeoutError):
|
except (OSError, TimeoutError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -338,7 +351,10 @@ class Session:
|
|||||||
while True:
|
while True:
|
||||||
packet = self.connection.recv()
|
packet = self.connection.recv()
|
||||||
|
|
||||||
if packet is None or (len(packet) == 4 and Int.read(BytesIO(packet)) == -404):
|
if packet is None or len(packet) == 4:
|
||||||
|
if packet:
|
||||||
|
log.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet))))
|
||||||
|
|
||||||
if self.is_connected.is_set():
|
if self.is_connected.is_set():
|
||||||
Thread(target=self.restart, name="RestartThread").start()
|
Thread(target=self.restart, name="RestartThread").start()
|
||||||
break
|
break
|
||||||
@ -370,6 +386,11 @@ class Session:
|
|||||||
raise TimeoutError
|
raise TimeoutError
|
||||||
elif isinstance(result, types.RpcError):
|
elif isinstance(result, types.RpcError):
|
||||||
Error.raise_it(result, type(data))
|
Error.raise_it(result, type(data))
|
||||||
|
elif isinstance(result, types.BadMsgNotification):
|
||||||
|
raise Exception(self.BAD_MSG_DESCRIPTION.get(
|
||||||
|
result.error_code,
|
||||||
|
"Error code {}".format(result.error_code)
|
||||||
|
))
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
12
setup.py
12
setup.py
@ -37,7 +37,7 @@ with open("pyrogram/__init__.py", encoding="utf-8") as f:
|
|||||||
# 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())
|
||||||
readme = re.sub(r"\|header\|", "|logo|\n\n|description|\n\n|scheme| |mtproto|", readme)
|
readme = re.sub(r"\|header\|", "|logo|\n\n|description|\n\n|scheme| |tgcrypto|", readme)
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="Pyrogram",
|
name="Pyrogram",
|
||||||
@ -67,6 +67,14 @@ setup(
|
|||||||
],
|
],
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
install_requires=["pyaes", "pysocks"],
|
install_requires=[
|
||||||
|
"pyaes",
|
||||||
|
"pysocks"
|
||||||
|
],
|
||||||
|
extras_require={
|
||||||
|
"tgcrypto": [
|
||||||
|
"tgcrypto"
|
||||||
|
]
|
||||||
|
},
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user