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 01/37] 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 02/37] 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 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 03/37] 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 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 04/37] 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 05/37] 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 06/37] 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 07/37] 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 08/37] 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 09/37] 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 10/37] 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 11/37] 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 12/37] 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 13/37] 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 14/37] 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 15/37] 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 16/37] 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 17/37] 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 18/37] 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 19/37] 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 20/37] 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 21/37] 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 22/37] 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 23/37] 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 24/37] 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 25/37] 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 26/37] 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 27/37] 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 28/37] 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 29/37] 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 30/37] 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 31/37] 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 32/37] 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 33/37] 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 34/37] 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 35/37] 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 36/37] 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 37/37] 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]