Merge branch 'master' into new-api

This commit is contained in:
Dan 2018-03-09 13:28:16 +01:00
commit 4132a1d266
34 changed files with 9548 additions and 409 deletions

View File

@ -3,7 +3,7 @@
Table of Contents
=================
- `Overview`_
- `About`_
- `Features`_
@ -26,8 +26,8 @@ Table of Contents
- `License`_
Overview
========
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
@ -37,20 +37,23 @@ developers keen on building custom Telegram applications.
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
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
Telegram Crypto Library written in C as a Python extension.
- **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.
@ -77,6 +80,12 @@ Installation
.. code:: shell
$ pip install --upgrade pyrogram
- Or, with TgCrypto_:
.. code:: shell
$ pip install --upgrade pyrogram[tgcrypto]
Configuration
-------------
@ -93,7 +102,7 @@ Configuration
Usage
-----
- And here's how Pyrogram looks:
- And here's how Pyrogram looks like:
.. code:: python
@ -130,6 +139,7 @@ Feedback
Means for getting in touch:
- `Community`_
- `Telegram`_
- `GitHub`_
- `Email`_
@ -156,12 +166,16 @@ License
.. _`Telegram`: https://t.me/haskell
.. _`Community`: https://t.me/PyrogramChat
.. _`bot-like`: https://core.telegram.org/bots/api#available-methods
.. _`GitHub`: https://github.com/pyrogram/pyrogram/issues
.. _`Email`: admin@pyrogram.ml
.. _TgCrypto: https://github.com/pyrogram/tgcrypto
.. _`GNU Lesser General Public License v3 or later (LGPLv3+)`: COPYING.lesser
.. |header| raw:: html
@ -177,7 +191,7 @@ License
<b>Telegram MTProto API Client Library for Python</b>
<br>
<a href="https://pypi.python.org/pypi/Pyrogram">
<a href="https://github.com/pyrogram/pyrogram/releases/latest">
Download
</a>
@ -187,15 +201,15 @@ License
<a href="https://t.me/PyrogramChat">
Community
</a
<br><br><br>
</a>
<br><br>
<a href="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 href="https://github.com/pyrogram/tgcrypto">
<img src="https://www.pyrogram.ml/images/tgcrypto.svg"
alt="TgCrypto">
</a>
</p>
@ -209,6 +223,6 @@ License
:target: compiler/api/source/main_api.tl
:alt: Scheme Layer 75
.. |mtproto| image:: https://www.pyrogram.ml/images/mtproto.svg
:target: https://core.telegram.org/mtproto
:alt: MTProto v2.0
.. |tgcrypto| image:: https://www.pyrogram.ml/images/tgcrypto.svg
:target: https://github.com/pyrogram/tgcrypto
:alt: TgCrypto

View File

@ -29,6 +29,7 @@ destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
---functions---
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;

View File

@ -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
FILE_ID_INVALID The file id 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
ABOUT_TOO_LONG The about text is too long
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
1 id message
42 VOLUME_LOC_NOT_FOUND The volume location can't be found
43 FILE_ID_INVALID The file id is invalid
44 LOCATION_INVALID The file location is invalid
45 CHAT_ADMIN_REQUIRED The method requires admin privileges The method requires chat admin privileges
46 PHONE_NUMBER_BANNED The phone number is banned
47 ABOUT_TOO_LONG The about text is too long
48 MULTI_MEDIA_TOO_LONG The album contains more than 10 items
49 USERNAME_OCCUPIED The username is already in use
50 BOT_INLINE_DISABLED The inline feature of the bot is disabled
51 INLINE_RESULT_EXPIRED The inline bot query expired

View File

@ -3,16 +3,77 @@ Welcome to Pyrogram
.. 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">
<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>
</div>
<p align="center">
<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>
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
-------
@ -28,12 +89,6 @@ Preview
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.
.. toctree::
@ -51,6 +106,9 @@ To get started, press Next.
resources/TextFormatting
resources/UpdateHandling
resources/ErrorHandling
resources/ProxyServer
resources/AutoAuthorization
resources/FastCrypto
.. toctree::
:hidden:
@ -64,3 +122,9 @@ To get started, press Next.
functions/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

View 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
View 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
View 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()

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

37
examples/get_history.py Normal file
View 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)

View 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
View 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
View 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
View 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
View 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
View 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()

View File

@ -23,10 +23,12 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance
"e" if sys.getfilesystemencoding() == "ascii" else "\xe8"
)
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
__version__ = "0.5.0"
__version__ = "0.6.2"
from .api.errors import Error
from .client import ChatAction
from .client import Client
from .client import ParseMode
from .client.input_media import InputMedia
from .client.input_phone_contact import InputPhoneContact
from .client import Emoji

View File

@ -19,3 +19,4 @@
from .chat_action import ChatAction
from .client import Client
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

File diff suppressed because it is too large Load Diff

View File

@ -20,10 +20,11 @@
class InputMedia:
class Photo:
"""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:
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.
caption (:obj:`str`):
@ -45,19 +46,32 @@ class InputMedia:
class Video:
"""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:
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.
caption (:obj:`str`):
caption (:obj:`str`, optional):
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
to show bold, italic, fixed-width text or inline URLs in your caption.
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,
@ -66,10 +80,12 @@ class InputMedia:
parse_mode: str = "",
width: int = 0,
height: int = 0,
duration: int = 0):
duration: int = 0,
supports_streaming: bool = None):
self.media = media
self.caption = caption
self.parse_mode = parse_mode
self.width = width
self.height = height
self.duration = duration
self.supports_streaming = supports_streaming

View File

@ -16,20 +16,29 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
try:
from pyaes import AESModeOfOperationCTR
except ImportError:
pass
from pyrogram.api.types import InputPhoneContact as RawInputPhoneContact
from pyrogram.session.internals import MsgId
class CTR:
def __init__(self, key: bytes, iv: bytes):
self.ctr = AESModeOfOperationCTR(key)
self.iv = iv
class InputPhoneContact:
"""This object represents a Phone Contact to be added in your Telegram address book.
It is intended to be used with :obj:`pyrogram.Client.add_contacts`
def decrypt(self, data: bytes, offset: int) -> bytes:
replace = int.to_bytes(offset // 16, byteorder="big", length=4)
iv = self.iv[:-4] + replace
self.ctr._counter._counter = list(iv)
Args:
phone (:obj:`str`):
Contact's phone number
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
)

View File

@ -31,7 +31,7 @@ from . import utils
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+)")
def __init__(self, peers_by_id):
@ -44,7 +44,7 @@ class HTML:
for match in self.HTML_RE.finditer(text):
start = match.start() - offset
style, url, body = match.groups()
style, url, body = match.group(1, 3, 4)
if url:
mention = self.MENTION_RE.match(url)

View File

@ -24,95 +24,84 @@ from pyrogram.api.types import (
MessageEntityCode as Code,
MessageEntityTextUrl as Url,
MessageEntityPre as Pre,
MessageEntityMentionName as MentionInvalid,
InputMessageEntityMentionName as Mention
)
from . import utils
class Markdown:
INLINE_DELIMITERS = {
"**": Bold,
"__": Italic,
"`": Code
}
BOLD_DELIMITER = "**"
ITALIC_DELIMITER = "__"
CODE_DELIMITER = "`"
PRE_DELIMITER = "```"
# ``` python
# 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(
MARKDOWN_RE = re.compile(r"```([\w ]*)\n([\w\W]*)(?:\n|)```|\[([^[(]+)\]\(([^])]+)\)|({d})(.+?)\5".format(
d="|".join(
["".join(i) for i in [
["\{}".format(j) for j in i]
for i in sorted( # Sort delimiters by length
INLINE_DELIMITERS.keys(),
key=lambda k: len(k), # Or: key=len
reverse=True
)
for i in [
PRE_DELIMITER,
CODE_DELIMITER,
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):
def __init__(self, peers_by_id: dict):
self.peers_by_id = peers_by_id
def parse(self, text):
def parse(self, message: str):
entities = []
text = utils.add_surrogates(text)
message = utils.add_surrogates(message).strip()
offset = 0
for match in self.MARKDOWN_RE.finditer(text):
for match in self.MARKDOWN_RE.finditer(message):
start = match.start() - offset
lang, pre, text, url, style, body = match.groups()
if match.group("pre"):
pattern = match.group("pre")
lang = match.group("lang")
replace = match.group("code")
entity = Pre(start, len(replace), lang.strip())
offset += len(lang) + 8
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 pre:
body = pre = pre.strip()
entity = Pre(start, len(pre), lang.strip() or "")
offset += len(lang) + len(self.PRE_DELIMITER) * 2
elif url:
mention = self.MENTION_RE.match(url)
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
entity = self.INLINE_DELIMITERS[start_delimiter](start, len(replace))
offset += len(start_delimiter) * 2
else:
continue
offset += len(style) * 2
entities.append(entity)
text = text.replace(pattern, replace)
message = message.replace(match.group(), body)
return dict(
message=utils.remove_surrogates(text),
message=utils.remove_surrogates(message),
entities=entities
)

View File

@ -30,6 +30,7 @@ Proxy = namedtuple("Proxy", ["enabled", "hostname", "port", "username", "passwor
class TCP(socks.socksocket):
def __init__(self, proxy: Proxy):
super().__init__()
self.settimeout(10)
self.proxy_enabled = False
if proxy and proxy.enabled:
@ -43,6 +44,11 @@ class TCP(socks.socksocket):
password=proxy.password
)
log.info("Using proxy {}:{}".format(
proxy.hostname,
proxy.port
))
def close(self):
try:
self.shutdown(socket.SHUT_RDWR)

View File

@ -16,8 +16,7 @@
# 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 .ctr import CTR
from .ige import IGE
from .aes import AES
from .kdf import KDF
from .prime import Prime
from .rsa import RSA

View File

@ -16,21 +16,52 @@
# 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 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
class IGE:
# TODO: Ugly IFs
class AES:
@classmethod
def encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return cls.ige(data, key, iv, True)
def ige_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
if is_fast:
return tgcrypto.ige_encrypt(data, key, iv)
else:
return cls.ige(data, key, iv, True)
@classmethod
def decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return cls.ige(data, key, iv, False)
def ige_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
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
def xor(a: bytes, b: bytes) -> bytes:
@ -42,12 +73,12 @@ class IGE:
@classmethod
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_2 = iv[BLOCK_SIZE:]
iv_1 = iv[:16]
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:
for i, chunk in enumerate(data):

View File

@ -23,10 +23,16 @@ PublicKey = namedtuple("PublicKey", ["m", "e"])
class RSA:
# To get modulus and exponent:
#
# [RSA PUBLIC KEY]:
# 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 = {
0xc3b42b026ce86b21 - (1 << 64): PublicKey( # Telegram servers
# -4344800451088585951
0xc3b42b026ce86b21 - (1 << 64): PublicKey( # Telegram servers #1
# -----BEGIN RSA PUBLIC KEY-----
# MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
# lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
@ -48,6 +54,108 @@ class RSA:
), # Modulus
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
# -----BEGIN RSA PUBLIC KEY-----
# MIIBCgKCAQEA+Lf3PvgE1yxbJUCMaEAkV0QySTVpnaDjiednB5RbtNWjCeqSVakY
@ -70,6 +178,8 @@ class RSA:
), # Modulus
int("010001", 16) # Exponent
),
# 2685959930972952888
0x1254672538e935938 - (1 << 64): PublicKey( # CDN DC-140
# -----BEGIN RSA PUBLIC KEY-----
# MIIBCgKCAQEAzuHVC7sE50Kho/yDVZtWnlmA5Bf/aM8KZY3WzS16w6w1sBqipj8o

View File

@ -25,7 +25,7 @@ from os import urandom
from pyrogram.api import functions, types
from pyrogram.api.core import Object, Long, Int
from pyrogram.connection import Connection
from pyrogram.crypto import IGE, RSA, Prime
from pyrogram.crypto import AES, RSA, Prime
from .internals import MsgId, DataCenter
log = logging.getLogger(__name__)
@ -51,12 +51,12 @@ class Auth:
self.test_mode = test_mode
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 (
bytes(8)
+ Long(self.msg_id())
+ Long(MsgId())
+ Int(len(data.write()))
+ data.write()
)
@ -91,8 +91,19 @@ class Auth:
# Step 1; Step 2
nonce = int.from_bytes(urandom(16), "little", signed=True)
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("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
pq = int.from_bytes(res_pq.pq, "big")
@ -118,7 +129,7 @@ class Auth:
sha = sha1(data).digest()
padding = urandom(- (len(data) + len(sha)) % 255)
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")
@ -130,7 +141,7 @@ class Auth:
server_nonce,
int.to_bytes(p, 4, "big"),
int.to_bytes(q, 4, "big"),
res_pq.server_public_key_fingerprints[0],
public_key_fingerprint,
encrypted_data
)
)
@ -152,7 +163,7 @@ class Auth:
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:]
server_dh_inner_data = Object.read(BytesIO(answer))
@ -181,7 +192,7 @@ class Auth:
sha = sha1(data).digest()
padding = urandom(- (len(data) + len(sha)) % 16)
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")
set_client_dh_params_answer = self.send(
@ -236,7 +247,7 @@ class Auth:
log.debug("Nonce fields check: OK")
# 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")))

View File

@ -26,14 +26,13 @@ not_content_related = [Ping, HttpWait, MsgsAck, MsgContainer]
class MsgFactory:
def __init__(self, msg_id: MsgId):
self.msg_id = msg_id
def __init__(self):
self.seq_no = SeqNo()
def __call__(self, body: Object) -> Message:
return Message(
body,
self.msg_id(),
MsgId(),
self.seq_no(type(body) not in not_content_related),
len(body)
)

View File

@ -16,19 +16,20 @@
# 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 threading import Lock
from time import time
class MsgId:
def __init__(self, delta_time: float = 0.0):
self.delta_time = delta_time
self.last_time = 0
self.offset = 0
last_time = 0
offset = 0
lock = Lock()
def __call__(self) -> int:
now = time()
self.offset = self.offset + 4 if now == self.last_time else 0
msg_id = int((now + self.delta_time) * 2 ** 32) + self.offset
self.last_time = now
def __new__(cls) -> int:
with cls.lock:
now = time()
cls.offset = cls.offset + 4 if now == cls.last_time else 0
msg_id = int(now * 2 ** 32) + cls.offset
cls.last_time = now
return msg_id
return msg_id

View File

@ -16,15 +16,19 @@
# 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 threading import Lock
class SeqNo:
def __init__(self):
self.content_related_messages_sent = 0
self.lock = Lock()
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:
self.content_related_messages_sent += 1
if is_content_related:
self.content_related_messages_sent += 1
return seq_no
return seq_no

View File

@ -33,7 +33,7 @@ from pyrogram.api.all import layer
from pyrogram.api.core import Message, Object, MsgContainer, Long, FutureSalt, Int
from pyrogram.api.errors import Error
from pyrogram.connection import Connection
from pyrogram.crypto import IGE, KDF
from pyrogram.crypto import AES, KDF
from .internals import MsgId, MsgFactory, DataCenter
log = logging.getLogger(__name__)
@ -60,7 +60,7 @@ class Session:
)
INITIAL_SALT = 0x616e67656c696361
NET_WORKERS = 2
NET_WORKERS = 1
WAIT_TIMEOUT = 10
MAX_RETRIES = 5
ACKS_THRESHOLD = 8
@ -68,6 +68,20 @@ class Session:
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,
dc_id: int,
test_mode: bool,
@ -89,9 +103,8 @@ class Session:
self.auth_key = auth_key
self.auth_key_id = sha1(auth_key).digest()[-8:]
self.msg_id = MsgId()
self.session_id = Long(self.msg_id())
self.msg_factory = MsgFactory(self.msg_id)
self.session_id = Long(MsgId())
self.msg_factory = MsgFactory()
self.current_salt = None
@ -146,7 +159,7 @@ class Session:
self.ping_thread.start()
log.info("Connection inited: Layer {}".format(layer))
except (OSError, TimeoutError):
except (OSError, TimeoutError, Error):
self.stop()
else:
break
@ -192,14 +205,14 @@ class Session:
msg_key = msg_key_large[8:24]
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:
assert b.read(8) == self.auth_key_id, b.getvalue()
msg_key = b.read(16)
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)
# https://core.telegram.org/mtproto/security_guidelines#checking-session-id
@ -270,7 +283,7 @@ class Session:
msg_id = msg.body.msg_id
else:
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:
self.results[msg_id].value = getattr(msg.body, "result", msg.body)
@ -296,7 +309,7 @@ class Session:
break
try:
self._send(functions.Ping(0), False)
self._send(functions.PingDelayDisconnect(0, self.PING_INTERVAL + 15), False)
except (OSError, TimeoutError):
pass
@ -338,7 +351,10 @@ class Session:
while True:
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():
Thread(target=self.restart, name="RestartThread").start()
break
@ -370,6 +386,11 @@ class Session:
raise TimeoutError
elif isinstance(result, types.RpcError):
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:
return result

View File

@ -37,7 +37,7 @@ with open("pyrogram/__init__.py", encoding="utf-8") as f:
# PyPI doesn't like raw html
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"\|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(
name="Pyrogram",
@ -67,6 +67,14 @@ setup(
],
packages=find_packages(),
zip_safe=False,
install_requires=["pyaes", "pysocks"],
install_requires=[
"pyaes",
"pysocks"
],
extras_require={
"tgcrypto": [
"tgcrypto"
]
},
include_package_data=True,
)