From ca54b62f636b8b1fa3b50517682079410eb6cff9 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 10 Mar 2018 15:21:31 +0100
Subject: [PATCH 01/88] Strip "+" away from phone numbers when logging in
---
pyrogram/client/client.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 5a834ee7..b382a1fd 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -572,6 +572,8 @@ class Client:
elif confirm in ("n", "2"):
self.phone_number = input("Enter phone number: ")
+ self.phone_number = self.phone_number.strip("+")
+
try:
r = self.send(
functions.auth.SendCode(
From 62e67f5257e1b49efa69ac309662ff52689d1bc4 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 11 Mar 2018 17:16:38 +0100
Subject: [PATCH 02/88] Add get_messages method
---
pyrogram/client/client.py | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index b382a1fd..e554ae3a 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2754,3 +2754,39 @@ class Client:
reply_to_msg_id=reply_to_message_id
)
)
+
+ def get_messages(self,
+ chat_id: int or str,
+ message_ids: list):
+ """Use this method to get messages that belong to a specific chat.
+ You can retrieve up to 200 messages at once.
+
+ Args:
+ chat_id (:obj:`int` | :obj:`str`):
+ Unique identifier for the target chat or username of the target channel/supergroup
+ (in the format @username). For your personal cloud storage (Saved Messages) you can
+ simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+
+ message_ids (:obj:`list`):
+ A list of Message identifiers in the chat specified in *chat_id*.
+
+ Returns:
+ List of the requested messages
+
+ Raises:
+ :class:`pyrogram.Error`
+ """
+ peer = self.resolve_peer(chat_id)
+ message_ids = [types.InputMessageID(i) for i in message_ids]
+
+ if isinstance(peer, types.InputPeerChannel):
+ rpc = functions.channels.GetMessages(
+ channel=peer,
+ id=message_ids
+ )
+ else:
+ rpc = functions.messages.GetMessages(
+ id=message_ids
+ )
+
+ return self.send(rpc)
From a2bab5d790ed95c524488d3359d95d36e389e618 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 11 Mar 2018 19:32:22 +0100
Subject: [PATCH 03/88] Use pip3 command instead of pip
---
README.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.rst b/README.rst
index 554ef7a1..9bdcf828 100644
--- a/README.rst
+++ b/README.rst
@@ -79,13 +79,13 @@ Installation
.. code:: shell
- $ pip install --upgrade pyrogram
+ $ pip3 install --upgrade pyrogram
- Or, with TgCrypto_:
.. code:: shell
- $ pip install --upgrade pyrogram[tgcrypto]
+ $ pip3 install --upgrade pyrogram[tgcrypto]
Configuration
-------------
From 2e5073f114781a71f525b2a15290aae03bb77af6 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 11 Mar 2018 19:36:07 +0100
Subject: [PATCH 04/88] Fix cross-reference link
---
README.rst | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/README.rst b/README.rst
index 9bdcf828..e3f8e9f8 100644
--- a/README.rst
+++ b/README.rst
@@ -160,9 +160,7 @@ License
.. _`Layer 75`: compiler/api/source/main_api.tl
-.. _`your own`: https://github.com/pyrogram/pyrogram/wiki/Getting-Started#api-keys
-
-.. _`Introduction`: https://github.com/pyrogram/pyrogram/wiki/Getting-Started
+.. _`your own`: https://docs.pyrogram.ml/start/ProjectSetup/#api-keys
.. _`Telegram`: https://t.me/haskell
From b757d626330e0cfb3806d605498721a647753f9f Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 12 Mar 2018 19:17:15 +0100
Subject: [PATCH 05/88] Update package info
---
setup.py | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/setup.py b/setup.py
index 10294494..a012d9a1 100644
--- a/setup.py
+++ b/setup.py
@@ -43,12 +43,12 @@ setup(
name="Pyrogram",
version=version,
description="Telegram MTProto API Client Library for Python",
- url="https://github.com/pyrogram/pyrogram",
+ long_description=readme,
+ url="https://github.com/pyrogram",
+ download_url="https://github.com/pyrogram/pyrogram/releases/latest",
author="Dan Tès",
author_email="admin@pyrogram.ml",
license="LGPLv3+",
- keywords="telegram mtproto api client library python",
- long_description=readme,
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
@@ -60,11 +60,24 @@ setup(
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: Implementation",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet",
+ "Topic :: Communications",
"Topic :: Communications :: Chat",
"Topic :: Software Development :: Libraries",
- "Topic :: Software Development :: Libraries :: Python Modules"
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Software Development :: Libraries :: Application Frameworks"
],
+ keywords="telegram mtproto api client library python",
+ project_urls={
+ "Tracker": "https://github.com/pyrogram/pyrogram/issues",
+ "Community": "https://t.me/PyrogramChat",
+ "Source": "https://github.com/pyrogram/pyrogram",
+ "Documentation": "https://docs.pyrogram.ml",
+ },
+ python_requires="~=3.3",
packages=find_packages(),
zip_safe=False,
install_requires=[
From 475012144d7ad97370112ffba7aa3ed1691f090b Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 13 Mar 2018 00:21:48 +0100
Subject: [PATCH 06/88] Increase WAIT_TIMEOUT to 30s
---
pyrogram/session/session.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 3ea7ed15..6225fe44 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -61,7 +61,7 @@ class Session:
INITIAL_SALT = 0x616e67656c696361
NET_WORKERS = 1
- WAIT_TIMEOUT = 10
+ WAIT_TIMEOUT = 30
MAX_RETRIES = 5
ACKS_THRESHOLD = 8
PING_INTERVAL = 5
From f997e9749477df12cb8f3473614dcaf74690798a Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 13 Mar 2018 16:26:53 +0100
Subject: [PATCH 07/88] Add support for downloading photos not contained inside
a Message
---
pyrogram/client/client.py | 62 +++++++++++++++++++++++++++++++++++----
1 file changed, 56 insertions(+), 6 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index e554ae3a..973265be 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -337,13 +337,17 @@ class Client:
size=document.size,
progress=progress
)
- elif isinstance(media, types.MessageMediaPhoto):
- photo = media.photo
+ elif isinstance(media, (types.MessageMediaPhoto, types.Photo)):
+ if isinstance(media, types.MessageMediaPhoto):
+ photo = media.photo
+ else:
+ photo = media
if isinstance(photo, types.Photo):
if not file_name:
- file_name = "photo_{}.jpg".format(
- datetime.fromtimestamp(photo.date).strftime("%Y-%m-%d_%H-%M-%S")
+ file_name = "photo_{}_{}.jpg".format(
+ datetime.fromtimestamp(photo.date).strftime("%Y-%m-%d_%H-%M-%S"),
+ self.rnd_id()
)
photo_loc = photo.sizes[-1].location
@@ -2587,11 +2591,15 @@ class Client:
Raises:
:class:`pyrogram.Error`
"""
- if isinstance(message, types.Message):
+ if isinstance(message, (types.Message, types.Photo)):
done = Event()
- media = message.media
path = [None]
+ if isinstance(message, types.Message):
+ media = message.media
+ else:
+ media = message
+
if media is not None:
self.download_queue.put((media, file_name, done, progress, path))
else:
@@ -2602,6 +2610,48 @@ class Client:
return path[0]
+ def download_photo(self,
+ photo: types.Photo or types.UserProfilePhoto or types.ChatPhoto,
+ file_name: str = None,
+ block: bool = True):
+ """Use this method to download a photo not contained inside a Message.
+ For example, a photo of a User or a Chat/Channel.
+
+ Photos are saved in the *downloads* folder.
+
+ Args:
+ photo (:obj:`Photo ` | :obj:`UserProfilePhoto ` | :obj:`ChatPhoto `):
+ The photo object.
+
+ file_name (:obj:`str`, optional):
+ Specify a custom *file_name* to be used.
+
+ block (:obj:`bool`, optional):
+ Blocks the code execution until the photo has been downloaded.
+ Defaults to True.
+
+ Returns:
+ The relative path of the downloaded photo.
+
+ Raises:
+ :class:`pyrogram.Error`
+ """
+ if isinstance(photo, (types.UserProfilePhoto, types.ChatPhoto)):
+ photo = types.Photo(
+ id=0,
+ access_hash=0,
+ date=int(time.time()),
+ sizes=[types.PhotoSize(
+ type="",
+ location=photo.photo_big,
+ w=0,
+ h=0,
+ size=0
+ )]
+ )
+
+ return self.download_media(photo, file_name, block)
+
def add_contacts(self, contacts: list):
"""Use this method to add contacts to your Telegram address book.
From 7beb611cea5d4585dba5d811e547c0256fa4278d Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 14 Mar 2018 10:59:35 +0100
Subject: [PATCH 08/88] Update docstrings
---
pyrogram/client/client.py | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 973265be..54a6d2b8 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -513,16 +513,16 @@ class Client:
update (:obj:`Update`):
The received update, which can be one of the many single Updates listed in the *updates*
- field you see in the :obj:`types.Update ` type.
+ field you see in the :obj:`Update ` type.
users (:obj:`dict`):
- Dictionary of all :obj:`types.User ` mentioned in the update.
+ Dictionary of all :obj:`User ` mentioned in the update.
You can access extra info about the user (such as *first_name*, *last_name*, etc...) by using
the IDs you find in the *update* argument (e.g.: *users[1768841572]*).
chats (:obj:`dict`):
- Dictionary of all :obj:`types.Chat ` and
- :obj:`types.Channel ` mentioned in the update.
+ Dictionary of all :obj:`Chat ` and
+ :obj:`Channel ` mentioned in the update.
You can access extra info about the chat (such as *title*, *participants_count*, etc...)
by using the IDs you find in the *update* argument (e.g.: *chats[1701277281]*).
@@ -530,10 +530,10 @@ class Client:
The following Empty or Forbidden types may exist inside the *users* and *chats* dictionaries.
They mean you have been blocked by the user or banned from the group/channel.
- - :obj:`types.UserEmpty `
- - :obj:`types.ChatEmpty `
- - :obj:`types.ChatForbidden `
- - :obj:`types.ChannelForbidden `
+ - :obj:`UserEmpty `
+ - :obj:`ChatEmpty `
+ - :obj:`ChatForbidden `
+ - :obj:`ChannelForbidden `
"""
self.update_handler = callback
@@ -2742,7 +2742,7 @@ class Client:
Useful for location-based results only.
Returns:
- On Success, `BotResults `_ is returned.
+ On Success, :obj:`BotResults `_ is returned.
Raises:
:class:`pyrogram.Error`
From 3b2d264f5e01e2f7d9d97f254169a8c0eb6b7f13 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 14 Mar 2018 11:01:33 +0100
Subject: [PATCH 09/88] Fix reference link
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 54a6d2b8..6c47ec68 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2742,7 +2742,7 @@ class Client:
Useful for location-based results only.
Returns:
- On Success, :obj:`BotResults `_ is returned.
+ On Success, :obj:`BotResults ` is returned.
Raises:
:class:`pyrogram.Error`
From 5fc61e2fc96b0448513371a4764a4690a2a84a5a Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 14 Mar 2018 12:03:10 +0100
Subject: [PATCH 10/88] Update to v0.6.3
---
pyrogram/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index b2b85d0c..084cce6e 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès
Date: Thu, 15 Mar 2018 09:50:37 +0100
Subject: [PATCH 11/88] Add get_participants2.py
---
examples/README.md | 1 +
examples/get_participants2.py | 63 +++++++++++++++++++++++++++++++++++
2 files changed, 64 insertions(+)
create mode 100644 examples/get_participants2.py
diff --git a/examples/README.md b/examples/README.md
index 6f640ef4..545516fa 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -8,6 +8,7 @@ you have to change are the target chats (username, id) and file paths for sendin
- [**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)
+- [**get_participants2.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/get_participants2.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)
diff --git a/examples/get_participants2.py b/examples/get_participants2.py
new file mode 100644
index 00000000..23ed328f
--- /dev/null
+++ b/examples/get_participants2.py
@@ -0,0 +1,63 @@
+import time
+from string import ascii_lowercase
+
+from pyrogram import Client
+from pyrogram.api import functions, types
+from pyrogram.api.errors import FloodWait
+
+"""
+This is an improved version of get_participants.py
+
+Since Telegram will return at most 10.000 users for a single query, this script
+repeats the search using numbers ("0" to "9") and all the available ascii letters ("a" to "z").
+
+This can be further improved by also searching for non-ascii characters (e.g.: Japanese script),
+as some user names may not contain ascii letters at all.
+"""
+
+client = Client("example")
+client.start()
+
+target = "username" # Target channel/supergroup username or id
+users = {} # To ensure uniqueness, users will be stored in a dictionary with user_id as key
+limit = 200 # Amount of users to retrieve for each API call (200 is the maximum)
+
+# "" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" (as list)
+queries = [""] + [str(i) for i in range(10)] + list(ascii_lowercase)
+
+for q in queries:
+ print("Searching for '{}'".format(q))
+ offset = 0 # For each query, offset restarts from 0
+
+ while True:
+ try:
+ participants = client.send(
+ functions.channels.GetParticipants(
+ channel=client.resolve_peer(target),
+ filter=types.ChannelParticipantsSearch(q),
+ offset=offset,
+ limit=limit,
+ hash=0
+ )
+ )
+ except FloodWait as e:
+ # Very large chats could trigger FloodWait.
+ # When happens, wait X seconds before continuing
+ print("Flood wait: {} seconds".format(e.x))
+ time.sleep(e.x)
+ continue
+
+ if not participants.participants:
+ print("Done searching for '{}'".format(q))
+ print()
+ break # No more participants left
+
+ # User information are stored in the participants.users list.
+ # Add those users to the dictionary
+ users.update({i.id: i for i in participants.users})
+
+ offset += len(participants.participants)
+
+ print("Total users: {}".format(len(users)))
+
+client.stop()
From 756311710c77ba43409ef94f24df51c488d4a44b Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 15 Mar 2018 12:03:02 +0100
Subject: [PATCH 12/88] Don't GetTermsOfService() anymore
---
pyrogram/session/session.py | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 6225fe44..c1269d49 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -122,8 +122,6 @@ class Session:
self.is_connected = Event()
def start(self):
- terms = None
-
while True:
try:
self.connection.connect()
@@ -141,7 +139,7 @@ class Session:
self.next_salt_thread.start()
if not self.is_cdn:
- terms = self._send(
+ self._send(
functions.InvokeWithLayer(
layer,
functions.InitConnection(
@@ -150,10 +148,10 @@ class Session:
self.SYSTEM_VERSION,
self.APP_VERSION,
"en", "", "en",
- functions.help.GetTermsOfService(),
+ functions.help.GetConfig(),
)
)
- ).text
+ )
self.ping_thread = Thread(target=self.ping, name="PingThread")
self.ping_thread.start()
@@ -168,8 +166,6 @@ class Session:
log.debug("Session started")
- return terms
-
def stop(self):
self.is_connected.clear()
From 6d536107aeb8333cbf45c78d6cf4dedfbc7b72a5 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 15 Mar 2018 12:18:48 +0100
Subject: [PATCH 13/88] Add support for bots login via token
---
pyrogram/client/client.py | 374 +++++++++++++++++++++-----------------
1 file changed, 205 insertions(+), 169 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 6c47ec68..410e167c 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -41,7 +41,7 @@ from pyrogram.api.errors import (
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
PasswordHashInvalid, FloodWait, PeerIdInvalid, FilePartMissing,
ChatAdminRequired, FirstnameInvalid, PhoneNumberBanned,
- VolumeLocNotFound)
+ VolumeLocNotFound, UserMigrate)
from pyrogram.api.types import (
User, Chat, Channel,
PeerUser, PeerChannel,
@@ -123,6 +123,7 @@ class Client:
api_key: tuple or ApiKey = None,
proxy: dict or Proxy = None,
test_mode: bool = False,
+ token: str = None,
phone_number: str = None,
phone_code: str or callable = None,
password: str = None,
@@ -134,6 +135,7 @@ class Client:
self.proxy = proxy
self.test_mode = test_mode
+ self.token = token
self.phone_number = phone_number
self.password = password
self.phone_code = phone_code
@@ -186,18 +188,21 @@ class Client:
client=self
)
- terms = self.session.start()
+ self.session.start()
if self.user_id is None:
- print("\n".join(terms.splitlines()), "\n")
+ if self.token is None:
+ self.authorize_user()
+ else:
+ self.authorize_bot()
- self.user_id = self.authorize()
- self.password = None
self.save_session()
+ if self.token is None:
+ self.get_dialogs()
+ self.get_contacts()
+
self.rnd_id = MsgId
- self.get_dialogs()
- self.get_contacts()
for i in range(self.UPDATES_WORKERS):
Thread(target=self.updates_worker, name="UpdatesWorker#{}".format(i + 1)).start()
@@ -225,6 +230,199 @@ class Client:
for _ in range(self.DOWNLOAD_WORKERS):
self.download_queue.put(None)
+ def authorize_bot(self):
+ try:
+ r = self.send(
+ functions.auth.ImportBotAuthorization(
+ flags=0,
+ api_id=self.api_key.api_id,
+ api_hash=self.api_key.api_hash,
+ bot_auth_token=self.token
+ )
+ )
+ except UserMigrate as e:
+ self.session.stop()
+
+ self.dc_id = e.x
+ self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create()
+
+ self.session = Session(
+ self.dc_id,
+ self.test_mode,
+ self.proxy,
+ self.auth_key,
+ self.api_key.api_id,
+ client=self
+ )
+
+ self.session.start()
+ self.authorize_bot()
+ else:
+ self.user_id = r.user.id
+
+ def authorize_user(self):
+ phone_number_invalid_raises = self.phone_number is not None
+ phone_code_invalid_raises = self.phone_code is not None
+ password_hash_invalid_raises = self.password is not None
+ first_name_invalid_raises = self.first_name is not None
+
+ while True:
+ if self.phone_number is None:
+ self.phone_number = input("Enter phone number: ")
+
+ while True:
+ confirm = input("Is \"{}\" correct? (y/n): ".format(self.phone_number))
+
+ if confirm in ("y", "1"):
+ break
+ elif confirm in ("n", "2"):
+ self.phone_number = input("Enter phone number: ")
+
+ self.phone_number = self.phone_number.strip("+")
+
+ try:
+ r = self.send(
+ functions.auth.SendCode(
+ self.phone_number,
+ self.api_key.api_id,
+ self.api_key.api_hash
+ )
+ )
+ except (PhoneMigrate, NetworkMigrate) as e:
+ self.session.stop()
+
+ self.dc_id = e.x
+ self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create()
+
+ self.session = Session(
+ self.dc_id,
+ self.test_mode,
+ self.proxy,
+ self.auth_key,
+ self.api_key.api_id,
+ client=self
+ )
+ self.session.start()
+
+ r = self.send(
+ functions.auth.SendCode(
+ self.phone_number,
+ self.api_key.api_id,
+ self.api_key.api_hash
+ )
+ )
+ break
+ except (PhoneNumberInvalid, PhoneNumberBanned) as e:
+ if phone_number_invalid_raises:
+ raise
+ else:
+ print(e.MESSAGE)
+ self.phone_number = None
+ except FloodWait as e:
+ print(e.MESSAGE.format(x=e.x))
+ time.sleep(e.x)
+ except Exception as e:
+ log.error(e, exc_info=True)
+ else:
+ break
+
+ phone_registered = r.phone_registered
+ phone_code_hash = r.phone_code_hash
+
+ while True:
+ self.phone_code = (
+ input("Enter phone code: ") if self.phone_code is None
+ else self.phone_code if type(self.phone_code) is str
+ else self.phone_code()
+ )
+
+ try:
+ if phone_registered:
+ r = self.send(
+ functions.auth.SignIn(
+ self.phone_number,
+ phone_code_hash,
+ self.phone_code
+ )
+ )
+ else:
+ try:
+ self.send(
+ functions.auth.SignIn(
+ self.phone_number,
+ phone_code_hash,
+ self.phone_code
+ )
+ )
+ except PhoneNumberUnoccupied:
+ pass
+
+ self.first_name = self.first_name if self.first_name is not None else input("First name: ")
+ self.last_name = self.last_name if self.last_name is not None else input("Last name: ")
+
+ r = self.send(
+ functions.auth.SignUp(
+ self.phone_number,
+ phone_code_hash,
+ self.phone_code,
+ self.first_name,
+ self.last_name
+ )
+ )
+ except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e:
+ if phone_code_invalid_raises:
+ raise
+ else:
+ print(e.MESSAGE)
+ self.phone_code = None
+ except FirstnameInvalid as e:
+ if first_name_invalid_raises:
+ raise
+ else:
+ print(e.MESSAGE)
+ self.first_name = None
+ except SessionPasswordNeeded as e:
+ print(e.MESSAGE)
+ r = self.send(functions.account.GetPassword())
+
+ while True:
+ try:
+
+ if self.password is None:
+ print("Hint: {}".format(r.hint))
+ self.password = input("Enter password: ") # TODO: Use getpass
+
+ if type(self.password) is str:
+ self.password = r.current_salt + self.password.encode() + r.current_salt
+
+ password_hash = sha256(self.password).digest()
+
+ r = self.send(functions.auth.CheckPassword(password_hash))
+ except PasswordHashInvalid as e:
+ if password_hash_invalid_raises:
+ raise
+ else:
+ print(e.MESSAGE)
+ self.password = None
+ except FloodWait as e:
+ print(e.MESSAGE.format(x=e.x))
+ time.sleep(e.x)
+ except Exception as e:
+ log.error(e, exc_info=True)
+ else:
+ break
+ break
+ except FloodWait as e:
+ print(e.MESSAGE.format(x=e.x))
+ time.sleep(e.x)
+ except Exception as e:
+ log.error(e, exc_info=True)
+ else:
+ break
+
+ self.password = None
+ self.user_id = r.user.id
+
def fetch_peers(self, entities: list):
for entity in entities:
if isinstance(entity, User):
@@ -558,168 +756,6 @@ class Client:
return r
- def authorize(self):
- phone_number_invalid_raises = self.phone_number is not None
- phone_code_invalid_raises = self.phone_code is not None
- password_hash_invalid_raises = self.password is not None
- first_name_invalid_raises = self.first_name is not None
-
- while True:
- if self.phone_number is None:
- self.phone_number = input("Enter phone number: ")
-
- while True:
- confirm = input("Is \"{}\" correct? (y/n): ".format(self.phone_number))
-
- if confirm in ("y", "1"):
- break
- elif confirm in ("n", "2"):
- self.phone_number = input("Enter phone number: ")
-
- self.phone_number = self.phone_number.strip("+")
-
- try:
- r = self.send(
- functions.auth.SendCode(
- self.phone_number,
- self.api_key.api_id,
- self.api_key.api_hash
- )
- )
- except (PhoneMigrate, NetworkMigrate) as e:
- self.session.stop()
-
- self.dc_id = e.x
- self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create()
-
- self.session = Session(
- self.dc_id,
- self.test_mode,
- self.proxy,
- self.auth_key,
- self.api_key.api_id,
- client=self
- )
- self.session.start()
-
- r = self.send(
- functions.auth.SendCode(
- self.phone_number,
- self.api_key.api_id,
- self.api_key.api_hash
- )
- )
- break
- except (PhoneNumberInvalid, PhoneNumberBanned) as e:
- if phone_number_invalid_raises:
- raise
- else:
- print(e.MESSAGE)
- self.phone_number = None
- except FloodWait as e:
- print(e.MESSAGE.format(x=e.x))
- time.sleep(e.x)
- except Exception as e:
- log.error(e, exc_info=True)
- else:
- break
-
- phone_registered = r.phone_registered
- phone_code_hash = r.phone_code_hash
-
- while True:
- self.phone_code = (
- input("Enter phone code: ") if self.phone_code is None
- else self.phone_code if type(self.phone_code) is str
- else self.phone_code()
- )
-
- try:
- if phone_registered:
- r = self.send(
- functions.auth.SignIn(
- self.phone_number,
- phone_code_hash,
- self.phone_code
- )
- )
- else:
- try:
- self.send(
- functions.auth.SignIn(
- self.phone_number,
- phone_code_hash,
- self.phone_code
- )
- )
- except PhoneNumberUnoccupied:
- pass
-
- self.first_name = self.first_name if self.first_name is not None else input("First name: ")
- self.last_name = self.last_name if self.last_name is not None else input("Last name: ")
-
- r = self.send(
- functions.auth.SignUp(
- self.phone_number,
- phone_code_hash,
- self.phone_code,
- self.first_name,
- self.last_name
- )
- )
- except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e:
- if phone_code_invalid_raises:
- raise
- else:
- print(e.MESSAGE)
- self.phone_code = None
- except FirstnameInvalid as e:
- if first_name_invalid_raises:
- raise
- else:
- print(e.MESSAGE)
- self.first_name = None
- except SessionPasswordNeeded as e:
- print(e.MESSAGE)
- r = self.send(functions.account.GetPassword())
-
- while True:
- try:
-
- if self.password is None:
- print("Hint: {}".format(r.hint))
- self.password = input("Enter password: ") # TODO: Use getpass
-
- if type(self.password) is str:
- self.password = r.current_salt + self.password.encode() + r.current_salt
-
- password_hash = sha256(self.password).digest()
-
- r = self.send(functions.auth.CheckPassword(password_hash))
- except PasswordHashInvalid as e:
- if password_hash_invalid_raises:
- raise
- else:
- print(e.MESSAGE)
- self.password = None
- except FloodWait as e:
- print(e.MESSAGE.format(x=e.x))
- time.sleep(e.x)
- except Exception as e:
- log.error(e, exc_info=True)
- else:
- break
- break
- except FloodWait as e:
- print(e.MESSAGE.format(x=e.x))
- time.sleep(e.x)
- except Exception as e:
- log.error(e, exc_info=True)
- else:
- break
-
- return r.user.id
-
def load_config(self):
parser = ConfigParser()
parser.read("config.ini")
From 839f7b99f43631d8e760e69d6074977c7945d014 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 15 Mar 2018 20:41:13 +0100
Subject: [PATCH 14/88] Call GetState for bots
---
pyrogram/client/client.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 410e167c..39ed55d1 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -201,6 +201,8 @@ class Client:
if self.token is None:
self.get_dialogs()
self.get_contacts()
+ else:
+ self.send(functions.updates.GetState())
self.rnd_id = MsgId
From 4dbf1ef5ac0e8826ee7d0dccddcf276dc9ac071d Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 16 Mar 2018 11:18:16 +0100
Subject: [PATCH 15/88] Improve re-connection speed
---
pyrogram/session/session.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index c1269d49..333947e4 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -186,6 +186,9 @@ class Session:
for i in range(self.NET_WORKERS):
self.recv_queue.put(None)
+ for i in self.results.values():
+ i.event.set()
+
log.debug("Session stopped")
def restart(self):
From 6fd8b582b21419b2daf3d20a1289779315b4af16 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 16 Mar 2018 11:30:05 +0100
Subject: [PATCH 16/88] More useful logging
---
pyrogram/session/session.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 333947e4..46d722fc 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -400,7 +400,7 @@ class Session:
try:
return self._send(data)
except (OSError, TimeoutError):
- log.warning("Retrying {}".format(type(data)))
+ (log.warning if i > 0 else log.info)("{}: {} Retrying {}".format(i, datetime.now(), type(data)))
continue
else:
return None
From 6c206616860657ddc25f831319632d7d59072ad0 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 17 Mar 2018 14:29:23 +0100
Subject: [PATCH 17/88] Don't use kwargs
---
pyrogram/crypto/aes.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py
index c571fb2d..05a01044 100644
--- a/pyrogram/crypto/aes.py
+++ b/pyrogram/crypto/aes.py
@@ -53,7 +53,7 @@ class AES:
@staticmethod
def ctr_decrypt(data: bytes, key: bytes, iv: bytes, offset: int) -> bytes:
- replace = int.to_bytes(offset // 16, byteorder="big", length=4)
+ replace = int.to_bytes(offset // 16, 4, "big")
iv = iv[:-4] + replace
if is_fast:
From 600e705d51e1aff97de1ed57a76c79e9f1f0c56b Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 17 Mar 2018 19:05:18 +0100
Subject: [PATCH 18/88] Add docstrings for "token" parameter
---
pyrogram/client/client.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 39ed55d1..d71534ed 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -88,6 +88,10 @@ class Client:
Only applicable for new sessions and will be ignored in case previously
created sessions are loaded.
+ token (:obj:`str`, optional):
+ Pass your Bot API token to log-in as Bot.
+ E.g.: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
+
phone_number (:obj:`str`, optional):
Pass your phone number (with your Country Code prefix included) to avoid
entering it manually. Only applicable for new sessions.
From 1da39efa2e5aaf87afbbb92bcbd14d12704ac6e6 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 17 Mar 2018 19:13:15 +0100
Subject: [PATCH 19/88] Update invite link regex
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index d71534ed..ab0d4ab7 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -117,7 +117,7 @@ class Client:
Thread pool size for handling incoming updates. Defaults to 4.
"""
- INVITE_LINK_RE = re.compile(r"^(?:https?://)?t\.me/joinchat/(.+)$")
+ INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)?(.+)$")
DIALOGS_AT_ONCE = 100
UPDATES_WORKERS = 2
DOWNLOAD_WORKERS = 1
From b45f2f4595a2b21e9d45d4708b88a6514bd4e728 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 17 Mar 2018 19:24:27 +0100
Subject: [PATCH 20/88] Add support for sending messages using joinchat links
and hashes
---
pyrogram/client/client.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index ab0d4ab7..a9aa2e9c 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -23,6 +23,7 @@ import math
import mimetypes
import os
import re
+import struct
import threading
import time
from collections import namedtuple
@@ -916,6 +917,12 @@ class Client:
if peer_id in ("self", "me"):
return InputPeerSelf()
+ match = self.INVITE_LINK_RE.match(peer_id)
+
+ if match:
+ decoded = base64.b64decode(match.group(1) + "=" * (-len(match.group(1)) % 4), altchars="-_")
+ return self.resolve_peer(struct.unpack(">2iq", decoded)[1])
+
peer_id = peer_id.lower().strip("@+")
try:
From 1d25b84cde1ab9c1510ea8b6813248d7232fda3f Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 18 Mar 2018 11:43:51 +0100
Subject: [PATCH 21/88] Update invite link regex pattern
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index a9aa2e9c..4cef260d 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -118,7 +118,7 @@ class Client:
Thread pool size for handling incoming updates. Defaults to 4.
"""
- INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)?(.+)$")
+ INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)?([\w-]+)$")
DIALOGS_AT_ONCE = 100
UPDATES_WORKERS = 2
DOWNLOAD_WORKERS = 1
From cbd3b71b7993f175b966a33e7440542fda61170a Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 18 Mar 2018 12:12:27 +0100
Subject: [PATCH 22/88] Handle mismatches in a more pythonic way
---
pyrogram/client/client.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 4cef260d..820c7dd1 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -17,6 +17,7 @@
# along with Pyrogram. If not, see .
import base64
+import binascii
import json
import logging
import math
@@ -919,9 +920,11 @@ class Client:
match = self.INVITE_LINK_RE.match(peer_id)
- if match:
- decoded = base64.b64decode(match.group(1) + "=" * (-len(match.group(1)) % 4), altchars="-_")
+ try:
+ decoded = base64.b64decode(match.group(1) + "=" * (-len(match.group(1)) % 4), "-_")
return self.resolve_peer(struct.unpack(">2iq", decoded)[1])
+ except (AttributeError, binascii.Error, struct.error):
+ pass
peer_id = peer_id.lower().strip("@+")
From 5b5fb6cbec6ea083418b7485fc766f18181b698c Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 18 Mar 2018 13:00:28 +0100
Subject: [PATCH 23/88] Update docstrings to accommodate joinchat links
---
pyrogram/client/client.py | 141 +++++++++++++++++++++-----------------
1 file changed, 80 insertions(+), 61 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 820c7dd1..9c49058a 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -986,9 +986,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
text (:obj:`str`):
Text of the message to be sent.
@@ -1036,15 +1037,16 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
from_chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the chat where the original message was sent
- (or channel/supergroup username in the format @username). For your personal cloud
- storage (Saved Messages) you can simply use "me" or "self".
- Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the source chat where the original message was sent.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
message_ids (:obj:`list`):
A list of Message identifiers in the chat specified in *from_chat_id*.
@@ -1082,9 +1084,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
photo (:obj:`str`):
Photo to send.
@@ -1167,9 +1170,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
audio (:obj:`str`):
Audio file to send.
@@ -1259,9 +1263,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
document (:obj:`str`):
File to send.
@@ -1335,9 +1340,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
sticker (:obj:`str`):
Sticker to send.
@@ -1409,9 +1415,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
video (:obj:`str`):
Video to send.
@@ -1513,9 +1520,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
voice (:obj:`str`):
Audio file to send.
@@ -1597,9 +1605,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
video_note (:obj:`str`):
Video note to send.
@@ -1676,9 +1685,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
media (:obj:`list`):
A list containing either :obj:`pyrogram.InputMedia.Photo` or :obj:`pyrogram.InputMedia.Video` objects
@@ -1774,9 +1784,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
latitude (:obj:`float`):
Latitude of the location.
@@ -1826,9 +1837,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
latitude (:obj:`float`):
Latitude of the venue.
@@ -1890,9 +1902,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
phone_number (:obj:`str`):
Contact's phone number.
@@ -1939,9 +1952,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
action (:obj:`callable`):
Type of action to broadcast.
@@ -2001,9 +2015,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
message_id (:obj:`int`):
Message identifier in the chat specified in chat_id.
@@ -2042,9 +2057,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
message_id (:obj:`int`):
Message identifier in the chat specified in chat_id.
@@ -2084,9 +2100,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
message_ids (:obj:`list`):
List of identifiers of the messages to delete.
@@ -2823,9 +2840,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
query_id (:obj:`int`):
Unique identifier for the answered query.
@@ -2865,9 +2883,10 @@ class Client:
Args:
chat_id (:obj:`int` | :obj:`str`):
- Unique identifier for the target chat or username of the target channel/supergroup
- (in the format @username). For your personal cloud storage (Saved Messages) you can
- simply use "me" or "self". Phone numbers that exist in your Telegram address book are also supported.
+ Unique identifier (int) or username (str) of the target chat.
+ For your personal cloud (Saved Messages) you can simply use "me" or "self".
+ For a contact that exists in your Telegram address book you can use his phone number (str).
+ For a private channel/supergroup you can use its *t.me/joinchat/* link.
message_ids (:obj:`list`):
A list of Message identifiers in the chat specified in *chat_id*.
From c4142753d03f6079472ed2ef95ca0ee2c50565c5 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 19 Mar 2018 01:08:34 +0100
Subject: [PATCH 24/88] Handle minified channel updates
---
pyrogram/client/client.py | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 9c49058a..e0a16c2b 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -616,6 +616,28 @@ class Client:
) or getattr(update, "channel_id", None)
pts = getattr(update, "pts", None)
+ pts_count = getattr(update, "pts_count", None)
+
+ if isinstance(update, types.UpdateNewChannelMessage):
+ diff = self.send(
+ functions.updates.GetChannelDifference(
+ channel=self.resolve_peer(update.message.to_id.channel_id),
+ filter=types.ChannelMessagesFilter(
+ ranges=[types.MessageRange(
+ min_id=update.message.id,
+ max_id=update.message.id
+ )]
+ ),
+ pts=pts - pts_count,
+ limit=pts
+ )
+ )
+
+ self.fetch_peers(diff.users)
+ self.fetch_peers(diff.chats)
+
+ updates.users += diff.users
+ updates.chats += diff.chats
if channel_id and pts:
if channel_id not in self.channels_pts:
From 390b0c12e2b665ac5069784da8e2ea0d66c15894 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 19 Mar 2018 01:08:59 +0100
Subject: [PATCH 25/88] Set updates_workers to 1
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index e0a16c2b..5e0d06ba 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -121,7 +121,7 @@ class Client:
INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)?([\w-]+)$")
DIALOGS_AT_ONCE = 100
- UPDATES_WORKERS = 2
+ UPDATES_WORKERS = 1
DOWNLOAD_WORKERS = 1
def __init__(self,
From 70ae7f0808532fa5a02eb5cafc9bbd94679ce8d2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 19 Mar 2018 01:40:36 +0100
Subject: [PATCH 26/88] Fix usernames not stored in lowercase
---
pyrogram/client/client.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 9c49058a..e3968b31 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -455,7 +455,7 @@ class Client:
self.peers_by_id[user_id] = input_peer
if username is not None:
- self.peers_by_username[username] = input_peer
+ self.peers_by_username[username.lower()] = input_peer
if phone is not None:
self.peers_by_phone[phone] = input_peer
@@ -495,7 +495,7 @@ class Client:
self.peers_by_id[peer_id] = input_peer
if username is not None:
- self.peers_by_username[username] = input_peer
+ self.peers_by_username[username.lower()] = input_peer
def download_worker(self):
name = threading.current_thread().name
@@ -889,7 +889,7 @@ class Client:
else:
raise PeerIdInvalid
- self.peers_by_username[username] = input_peer
+ self.peers_by_username[username.lower()] = input_peer
self.peers_by_id[peer_id] = input_peer
return input_peer
From 9e386ed24c54eb0b017d5b13a4282592b92be530 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 19 Mar 2018 01:51:47 +0100
Subject: [PATCH 27/88] Remove resolve_username method
---
pyrogram/client/client.py | 32 ++------------------------------
1 file changed, 2 insertions(+), 30 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index e3968b31..57706657 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -865,35 +865,6 @@ class Client:
offset_date = parse_dialogs(dialogs)
log.info("Entities count: {}".format(len(self.peers_by_id)))
- def resolve_username(self, username: str):
- username = username.lower().strip("@")
-
- resolved_peer = self.send(
- functions.contacts.ResolveUsername(
- username=username
- )
- ) # type: types.contacts.ResolvedPeer
-
- if type(resolved_peer.peer) is PeerUser:
- input_peer = InputPeerUser(
- user_id=resolved_peer.users[0].id,
- access_hash=resolved_peer.users[0].access_hash
- )
- peer_id = input_peer.user_id
- elif type(resolved_peer.peer) is PeerChannel:
- input_peer = InputPeerChannel(
- channel_id=resolved_peer.chats[0].id,
- access_hash=resolved_peer.chats[0].access_hash
- )
- peer_id = int("-100" + str(input_peer.channel_id))
- else:
- raise PeerIdInvalid
-
- self.peers_by_username[username.lower()] = input_peer
- self.peers_by_id[peer_id] = input_peer
-
- return input_peer
-
def resolve_peer(self, peer_id: int or str):
"""Use this method to get the *InputPeer* of a known *peer_id*.
@@ -934,7 +905,8 @@ class Client:
try:
return self.peers_by_username[peer_id]
except KeyError:
- return self.resolve_username(peer_id)
+ self.send(functions.contacts.ResolveUsername(peer_id))
+ return self.peers_by_username[peer_id]
else:
try:
return self.peers_by_phone[peer_id]
From 0f2dc6c62442eacee841759ccd2413ceef97a795 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 19 Mar 2018 01:53:07 +0100
Subject: [PATCH 28/88] Remove unnecessary method calls
---
pyrogram/client/client.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 57706657..736092b9 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -639,9 +639,6 @@ class Client:
)
)
- self.fetch_peers(diff.users)
- self.fetch_peers(diff.chats)
-
self.update_queue.put((
types.UpdateNewMessage(
message=diff.new_messages[0],
From 310f9080c428b7f8f186f38e63663bd4784eb5bc Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 19 Mar 2018 01:54:45 +0100
Subject: [PATCH 29/88] Remove unnecessary method calls
---
pyrogram/client/client.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 10171347..80b07628 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -633,9 +633,6 @@ class Client:
)
)
- self.fetch_peers(diff.users)
- self.fetch_peers(diff.chats)
-
updates.users += diff.users
updates.chats += diff.chats
From 299d6aca5c035878ec9d4212e34d15297cb28ff9 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 19 Mar 2018 03:37:43 +0100
Subject: [PATCH 30/88] Raise ConnectionError if client is not started
---
pyrogram/client/client.py | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 736092b9..c708c2de 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -154,7 +154,7 @@ class Client:
self.auth_key = None
self.user_id = None
- self.rnd_id = None
+ self.rnd_id = MsgId
self.peers_by_id = {}
self.peers_by_username = {}
@@ -167,6 +167,7 @@ class Client:
self.session = None
+ self.is_started = None
self.is_idle = None
self.updates_queue = Queue()
@@ -195,6 +196,7 @@ class Client:
)
self.session.start()
+ self.is_started = True
if self.user_id is None:
if self.token is None:
@@ -210,8 +212,6 @@ class Client:
else:
self.send(functions.updates.GetState())
- self.rnd_id = MsgId
-
for i in range(self.UPDATES_WORKERS):
Thread(target=self.updates_worker, name="UpdatesWorker#{}".format(i + 1)).start()
@@ -227,6 +227,7 @@ class Client:
"""Use this method to manually stop the Client.
Requires no parameters.
"""
+ self.is_started = False
self.session.stop()
for _ in range(self.UPDATES_WORKERS):
@@ -754,12 +755,15 @@ class Client:
Raises:
:class:`pyrogram.Error`
"""
- r = self.session.send(data)
+ if self.is_started:
+ r = self.session.send(data)
- self.fetch_peers(getattr(r, "users", []))
- self.fetch_peers(getattr(r, "chats", []))
+ self.fetch_peers(getattr(r, "users", []))
+ self.fetch_peers(getattr(r, "chats", []))
- return r
+ return r
+ else:
+ raise ConnectionError("client '{}' is not started".format(self.session_name))
def load_config(self):
parser = ConfigParser()
From 2deea2e4a6e2eb682cf63cb944b203cfefb5aba8 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 19 Mar 2018 03:38:09 +0100
Subject: [PATCH 31/88] Remove unused imports
---
pyrogram/client/client.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index c708c2de..20ac58bc 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -46,7 +46,6 @@ from pyrogram.api.errors import (
VolumeLocNotFound, UserMigrate)
from pyrogram.api.types import (
User, Chat, Channel,
- PeerUser, PeerChannel,
InputPeerEmpty, InputPeerSelf,
InputPeerUser, InputPeerChat, InputPeerChannel
)
From 2fd7cd0054a3f3076df1e86a373e06af0f8db656 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 19 Mar 2018 21:02:54 +0100
Subject: [PATCH 32/88] Small fix in the markdown regex
---
pyrogram/client/style/markdown.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py
index e39ac876..f3c7805d 100644
--- a/pyrogram/client/style/markdown.py
+++ b/pyrogram/client/style/markdown.py
@@ -36,7 +36,7 @@ class Markdown:
CODE_DELIMITER = "`"
PRE_DELIMITER = "```"
- MARKDOWN_RE = re.compile(r"```([\w ]*)\n([\w\W]*)(?:\n|)```|\[([^[(]+)\]\(([^])]+)\)|({d})(.+?)\5".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]
From 19b1bbb94297a54ece9bb310ea274796e9121e8a Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 07:04:35 -0500
Subject: [PATCH 33/88] Allow download_media to download media to anywhere
Remove the use of a temporary file in the programs
working directory.
---
pyrogram/client/client.py | 180 ++++++++++++++++++++------------------
1 file changed, 97 insertions(+), 83 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 20ac58bc..a20ba521 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -55,6 +55,8 @@ from pyrogram.session.internals import MsgId
from .input_media import InputMedia
from .style import Markdown, HTML
+from typing import Any
+
log = logging.getLogger(__name__)
ApiKey = namedtuple("ApiKey", ["api_id", "api_hash"])
@@ -509,7 +511,6 @@ class Client:
try:
media, file_name, done, progress, path = media
- tmp_file_name = None
if isinstance(media, types.MessageMediaDocument):
document = media.document
@@ -535,13 +536,14 @@ class Client:
elif isinstance(i, types.DocumentAttributeAnimated):
file_name = file_name.replace("doc", "gif")
- tmp_file_name = self.get_file(
+ self.get_file(
dc_id=document.dc_id,
id=document.id,
access_hash=document.access_hash,
version=document.version,
size=document.size,
- progress=progress
+ progress=progress,
+ file_out=file_name
)
elif isinstance(media, (types.MessageMediaPhoto, types.Photo)):
if isinstance(media, types.MessageMediaPhoto):
@@ -558,37 +560,23 @@ class Client:
photo_loc = photo.sizes[-1].location
- tmp_file_name = self.get_file(
+ self.get_file(
dc_id=photo_loc.dc_id,
volume_id=photo_loc.volume_id,
local_id=photo_loc.local_id,
secret=photo_loc.secret,
size=photo.sizes[-1].size,
- progress=progress
+ progress=progress,
+ file_out=file_name
)
if file_name is not None:
- path[0] = "downloads/{}".format(file_name)
-
- try:
- os.remove("downloads/{}".format(file_name))
- except OSError:
- pass
- finally:
- try:
- os.renames("{}".format(tmp_file_name), "downloads/{}".format(file_name))
- except OSError:
- pass
+ path[0] = file_name
except Exception as e:
log.error(e, exc_info=True)
finally:
done.set()
- try:
- os.remove("{}".format(tmp_file_name))
- except OSError:
- pass
-
log.debug("{} stopped".format(name))
def updates_worker(self):
@@ -2177,7 +2165,9 @@ class Client:
secret: int = None,
version: int = 0,
size: int = None,
- progress: callable = None) -> str:
+ progress: callable = None,
+ file_out: Any = None) -> str:
+
if dc_id != self.dc_id:
exported_auth = self.send(
functions.auth.ExportAuthorization(
@@ -2225,10 +2215,13 @@ class Client:
version=version
)
- file_name = "download_{}.temp".format(MsgId())
limit = 1024 * 1024
offset = 0
+ # file object being written
+ f = None
+ close_file, call_flush, call_fsync = False, False, False
+
try:
r = session.send(
functions.upload.GetFile(
@@ -2238,30 +2231,49 @@ class Client:
)
)
+ if file_out is None:
+ f = open("download_{}.temp".format(MsgId(), 'wb'))
+ close_file = True
+
+ elif isinstance(file_out, str):
+ f = open(file_out, 'wb')
+ elif hasattr(file_out, 'write'):
+ f = file_out
+
+ if hasattr(file_out, 'flush'):
+ call_flush = True
+ if hasattr(file_out, 'fileno'):
+ call_fsync = True
+ else:
+ raise ValueError('file_out argument of client.get_file must at least implement a write method if not a '
+ 'string.')
+
if isinstance(r, types.upload.File):
- with open(file_name, "wb") as f:
- while True:
- chunk = r.bytes
+ while True:
+ chunk = r.bytes
- if not chunk:
- break
+ if not chunk:
+ break
- f.write(chunk)
+ f.write(chunk)
+
+ if call_flush:
f.flush()
+ if call_fsync:
os.fsync(f.fileno())
- offset += limit
+ offset += limit
- if progress:
- progress(min(offset, size), size)
+ if progress:
+ progress(min(offset, size), size)
- r = session.send(
- functions.upload.GetFile(
- location=location,
- offset=offset,
- limit=limit
- )
+ r = session.send(
+ functions.upload.GetFile(
+ location=location,
+ offset=offset,
+ limit=limit
)
+ )
if isinstance(r, types.upload.FileCdnRedirect):
cdn_session = Session(
@@ -2276,63 +2288,65 @@ class Client:
cdn_session.start()
try:
- with open(file_name, "wb") as f:
- while True:
- r2 = cdn_session.send(
- functions.upload.GetCdnFile(
- location=location,
- file_token=r.file_token,
- offset=offset,
- limit=limit
- )
+ while True:
+ r2 = cdn_session.send(
+ functions.upload.GetCdnFile(
+ location=location,
+ file_token=r.file_token,
+ offset=offset,
+ limit=limit
)
+ )
- if isinstance(r2, types.upload.CdnFileReuploadNeeded):
- try:
- session.send(
- functions.upload.ReuploadCdnFile(
- file_token=r.file_token,
- request_token=r2.request_token
- )
+ if isinstance(r2, types.upload.CdnFileReuploadNeeded):
+ try:
+ session.send(
+ functions.upload.ReuploadCdnFile(
+ file_token=r.file_token,
+ request_token=r2.request_token
)
- except VolumeLocNotFound:
- break
- else:
- continue
+ )
+ except VolumeLocNotFound:
+ break
+ else:
+ continue
- chunk = r2.bytes
+ chunk = r2.bytes
- # https://core.telegram.org/cdn#decrypting-files
- decrypted_chunk = AES.ctr_decrypt(
- chunk,
- r.encryption_key,
- r.encryption_iv,
+ # https://core.telegram.org/cdn#decrypting-files
+ decrypted_chunk = AES.ctr_decrypt(
+ chunk,
+ r.encryption_key,
+ r.encryption_iv,
+ offset
+ )
+
+ hashes = session.send(
+ functions.upload.GetCdnFileHashes(
+ r.file_token,
offset
)
+ )
- hashes = session.send(
- functions.upload.GetCdnFileHashes(
- r.file_token,
- offset
- )
- )
+ # https://core.telegram.org/cdn#verifying-files
+ for i, h in enumerate(hashes):
+ cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)]
+ assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i)
- # https://core.telegram.org/cdn#verifying-files
- for i, h in enumerate(hashes):
- cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)]
- assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i)
+ f.write(decrypted_chunk)
- f.write(decrypted_chunk)
+ if call_flush:
f.flush()
+ if call_fsync:
os.fsync(f.fileno())
- offset += limit
+ offset += limit
- if progress:
- progress(min(offset, size), size)
+ if progress:
+ progress(min(offset, size), size)
- if len(chunk) < limit:
- break
+ if len(chunk) < limit:
+ break
except Exception as e:
log.error(e)
finally:
@@ -2340,8 +2354,10 @@ class Client:
except Exception as e:
log.error(e)
else:
- return file_name
+ return file_out
finally:
+ if close_file and f and hasattr(f, 'close'):
+ f.close()
session.stop()
def join_chat(self, chat_id: str):
@@ -2602,8 +2618,6 @@ class Client:
progress: callable = None):
"""Use this method to download the media from a Message.
- Files are saved in the *downloads* folder.
-
Args:
message (:obj:`Message `):
The Message containing the media.
From 6bb004fc83d92dfc328bd1cdeb1e26bc1856f336 Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 07:34:38 -0500
Subject: [PATCH 34/88] Add file_dir parameter to client.download_media
---
pyrogram/client/client.py | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index a20ba521..89b256c5 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -510,7 +510,7 @@ class Client:
break
try:
- media, file_name, done, progress, path = media
+ media, file_dir, file_name, done, progress, path = media
if isinstance(media, types.MessageMediaDocument):
document = media.document
@@ -536,6 +536,8 @@ class Client:
elif isinstance(i, types.DocumentAttributeAnimated):
file_name = file_name.replace("doc", "gif")
+ file_name = os.path.join(file_dir if file_dir is not None else '', file_name)
+
self.get_file(
dc_id=document.dc_id,
id=document.id,
@@ -558,6 +560,8 @@ class Client:
self.rnd_id()
)
+ file_name = os.path.join(file_dir if file_dir is not None else '', file_name)
+
photo_loc = photo.sizes[-1].location
self.get_file(
@@ -2614,6 +2618,7 @@ class Client:
def download_media(self,
message: types.Message,
file_name: str = None,
+ file_dir: str = 'downloads',
block: bool = True,
progress: callable = None):
"""Use this method to download the media from a Message.
@@ -2624,6 +2629,14 @@ class Client:
file_name (:obj:`str`, optional):
Specify a custom *file_name* to be used instead of the one provided by Telegram.
+ This parameter is expected to be a full file path to the location you want the
+ file to be placed. If not specified, the file will be put into the directory
+ specified by *file_dir* with a generated name.
+
+ file_dir (:obj:`str`, optional):
+ Specify a directory to place the file in if no *file_name* is specified.
+ If *file_dir* is *None*, the current working directory is used. The default
+ value is the "downloads" folder in the current working directory.
block (:obj:`bool`, optional):
Blocks the code execution until the file has been downloaded.
@@ -2656,7 +2669,7 @@ class Client:
media = message
if media is not None:
- self.download_queue.put((media, file_name, done, progress, path))
+ self.download_queue.put((media, file_dir, file_name, done, progress, path))
else:
return
From b9f623921dead51872a424d6d251cdb89b13e8ad Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 07:47:38 -0500
Subject: [PATCH 35/88] Make file_name and file_dir mutually exclusive
---
pyrogram/client/client.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 89b256c5..4862b9e8 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2659,6 +2659,10 @@ class Client:
Raises:
:class:`pyrogram.Error`
"""
+
+ if file_name is not None and file_dir is not None:
+ ValueError('file_name and file_dir may not be specified together.')
+
if isinstance(message, (types.Message, types.Photo)):
done = Event()
path = [None]
From 4ae9a5ad38ea240eee60d052cf3b9ddd366f63b3 Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 08:05:41 -0500
Subject: [PATCH 36/88] Make sure file_dir is created
---
pyrogram/client/client.py | 25 +++++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 4862b9e8..2f2d1a23 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -512,6 +512,10 @@ class Client:
try:
media, file_dir, file_name, done, progress, path = media
+ if file_dir is not None:
+ # Make file_dir if it was specified
+ os.makedirs(file_dir, exist_ok=True)
+
if isinstance(media, types.MessageMediaDocument):
document = media.document
@@ -2620,7 +2624,8 @@ class Client:
file_name: str = None,
file_dir: str = 'downloads',
block: bool = True,
- progress: callable = None):
+ progress: callable = None
+ ):
"""Use this method to download the media from a Message.
Args:
@@ -2636,7 +2641,8 @@ class Client:
file_dir (:obj:`str`, optional):
Specify a directory to place the file in if no *file_name* is specified.
If *file_dir* is *None*, the current working directory is used. The default
- value is the "downloads" folder in the current working directory.
+ value is the "downloads" folder in the current working directory. The
+ directory tree will be created if it does not exist.
block (:obj:`bool`, optional):
Blocks the code execution until the file has been downloaded.
@@ -2658,6 +2664,7 @@ class Client:
Raises:
:class:`pyrogram.Error`
+ :class:`ValueError` if both file_name and file_dir are specified.
"""
if file_name is not None and file_dir is not None:
@@ -2685,6 +2692,7 @@ class Client:
def download_photo(self,
photo: types.Photo or types.UserProfilePhoto or types.ChatPhoto,
file_name: str = None,
+ file_dir: str = None,
block: bool = True):
"""Use this method to download a photo not contained inside a Message.
For example, a photo of a User or a Chat/Channel.
@@ -2696,7 +2704,16 @@ class Client:
The photo object.
file_name (:obj:`str`, optional):
- Specify a custom *file_name* to be used.
+ Specify a custom *file_name* to be used instead of the one provided by Telegram.
+ This parameter is expected to be a full file path to the location you want the
+ photo to be placed. If not specified, the photo will be put into the directory
+ specified by *file_dir* with a generated name.
+
+ file_dir (:obj:`str`, optional):
+ Specify a directory to place the photo in if no *file_name* is specified.
+ If *file_dir* is *None*, the current working directory is used. The default
+ value is the "downloads" folder in the current working directory. The
+ directory tree will be created if it does not exist.
block (:obj:`bool`, optional):
Blocks the code execution until the photo has been downloaded.
@@ -2722,7 +2739,7 @@ class Client:
)]
)
- return self.download_media(photo, file_name, block)
+ return self.download_media(photo, file_name, file_dir, block)
def add_contacts(self, contacts: list):
"""Use this method to add contacts to your Telegram address book.
From 19854a5d4f3cae71bf4b32eebf7603882c3d7065 Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 08:10:24 -0500
Subject: [PATCH 37/88] Actually raise mutually exclusive arg error
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 2f2d1a23..6c8ee1b5 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2668,7 +2668,7 @@ class Client:
"""
if file_name is not None and file_dir is not None:
- ValueError('file_name and file_dir may not be specified together.')
+ raise ValueError('file_name and file_dir may not be specified together.')
if isinstance(message, (types.Message, types.Photo)):
done = Event()
From c0212a7b104c8cf03a67cd7e685f13401a50f7dd Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 08:20:03 -0500
Subject: [PATCH 38/88] Correct default file_dir value behaviour
---
pyrogram/client/client.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 6c8ee1b5..9955dd77 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2622,7 +2622,7 @@ class Client:
def download_media(self,
message: types.Message,
file_name: str = None,
- file_dir: str = 'downloads',
+ file_dir: str = None,
block: bool = True,
progress: callable = None
):
@@ -2670,6 +2670,9 @@ class Client:
if file_name is not None and file_dir is not None:
raise ValueError('file_name and file_dir may not be specified together.')
+ if file_name is None and file_dir is None:
+ file_dir = 'downloads'
+
if isinstance(message, (types.Message, types.Photo)):
done = Event()
path = [None]
From db80c72b08506ba03c6c33c5a2e56db8a88f349e Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 08:27:44 -0500
Subject: [PATCH 39/88] Create file_name directory trees in download_worker
---
pyrogram/client/client.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 9955dd77..c2854796 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -516,6 +516,9 @@ class Client:
# Make file_dir if it was specified
os.makedirs(file_dir, exist_ok=True)
+ if file_name is not None:
+ os.makedirs(os.path.dirname(file_name), exist_ok=True)
+
if isinstance(media, types.MessageMediaDocument):
document = media.document
From 0694480a461337bb5764f900d7294487b0c73492 Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 08:33:14 -0500
Subject: [PATCH 40/88] allow file objects be passed to file_name arg of
client.download_media
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index c2854796..a53225a0 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -516,7 +516,7 @@ class Client:
# Make file_dir if it was specified
os.makedirs(file_dir, exist_ok=True)
- if file_name is not None:
+ if isinstance(file_name, str) and file_name is not None:
os.makedirs(os.path.dirname(file_name), exist_ok=True)
if isinstance(media, types.MessageMediaDocument):
From 5758338f8c2a3f338988cfc04706b4451194e897 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 20 Mar 2018 14:51:35 +0100
Subject: [PATCH 41/88] Include *.py files in manifest
---
MANIFEST.in | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MANIFEST.in b/MANIFEST.in
index 84d50dd4..168fb020 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,6 @@
## Include
include COPYING COPYING.lesser NOTICE
-recursive-include compiler *.tl *.tsv *.txt
+recursive-include compiler *.py *.tl *.tsv *.txt
## Exclude
prune pyrogram/api/errors/exceptions
From 8ca7cd73deaf81b3299d1d43980d6de95f8e3c83 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 20 Mar 2018 14:52:08 +0100
Subject: [PATCH 42/88] Exclude compiler package
---
setup.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/setup.py b/setup.py
index a012d9a1..473aa921 100644
--- a/setup.py
+++ b/setup.py
@@ -78,7 +78,7 @@ setup(
"Documentation": "https://docs.pyrogram.ml",
},
python_requires="~=3.3",
- packages=find_packages(),
+ packages=find_packages(exclude=["compiler*"]),
zip_safe=False,
install_requires=[
"pyaes",
@@ -88,6 +88,5 @@ setup(
"tgcrypto": [
"tgcrypto"
]
- },
- include_package_data=True,
+ }
)
From bd1234f227a27e5ab506c15782a32d8b5055ec35 Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 09:02:17 -0500
Subject: [PATCH 43/88] fix open file leak in client.download_media
---
pyrogram/client/client.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index a53225a0..57351887 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2248,6 +2248,8 @@ class Client:
elif isinstance(file_out, str):
f = open(file_out, 'wb')
+ close_file = True
+
elif hasattr(file_out, 'write'):
f = file_out
@@ -2367,7 +2369,7 @@ class Client:
else:
return file_out
finally:
- if close_file and f and hasattr(f, 'close'):
+ if close_file and f is not None:
f.close()
session.stop()
From 62831001b799039e149a53e826ce4aa21932d064 Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 09:39:58 -0500
Subject: [PATCH 44/88] Slight amendment to client.download_(media/photo) doc
---
pyrogram/client/client.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 57351887..765de03b 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2640,8 +2640,8 @@ class Client:
file_name (:obj:`str`, optional):
Specify a custom *file_name* to be used instead of the one provided by Telegram.
This parameter is expected to be a full file path to the location you want the
- file to be placed. If not specified, the file will be put into the directory
- specified by *file_dir* with a generated name.
+ file to be placed, or a file like object. If not specified, the file will
+ be put into the directory specified by *file_dir* with a generated name.
file_dir (:obj:`str`, optional):
Specify a directory to place the file in if no *file_name* is specified.
@@ -2714,8 +2714,8 @@ class Client:
file_name (:obj:`str`, optional):
Specify a custom *file_name* to be used instead of the one provided by Telegram.
This parameter is expected to be a full file path to the location you want the
- photo to be placed. If not specified, the photo will be put into the directory
- specified by *file_dir* with a generated name.
+ photo to be placed, or a file like object. If not specified, the photo will
+ be put into the directory specified by *file_dir* with a generated name.
file_dir (:obj:`str`, optional):
Specify a directory to place the photo in if no *file_name* is specified.
From 672515f11a774390904fd5907ef871609d5bd41f Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 20 Mar 2018 18:53:00 +0100
Subject: [PATCH 45/88] Update to Layer 76
---
compiler/api/source/main_api.tl | 56 ++++++++++++++++++++-------------
1 file changed, 34 insertions(+), 22 deletions(-)
diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl
index b4d0e7a8..9ede3e28 100644
--- a/compiler/api/source/main_api.tl
+++ b/compiler/api/source/main_api.tl
@@ -148,6 +148,7 @@ messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAct
messageActionPhoneCall#80e11a7f flags:# call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction;
messageActionScreenshotTaken#4792929b = MessageAction;
messageActionCustomAction#fae69f56 message:string = MessageAction;
+messageActionBotAllowed#abe9affe domain:string = MessageAction;
dialog#e4def5db flags:# pinned:flags.2?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog;
@@ -300,8 +301,8 @@ updateRecentStickers#9a422c20 = Update;
updateConfig#a229dd06 = Update;
updatePtsChanged#3354678f = Update;
updateChannelWebPage#40771900 channel_id:int webpage:WebPage pts:int pts_count:int = Update;
-updateDialogPinned#d711a2cc flags:# pinned:flags.0?true peer:Peer = Update;
-updatePinnedDialogs#d8caf68d flags:# order:flags.0?Vector = Update;
+updateDialogPinned#19d27f3c flags:# pinned:flags.0?true peer:DialogPeer = Update;
+updatePinnedDialogs#ea4cb65b flags:# order:flags.0?Vector = Update;
updateBotWebhookJSON#8317c0c3 data:DataJSON = Update;
updateBotWebhookJSONQuery#9b9240a6 query_id:long data:DataJSON timeout:int = Update;
updateBotShippingQuery#e0cdc940 query_id:long user_id:int payload:bytes shipping_address:PostAddress = Update;
@@ -335,11 +336,11 @@ photos.photosSlice#15051f54 count:int photos:Vector users:Vector =
photos.photo#20212ca8 photo:Photo users:Vector = photos.Photo;
upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
-upload.fileCdnRedirect#ea52fe5a dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes cdn_file_hashes:Vector = upload.File;
+upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector = upload.File;
dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int = DcOption;
-config#9c840964 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string suggested_lang_code:flags.2?string lang_pack_version:flags.2?int disabled_features:Vector = Config;
+config#86b5778e flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string suggested_lang_code:flags.2?string lang_pack_version:flags.2?int = Config;
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
@@ -444,8 +445,6 @@ stickerPack#12b299d4 emoticon:string documents:Vector = StickerPack;
messages.allStickersNotModified#e86602c3 = messages.AllStickers;
messages.allStickers#edfd405f hash:int sets:Vector = messages.AllStickers;
-disabledFeature#ae636f24 feature:string description:string = DisabledFeature;
-
messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMessages;
contactLinkUnknown#5f4f9247 = ContactLink;
@@ -483,7 +482,7 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet;
inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet;
inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
-stickerSet#cd303b41 flags:# installed:flags.0?true archived:flags.1?true official:flags.2?true masks:flags.3?true id:long access_hash:long title:string short_name:string count:int hash:int = StickerSet;
+stickerSet#5585a139 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string count:int hash:int = StickerSet;
messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet;
@@ -520,6 +519,8 @@ messageEntityPre#73924be0 offset:int length:int language:string = MessageEntity;
messageEntityTextUrl#76a6d327 offset:int length:int url:string = MessageEntity;
messageEntityMentionName#352dca58 offset:int length:int user_id:int = MessageEntity;
inputMessageEntityMentionName#208e68c9 offset:int length:int user_id:InputUser = MessageEntity;
+messageEntityPhone#9b69e34b offset:int length:int = MessageEntity;
+messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity;
inputChannelEmpty#ee8c1e86 = InputChannel;
inputChannel#afeb712e channel_id:int access_hash:long = InputChannel;
@@ -570,7 +571,7 @@ inputBotInlineMessageMediaVenue#aaafadc8 flags:# geo_point:InputGeoPoint title:s
inputBotInlineMessageMediaContact#2daf01a7 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
-inputBotInlineResult#2cbbe15a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb_url:flags.4?string content_url:flags.5?string content_type:flags.5?string w:flags.6?int h:flags.6?int duration:flags.7?int send_message:InputBotInlineMessage = InputBotInlineResult;
+inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult;
inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult;
inputBotInlineResultDocument#fff8fdc4 flags:# id:string type:string title:flags.1?string description:flags.2?string document:InputDocument send_message:InputBotInlineMessage = InputBotInlineResult;
inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:InputBotInlineMessage = InputBotInlineResult;
@@ -581,7 +582,7 @@ botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:f
botInlineMessageMediaVenue#4366232e flags:# geo:GeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaContact#35edb4d4 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
-botInlineResult#9bebaeb9 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb_url:flags.4?string content_url:flags.5?string content_type:flags.5?string w:flags.6?int h:flags.6?int duration:flags.7?int send_message:BotInlineMessage = BotInlineResult;
+botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult;
botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult;
messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM results:Vector cache_time:int users:Vector = messages.BotResults;
@@ -630,7 +631,7 @@ messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers;
messages.featuredStickers#f89d88e5 hash:int sets:Vector unread:Vector = messages.FeaturedStickers;
messages.recentStickersNotModified#b17f890 = messages.RecentStickers;
-messages.recentStickers#5ce20970 hash:int stickers:Vector = messages.RecentStickers;
+messages.recentStickers#22f3afb3 hash:int packs:Vector stickers:Vector dates:Vector = messages.RecentStickers;
messages.archivedStickers#4fcba9c8 count:int sets:Vector = messages.ArchivedStickers;
@@ -712,6 +713,7 @@ paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string e
paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials;
webDocument#c61acbd8 url:string access_hash:long size:int mime_type:string attributes:Vector dc_id:int = WebDocument;
+webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attributes:Vector = WebDocument;
inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector = InputWebDocument;
@@ -800,8 +802,6 @@ channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?tru
popularContact#5ce14175 client_id:long importers:int = PopularContact;
-cdnFileHash#77eec38f offset:int limit:int hash:bytes = CdnFileHash;
-
messages.favedStickersNotModified#9e8fa6d3 = messages.FavedStickers;
messages.favedStickers#f37f2f16 hash:int packs:Vector stickers:Vector = messages.FavedStickers;
@@ -823,6 +823,15 @@ inputMessageID#a676a322 id:int = InputMessage;
inputMessageReplyTo#bad88395 id:int = InputMessage;
inputMessagePinned#86872538 = InputMessage;
+inputDialogPeer#fcaafeb7 peer:InputPeer = InputDialogPeer;
+
+dialogPeer#e56dbf05 peer:Peer = DialogPeer;
+
+messages.foundStickerSetsNotModified#d54b65d = messages.FoundStickerSets;
+messages.foundStickerSets#5108d648 hash:int sets:Vector = messages.FoundStickerSets;
+
+fileHash#6242c773 offset:int limit:int hash:bytes = FileHash;
+
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -849,7 +858,7 @@ auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentC
auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool;
auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector = Bool;
-account.registerDevice#1389cc token_type:int token:string app_sandbox:Bool other_uids:Vector = Bool;
+account.registerDevice#5cbea590 token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool;
account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector = Bool;
account.updateNotifySettings#84be5b93 peer:InputNotifyPeer settings:InputPeerNotifySettings = Bool;
account.getNotifySettings#12b3ad31 peer:InputNotifyPeer = PeerNotifySettings;
@@ -902,7 +911,7 @@ contacts.resetSaved#879537f1 = Bool;
messages.getMessages#63c66506 id:Vector = messages.Messages;
messages.getDialogs#191ba9c5 flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int = messages.Dialogs;
messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
-messages.search#39e9ea0 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
+messages.search#8614ef68 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages;
messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true peer:InputPeer max_id:int = messages.AffectedHistory;
messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages;
@@ -914,6 +923,7 @@ messages.forwardMessages#708e0195 flags:# silent:flags.5?true background:flags.6
messages.reportSpam#cf1592db peer:InputPeer = Bool;
messages.hideReportSpam#a8f1709b peer:InputPeer = Bool;
messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings;
+messages.report#bd82b658 peer:InputPeer id:Vector reason:ReportReason = Bool;
messages.getChats#3c6aa187 id:Vector = messages.Chats;
messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull;
messages.editChatTitle#dc452855 chat_id:int title:string = Updates;
@@ -933,7 +943,7 @@ messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long da
messages.receivedQueue#55a5bb66 max_qts:int = Vector;
messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool;
messages.readMessageContents#36a73f77 id:Vector = messages.AffectedMessages;
-messages.getStickers#ae22e045 emoticon:string hash:string = messages.Stickers;
+messages.getStickers#85cb5182 flags:# exclude_featured:flags.0?true emoticon:string hash:string = messages.Stickers;
messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers;
messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector = MessageMedia;
messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite;
@@ -961,7 +971,7 @@ messages.editMessage#5d1b8dd flags:# no_webpage:flags.1?true stop_geo_live:flags
messages.editInlineBotMessage#b0e08243 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector geo_point:flags.13?InputGeoPoint = Bool;
messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer;
messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool;
-messages.getPeerDialogs#2d9776b9 peers:Vector = messages.PeerDialogs;
+messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs;
messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool;
messages.getAllDrafts#6a3f8d65 = Updates;
messages.getFeaturedStickers#2dacca4f hash:int = messages.FeaturedStickers;
@@ -979,8 +989,8 @@ messages.getInlineGameHighScores#f635e1b id:InputBotInlineMessageID user_id:Inpu
messages.getCommonChats#d0a48c4 user_id:InputUser max_id:int limit:int = messages.Chats;
messages.getAllChats#eba80ff0 except_ids:Vector = messages.Chats;
messages.getWebPage#32ca8f91 url:string hash:int = WebPage;
-messages.toggleDialogPin#3289be6a flags:# pinned:flags.0?true peer:InputPeer = Bool;
-messages.reorderPinnedDialogs#959ff644 flags:# force:flags.0?true order:Vector = Bool;
+messages.toggleDialogPin#a731e257 flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
+messages.reorderPinnedDialogs#5b51d63f flags:# force:flags.0?true order:Vector = Bool;
messages.getPinnedDialogs#e254d64e = messages.PeerDialogs;
messages.setBotShippingResults#e5f672fa flags:# query_id:long error:flags.0?string shipping_options:flags.1?Vector = Bool;
messages.setBotPrecheckoutResults#9c2dd95 flags:# success:flags.1?true query_id:long error:flags.0?string = Bool;
@@ -990,9 +1000,10 @@ messages.getFavedStickers#21ce0b0e hash:int = messages.FavedStickers;
messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool;
messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory;
-messages.getRecentLocations#249431e2 peer:InputPeer limit:int = messages.Messages;
+messages.getRecentLocations#bbc45b09 peer:InputPeer limit:int hash:int = messages.Messages;
messages.sendMultiMedia#2095512f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector = Updates;
messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile;
+messages.searchStickerSets#c2b7d08b flags:# exclude_featured:flags.0?true q:string hash:int = messages.FoundStickerSets;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1008,8 +1019,9 @@ upload.getFile#e3a6cfb5 location:InputFileLocation offset:int limit:int = upload
upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool;
upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile;
upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile;
-upload.reuploadCdnFile#1af91c09 file_token:bytes request_token:bytes = Vector;
-upload.getCdnFileHashes#f715c87b file_token:bytes offset:int = Vector;
+upload.reuploadCdnFile#9b2754a8 file_token:bytes request_token:bytes = Vector;
+upload.getCdnFileHashes#4da54231 file_token:bytes offset:int = Vector;
+upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector;
help.getConfig#c4f9186b = Config;
help.getNearestDc#1fb33026 = NearestDc;
@@ -1085,4 +1097,4 @@ langpack.getStrings#2e1ee318 lang_code:string keys:Vector = Vector;
-// LAYER 75
\ No newline at end of file
+// LAYER 76
From 440654a63f545f6db5aa6114f8ec26f1eebf14f2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 20 Mar 2018 18:54:05 +0100
Subject: [PATCH 46/88] Log info when disconnecting
---
pyrogram/connection/connection.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py
index aa958a7a..02f57efc 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -54,6 +54,7 @@ class Connection:
def close(self):
self.connection.close()
+ log.info("Disconnected")
def send(self, data: bytes):
with self.lock:
From 2b33f239900fa337831645c3d9d67d6ec8a3b2b2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 20 Mar 2018 19:25:23 +0100
Subject: [PATCH 47/88] Check whether get_file failed or not #37 If it failed,
also delete any eventual temporary file
---
pyrogram/client/client.py | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 20ac58bc..5a16f482 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -567,6 +567,9 @@ class Client:
progress=progress
)
+ if tmp_file_name is None:
+ return None
+
if file_name is not None:
path[0] = "downloads/{}".format(file_name)
@@ -2334,11 +2337,16 @@ class Client:
if len(chunk) < limit:
break
except Exception as e:
- log.error(e)
+ raise e
finally:
cdn_session.stop()
except Exception as e:
log.error(e)
+
+ try:
+ os.remove(file_name)
+ except OSError:
+ pass
else:
return file_name
finally:
From aa8125d7a2c25095b8dbd0159f0a982c6f22fd31 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 20 Mar 2018 19:26:06 +0100
Subject: [PATCH 48/88] Log more info in case there is an exception in get_file
#37
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 5a16f482..f5b47099 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2341,7 +2341,7 @@ class Client:
finally:
cdn_session.stop()
except Exception as e:
- log.error(e)
+ log.error(e, exc_info=True)
try:
os.remove(file_name)
From 5bc10b45a33fb61ff169ba126930d38de1de642f Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 15:20:04 -0500
Subject: [PATCH 49/88] Use OS temp file, specific path download via path
seperator inspection
---
pyrogram/client/client.py | 262 +++++++++++++++++---------------------
1 file changed, 117 insertions(+), 145 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 765de03b..9ced004f 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -34,6 +34,11 @@ from hashlib import sha256, md5
from queue import Queue
from signal import signal, SIGINT, SIGTERM, SIGABRT
from threading import Event, Thread
+import tempfile
+
+import shutil
+
+import errno
from pyrogram.api import functions, types
from pyrogram.api.core import Object
@@ -55,8 +60,6 @@ from pyrogram.session.internals import MsgId
from .input_media import InputMedia
from .style import Markdown, HTML
-from typing import Any
-
log = logging.getLogger(__name__)
ApiKey = namedtuple("ApiKey", ["api_id", "api_hash"])
@@ -510,14 +513,18 @@ class Client:
break
try:
- media, file_dir, file_name, done, progress, path = media
+ media, file_name, done, progress, path = media
+ tmp_file_name = None
- if file_dir is not None:
- # Make file_dir if it was specified
- os.makedirs(file_dir, exist_ok=True)
+ download_directory = "downloads"
- if isinstance(file_name, str) and file_name is not None:
- os.makedirs(os.path.dirname(file_name), exist_ok=True)
+ if file_name.endswith('/') or file_name.endswith('\\'):
+ # treat the file name as a directory
+ download_directory = file_name
+ file_name = None
+ elif '/' in file_name or '\\' in file_name:
+ # use file_name as a full path instead
+ download_directory = ''
if isinstance(media, types.MessageMediaDocument):
document = media.document
@@ -543,16 +550,13 @@ class Client:
elif isinstance(i, types.DocumentAttributeAnimated):
file_name = file_name.replace("doc", "gif")
- file_name = os.path.join(file_dir if file_dir is not None else '', file_name)
-
- self.get_file(
+ tmp_file_name = self.get_file(
dc_id=document.dc_id,
id=document.id,
access_hash=document.access_hash,
version=document.version,
size=document.size,
- progress=progress,
- file_out=file_name
+ progress=progress
)
elif isinstance(media, (types.MessageMediaPhoto, types.Photo)):
if isinstance(media, types.MessageMediaPhoto):
@@ -567,27 +571,46 @@ class Client:
self.rnd_id()
)
- file_name = os.path.join(file_dir if file_dir is not None else '', file_name)
-
photo_loc = photo.sizes[-1].location
- self.get_file(
+ tmp_file_name = self.get_file(
dc_id=photo_loc.dc_id,
volume_id=photo_loc.volume_id,
local_id=photo_loc.local_id,
secret=photo_loc.secret,
size=photo.sizes[-1].size,
- progress=progress,
- file_out=file_name
+ progress=progress
)
if file_name is not None:
- path[0] = file_name
+ path[0] = os.path.join(download_directory, file_name)
+
+ try:
+ os.remove(os.path.join(download_directory, file_name))
+ except OSError:
+ pass
+ finally:
+ try:
+ if download_directory:
+ os.makedirs(download_directory, exist_ok=True)
+ else:
+ os.makedirs(os.path.dirname(file_name), exist_ok=True)
+
+ # avoid errors moving between drives on windows
+ shutil.move(tmp_file_name, os.path.join(download_directory, file_name))
+ except OSError as e:
+ log.error(e, exc_info=True)
except Exception as e:
log.error(e, exc_info=True)
finally:
done.set()
+ try:
+ os.remove(tmp_file_name)
+ except OSError as e:
+ if not e.errno == errno.ENOENT:
+ log.error(e, exc_info=True)
+
log.debug("{} stopped".format(name))
def updates_worker(self):
@@ -2176,9 +2199,7 @@ class Client:
secret: int = None,
version: int = 0,
size: int = None,
- progress: callable = None,
- file_out: Any = None) -> str:
-
+ progress: callable = None) -> str:
if dc_id != self.dc_id:
exported_auth = self.send(
functions.auth.ExportAuthorization(
@@ -2226,13 +2247,11 @@ class Client:
version=version
)
+ fd, file_name = tempfile.mkstemp()
+
limit = 1024 * 1024
offset = 0
- # file object being written
- f = None
- close_file, call_flush, call_fsync = False, False, False
-
try:
r = session.send(
functions.upload.GetFile(
@@ -2242,51 +2261,30 @@ class Client:
)
)
- if file_out is None:
- f = open("download_{}.temp".format(MsgId(), 'wb'))
- close_file = True
-
- elif isinstance(file_out, str):
- f = open(file_out, 'wb')
- close_file = True
-
- elif hasattr(file_out, 'write'):
- f = file_out
-
- if hasattr(file_out, 'flush'):
- call_flush = True
- if hasattr(file_out, 'fileno'):
- call_fsync = True
- else:
- raise ValueError('file_out argument of client.get_file must at least implement a write method if not a '
- 'string.')
-
if isinstance(r, types.upload.File):
- while True:
- chunk = r.bytes
+ with os.fdopen(fd, "wb") as f:
+ while True:
+ chunk = r.bytes
- if not chunk:
- break
+ if not chunk:
+ break
- f.write(chunk)
-
- if call_flush:
+ f.write(chunk)
f.flush()
- if call_fsync:
os.fsync(f.fileno())
- offset += limit
+ offset += limit
- if progress:
- progress(min(offset, size), size)
+ if progress:
+ progress(min(offset, size), size)
- r = session.send(
- functions.upload.GetFile(
- location=location,
- offset=offset,
- limit=limit
+ r = session.send(
+ functions.upload.GetFile(
+ location=location,
+ offset=offset,
+ limit=limit
+ )
)
- )
if isinstance(r, types.upload.FileCdnRedirect):
cdn_session = Session(
@@ -2301,76 +2299,77 @@ class Client:
cdn_session.start()
try:
- while True:
- r2 = cdn_session.send(
- functions.upload.GetCdnFile(
- location=location,
- file_token=r.file_token,
- offset=offset,
- limit=limit
- )
- )
-
- if isinstance(r2, types.upload.CdnFileReuploadNeeded):
- try:
- session.send(
- functions.upload.ReuploadCdnFile(
- file_token=r.file_token,
- request_token=r2.request_token
- )
+ with os.fdopen(fd, "wb") as f:
+ while True:
+ r2 = cdn_session.send(
+ functions.upload.GetCdnFile(
+ location=location,
+ file_token=r.file_token,
+ offset=offset,
+ limit=limit
)
- except VolumeLocNotFound:
- break
- else:
- continue
+ )
- chunk = r2.bytes
+ if isinstance(r2, types.upload.CdnFileReuploadNeeded):
+ try:
+ session.send(
+ functions.upload.ReuploadCdnFile(
+ file_token=r.file_token,
+ request_token=r2.request_token
+ )
+ )
+ except VolumeLocNotFound:
+ break
+ else:
+ continue
- # https://core.telegram.org/cdn#decrypting-files
- decrypted_chunk = AES.ctr_decrypt(
- chunk,
- r.encryption_key,
- r.encryption_iv,
- offset
- )
+ chunk = r2.bytes
- hashes = session.send(
- functions.upload.GetCdnFileHashes(
- r.file_token,
+ # https://core.telegram.org/cdn#decrypting-files
+ decrypted_chunk = AES.ctr_decrypt(
+ chunk,
+ r.encryption_key,
+ r.encryption_iv,
offset
)
- )
- # https://core.telegram.org/cdn#verifying-files
- for i, h in enumerate(hashes):
- cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)]
- assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i)
+ hashes = session.send(
+ functions.upload.GetCdnFileHashes(
+ r.file_token,
+ offset
+ )
+ )
- f.write(decrypted_chunk)
+ # https://core.telegram.org/cdn#verifying-files
+ for i, h in enumerate(hashes):
+ cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)]
+ assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i)
- if call_flush:
+ f.write(decrypted_chunk)
f.flush()
- if call_fsync:
os.fsync(f.fileno())
- offset += limit
+ offset += limit
- if progress:
- progress(min(offset, size), size)
+ if progress:
+ progress(min(offset, size), size)
- if len(chunk) < limit:
- break
+ if len(chunk) < limit:
+ break
except Exception as e:
- log.error(e)
+ raise e
finally:
cdn_session.stop()
except Exception as e:
- log.error(e)
+ log.error(e, exc_info=True)
+
+ try:
+ os.remove(file_name)
+ except OSError:
+ pass
else:
- return file_out
+ return file_name
finally:
- if close_file and f is not None:
- f.close()
session.stop()
def join_chat(self, chat_id: str):
@@ -2627,27 +2626,18 @@ class Client:
def download_media(self,
message: types.Message,
file_name: str = None,
- file_dir: str = None,
block: bool = True,
- progress: callable = None
- ):
+ progress: callable = None):
"""Use this method to download the media from a Message.
+ Files are saved in the *downloads* folder.
+
Args:
message (:obj:`Message `):
The Message containing the media.
file_name (:obj:`str`, optional):
Specify a custom *file_name* to be used instead of the one provided by Telegram.
- This parameter is expected to be a full file path to the location you want the
- file to be placed, or a file like object. If not specified, the file will
- be put into the directory specified by *file_dir* with a generated name.
-
- file_dir (:obj:`str`, optional):
- Specify a directory to place the file in if no *file_name* is specified.
- If *file_dir* is *None*, the current working directory is used. The default
- value is the "downloads" folder in the current working directory. The
- directory tree will be created if it does not exist.
block (:obj:`bool`, optional):
Blocks the code execution until the file has been downloaded.
@@ -2669,15 +2659,7 @@ class Client:
Raises:
:class:`pyrogram.Error`
- :class:`ValueError` if both file_name and file_dir are specified.
"""
-
- if file_name is not None and file_dir is not None:
- raise ValueError('file_name and file_dir may not be specified together.')
-
- if file_name is None and file_dir is None:
- file_dir = 'downloads'
-
if isinstance(message, (types.Message, types.Photo)):
done = Event()
path = [None]
@@ -2688,7 +2670,7 @@ class Client:
media = message
if media is not None:
- self.download_queue.put((media, file_dir, file_name, done, progress, path))
+ self.download_queue.put((media, file_name, done, progress, path))
else:
return
@@ -2700,7 +2682,6 @@ class Client:
def download_photo(self,
photo: types.Photo or types.UserProfilePhoto or types.ChatPhoto,
file_name: str = None,
- file_dir: str = None,
block: bool = True):
"""Use this method to download a photo not contained inside a Message.
For example, a photo of a User or a Chat/Channel.
@@ -2712,16 +2693,7 @@ class Client:
The photo object.
file_name (:obj:`str`, optional):
- Specify a custom *file_name* to be used instead of the one provided by Telegram.
- This parameter is expected to be a full file path to the location you want the
- photo to be placed, or a file like object. If not specified, the photo will
- be put into the directory specified by *file_dir* with a generated name.
-
- file_dir (:obj:`str`, optional):
- Specify a directory to place the photo in if no *file_name* is specified.
- If *file_dir* is *None*, the current working directory is used. The default
- value is the "downloads" folder in the current working directory. The
- directory tree will be created if it does not exist.
+ Specify a custom *file_name* to be used.
block (:obj:`bool`, optional):
Blocks the code execution until the photo has been downloaded.
@@ -2747,7 +2719,7 @@ class Client:
)]
)
- return self.download_media(photo, file_name, file_dir, block)
+ return self.download_media(photo, file_name, block)
def add_contacts(self, contacts: list):
"""Use this method to add contacts to your Telegram address book.
From cd0e585d0d3c5555a2496e402b044b7e4bedbbe1 Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 15:42:31 -0500
Subject: [PATCH 50/88] Avoid calling fdopen on closed descriptor
---
pyrogram/client/client.py | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 9ced004f..b29792d6 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2299,6 +2299,17 @@ class Client:
cdn_session.start()
try:
+ # cant fdopen the closed file descriptor from above
+ # which is closed due to the with statement in the branch just above
+ # make a new temp file to write to
+
+ try:
+ os.remove(file_name)
+ except OSError:
+ pass
+
+ fd, file_name = tempfile.mkstemp()
+
with os.fdopen(fd, "wb") as f:
while True:
r2 = cdn_session.send(
From 4c9e4df53291876ece62610401c7a94dbdd343b9 Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 16:18:32 -0500
Subject: [PATCH 51/88] Amendment to comment on fdopen usage in get_file
---
pyrogram/client/client.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index b29792d6..b63d0b4f 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2299,8 +2299,8 @@ class Client:
cdn_session.start()
try:
- # cant fdopen the closed file descriptor from above
- # which is closed due to the with statement in the branch just above
+ # cant fdopen the closed file descriptor which could be closed due
+ # to the with statement in the branch just above.
# make a new temp file to write to
try:
From f0c00c88013ba88a5c9e98b43f1446945cbbbe3d Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 16:30:48 -0500
Subject: [PATCH 52/88] move first mkstemp to exception safe location in
get_file
---
pyrogram/client/client.py | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index b63d0b4f..529873ff 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2247,10 +2247,9 @@ class Client:
version=version
)
- fd, file_name = tempfile.mkstemp()
-
limit = 1024 * 1024
offset = 0
+ file_name = None
try:
r = session.send(
@@ -2261,6 +2260,8 @@ class Client:
)
)
+ fd, file_name = tempfile.mkstemp()
+
if isinstance(r, types.upload.File):
with os.fdopen(fd, "wb") as f:
while True:
@@ -2374,10 +2375,11 @@ class Client:
except Exception as e:
log.error(e, exc_info=True)
- try:
- os.remove(file_name)
- except OSError:
- pass
+ if file_name:
+ try:
+ os.remove(file_name)
+ except OSError:
+ pass
else:
return file_name
finally:
From 8796e857af44918a05cf37fb3b5f42cbca6fb7c4 Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Tue, 20 Mar 2018 23:20:08 -0500
Subject: [PATCH 53/88] Amend comment on shutil.move in download_worker
os.renames cannot move across drives/partitions on any platform.
that is why shutil.move is used, because the OS allotted temp file could
possibly be on another drive or partition.
Also fix code formatting on new import statements.
---
pyrogram/client/client.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 211a23d5..26d5e9df 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -35,9 +35,7 @@ from queue import Queue
from signal import signal, SIGINT, SIGTERM, SIGABRT
from threading import Event, Thread
import tempfile
-
import shutil
-
import errno
from pyrogram.api import functions, types
@@ -599,7 +597,7 @@ class Client:
else:
os.makedirs(os.path.dirname(file_name), exist_ok=True)
- # avoid errors moving between drives on windows
+ # avoid errors moving between drives/partitions etc.
shutil.move(tmp_file_name, os.path.join(download_directory, file_name))
except OSError as e:
log.error(e, exc_info=True)
From b6a42aa8cd662d7839ada632e2e70ac5d63e5fee Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 21 Mar 2018 09:01:18 +0100
Subject: [PATCH 54/88] Do not mkstemp twice
Also use elif to make it less confusing
---
pyrogram/client/client.py | 26 ++++++--------------------
1 file changed, 6 insertions(+), 20 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 26d5e9df..0776c8d3 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2250,7 +2250,7 @@ class Client:
limit = 1024 * 1024
offset = 0
- file_name = None
+ fd, file_name = tempfile.mkstemp()
try:
r = session.send(
@@ -2261,8 +2261,6 @@ class Client:
)
)
- fd, file_name = tempfile.mkstemp()
-
if isinstance(r, types.upload.File):
with os.fdopen(fd, "wb") as f:
while True:
@@ -2288,7 +2286,7 @@ class Client:
)
)
- if isinstance(r, types.upload.FileCdnRedirect):
+ elif isinstance(r, types.upload.FileCdnRedirect):
cdn_session = Session(
r.dc_id,
self.test_mode,
@@ -2301,17 +2299,6 @@ class Client:
cdn_session.start()
try:
- # cant fdopen the closed file descriptor which could be closed due
- # to the with statement in the branch just above.
- # make a new temp file to write to
-
- try:
- os.remove(file_name)
- except OSError:
- pass
-
- fd, file_name = tempfile.mkstemp()
-
with os.fdopen(fd, "wb") as f:
while True:
r2 = cdn_session.send(
@@ -2376,11 +2363,10 @@ class Client:
except Exception as e:
log.error(e, exc_info=True)
- if file_name:
- try:
- os.remove(file_name)
- except OSError:
- pass
+ try:
+ os.remove(file_name)
+ except OSError:
+ pass
else:
return file_name
finally:
From b45960212b86a4277d155e74e9c8fbb7c2e54503 Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Wed, 21 Mar 2018 03:19:09 -0500
Subject: [PATCH 55/88] Simplify branch in download_worker exception handler
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 0776c8d3..cf4deb85 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -609,7 +609,7 @@ class Client:
try:
os.remove(tmp_file_name)
except OSError as e:
- if not e.errno == errno.ENOENT:
+ if e.errno != errno.ENOENT:
log.error(e, exc_info=True)
log.debug("{} stopped".format(name))
From 0f4e29584ac46af38e8605f9720e3b0e8189693d Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Wed, 21 Mar 2018 04:07:55 -0500
Subject: [PATCH 56/88] Make use of tempfile.NamedTemporaryFile in getfile, use
context managers
---
pyrogram/client/client.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index cf4deb85..50009ba4 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -2250,7 +2250,7 @@ class Client:
limit = 1024 * 1024
offset = 0
- fd, file_name = tempfile.mkstemp()
+ file_name = None
try:
r = session.send(
@@ -2262,7 +2262,9 @@ class Client:
)
if isinstance(r, types.upload.File):
- with os.fdopen(fd, "wb") as f:
+ with tempfile.NamedTemporaryFile('wb', delete=False) as f:
+ file_name = f.name
+
while True:
chunk = r.bytes
@@ -2299,7 +2301,8 @@ class Client:
cdn_session.start()
try:
- with os.fdopen(fd, "wb") as f:
+ with tempfile.NamedTemporaryFile('wb', delete=False) as f:
+ file_name = f.name
while True:
r2 = cdn_session.send(
functions.upload.GetCdnFile(
From f6ea3e9b424538c1d7995852877a056400a7d790 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 21 Mar 2018 13:39:23 +0100
Subject: [PATCH 57/88] Cleaner code and some little changes
TODO: "" or None for faulty download, which is better?
---
pyrogram/client/client.py | 71 ++++++++++++++++-----------------------
1 file changed, 29 insertions(+), 42 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 50009ba4..ea77ff75 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -25,6 +25,7 @@ import mimetypes
import os
import re
import struct
+import tempfile
import threading
import time
from collections import namedtuple
@@ -34,9 +35,6 @@ from hashlib import sha256, md5
from queue import Queue
from signal import signal, SIGINT, SIGTERM, SIGABRT
from threading import Event, Thread
-import tempfile
-import shutil
-import errno
from pyrogram.api import functions, types
from pyrogram.api.core import Object
@@ -506,23 +504,17 @@ class Client:
while True:
media = self.download_queue.get()
+ temp_file_path = ""
+ final_file_path = ""
if media is None:
break
try:
media, file_name, done, progress, path = media
- tmp_file_name = None
- download_directory = "downloads"
-
- if file_name.endswith('/') or file_name.endswith('\\'):
- # treat the file name as a directory
- download_directory = file_name
- file_name = None
- elif '/' in file_name or '\\' in file_name:
- # use file_name as a full path instead
- download_directory = ''
+ directory, file_name = os.path.split(file_name)
+ directory = directory or "downloads"
if isinstance(media, types.MessageMediaDocument):
document = media.document
@@ -548,7 +540,7 @@ class Client:
elif isinstance(i, types.DocumentAttributeAnimated):
file_name = file_name.replace("doc", "gif")
- tmp_file_name = self.get_file(
+ temp_file_path = self.get_file(
dc_id=document.dc_id,
id=document.id,
access_hash=document.access_hash,
@@ -571,7 +563,7 @@ class Client:
photo_loc = photo.sizes[-1].location
- tmp_file_name = self.get_file(
+ temp_file_path = self.get_file(
dc_id=photo_loc.dc_id,
volume_id=photo_loc.volume_id,
local_id=photo_loc.local_id,
@@ -580,37 +572,29 @@ class Client:
progress=progress
)
- if tmp_file_name is None:
- return None
+ if temp_file_path:
+ final_file_path = os.path.join(directory, file_name)
- if file_name is not None:
- path[0] = os.path.join(download_directory, file_name)
-
- try:
- os.remove(os.path.join(download_directory, file_name))
- except OSError:
- pass
- finally:
try:
- if download_directory:
- os.makedirs(download_directory, exist_ok=True)
- else:
- os.makedirs(os.path.dirname(file_name), exist_ok=True)
+ os.remove(final_file_path)
+ except OSError:
+ pass
- # avoid errors moving between drives/partitions etc.
- shutil.move(tmp_file_name, os.path.join(download_directory, file_name))
- except OSError as e:
- log.error(e, exc_info=True)
+ os.renames(temp_file_path, final_file_path)
except Exception as e:
log.error(e, exc_info=True)
- finally:
- done.set()
try:
- os.remove(tmp_file_name)
- except OSError as e:
- if e.errno != errno.ENOENT:
- log.error(e, exc_info=True)
+ os.remove(temp_file_path)
+ except OSError:
+ pass
+ else:
+ # TODO: "" or None for faulty download, which is better?
+ # os.path methods return "" in case something does not exist, I prefer this.
+ # For now let's keep None
+ path[0] = final_file_path or None
+ finally:
+ done.set()
log.debug("{} stopped".format(name))
@@ -2250,7 +2234,7 @@ class Client:
limit = 1024 * 1024
offset = 0
- file_name = None
+ file_name = ""
try:
r = session.send(
@@ -2303,6 +2287,7 @@ class Client:
try:
with tempfile.NamedTemporaryFile('wb', delete=False) as f:
file_name = f.name
+
while True:
r2 = cdn_session.send(
functions.upload.GetCdnFile(
@@ -2370,6 +2355,8 @@ class Client:
os.remove(file_name)
except OSError:
pass
+
+ return ""
else:
return file_name
finally:
@@ -2628,7 +2615,7 @@ class Client:
def download_media(self,
message: types.Message,
- file_name: str = None,
+ file_name: str = "",
block: bool = True,
progress: callable = None):
"""Use this method to download the media from a Message.
@@ -2684,7 +2671,7 @@ class Client:
def download_photo(self,
photo: types.Photo or types.UserProfilePhoto or types.ChatPhoto,
- file_name: str = None,
+ file_name: str = "",
block: bool = True):
"""Use this method to download a photo not contained inside a Message.
For example, a photo of a User or a Chat/Channel.
From 76ad29ae11099bce3137633cfba9d1a141838347 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 21 Mar 2018 15:42:32 +0100
Subject: [PATCH 58/88] Fix saving files on another drive (windows) @EriHoss
---
pyrogram/client/client.py | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index ea77ff75..dd0d64fe 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -574,13 +574,8 @@ class Client:
if temp_file_path:
final_file_path = os.path.join(directory, file_name)
-
- try:
- os.remove(final_file_path)
- except OSError:
- pass
-
- os.renames(temp_file_path, final_file_path)
+ os.makedirs(directory, exist_ok=True)
+ shutil.move(temp_file_path, final_file_path)
except Exception as e:
log.error(e, exc_info=True)
From 40e7d72e873239b7f11d88ce0b8345010627a01e Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 21 Mar 2018 15:43:58 +0100
Subject: [PATCH 59/88] Make paths good looking
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index dd0d64fe..746d1929 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -573,7 +573,7 @@ class Client:
)
if temp_file_path:
- final_file_path = os.path.join(directory, file_name)
+ final_file_path = re.sub("\\\\", "/", os.path.join(directory, file_name))
os.makedirs(directory, exist_ok=True)
shutil.move(temp_file_path, final_file_path)
except Exception as e:
From fa6af8695e9c43ca41a982ab0eff47680c294624 Mon Sep 17 00:00:00 2001
From: Eric Blundell
Date: Wed, 21 Mar 2018 10:13:45 -0500
Subject: [PATCH 60/88] Fix missing shutil import
---
pyrogram/client/client.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 746d1929..f024db96 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -28,6 +28,8 @@ import struct
import tempfile
import threading
import time
+import shutil
+
from collections import namedtuple
from configparser import ConfigParser
from datetime import datetime
From 569ab1696ac4245c96521ae6d48c3225c45c1f79 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 21 Mar 2018 16:17:13 +0100
Subject: [PATCH 61/88] Return the good looking absolute path instead of an
ugly relative one #37
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index f024db96..869ca39d 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -575,7 +575,7 @@ class Client:
)
if temp_file_path:
- final_file_path = re.sub("\\\\", "/", os.path.join(directory, file_name))
+ final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name)))
os.makedirs(directory, exist_ok=True)
shutil.move(temp_file_path, final_file_path)
except Exception as e:
From e4642266084c40ffbb1f58cc7c57d717d2f832cc Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 21 Mar 2018 17:39:53 +0100
Subject: [PATCH 62/88] Update docs
---
pyrogram/client/client.py | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 869ca39d..8277debb 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -24,12 +24,11 @@ import math
import mimetypes
import os
import re
+import shutil
import struct
import tempfile
import threading
import time
-import shutil
-
from collections import namedtuple
from configparser import ConfigParser
from datetime import datetime
@@ -2617,14 +2616,15 @@ class Client:
progress: callable = None):
"""Use this method to download the media from a Message.
- Files are saved in the *downloads* folder.
-
Args:
message (:obj:`Message `):
The Message containing the media.
file_name (:obj:`str`, optional):
- Specify a custom *file_name* to be used instead of the one provided by Telegram.
+ A custom *file_name* to be used instead of the one provided by Telegram.
+ By default, all files are downloaded in the *downloads* folder in your working directory.
+ You can also specify a path for downloading files in a custom location: paths that end with "/"
+ are considered directories. All non-existent folders will be created automatically.
block (:obj:`bool`, optional):
Blocks the code execution until the file has been downloaded.
@@ -2642,7 +2642,7 @@ class Client:
The size of the file.
Returns:
- The relative path of the downloaded file.
+ On success, the absolute path of the downloaded file as string is returned, None otherwise.
Raises:
:class:`pyrogram.Error`
@@ -2673,21 +2673,22 @@ class Client:
"""Use this method to download a photo not contained inside a Message.
For example, a photo of a User or a Chat/Channel.
- Photos are saved in the *downloads* folder.
-
Args:
photo (:obj:`Photo ` | :obj:`UserProfilePhoto ` | :obj:`ChatPhoto `):
The photo object.
file_name (:obj:`str`, optional):
- Specify a custom *file_name* to be used.
+ A custom *file_name* to be used instead of the one provided by Telegram.
+ By default, all photos are downloaded in the *downloads* folder in your working directory.
+ You can also specify a path for downloading photos in a custom location: paths that end with "/"
+ are considered directories. All non-existent folders will be created automatically.
block (:obj:`bool`, optional):
Blocks the code execution until the photo has been downloaded.
Defaults to True.
Returns:
- The relative path of the downloaded photo.
+ On success, the absolute path of the downloaded photo as string is returned, None otherwise.
Raises:
:class:`pyrogram.Error`
From 700bdd08b19220c65ae83862e374bbf514257752 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 21 Mar 2018 18:42:45 +0100
Subject: [PATCH 63/88] Add tgcrypto to install_requires and remove pyaes
---
setup.py | 11 +++--------
1 file changed, 3 insertions(+), 8 deletions(-)
diff --git a/setup.py b/setup.py
index 473aa921..6bbf8da5 100644
--- a/setup.py
+++ b/setup.py
@@ -81,12 +81,7 @@ setup(
packages=find_packages(exclude=["compiler*"]),
zip_safe=False,
install_requires=[
- "pyaes",
- "pysocks"
- ],
- extras_require={
- "tgcrypto": [
- "tgcrypto"
- ]
- }
+ "pysocks",
+ "tgcrypto"
+ ]
)
From b200f9d7dd344d0362001e45655aa47eb970d3fa Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 21 Mar 2018 18:43:30 +0100
Subject: [PATCH 64/88] Set Python 3.4 as min version
---
setup.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/setup.py b/setup.py
index 6bbf8da5..a04af486 100644
--- a/setup.py
+++ b/setup.py
@@ -56,7 +56,6 @@ setup(
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
@@ -77,7 +76,7 @@ setup(
"Source": "https://github.com/pyrogram/pyrogram",
"Documentation": "https://docs.pyrogram.ml",
},
- python_requires="~=3.3",
+ python_requires="~=3.4",
packages=find_packages(exclude=["compiler*"]),
zip_safe=False,
install_requires=[
From bf0b8aa692d069c0c3aea0bbcf6cc82a15196d88 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 21 Mar 2018 18:43:48 +0100
Subject: [PATCH 65/88] Clean code
---
setup.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/setup.py b/setup.py
index a04af486..9c0068c6 100644
--- a/setup.py
+++ b/setup.py
@@ -24,12 +24,9 @@ from setuptools import setup, find_packages
from compiler.api import compiler as api_compiler
from compiler.error import compiler as error_compiler
-# from compiler.docs import compiler as docs_compiler
-
if len(argv) > 1 and argv[1] != "sdist":
api_compiler.start()
error_compiler.start()
- # docs_compiler.start()
with open("pyrogram/__init__.py", encoding="utf-8") as f:
version = re.findall(r"__version__ = \"(.+)\"", f.read())[0]
From 7149adba2847785e308a0e8d927bb578a5ac6ad0 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 22 Mar 2018 14:34:33 +0100
Subject: [PATCH 66/88] Update README.md
---
examples/README.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/examples/README.md b/examples/README.md
index 545516fa..6b14e3e5 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -2,8 +2,9 @@
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, ...).
+with the more advanced examples. Every script is working right away (provided you correctly set up your credentials), 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)
From a9b1783910dd970daf670eda24a0016c758b522e Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 22 Mar 2018 14:36:46 +0100
Subject: [PATCH 67/88] Let api_key and proxy parameters override the
config.ini file
---
pyrogram/client/client.py | 52 ++++++++++++++++++++++++---------------
1 file changed, 32 insertions(+), 20 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 0c3bdb1d..a86a4c97 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -29,7 +29,6 @@ import struct
import tempfile
import threading
import time
-from collections import namedtuple
from configparser import ConfigParser
from datetime import datetime
from hashlib import sha256, md5
@@ -59,8 +58,20 @@ from .style import Markdown, HTML
log = logging.getLogger(__name__)
-ApiKey = namedtuple("ApiKey", ["api_id", "api_hash"])
-Proxy = namedtuple("Proxy", ["enabled", "hostname", "port", "username", "password"])
+
+class APIKey:
+ def __init__(self, api_id: int, api_hash: str):
+ self.api_id = api_id
+ self.api_hash = api_hash
+
+
+class Proxy:
+ def __init__(self, enabled: bool, hostname: str, port: int, username: str, password: str):
+ self.enabled = enabled
+ self.hostname = hostname
+ self.port = port
+ self.username = username
+ self.password = password
class Client:
@@ -127,7 +138,7 @@ class Client:
def __init__(self,
session_name: str,
- api_key: tuple or ApiKey = None,
+ api_key: tuple or APIKey = None,
proxy: dict or Proxy = None,
test_mode: bool = False,
token: str = None,
@@ -790,18 +801,28 @@ class Client:
parser = ConfigParser()
parser.read("config.ini")
- if parser.has_section("pyrogram"):
- self.api_key = ApiKey(
+ if self.api_key is not None:
+ self.api_key = APIKey(
+ api_id=int(self.api_key[0]),
+ api_hash=self.api_key[1]
+ )
+ elif parser.has_section("pyrogram"):
+ self.api_key = APIKey(
api_id=parser.getint("pyrogram", "api_id"),
api_hash=parser.get("pyrogram", "api_hash")
)
else:
- self.api_key = ApiKey(
- api_id=int(self.api_key[0]),
- api_hash=self.api_key[1]
- )
+ raise AttributeError("No API Key found")
- if parser.has_section("proxy"):
+ if self.proxy is not None:
+ self.proxy = Proxy(
+ enabled=True,
+ hostname=self.proxy["hostname"],
+ port=int(self.proxy["port"]),
+ username=self.proxy.get("username", None),
+ password=self.proxy.get("password", None)
+ )
+ elif parser.has_section("proxy"):
self.proxy = Proxy(
enabled=parser.getboolean("proxy", "enabled"),
hostname=parser.get("proxy", "hostname"),
@@ -809,15 +830,6 @@ class Client:
username=parser.get("proxy", "username", fallback=None) or None,
password=parser.get("proxy", "password", fallback=None) or None
)
- else:
- if self.proxy is not None:
- self.proxy = Proxy(
- enabled=True,
- hostname=self.proxy["hostname"],
- port=int(self.proxy["port"]),
- username=self.proxy.get("username", None),
- password=self.proxy.get("password", None)
- )
def load_session(self, session_name):
try:
From cc50d01bb155e300a0d6c529fb17bf556c7997c4 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 22 Mar 2018 15:30:18 +0100
Subject: [PATCH 68/88] Add requirements.txt
---
requirements.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 requirements.txt
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..21c697f1
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+pysocks
+tgcrypto
\ No newline at end of file
From 5846c721501bd756676684d27b2512c23dc5cd9e Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 22 Mar 2018 15:30:41 +0100
Subject: [PATCH 69/88] Load requirements from file
---
setup.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/setup.py b/setup.py
index 9c0068c6..64cc7f2d 100644
--- a/setup.py
+++ b/setup.py
@@ -24,6 +24,12 @@ from setuptools import setup, find_packages
from compiler.api import compiler as api_compiler
from compiler.error import compiler as error_compiler
+
+def requirements():
+ with open("requirements.txt", encoding="utf-8") as r:
+ return [i.strip() for i in r]
+
+
if len(argv) > 1 and argv[1] != "sdist":
api_compiler.start()
error_compiler.start()
@@ -76,8 +82,5 @@ setup(
python_requires="~=3.4",
packages=find_packages(exclude=["compiler*"]),
zip_safe=False,
- install_requires=[
- "pysocks",
- "tgcrypto"
- ]
+ install_requires=requirements()
)
From 4553bdda5e0111001aaf2aa4542fa0feb0d8baee Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 22 Mar 2018 15:36:32 +0100
Subject: [PATCH 70/88] Add requirements.txt to MANIFEST.in
---
MANIFEST.in | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MANIFEST.in b/MANIFEST.in
index 168fb020..f818e13a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,5 @@
## Include
-include COPYING COPYING.lesser NOTICE
+include COPYING COPYING.lesser NOTICE requirements.txt
recursive-include compiler *.py *.tl *.tsv *.txt
## Exclude
From 9fe98cf689f0a43beb57b61bce0534b4b82eff64 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 22 Mar 2018 16:22:20 +0100
Subject: [PATCH 71/88] Update README.md
---
examples/README.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/examples/README.md b/examples/README.md
index 6b14e3e5..66ca9405 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -2,7 +2,9 @@
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 (provided you correctly set up your credentials), meaning
+with the more advanced examples.
+
+Every script is working right away (provided you correctly set up your credentials), 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, ...).
From a0e3ab419917da35b6748adbfb317789a8736ef8 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 23 Mar 2018 08:27:23 +0100
Subject: [PATCH 72/88] Yet another markdown pattern fix
---
pyrogram/client/style/markdown.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py
index f3c7805d..d9e8c571 100644
--- a/pyrogram/client/style/markdown.py
+++ b/pyrogram/client/style/markdown.py
@@ -36,7 +36,7 @@ class Markdown:
CODE_DELIMITER = "`"
PRE_DELIMITER = "```"
- MARKDOWN_RE = re.compile(r"```([\w ]*)\n([\w\W]*)(?:\n|)```|\[(.+)\]\((.+)\)|({d})(.+?)\5".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]
From 1d9bb18a385ed1a8013b43197fa4f122a1386641 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 23 Mar 2018 12:59:03 +0100
Subject: [PATCH 73/88] Only match full t.me/joinchat links
---
pyrogram/client/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index a86a4c97..859f2be5 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -131,7 +131,7 @@ class Client:
Thread pool size for handling incoming updates. Defaults to 4.
"""
- INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)?([\w-]+)$")
+ INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)([\w-]+)$")
DIALOGS_AT_ONCE = 100
UPDATES_WORKERS = 1
DOWNLOAD_WORKERS = 1
From ef71dcf56ac80e478ac09eb44477ad2ea5cf6229 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 23 Mar 2018 13:46:43 +0100
Subject: [PATCH 74/88] Remove **kwargs for generated classes (function/types)
---
compiler/api/template/class.txt | 2 +-
pyrogram/client/client.py | 47 +++++++++++++++++++++++----------
2 files changed, 34 insertions(+), 15 deletions(-)
diff --git a/compiler/api/template/class.txt b/compiler/api/template/class.txt
index 2cba4bb9..d29caf05 100644
--- a/compiler/api/template/class.txt
+++ b/compiler/api/template/class.txt
@@ -11,7 +11,7 @@ class {class_name}(Object):
"""
ID = {object_id}
- def __init__(self{arguments}, **kwargs):
+ def __init__(self{arguments}):
{fields}
@staticmethod
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 859f2be5..648c3b98 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -1974,10 +1974,15 @@ class Client:
Raises:
:class:`pyrogram.Error`
"""
+ if "Upload" in action.__name__:
+ action = action(progress)
+ else:
+ action = action()
+
return self.send(
functions.messages.SetTyping(
peer=self.resolve_peer(chat_id),
- action=action(progress=progress)
+ action=action
)
)
@@ -2171,14 +2176,21 @@ class Client:
md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()])
break
- session.send(
- (functions.upload.SaveBigFilePart if is_big else functions.upload.SaveFilePart)(
+ if is_big:
+ rpc = functions.upload.SaveBigFilePart(
file_id=file_id,
file_part=file_part,
- bytes=chunk,
- file_total_parts=file_total_parts
+ file_total_parts=file_total_parts,
+ bytes=chunk
)
- )
+ else:
+ rpc = functions.upload.SaveFilePart(
+ file_id=file_id,
+ file_part=file_part,
+ bytes=chunk
+ )
+
+ assert self.send(rpc), "Couldn't upload file"
if is_missing_part:
return
@@ -2191,14 +2203,22 @@ class Client:
if progress:
progress(min(file_part * part_size, file_size), file_size)
except Exception as e:
- log.error(e)
+ log.error(e, exc_info=True)
else:
- return (types.InputFileBig if is_big else types.InputFile)(
- id=file_id,
- parts=file_total_parts,
- name=os.path.basename(path),
- md5_checksum=md5_sum
- )
+ if is_big:
+ return types.InputFileBig(
+ id=file_id,
+ parts=file_total_parts,
+ name=os.path.basename(path),
+
+ )
+ else:
+ return types.InputFile(
+ id=file_id,
+ parts=file_total_parts,
+ name=os.path.basename(path),
+ md5_checksum=md5_sum
+ )
finally:
session.stop()
@@ -2318,7 +2338,6 @@ class Client:
while True:
r2 = cdn_session.send(
functions.upload.GetCdnFile(
- location=location,
file_token=r.file_token,
offset=offset,
limit=limit
From 813b7958e336b8489e65bb7190a00cd4ec0edc53 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 24 Mar 2018 15:02:03 +0100
Subject: [PATCH 75/88] Remove token parameter
---
pyrogram/client/client.py | 25 +++++++++++++------------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 648c3b98..70253563 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -81,10 +81,10 @@ class Client:
Args:
session_name (:obj:`str`):
- Name to uniquely identify an authorized session. It will be used
- to save the session to a file named *.session* and to load
- it when you restart your script. As long as a valid session file exists,
- Pyrogram won't ask you again to input your phone number.
+ Name to uniquely identify a session of either a User or a Bot.
+ For Users: pass a string of your choice, e.g.: "my_main_account".
+ For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
+ Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number.
api_key (:obj:`tuple`, optional):
Your Telegram API Key as tuple: *(api_id, api_hash)*.
@@ -92,8 +92,8 @@ class Client:
don't want to use the *config.ini* file.
proxy (:obj:`dict`, optional):
- Your SOCKS5 Proxy settings as dict: *{hostname: str, port: int, username: str, password: str}*.
- E.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*.
+ Your SOCKS5 Proxy settings as dict,
+ e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*.
*username* and *password* can be omitted if your proxy doesn't require authorization.
This is an alternative way to setup a proxy if you don't want to use the *config.ini* file.
@@ -102,10 +102,6 @@ class Client:
Only applicable for new sessions and will be ignored in case previously
created sessions are loaded.
- token (:obj:`str`, optional):
- Pass your Bot API token to log-in as Bot.
- E.g.: 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
-
phone_number (:obj:`str`, optional):
Pass your phone number (with your Country Code prefix included) to avoid
entering it manually. Only applicable for new sessions.
@@ -132,6 +128,7 @@ class Client:
"""
INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)([\w-]+)$")
+ BOT_TOKEN_RE = re.compile(r"^\d+:[\w-]+$")
DIALOGS_AT_ONCE = 100
UPDATES_WORKERS = 1
DOWNLOAD_WORKERS = 1
@@ -141,7 +138,6 @@ class Client:
api_key: tuple or APIKey = None,
proxy: dict or Proxy = None,
test_mode: bool = False,
- token: str = None,
phone_number: str = None,
phone_code: str or callable = None,
password: str = None,
@@ -153,7 +149,6 @@ class Client:
self.proxy = proxy
self.test_mode = test_mode
- self.token = token
self.phone_number = phone_number
self.password = password
self.phone_code = phone_code
@@ -162,6 +157,8 @@ class Client:
self.workers = workers
+ self.token = None
+
self.dc_id = None
self.auth_key = None
self.user_id = None
@@ -195,6 +192,10 @@ class Client:
Raises:
:class:`pyrogram.Error`
"""
+ if self.BOT_TOKEN_RE.match(self.session_name):
+ self.token = self.session_name
+ self.session_name = self.session_name.split(":")[0]
+
self.load_config()
self.load_session(self.session_name)
From bbe3a8a487b2624b24ce0b47975de53647fb21c3 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 24 Mar 2018 15:10:27 +0100
Subject: [PATCH 76/88] Set supports_streaming to True by default
---
pyrogram/client/client.py | 6 +++---
pyrogram/client/input_media.py | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 70253563..24d84a02 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -1414,7 +1414,7 @@ class Client:
width: int = 0,
height: int = 0,
thumb: str = None,
- supports_streaming: bool = None,
+ supports_streaming: bool = True,
disable_notification: bool = None,
reply_to_message_id: int = None,
progress: callable = None):
@@ -1495,7 +1495,7 @@ class Client:
thumb=file_thumb,
attributes=[
types.DocumentAttributeVideo(
- supports_streaming=supports_streaming,
+ supports_streaming=supports_streaming or None,
duration=duration,
w=width,
h=height
@@ -1748,7 +1748,7 @@ class Client:
mime_type=mimetypes.types_map[".mp4"],
attributes=[
types.DocumentAttributeVideo(
- supports_streaming=i.supports_streaming,
+ supports_streaming=i.supports_streaming or None,
duration=i.duration,
w=i.width,
h=i.height
diff --git a/pyrogram/client/input_media.py b/pyrogram/client/input_media.py
index da270c2e..f4110f1b 100644
--- a/pyrogram/client/input_media.py
+++ b/pyrogram/client/input_media.py
@@ -81,7 +81,7 @@ class InputMedia:
width: int = 0,
height: int = 0,
duration: int = 0,
- supports_streaming: bool = None):
+ supports_streaming: bool = True):
self.media = media
self.caption = caption
self.parse_mode = parse_mode
From a956463a570ce5ad51dc4d56f601f0fb63b29d80 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 24 Mar 2018 17:15:24 +0100
Subject: [PATCH 77/88] Update to v0.6.4
---
pyrogram/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 084cce6e..469dd399 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès
Date: Sat, 24 Mar 2018 17:22:57 +0100
Subject: [PATCH 78/88] Update README.rst
---
README.rst | 83 ++++++++++++++++++++----------------------------------
1 file changed, 31 insertions(+), 52 deletions(-)
diff --git a/README.rst b/README.rst
index e3f8e9f8..ae6f084f 100644
--- a/README.rst
+++ b/README.rst
@@ -29,42 +29,33 @@ Table of Contents
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.
-
+**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building
+custom Telegram applications in Python that interact with the MTProto API as both User and Bot.
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 setup**: Pyrogram can be easily installed using pip and requires very few lines of code to get started with.
+
+- **Easy to use**: Pyrogram provides idiomatic, clean and readable Python code making the Telegram API simple to use.
-- **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 Telegram servers.
-- **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.
+- **Updated**: Pyrogram makes use of the latest Telegram MTProto API version, currently Layer 76.
-- **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.
+- **Fast**: Pyrogram critical parts are boosted up by `TgCrypto`_, a high-performance Crypto Library written in pure C.
+
+- **Documented**: Pyrogram API methods are documented and resemble the well established Telegram Bot API,
+ thus offering a familiar look to Bot developers.
-- **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.
+- **Full API support**: Beside the simple Bot API-like methods, Pyrogram also provides an easy access to every single
+ Telegram MTProto API method allowing you to programmatically execute any action an official client is able to do, and more.
Requirements
------------
-- Python 3.3 or higher.
+- Python 3.4 or higher.
- A Telegram API key.
@@ -75,17 +66,11 @@ Getting Started
Installation
------------
-- You can easily install and upgrade the library using standard Python tools:
+- You can install and upgrade Pyrogram using pip:
.. code:: shell
$ pip3 install --upgrade pyrogram
-
-- Or, with TgCrypto_:
-
- .. code:: shell
-
- $ pip3 install --upgrade pyrogram[tgcrypto]
Configuration
-------------
@@ -102,7 +87,7 @@ Configuration
Usage
-----
-- And here's how Pyrogram looks like:
+- And here is how Pyrogram looks like:
.. code:: python
@@ -112,26 +97,25 @@ Usage
client.start()
client.send_message("me", "Hi there! I'm using Pyrogram")
- client.send_photo("me", "/home/dan/pic.jpg", "Nice photo!")
client.stop()
That's all you need for getting started with Pyrogram. For more detailed information,
-please refer to the Documentation_.
+please refer to the Documentation_ and the Examples_ folder.
Documentation
=============
-- The entire Pyrogram's documentation resides at https://docs.pyrogram.ml.
+- The entire Pyrogram documentation resides at https://docs.pyrogram.ml.
Contribution
============
-**You are very welcome to contribute** by either submitting pull requests or
-reporting issues/bugs as well as suggesting best practices, ideas, enhancements
-on both code and documentation. Any help is appreciated!
+Pyrogram is brand new! **You are welcome to try it and help make it better** by either submitting pull
+requests or reporting issues/bugs as well as suggesting best practices, ideas, enhancements on both code
+and documentation. Any help is appreciated!
Feedback
@@ -140,7 +124,6 @@ Feedback
Means for getting in touch:
- `Community`_
-- `Telegram`_
- `GitHub`_
- `Email`_
@@ -154,15 +137,11 @@ License
`GNU Lesser General Public License v3 or later (LGPLv3+)`_
-.. _`Telegram Messenger API`: https://core.telegram.org/api#telegram-api
+.. _`Telegram`: https://telegram.org/
-.. _`MTProto Mobile Protocol v2.0`: https://core.telegram.org/mtproto
+.. _`your own`: https://docs.pyrogram.ml/start/ProjectSetup#api-keys
-.. _`Layer 75`: compiler/api/source/main_api.tl
-
-.. _`your own`: https://docs.pyrogram.ml/start/ProjectSetup/#api-keys
-
-.. _`Telegram`: https://t.me/haskell
+.. _`Examples`: https://github.com/pyrogram/pyrogram/blob/master/examples/README.md
.. _`Community`: https://t.me/PyrogramChat
@@ -179,9 +158,9 @@ License
.. |header| raw:: html
@@ -202,11 +181,11 @@ License
-
+
-
@@ -219,7 +198,7 @@ License
.. |scheme| image:: https://www.pyrogram.ml/images/scheme.svg
:target: compiler/api/source/main_api.tl
- :alt: Scheme Layer 75
+ :alt: Scheme Layer 76
.. |tgcrypto| image:: https://www.pyrogram.ml/images/tgcrypto.svg
:target: https://github.com/pyrogram/tgcrypto
From 5bf64ac44491adf302558332b129b92d9c2faec9 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 25 Mar 2018 17:49:43 +0200
Subject: [PATCH 79/88] Don't process empty messages
---
pyrogram/client/client.py | 33 ++++++++++++++++++---------------
1 file changed, 18 insertions(+), 15 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 24d84a02..97f5851f 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -634,22 +634,25 @@ class Client:
pts_count = getattr(update, "pts_count", None)
if isinstance(update, types.UpdateNewChannelMessage):
- diff = self.send(
- functions.updates.GetChannelDifference(
- channel=self.resolve_peer(update.message.to_id.channel_id),
- filter=types.ChannelMessagesFilter(
- ranges=[types.MessageRange(
- min_id=update.message.id,
- max_id=update.message.id
- )]
- ),
- pts=pts - pts_count,
- limit=pts
- )
- )
+ message = update.message
- updates.users += diff.users
- updates.chats += diff.chats
+ if not isinstance(message, types.MessageEmpty):
+ diff = self.send(
+ functions.updates.GetChannelDifference(
+ channel=self.resolve_peer(update.message.to_id.channel_id),
+ filter=types.ChannelMessagesFilter(
+ ranges=[types.MessageRange(
+ min_id=update.message.id,
+ max_id=update.message.id
+ )]
+ ),
+ pts=pts - pts_count,
+ limit=pts
+ )
+ )
+
+ updates.users += diff.users
+ updates.chats += diff.chats
if channel_id and pts:
if channel_id not in self.channels_pts:
From faa363fee825ca240fce225e15f9836abf528682 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 25 Mar 2018 21:41:19 +0200
Subject: [PATCH 80/88] Update docstrings
---
pyrogram/client/client.py | 350 +++++++++++++++++++-------------------
1 file changed, 175 insertions(+), 175 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 97f5851f..9d89010a 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -80,50 +80,50 @@ class Client:
invoke every single Telegram API method available.
Args:
- session_name (:obj:`str`):
+ session_name (``str``):
Name to uniquely identify a session of either a User or a Bot.
For Users: pass a string of your choice, e.g.: "my_main_account".
For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number.
- api_key (:obj:`tuple`, optional):
+ api_key (``tuple``, optional):
Your Telegram API Key as tuple: *(api_id, api_hash)*.
E.g.: *(12345, "0123456789abcdef0123456789abcdef")*. This is an alternative way to pass it if you
don't want to use the *config.ini* file.
- proxy (:obj:`dict`, optional):
+ proxy (``dict``, optional):
Your SOCKS5 Proxy settings as dict,
e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*.
*username* and *password* can be omitted if your proxy doesn't require authorization.
This is an alternative way to setup a proxy if you don't want to use the *config.ini* file.
- test_mode (:obj:`bool`, optional):
+ test_mode (``bool``, optional):
Enable or disable log-in to testing servers. Defaults to False.
Only applicable for new sessions and will be ignored in case previously
created sessions are loaded.
- phone_number (:obj:`str`, optional):
+ phone_number (``str``, optional):
Pass your phone number (with your Country Code prefix included) to avoid
entering it manually. Only applicable for new sessions.
- phone_code (:obj:`str` | :obj:`callable`, optional):
+ phone_code (``str`` | ``callable``, optional):
Pass the phone code as string (for test numbers only), or pass a callback function
which must return the correct phone code as string (e.g., "12345").
Only applicable for new sessions.
- password (:obj:`str`, optional):
+ password (``str``, optional):
Pass your Two-Step Verification password (if you have one) to avoid entering it
manually. Only applicable for new sessions.
- first_name (:obj:`str`, optional):
+ first_name (``str``, optional):
Pass a First Name to avoid entering it manually. It will be used to automatically
create a new Telegram account in case the phone number you passed is not registered yet.
- last_name (:obj:`str`, optional):
+ last_name (``str``, optional):
Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can
be an empty string: ""
- workers (:obj:`int`, optional):
+ workers (``int``, optional):
Thread pool size for handling incoming updates. Defaults to 4.
"""
@@ -724,7 +724,7 @@ class Client:
then gently stop the Client by closing the underlying connection.
Args:
- stop_signals (:obj:`tuple`, optional):
+ stop_signals (``tuple``, optional):
Iterable containing signals the signal handler will listen to.
Defaults to (SIGINT, SIGTERM, SIGABRT).
"""
@@ -742,25 +742,25 @@ class Client:
You must call this method *before* you *start()* the Client.
Args:
- callback (:obj:`callable`):
+ callback (``callable``):
A function that will be called when a new update is received from the server. It takes
:obj:`(client, update, users, chats)` as positional arguments (Look at the section below for
a detailed description).
Other Parameters:
- client (:obj:`pyrogram.Client`):
+ client (:class:`Client `):
The Client itself, useful when you want to call other API methods inside the update handler.
- update (:obj:`Update`):
+ update (``Update``):
The received update, which can be one of the many single Updates listed in the *updates*
field you see in the :obj:`Update ` type.
- users (:obj:`dict`):
+ users (``dict``):
Dictionary of all :obj:`User ` mentioned in the update.
You can access extra info about the user (such as *first_name*, *last_name*, etc...) by using
the IDs you find in the *update* argument (e.g.: *users[1768841572]*).
- chats (:obj:`dict`):
+ chats (``dict``):
Dictionary of all :obj:`Chat ` and
:obj:`Channel ` mentioned in the update.
You can access extra info about the chat (such as *title*, *participants_count*, etc...)
@@ -782,10 +782,10 @@ class Client:
This method makes possible to manually call every single Telegram API method in a low-level manner.
Available functions are listed in the :obj:`pyrogram.api.functions` package and may accept compound
- data types from :obj:`pyrogram.api.types` as well as bare types such as :obj:`int`, :obj:`str`, etc...
+ data types from :obj:`pyrogram.api.types` as well as bare types such as ``int``, ``str``, etc...
Args:
- data (:obj:`Object`):
+ data (``Object``):
The API Scheme function filled with proper arguments.
Raises:
@@ -910,9 +910,9 @@ class Client:
not available yet in the Client class as an easy-to-use method).
Args:
- peer_id (:obj:`int` | :obj:`str` | :obj:`Peer`):
- The Peer ID you want to extract the InputPeer from. Can be one of these types: :obj:`int` (direct ID),
- :obj:`str` (@username), :obj:`PeerUser `,
+ peer_id (``int`` | ``str`` | ``Peer``):
+ The Peer ID you want to extract the InputPeer from. Can be one of these types: ``int`` (direct ID),
+ ``str`` (@username), :obj:`PeerUser `,
:obj:`PeerChat `, :obj:`PeerChannel `
Returns:
@@ -995,28 +995,28 @@ class Client:
"""Use this method to send text messages.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- text (:obj:`str`):
+ text (``str``):
Text of the message to be sent.
- parse_mode (:obj:`str`):
+ parse_mode (``str``):
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 message.
Defaults to Markdown.
- disable_web_page_preview (:obj:`bool`, optional):
+ disable_web_page_preview (``bool``, optional):
Disables link previews for links in this message.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`bool`, optional):
+ reply_to_message_id (``bool``, optional):
If the message is a reply, ID of the original message.
Returns:
@@ -1046,22 +1046,22 @@ class Client:
"""Use this method to forward messages of any kind.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- from_chat_id (:obj:`int` | :obj:`str`):
+ from_chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the source chat where the original message was sent.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- message_ids (:obj:`list`):
+ message_ids (``list``):
A list of Message identifiers in the chat specified in *from_chat_id*.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
@@ -1093,45 +1093,45 @@ class Client:
"""Use this method to send photos.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- photo (:obj:`str`):
+ photo (``str``):
Photo to send.
Pass a file path as string to send a photo that exists on your local machine.
- caption (:obj:`bool`, optional):
+ caption (``bool``, optional):
Photo caption, 0-200 characters.
- parse_mode (:obj:`str`):
+ parse_mode (``str``):
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.
- ttl_seconds (:obj:`int`, optional):
+ ttl_seconds (``int``, optional):
Self-Destruct Timer.
If you set a timer, the photo will self-destruct in :obj:`ttl_seconds`
seconds after it was viewed.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`int`, optional):
+ reply_to_message_id (``int``, optional):
If the message is a reply, ID of the original message.
- progress (:obj:`callable`):
+ progress (``callable``):
Pass a callback function to view the upload progress.
The function must accept two arguments (current, total).
Other Parameters:
- current (:obj:`int`):
+ current (``int``):
The amount of bytes uploaded so far.
- total (:obj:`int`):
+ total (``int``):
The size of the file.
Returns:
@@ -1179,49 +1179,49 @@ class Client:
For sending voice messages, use the :obj:`send_voice` method instead.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- audio (:obj:`str`):
+ audio (``str``):
Audio file to send.
Pass a file path as string to send an audio file that exists on your local machine.
- caption (:obj:`str`, optional):
+ caption (``str``, optional):
Audio caption, 0-200 characters.
- parse_mode (:obj:`str`):
+ parse_mode (``str``):
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.
- duration (:obj:`int`, optional):
+ duration (``int``, optional):
Duration of the audio in seconds.
- performer (:obj:`str`, optional):
+ performer (``str``, optional):
Performer.
- title (:obj:`str`, optional):
+ title (``str``, optional):
Track name.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`int`, optional):
+ reply_to_message_id (``int``, optional):
If the message is a reply, ID of the original message.
- progress (:obj:`callable`):
+ progress (``callable``):
Pass a callback function to view the upload progress.
The function must accept two arguments (current, total).
Other Parameters:
- current (:obj:`int`):
+ current (``int``):
The amount of bytes uploaded so far.
- total (:obj:`int`):
+ total (``int``):
The size of the file.
Returns:
@@ -1272,40 +1272,40 @@ class Client:
"""Use this method to send general files.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- document (:obj:`str`):
+ document (``str``):
File to send.
Pass a file path as string to send a file that exists on your local machine.
- caption (:obj:`str`, optional):
+ caption (``str``, optional):
Document caption, 0-200 characters.
- parse_mode (:obj:`str`):
+ parse_mode (``str``):
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.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`int`, optional):
+ reply_to_message_id (``int``, optional):
If the message is a reply, ID of the original message.
- progress (:obj:`callable`):
+ progress (``callable``):
Pass a callback function to view the upload progress.
The function must accept two arguments (current, total).
Other Parameters:
- current (:obj:`int`):
+ current (``int``):
The amount of bytes uploaded so far.
- total (:obj:`int`):
+ total (``int``):
The size of the file.
Returns:
@@ -1349,32 +1349,32 @@ class Client:
"""Use this method to send .webp stickers.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- sticker (:obj:`str`):
+ sticker (``str``):
Sticker to send.
Pass a file path as string to send a sticker that exists on your local machine.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`int`, optional):
+ reply_to_message_id (``int``, optional):
If the message is a reply, ID of the original message.
- progress (:obj:`callable`):
+ progress (``callable``):
Pass a callback function to view the upload progress.
The function must accept two arguments (current, total).
Other Parameters:
- current (:obj:`int`):
+ current (``int``):
The amount of bytes uploaded so far.
- total (:obj:`int`):
+ total (``int``):
The size of the file.
Returns:
@@ -1424,57 +1424,57 @@ class Client:
"""Use this method to send video files.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- video (:obj:`str`):
+ video (``str``):
Video to send.
Pass a file path as string to send a video that exists on your local machine.
- caption (:obj:`str`, optional):
+ caption (``str``, optional):
Video caption, 0-200 characters.
- parse_mode (:obj:`str`):
+ parse_mode (``str``):
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.
- duration (:obj:`int`, optional):
+ duration (``int``, optional):
Duration of sent video in seconds.
- width (:obj:`int`, optional):
+ width (``int``, optional):
Video width.
- height (:obj:`int`, optional):
+ height (``int``, optional):
Video height.
- thumb (:obj:`str`, optional):
+ thumb (``str``, optional):
Video thumbnail.
Pass a file path as string to send an image that exists on your local machine.
Thumbnail should have 90 or less pixels of width and 90 or less pixels of height.
- supports_streaming (:obj:`bool`, optional):
+ supports_streaming (``bool``, optional):
Pass True, if the uploaded video is suitable for streaming.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`int`, optional):
+ reply_to_message_id (``int``, optional):
If the message is a reply, ID of the original message.
- progress (:obj:`callable`):
+ progress (``callable``):
Pass a callback function to view the upload progress.
The function must accept two arguments (current, total).
Other Parameters:
- current (:obj:`int`):
+ current (``int``):
The amount of bytes uploaded so far.
- total (:obj:`int`):
+ total (``int``):
The size of the file.
Returns:
@@ -1529,43 +1529,43 @@ class Client:
"""Use this method to send audio files.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- voice (:obj:`str`):
+ voice (``str``):
Audio file to send.
Pass a file path as string to send an audio file that exists on your local machine.
- caption (:obj:`str`, optional):
+ caption (``str``, optional):
Voice message caption, 0-200 characters.
- parse_mode (:obj:`str`):
+ parse_mode (``str``):
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.
- duration (:obj:`int`, optional):
+ duration (``int``, optional):
Duration of the voice message in seconds.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`int`, optional):
+ reply_to_message_id (``int``, optional):
If the message is a reply, ID of the original message
- progress (:obj:`callable`):
+ progress (``callable``):
Pass a callback function to view the upload progress.
The function must accept two arguments (current, total).
Other Parameters:
- current (:obj:`int`):
+ current (``int``):
The amount of bytes uploaded so far.
- total (:obj:`int`):
+ total (``int``):
The size of the file.
Returns:
@@ -1614,38 +1614,38 @@ class Client:
"""Use this method to send video messages.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- video_note (:obj:`str`):
+ video_note (``str``):
Video note to send.
Pass a file path as string to send a video note that exists on your local machine.
- duration (:obj:`int`, optional):
+ duration (``int``, optional):
Duration of sent video in seconds.
- length (:obj:`int`, optional):
+ length (``int``, optional):
Video width and height.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`int`, optional):
+ reply_to_message_id (``int``, optional):
If the message is a reply, ID of the original message
- progress (:obj:`callable`):
+ progress (``callable``):
Pass a callback function to view the upload progress.
The function must accept two arguments (current, total).
Other Parameters:
- current (:obj:`int`):
+ current (``int``):
The amount of bytes uploaded so far.
- total (:obj:`int`):
+ total (``int``):
The size of the file.
Returns:
@@ -1694,21 +1694,21 @@ class Client:
On success, an Update containing the sent Messages is returned.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- media (:obj:`list`):
+ media (``list``):
A list containing either :obj:`pyrogram.InputMedia.Photo` or :obj:`pyrogram.InputMedia.Video` objects
describing photos and videos to be sent, must include 2–10 items.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`int`, optional):
+ reply_to_message_id (``int``, optional):
If the message is a reply, ID of the original message.
"""
multi_media = []
@@ -1793,23 +1793,23 @@ class Client:
"""Use this method to send points on the map.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- latitude (:obj:`float`):
+ latitude (``float``):
Latitude of the location.
- longitude (:obj:`float`):
+ longitude (``float``):
Longitude of the location.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`int`, optional):
+ reply_to_message_id (``int``, optional):
If the message is a reply, ID of the original message
Returns:
@@ -1846,32 +1846,32 @@ class Client:
"""Use this method to send information about a venue.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- latitude (:obj:`float`):
+ latitude (``float``):
Latitude of the venue.
- longitude (:obj:`float`):
+ longitude (``float``):
Longitude of the venue.
- title (:obj:`str`):
+ title (``str``):
Name of the venue.
- address (:obj:`str`):
+ address (``str``):
Address of the venue.
- foursquare_id (:obj:`str`, optional):
+ foursquare_id (``str``, optional):
Foursquare identifier of the venue.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`int`, optional):
+ reply_to_message_id (``int``, optional):
If the message is a reply, ID of the original message
Returns:
@@ -1911,26 +1911,26 @@ class Client:
"""Use this method to send phone contacts.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- phone_number (:obj:`str`):
+ phone_number (``str``):
Contact's phone number.
- first_name (:obj:`str`):
+ first_name (``str``):
Contact's first name.
- last_name (:obj:`str`):
+ last_name (``str``):
Contact's last name.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`int`, optional):
+ reply_to_message_id (``int``, optional):
If the message is a reply, ID of the original message.
Returns:
@@ -1961,18 +1961,18 @@ class Client:
"""Use this method when you need to tell the other party that something is happening on your side.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- action (:obj:`callable`):
+ action (``callable``):
Type of action to broadcast.
Choose one from the :class:`pyrogram.ChatAction` class,
depending on what the user is about to receive.
- progress (:obj:`int`, optional):
+ progress (``int``, optional):
Progress of the upload process.
Raises:
@@ -1997,14 +1997,14 @@ class Client:
"""Use this method to get a list of profile pictures for a user.
Args:
- user_id (:obj:`int` | :obj:`str`):
+ user_id (``int`` | ``str``):
Unique identifier of the target user.
- offset (:obj:`int`, optional):
+ offset (``int``, optional):
Sequential number of the first photo to be returned.
By default, all photos are returned.
- limit (:obj:`int`, optional):
+ limit (``int``, optional):
Limits the number of photos to be retrieved.
Values between 1—100 are accepted. Defaults to 100.
@@ -2029,24 +2029,24 @@ class Client:
"""Use this method to edit text messages.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- message_id (:obj:`int`):
+ message_id (``int``):
Message identifier in the chat specified in chat_id.
- text (:obj:`str`):
+ text (``str``):
New text of the message.
- parse_mode (:obj:`str`):
+ parse_mode (``str``):
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 message.
Defaults to Markdown.
- disable_web_page_preview (:obj:`bool`, optional):
+ disable_web_page_preview (``bool``, optional):
Disables link previews for links in this message.
Raises:
@@ -2071,19 +2071,19 @@ class Client:
"""Use this method to edit captions of messages.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- message_id (:obj:`int`):
+ message_id (``int``):
Message identifier in the chat specified in chat_id.
- caption (:obj:`str`):
+ caption (``str``):
New caption of the message.
- parse_mode (:obj:`str`):
+ parse_mode (``str``):
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.
@@ -2114,16 +2114,16 @@ class Client:
- If the user has *can_delete_messages* permission in a supergroup or a channel, it can delete any message there.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- message_ids (:obj:`list`):
+ message_ids (``list``):
List of identifiers of the messages to delete.
- revoke (:obj:`bool`, optional):
+ revoke (``bool``, optional):
Deletes messages on both parts.
This is only for private cloud chats and normal groups, messages on
channels and supergroups are always revoked (i.e.: deleted for everyone).
@@ -2416,7 +2416,7 @@ class Client:
"""Use this method to join a group chat or channel.
Args:
- chat_id (:obj:`str`):
+ chat_id (``str``):
Unique identifier for the target chat in form of *t.me/joinchat/* links or username of the target
channel/supergroup (in the format @username).
@@ -2453,11 +2453,11 @@ class Client:
"""Use this method to leave a group chat or channel.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier for the target chat or username of the target channel/supergroup
(in the format @username).
- delete (:obj:`bool`, optional):
+ delete (``bool``, optional):
Deletes the group chat dialog after leaving (for simple group chats, not supergroups).
Raises:
@@ -2495,11 +2495,11 @@ class Client:
The user must be an administrator in the chat for this to work and must have the appropriate admin rights.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier for the target chat or username of the target channel/supergroup
(in the format @username).
- new (:obj:`bool`):
+ new (``bool``):
The previous link will be deactivated and a new link will be generated.
This is also used to create the invite link in case it doesn't exist yet.
@@ -2557,13 +2557,13 @@ class Client:
This password will be asked when you log in on a new device in addition to the SMS code.
Args:
- password (:obj:`str`):
+ password (``str``):
Your password.
- hint (:obj:`str`, optional):
+ hint (``str``, optional):
A password hint.
- email (:obj:`str`, optional):
+ email (``str``, optional):
Recovery e-mail.
Returns:
@@ -2596,13 +2596,13 @@ class Client:
"""Use this method to change your Two-Step Verification password (Cloud Password) with a new one.
Args:
- current_password (:obj:`str`):
+ current_password (``str``):
Your current password.
- new_password (:obj:`str`):
+ new_password (``str``):
Your new password.
- new_hint (:obj:`str`, optional):
+ new_hint (``str``, optional):
A new password hint.
Returns:
@@ -2636,7 +2636,7 @@ class Client:
"""Use this method to turn off the Two-Step Verification security feature (Cloud Password) on your account.
Args:
- password (:obj:`str`):
+ password (``str``):
Your current password.
Returns:
@@ -2674,25 +2674,25 @@ class Client:
message (:obj:`Message `):
The Message containing the media.
- file_name (:obj:`str`, optional):
+ file_name (``str``, optional):
A custom *file_name* to be used instead of the one provided by Telegram.
By default, all files are downloaded in the *downloads* folder in your working directory.
You can also specify a path for downloading files in a custom location: paths that end with "/"
are considered directories. All non-existent folders will be created automatically.
- block (:obj:`bool`, optional):
+ block (``bool``, optional):
Blocks the code execution until the file has been downloaded.
Defaults to True.
- progress (:obj:`callable`):
+ progress (``callable``):
Pass a callback function to view the download progress.
The function must accept two arguments (current, total).
Other Parameters:
- current (:obj:`int`):
+ current (``int``):
The amount of bytes downloaded so far.
- total (:obj:`int`):
+ total (``int``):
The size of the file.
Returns:
@@ -2731,13 +2731,13 @@ class Client:
photo (:obj:`Photo ` | :obj:`UserProfilePhoto ` | :obj:`ChatPhoto `):
The photo object.
- file_name (:obj:`str`, optional):
+ file_name (``str``, optional):
A custom *file_name* to be used instead of the one provided by Telegram.
By default, all photos are downloaded in the *downloads* folder in your working directory.
You can also specify a path for downloading photos in a custom location: paths that end with "/"
are considered directories. All non-existent folders will be created automatically.
- block (:obj:`bool`, optional):
+ block (``bool``, optional):
Blocks the code execution until the photo has been downloaded.
Defaults to True.
@@ -2767,7 +2767,7 @@ class Client:
"""Use this method to add contacts to your Telegram address book.
Args:
- contacts (:obj:`list`):
+ contacts (``list``):
A list of :obj:`InputPhoneContact `
Returns:
@@ -2788,7 +2788,7 @@ class Client:
"""Use this method to delete contacts from your Telegram address book
Args:
- ids (:obj:`list`):
+ ids (``list``):
A list of unique identifiers for the target users.
Can be an ID (int), a username (string) or phone number (string).
@@ -2838,17 +2838,17 @@ class Client:
You can then send a result using :obj:`send_inline_bot_result `
Args:
- bot (:obj:`int` | :obj:`str`):
+ bot (``int`` | ``str``):
Unique identifier of the inline bot you want to get results from. You can specify
a @username (str) or a bot ID (int).
- query (:obj:`str`):
+ query (``str``):
Text of the query (up to 512 characters).
- offset (:obj:`str`):
+ offset (``str``):
Offset of the results to be returned.
- location (:obj:`tuple`, optional):
+ location (``tuple``, optional):
Your location in tuple format (latitude, longitude), e.g.: (51.500729, -0.124583).
Useful for location-based results only.
@@ -2881,23 +2881,23 @@ class Client:
Bot results can be retrieved using :obj:`get_inline_bot_results `
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- query_id (:obj:`int`):
+ query_id (``int``):
Unique identifier for the answered query.
- result_id (:obj:`str`):
+ result_id (``str``):
Unique identifier for the result that was chosen.
- disable_notification (:obj:`bool`, optional):
+ disable_notification (``bool``, optional):
Sends the message silently.
Users will receive a notification with no sound.
- reply_to_message_id (:obj:`bool`, optional):
+ reply_to_message_id (``bool``, optional):
If the message is a reply, ID of the original message.
Returns:
@@ -2924,13 +2924,13 @@ class Client:
You can retrieve up to 200 messages at once.
Args:
- chat_id (:obj:`int` | :obj:`str`):
+ chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
- message_ids (:obj:`list`):
+ message_ids (``list``):
A list of Message identifiers in the chat specified in *chat_id*.
Returns:
From 16eee1dabb19afa73b16fa01c804bd23329c99b1 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 25 Mar 2018 21:59:04 +0200
Subject: [PATCH 81/88] Update docstrings
---
pyrogram/client/client.py | 68 +++++++++++++++++++--------------------
1 file changed, 34 insertions(+), 34 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 9d89010a..daeae869 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -190,7 +190,7 @@ class Client:
Requires no parameters.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
if self.BOT_TOKEN_RE.match(self.session_name):
self.token = self.session_name
@@ -789,7 +789,7 @@ class Client:
The API Scheme function filled with proper arguments.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
if self.is_started:
r = self.session.send(data)
@@ -921,7 +921,7 @@ class Client:
:obj:`InputPeerChannel ` depending on the *peer_id*.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
if type(peer_id) is str:
if peer_id in ("self", "me"):
@@ -977,7 +977,7 @@ class Client:
Full information about the user in form of a :obj:`UserFull ` object.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
return self.send(
functions.users.GetFullUser(
@@ -1023,7 +1023,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
@@ -1069,7 +1069,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
return self.send(
functions.messages.ForwardMessages(
@@ -1138,7 +1138,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
file = self.save_file(photo, progress=progress)
@@ -1228,7 +1228,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
file = self.save_file(audio, progress=progress)
@@ -1312,7 +1312,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
file = self.save_file(document, progress=progress)
@@ -1381,7 +1381,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
file = self.save_file(sticker, progress=progress)
@@ -1481,7 +1481,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
file = self.save_file(video, progress=progress)
@@ -1572,7 +1572,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
file = self.save_file(voice, progress=progress)
@@ -1652,7 +1652,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
file = self.save_file(video_note, progress=progress)
@@ -1816,7 +1816,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
return self.send(
functions.messages.SendMedia(
@@ -1878,7 +1878,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
return self.send(
functions.messages.SendMedia(
@@ -1937,7 +1937,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
return self.send(
functions.messages.SendMedia(
@@ -1976,7 +1976,7 @@ class Client:
Progress of the upload process.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
if "Upload" in action.__name__:
action = action(progress)
@@ -2009,7 +2009,7 @@ class Client:
Values between 1—100 are accepted. Defaults to 100.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
return self.send(
functions.photos.GetUserPhotos(
@@ -2050,7 +2050,7 @@ class Client:
Disables link previews for links in this message.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
@@ -2089,7 +2089,7 @@ class Client:
Defaults to Markdown.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
@@ -2129,7 +2129,7 @@ class Client:
channels and supergroups are always revoked (i.e.: deleted for everyone).
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
peer = self.resolve_peer(chat_id)
@@ -2421,7 +2421,7 @@ class Client:
channel/supergroup (in the format @username).
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
match = self.INVITE_LINK_RE.match(chat_id)
@@ -2461,7 +2461,7 @@ class Client:
Deletes the group chat dialog after leaving (for simple group chats, not supergroups).
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
peer = self.resolve_peer(chat_id)
@@ -2507,7 +2507,7 @@ class Client:
On success, the exported invite link as string is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
Note:
If the returned link is a new one it may take a while for it to be activated.
@@ -2570,7 +2570,7 @@ class Client:
True on success, False otherwise.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
r = self.send(functions.account.GetPassword())
@@ -2609,7 +2609,7 @@ class Client:
True on success, False otherwise.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
r = self.send(functions.account.GetPassword())
@@ -2643,7 +2643,7 @@ class Client:
True on success, False otherwise.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
r = self.send(functions.account.GetPassword())
@@ -2699,7 +2699,7 @@ class Client:
On success, the absolute path of the downloaded file as string is returned, None otherwise.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
if isinstance(message, (types.Message, types.Photo)):
done = Event()
@@ -2745,7 +2745,7 @@ class Client:
On success, the absolute path of the downloaded photo as string is returned, None otherwise.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
if isinstance(photo, (types.UserProfilePhoto, types.ChatPhoto)):
photo = types.Photo(
@@ -2774,7 +2774,7 @@ class Client:
On success, the added contacts are returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
imported_contacts = self.send(
functions.contacts.ImportContacts(
@@ -2796,7 +2796,7 @@ class Client:
True on success.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
contacts = []
@@ -2856,7 +2856,7 @@ class Client:
On Success, :obj:`BotResults ` is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
return self.send(
functions.messages.GetInlineBotResults(
@@ -2904,7 +2904,7 @@ class Client:
On success, the sent Message is returned.
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
return self.send(
functions.messages.SendInlineBotResult(
@@ -2937,7 +2937,7 @@ class Client:
List of the requested messages
Raises:
- :class:`pyrogram.Error`
+ :class:`Error `
"""
peer = self.resolve_peer(chat_id)
message_ids = [types.InputMessageID(i) for i in message_ids]
From e680cce5faf80caa7b98381f08ced95bd43307a6 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 25 Mar 2018 22:12:52 +0200
Subject: [PATCH 82/88] Update docstrings
---
pyrogram/client/client.py | 42 +++++++++++++++++++--------------------
1 file changed, 21 insertions(+), 21 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index daeae869..f8bc4e4d 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -744,7 +744,7 @@ class Client:
Args:
callback (``callable``):
A function that will be called when a new update is received from the server. It takes
- :obj:`(client, update, users, chats)` as positional arguments (Look at the section below for
+ *(client, update, users, chats)* as positional arguments (Look at the section below for
a detailed description).
Other Parameters:
@@ -781,8 +781,8 @@ class Client:
"""Use this method to send Raw Function queries.
This method makes possible to manually call every single Telegram API method in a low-level manner.
- Available functions are listed in the :obj:`pyrogram.api.functions` package and may accept compound
- data types from :obj:`pyrogram.api.types` as well as bare types such as ``int``, ``str``, etc...
+ Available functions are listed in the :obj:`functions ` package and may accept compound
+ data types from :obj:`types ` as well as bare types such as ``int``, ``str``, etc...
Args:
data (``Object``):
@@ -1005,8 +1005,8 @@ class Client:
Text of the message to be sent.
parse_mode (``str``):
- 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 message.
+ Use :obj:`MARKDOWN ` or :obj:`HTML `
+ if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your message.
Defaults to Markdown.
disable_web_page_preview (``bool``, optional):
@@ -1107,13 +1107,13 @@ class Client:
Photo caption, 0-200 characters.
parse_mode (``str``):
- 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.
+ Use :obj:`MARKDOWN ` or :obj:`HTML `
+ if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
Defaults to Markdown.
ttl_seconds (``int``, optional):
Self-Destruct Timer.
- If you set a timer, the photo will self-destruct in :obj:`ttl_seconds`
+ If you set a timer, the photo will self-destruct in *ttl_seconds*
seconds after it was viewed.
disable_notification (``bool``, optional):
@@ -1193,8 +1193,8 @@ class Client:
Audio caption, 0-200 characters.
parse_mode (``str``):
- 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.
+ Use :obj:`MARKDOWN ` or :obj:`HTML `
+ if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
Defaults to Markdown.
duration (``int``, optional):
@@ -1286,8 +1286,8 @@ class Client:
Document caption, 0-200 characters.
parse_mode (``str``):
- 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.
+ Use :obj:`MARKDOWN ` or :obj:`HTML `
+ if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
Defaults to Markdown.
disable_notification (``bool``, optional):
@@ -1438,8 +1438,8 @@ class Client:
Video caption, 0-200 characters.
parse_mode (``str``):
- 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.
+ Use :obj:`MARKDOWN ` or :obj:`HTML `
+ if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
Defaults to Markdown.
duration (``int``, optional):
@@ -1543,8 +1543,8 @@ class Client:
Voice message caption, 0-200 characters.
parse_mode (``str``):
- 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.
+ Use :obj:`MARKDOWN ` or :obj:`HTML `
+ if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
Defaults to Markdown.
duration (``int``, optional):
@@ -1969,7 +1969,7 @@ class Client:
action (``callable``):
Type of action to broadcast.
- Choose one from the :class:`pyrogram.ChatAction` class,
+ Choose one from the :class:`ChatAction ` class,
depending on what the user is about to receive.
progress (``int``, optional):
@@ -2042,8 +2042,8 @@ class Client:
New text of the message.
parse_mode (``str``):
- 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 message.
+ Use :obj:`MARKDOWN ` or :obj:`HTML `
+ if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your message.
Defaults to Markdown.
disable_web_page_preview (``bool``, optional):
@@ -2084,8 +2084,8 @@ class Client:
New caption of the message.
parse_mode (``str``):
- 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.
+ Use :obj:`MARKDOWN ` or :obj:`HTML `
+ if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
Defaults to Markdown.
Raises:
From 7f13eef44ba21cbe81bc111ef2299bbabbcefe25 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 26 Mar 2018 02:02:57 +0200
Subject: [PATCH 83/88] Remove old code and use a better error message
---
pyrogram/crypto/aes.py | 52 ++++++------------------------------------
1 file changed, 7 insertions(+), 45 deletions(-)
diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py
index 05a01044..331dd5dd 100644
--- a/pyrogram/crypto/aes.py
+++ b/pyrogram/crypto/aes.py
@@ -16,52 +16,34 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-import logging
-
-log = logging.getLogger(__name__)
+import sys
try:
import tgcrypto
except ImportError:
- log.warning(
- "TgCrypto is missing! "
- "Pyrogram will work the same, but at a much slower speed. "
+ sys.exit(
+ "TgCrypto is missing and Pyrogram can't run without. "
+ "Please install it using \"pip3 install tgcrypto\". "
"More info: https://docs.pyrogram.ml/resources/TgCrypto"
)
- is_fast = False
- import pyaes
-else:
- log.info("Using TgCrypto")
- is_fast = True
# TODO: Ugly IFs
class AES:
@classmethod
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)
+ return tgcrypto.ige_encrypt(data, key, iv)
@classmethod
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)
+ return tgcrypto.ige_decrypt(data, key, iv)
@staticmethod
def ctr_decrypt(data: bytes, key: bytes, iv: bytes, offset: int) -> bytes:
replace = int.to_bytes(offset // 16, 4, "big")
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)
+ return tgcrypto.ctr_decrypt(data, key, iv)
@staticmethod
def xor(a: bytes, b: bytes) -> bytes:
@@ -70,23 +52,3 @@ class AES:
len(a),
"big",
)
-
- @classmethod
- def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
- cipher = pyaes.AES(key)
-
- iv_1 = iv[:16]
- iv_2 = iv[16:]
-
- data = [data[i: i + 16] for i in range(0, len(data), 16)]
-
- if encrypt:
- for i, chunk in enumerate(data):
- iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2)
- iv_2 = chunk
- else:
- for i, chunk in enumerate(data):
- iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1)
- iv_1 = chunk
-
- return b"".join(data)
From 52c482f1e4cd7f87544c121ba3ab73a3d2e2e87f Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 26 Mar 2018 02:03:36 +0200
Subject: [PATCH 84/88] Use a better error message
---
pyrogram/connection/transport/tcp/tcp.py | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py
index a6195586..09df8c72 100644
--- a/pyrogram/connection/transport/tcp/tcp.py
+++ b/pyrogram/connection/transport/tcp/tcp.py
@@ -18,9 +18,16 @@
import logging
import socket
+import sys
from collections import namedtuple
-import socks
+try:
+ import socks
+except ImportError:
+ sys.exit(
+ "PySocks is missing and Pyrogram can't run without. "
+ "Please install it using \"pip3 install pysocks\"."
+ )
log = logging.getLogger(__name__)
From ae21ada0f41f6b29b4a41d6152e621d33c0c1ad6 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 26 Mar 2018 02:05:56 +0200
Subject: [PATCH 85/88] Add some keywords
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 64cc7f2d..c82c1a9d 100644
--- a/setup.py
+++ b/setup.py
@@ -72,7 +72,7 @@ setup(
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Libraries :: Application Frameworks"
],
- keywords="telegram mtproto api client library python",
+ keywords="telegram chat messenger mtproto api client library python",
project_urls={
"Tracker": "https://github.com/pyrogram/pyrogram/issues",
"Community": "https://t.me/PyrogramChat",
From 42a287884258476a4bd3679a24557f5e2e38d8f8 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 26 Mar 2018 03:39:30 +0200
Subject: [PATCH 86/88] Don't use sys.exit(), re-raise ImportError instead
---
pyrogram/connection/transport/tcp/tcp.py | 7 ++++---
pyrogram/crypto/aes.py | 9 ++++-----
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py
index 09df8c72..22b953c1 100644
--- a/pyrogram/connection/transport/tcp/tcp.py
+++ b/pyrogram/connection/transport/tcp/tcp.py
@@ -18,17 +18,18 @@
import logging
import socket
-import sys
from collections import namedtuple
try:
import socks
-except ImportError:
- sys.exit(
+except ImportError as e:
+ e.msg = (
"PySocks is missing and Pyrogram can't run without. "
"Please install it using \"pip3 install pysocks\"."
)
+ raise e
+
log = logging.getLogger(__name__)
Proxy = namedtuple("Proxy", ["enabled", "hostname", "port", "username", "password"])
diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py
index 331dd5dd..8ca72535 100644
--- a/pyrogram/crypto/aes.py
+++ b/pyrogram/crypto/aes.py
@@ -16,19 +16,18 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-import sys
-
try:
import tgcrypto
-except ImportError:
- sys.exit(
+except ImportError as e:
+ e.msg = (
"TgCrypto is missing and Pyrogram can't run without. "
"Please install it using \"pip3 install tgcrypto\". "
"More info: https://docs.pyrogram.ml/resources/TgCrypto"
)
+ raise e
+
-# TODO: Ugly IFs
class AES:
@classmethod
def ige_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
From e397c4d1819e1fe05a73123f8e022c2d53211bec Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 26 Mar 2018 13:34:54 +0200
Subject: [PATCH 87/88] Don't process empty differences
---
pyrogram/client/client.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index f8bc4e4d..89bab694 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -651,8 +651,9 @@ class Client:
)
)
- updates.users += diff.users
- updates.chats += diff.chats
+ if not isinstance(diff, types.updates.ChannelDifferenceEmpty):
+ updates.users += diff.users
+ updates.chats += diff.chats
if channel_id and pts:
if channel_id not in self.channels_pts:
From beaf88adeedd4a2821304ff05d5a274a25924a29 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 26 Mar 2018 13:41:00 +0200
Subject: [PATCH 88/88] Remove imports, use namespaces
---
pyrogram/client/client.py | 25 ++++++++++---------------
1 file changed, 10 insertions(+), 15 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 89bab694..98cf5e65 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -45,11 +45,6 @@ from pyrogram.api.errors import (
PasswordHashInvalid, FloodWait, PeerIdInvalid, FilePartMissing,
ChatAdminRequired, FirstnameInvalid, PhoneNumberBanned,
VolumeLocNotFound, UserMigrate)
-from pyrogram.api.types import (
- User, Chat, Channel,
- InputPeerEmpty, InputPeerSelf,
- InputPeerUser, InputPeerChat, InputPeerChannel
-)
from pyrogram.crypto import AES
from pyrogram.session import Auth, Session
from pyrogram.session.internals import MsgId
@@ -447,7 +442,7 @@ class Client:
def fetch_peers(self, entities: list):
for entity in entities:
- if isinstance(entity, User):
+ if isinstance(entity, types.User):
user_id = entity.id
if user_id in self.peers_by_id:
@@ -461,7 +456,7 @@ class Client:
username = entity.username
phone = entity.phone
- input_peer = InputPeerUser(
+ input_peer = types.InputPeerUser(
user_id=user_id,
access_hash=access_hash
)
@@ -474,20 +469,20 @@ class Client:
if phone is not None:
self.peers_by_phone[phone] = input_peer
- if isinstance(entity, Chat):
+ if isinstance(entity, types.Chat):
chat_id = entity.id
peer_id = -chat_id
if peer_id in self.peers_by_id:
continue
- input_peer = InputPeerChat(
+ input_peer = types.InputPeerChat(
chat_id=chat_id
)
self.peers_by_id[peer_id] = input_peer
- if isinstance(entity, Channel):
+ if isinstance(entity, types.Channel):
channel_id = entity.id
peer_id = int("-100" + str(channel_id))
@@ -501,7 +496,7 @@ class Client:
username = entity.username
- input_peer = InputPeerChannel(
+ input_peer = types.InputPeerChannel(
channel_id=channel_id,
access_hash=access_hash
)
@@ -880,7 +875,7 @@ class Client:
dialogs = self.send(
functions.messages.GetDialogs(
- 0, 0, InputPeerEmpty(),
+ 0, 0, types.InputPeerEmpty(),
self.DIALOGS_AT_ONCE, True
)
)
@@ -926,7 +921,7 @@ class Client:
"""
if type(peer_id) is str:
if peer_id in ("self", "me"):
- return InputPeerSelf()
+ return types.InputPeerSelf()
match = self.INVITE_LINK_RE.match(peer_id)
@@ -982,7 +977,7 @@ class Client:
"""
return self.send(
functions.users.GetFullUser(
- InputPeerSelf()
+ types.InputPeerSelf()
)
)
@@ -2439,7 +2434,7 @@ class Client:
)
)
- channel = InputPeerChannel(
+ channel = types.InputPeerChannel(
channel_id=resolved_peer.chats[0].id,
access_hash=resolved_peer.chats[0].access_hash
)