From 55d0b93cf079aa4622375b5d4e954ce6f89577e4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 8 Jul 2020 17:16:06 +0200 Subject: [PATCH 01/11] Extend set_slow_mode to accept None --- pyrogram/client/methods/chats/set_slow_mode.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/methods/chats/set_slow_mode.py b/pyrogram/client/methods/chats/set_slow_mode.py index cf6c7096..8215c3b9 100644 --- a/pyrogram/client/methods/chats/set_slow_mode.py +++ b/pyrogram/client/methods/chats/set_slow_mode.py @@ -26,7 +26,7 @@ class SetSlowMode(BaseClient): def set_slow_mode( self, chat_id: Union[int, str], - seconds: int, + seconds: Union[int, None] ) -> bool: """Set the slow mode interval for a chat. @@ -34,9 +34,9 @@ class SetSlowMode(BaseClient): chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. - seconds (``int`` | ``str``): + seconds (``int`` | ``None``): Seconds in which members will be able to send only one message per this interval. - Valid values are: 0 (off), 10, 30, 60 (1m), 300 (5m), 900 (15m) or 3600 (1h). + Valid values are: 0 or None (off), 10, 30, 60 (1m), 300 (5m), 900 (15m) or 3600 (1h). Returns: ``bool``: True on success. @@ -44,13 +44,17 @@ class SetSlowMode(BaseClient): Example: .. code-block:: python + # Set slow mode to 60 seconds app.set_slow_mode("pyrogramchat", 60) + + # Disable slow mode + app.set_slow_mode("pyrogramchat", None) """ self.send( functions.channels.ToggleSlowMode( channel=self.resolve_peer(chat_id), - seconds=seconds + seconds=0 if seconds is None else seconds ) ) From 1e8c9812a110dd612d7035003ec0f18ff4e523ff Mon Sep 17 00:00:00 2001 From: Octo Date: Sat, 20 Jul 2019 09:20:57 +0300 Subject: [PATCH 02/11] Add support for downloading files to file pointer, fix for https://github.com/pyrogram/pyrogram/issues/284 --- pyrogram/client/client.py | 189 +++++++++--------- .../client/methods/messages/download_media.py | 13 ++ .../types/messages_and_media/message.py | 11 +- 3 files changed, 120 insertions(+), 93 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 0e186d85..b68bd891 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import logging import math import os @@ -1231,9 +1231,9 @@ class Client(Methods, BaseClient): temp_file_path = "" final_file_path = "" - + path = [None] try: - data, directory, file_name, done, progress, progress_args, path = packet + data, done, progress, progress_args, out, path, to_file = packet temp_file_path = self.get_file( media_type=data.media_type, @@ -1250,13 +1250,15 @@ class Client(Methods, BaseClient): file_size=data.file_size, is_big=data.is_big, progress=progress, - progress_args=progress_args + progress_args=progress_args, + out=out ) - - if temp_file_path: - 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) + if to_file: + final_file_path = out.name + else: + final_file_path = '' + if to_file: + out.close() except Exception as e: log.error(e, exc_info=True) @@ -1864,7 +1866,8 @@ class Client(Methods, BaseClient): file_size: int, is_big: bool, progress: callable, - progress_args: tuple = () + progress_args: tuple = (), + out: io.IOBase = None ) -> str: with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) @@ -1950,7 +1953,10 @@ class Client(Methods, BaseClient): limit = 1024 * 1024 offset = 0 file_name = "" - + if not out: + f = tempfile.NamedTemporaryFile("wb", delete=False) + else: + f = out try: r = session.send( functions.upload.GetFile( @@ -1961,36 +1967,37 @@ class Client(Methods, BaseClient): ) if isinstance(r, types.upload.File): - with tempfile.NamedTemporaryFile("wb", delete=False) as f: + if hasattr(f, "name"): file_name = f.name - while True: - chunk = r.bytes + while True: + chunk = r.bytes - if not chunk: - break + if not chunk: + break - f.write(chunk) + f.write(chunk) - offset += limit + offset += limit - if progress: - progress( - min(offset, file_size) - if file_size != 0 - else offset, - file_size, - *progress_args - ) + if progress: + progress( - r = session.send( - functions.upload.GetFile( - location=location, - offset=offset, - limit=limit - ) + min(offset, file_size) + if file_size != 0 + else offset, + file_size, + *progress_args ) + r = session.send( + functions.upload.GetFile( + location=location, + offset=offset, + limit=limit + ) + ) + elif isinstance(r, types.upload.FileCdnRedirect): with self.media_sessions_lock: cdn_session = self.media_sessions.get(r.dc_id, None) @@ -2003,70 +2010,71 @@ class Client(Methods, BaseClient): self.media_sessions[r.dc_id] = cdn_session try: - with tempfile.NamedTemporaryFile("wb", delete=False) as f: + if hasattr(f, "name"): file_name = f.name - while True: - r2 = cdn_session.send( - functions.upload.GetCdnFile( - file_token=r.file_token, - offset=offset, - limit=limit - ) + while True: + r2 = cdn_session.send( + functions.upload.GetCdnFile( + 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 - - chunk = r2.bytes - - # https://core.telegram.org/cdn#decrypting-files - decrypted_chunk = AES.ctr256_decrypt( - chunk, - r.encryption_key, - bytearray( - r.encryption_iv[:-4] - + (offset // 16).to_bytes(4, "big") ) - ) - - hashes = session.send( - functions.upload.GetCdnFileHashes( - file_token=r.file_token, - offset=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) - - f.write(decrypted_chunk) - - offset += limit - - if progress: - progress( - min(offset, file_size) - if file_size != 0 - else offset, - file_size, - *progress_args - ) - - if len(chunk) < limit: + except VolumeLocNotFound: break + else: + continue + + chunk = r2.bytes + + # https://core.telegram.org/cdn#decrypting-files + decrypted_chunk = AES.ctr256_decrypt( + chunk, + r.encryption_key, + bytearray( + r.encryption_iv[:-4] + + (offset // 16).to_bytes(4, "big") + ) + ) + + hashes = session.send( + functions.upload.GetCdnFileHashes( + file_token=r.file_token, + offset=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) + + f.write(decrypted_chunk) + + offset += limit + + if progress: + progress( + + min(offset, file_size) + if file_size != 0 + else offset, + file_size, + *progress_args + ) + + if len(chunk) < limit: + break except Exception as e: raise e except Exception as e: @@ -2074,7 +2082,8 @@ class Client(Methods, BaseClient): log.error(e, exc_info=True) try: - os.remove(file_name) + if out: + os.remove(file_name) except OSError: pass diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 22054397..2176e4aa 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -17,7 +17,9 @@ # along with Pyrogram. If not, see . import binascii +import io import os +import re import struct import time from datetime import datetime @@ -37,6 +39,7 @@ class DownloadMedia(BaseClient): message: Union["pyrogram.Message", str], file_ref: str = None, file_name: str = DEFAULT_DOWNLOAD_DIR, + out: io.IOBase = None, block: bool = True, progress: callable = None, progress_args: tuple = () @@ -58,6 +61,9 @@ class DownloadMedia(BaseClient): 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. + out (``io.IOBase``, *optional*): + A custom *file-like object* to be used when downloading file. Overrides file_name + block (``bool``, *optional*): Blocks the code execution until the file has been downloaded. Defaults to True. @@ -238,6 +244,13 @@ class DownloadMedia(BaseClient): extension ) + if not out: + out = open(os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))), 'wb') + os.makedirs(directory, exist_ok=True) + to_file = True + else: + to_file = False + self.download_queue.put((data, done, progress, progress_args, out, path, to_file)) # Cast to string because Path objects aren't supported by Python 3.5 self.download_queue.put((data, str(directory), str(file_name), done, progress, progress_args, path)) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 215f86d0..cff8c578 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io from functools import partial from typing import List, Match, Union @@ -2964,7 +2964,7 @@ class Message(Object, Update): chat_id=message.chat.id, message_id=message_id, ) - + Example: .. code-block:: python @@ -2985,6 +2985,7 @@ class Message(Object, Update): def download( self, file_name: str = "", + out: io.IOBase = None, block: bool = True, progress: callable = None, progress_args: tuple = () @@ -3009,6 +3010,9 @@ class Message(Object, Update): 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. + out (``io.IOBase``, *optional*): + A custom *file-like object* to be used when downloading file. Overrides file_name + block (``bool``, *optional*): Blocks the code execution until the file has been downloaded. Defaults to True. @@ -3045,6 +3049,7 @@ class Message(Object, Update): return self._client.download_media( message=self, file_name=file_name, + out=out, block=block, progress=progress, progress_args=progress_args, @@ -3074,7 +3079,7 @@ class Message(Object, Update): Parameters: option (``int``): Index of the poll option you want to vote for (0 to 9). - + Returns: :obj:`Poll`: On success, the poll with the chosen option is returned. From c13392d2ce59e80aab02689bc01866c789bb038f Mon Sep 17 00:00:00 2001 From: Octo Date: Sat, 20 Jul 2019 10:22:12 +0300 Subject: [PATCH 03/11] Add support for uploading from file pointers, fixes https://github.com/pyrogram/pyrogram/issues/261 --- pyrogram/client/client.py | 98 ++++++----- .../methods/messages/send_animated_sticker.py | 153 ++++++++++++++++++ .../client/methods/messages/send_animation.py | 45 ++++-- .../client/methods/messages/send_audio.py | 41 +++-- .../client/methods/messages/send_document.py | 35 ++-- .../client/methods/messages/send_photo.py | 29 ++-- .../client/methods/messages/send_sticker.py | 30 ++-- .../client/methods/messages/send_video.py | 40 +++-- .../methods/messages/send_video_note.py | 31 +++- .../client/methods/messages/send_voice.py | 37 +++-- 10 files changed, 420 insertions(+), 119 deletions(-) create mode 100644 pyrogram/client/methods/messages/send_animated_sticker.py diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index b68bd891..1efb6f06 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1715,7 +1715,7 @@ class Client(Methods, BaseClient): def save_file( self, - path: str, + path: Union[str, io.IOBase], file_id: int = None, file_part: int = 0, progress: callable = None, @@ -1767,9 +1767,20 @@ class Client(Methods, BaseClient): Raises: RPCError: In case of a Telegram RPC error. + ValueError: if path is not str or file-like readable object """ part_size = 512 * 1024 - file_size = os.path.getsize(path) + if isinstance(path, str): + fp = open(path, 'rb') + filename = os.path.basename(path) + elif hasattr(path, 'write'): + fp = path + filename = fp.name + else: + raise ValueError("Invalid path passed! Pass file pointer or path to file") + fp.seek(0, os.SEEK_END) + file_size = fp.tell() + fp.seek(0) if file_size == 0: raise ValueError("File size equals to 0 B") @@ -1787,67 +1798,74 @@ class Client(Methods, BaseClient): session.start() try: - with open(path, "rb") as f: - f.seek(part_size * file_part) + fp.seek(part_size * file_part) - while True: - chunk = f.read(part_size) - - if not chunk: - if not is_big: - md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) - break - - for _ in range(3): - if is_big: - rpc = functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk - ) - else: - rpc = functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk - ) - - if session.send(rpc): - break - else: - raise AssertionError("Telegram didn't accept chunk #{} of {}".format(file_part, path)) - - if is_missing_part: - return + while True: + chunk = fp.read(part_size) + if not chunk: if not is_big: - md5_sum.update(chunk) + md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) + break - file_part += 1 + for _ in range(3): + if is_big: + rpc = functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk + ) + else: + rpc = functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk + ) - if progress: - progress(min(file_part * part_size, file_size), file_size, *progress_args) + if session.send(rpc): + break + else: + raise AssertionError("Telegram didn't accept chunk #{} of {}".format(file_part, path)) + + if is_missing_part: + return + + if not is_big: + md5_sum.update(chunk) + + file_part += 1 + + if progress: + progress(min(file_part * part_size, file_size), file_size, *progress_args) except Client.StopTransmission: + if isinstance(path, str): + fp.close() raise except Exception as e: + if isinstance(path, str): + fp.close() log.error(e, exc_info=True) else: + if isinstance(path, str): + fp.close() if is_big: return types.InputFileBig( id=file_id, parts=file_total_parts, - name=os.path.basename(path), + name=filename, ) else: return types.InputFile( id=file_id, parts=file_total_parts, - name=os.path.basename(path), + name=filename, md5_checksum=md5_sum ) finally: + if isinstance(path, str): + fp.close() session.stop() def get_file( diff --git a/pyrogram/client/methods/messages/send_animated_sticker.py b/pyrogram/client/methods/messages/send_animated_sticker.py new file mode 100644 index 00000000..b2959394 --- /dev/null +++ b/pyrogram/client/methods/messages/send_animated_sticker.py @@ -0,0 +1,153 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . +import io +import os +from typing import Union + +import pyrogram +from pyrogram.api import functions, types +from pyrogram.client.ext import BaseClient, utils +from pyrogram.errors import FilePartMissing + + +class SendAnimatedSticker(BaseClient): + def send_animated_sticker( + self, + chat_id: Union[int, str], + animated_sticker: Union[str, io.IOBase], + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> Union["pyrogram.Message", None]: + """Send .tgs animated stickers. + + Parameters: + 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). + + animated_sticker (``str`` | file-like object): + Animated sticker to send. + Pass a file_id as string to send a animated sticker that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a .webp animated sticker file from the Internet, or + pass a file path as string to upload a new animated sticker that exists on your local machine. + pass a readable file-like object with .name + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client`): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + :obj:`Message` | ``None``: On success, the sent animated sticker message is returned, otherwise, in case the + upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + Raises: + RPCError: In case of a Telegram RPC error. + """ + file = None + + try: + if isinstance(animated_sticker, str): + if os.path.exists(animated_sticker): + file = self.save_file(animated_sticker, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(animated_sticker) or "application/x-tgsticker", + file=file, + attributes=[ + types.DocumentAttributeFilename(file_name=os.path.basename(animated_sticker)) + ] + ) + elif animated_sticker.startswith("http"): + media = types.InputMediaDocumentExternal( + url=animated_sticker + ) + else: + media = utils.get_input_media_from_file_id(animated_sticker, 5) + elif hasattr(animated_sticker, "read"): + file = self.save_file(animated_sticker, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(animated_sticker.name) or "application/x-tgsticker", + file=file, + attributes=[ + types.DocumentAttributeFilename(file_name=animated_sticker.name) + ] + ) + + + while True: + try: + r = self.send( + functions.messages.SendMedia( + peer=self.resolve_peer(chat_id), + media=media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None, + message="" + ) + ) + except FilePartMissing as e: + self.save_file(animated_sticker, file_id=file.id, file_part=e.x) + else: + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) + except BaseClient.StopTransmission: + return None diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 288ed04e..f8078a7c 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendAnimation(BaseClient): def send_animation( self, chat_id: Union[int, str], + animation: Union[str, io.IOBase], animation: str, file_ref: str = None, caption: str = "", @@ -59,11 +60,13 @@ class SendAnimation(BaseClient): 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). - animation (``str``): + animation (``str``| file-like object): Animation to send. Pass a file_id as string to send an animation that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an animation from the Internet, or pass a file path as string to upload a new animation that exists on your local machine. + pass a readable file-like object with .name + file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -163,11 +166,36 @@ class SendAnimation(BaseClient): file = None try: - if os.path.exists(animation): + if isinstance(animation, str): + if os.path.exists(animation): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(animation, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(animation) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + supports_streaming=True, + duration=duration, + w=width, + h=height + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), + types.DocumentAttributeAnimated() + ] + ) + elif animation.startswith("http"): + media = types.InputMediaDocumentExternal( + url=animation + ) + else: + media = utils.get_input_media_from_file_id(animation, file_ref, 10) + elif hasattr(animation, "read"): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animation) or "video/mp4", + mime_type=self.guess_mime_type(animation.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -177,17 +205,10 @@ class SendAnimation(BaseClient): w=width, h=height ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), + types.DocumentAttributeFilename(file_name=animation.name), types.DocumentAttributeAnimated() ] ) - elif animation.startswith("http"): - media = types.InputMediaDocumentExternal( - url=animation - ) - else: - media = utils.get_input_media_from_file_id(animation, file_ref, 10) - while True: try: r = self.send( diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index e271d96c..0ff588d8 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendAudio(BaseClient): def send_audio( self, chat_id: Union[int, str], + audio: Union[str, io.IOBase], audio: str, file_ref: str = None, caption: str = "", @@ -60,11 +61,12 @@ class SendAudio(BaseClient): 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). - audio (``str``): + audio (``str``, file-like object): Audio file to send. Pass a file_id as string to send an audio file that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an audio file from the Internet, or pass a file path as string to upload a new audio file that exists on your local machine. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -163,11 +165,34 @@ class SendAudio(BaseClient): file = None try: - if os.path.exists(audio): + if isinstance(audio, str): + if os.path.exists(audio): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(audio, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(audio) or "audio/mpeg", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeAudio( + duration=duration, + performer=performer, + title=title + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) + ] + ) + elif audio.startswith("http"): + media = types.InputMediaDocumentExternal( + url=audio + ) + else: + media = utils.get_input_media_from_file_id(audio, file_ref, 9) + elif hasattr(audio, "read"): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(audio) or "audio/mpeg", + mime_type=self.guess_mime_type(audio.name) or "audio/mpeg", file=file, thumb=thumb, attributes=[ @@ -176,15 +201,9 @@ class SendAudio(BaseClient): performer=performer, title=title ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) + types.DocumentAttributeFilename(file_name=os.path.basename(audio.name)) ] ) - elif audio.startswith("http"): - media = types.InputMediaDocumentExternal( - url=audio - ) - else: - media = utils.get_input_media_from_file_id(audio, file_ref, 9) while True: try: diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 24a754f0..c4a7252b 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendDocument(BaseClient): def send_document( self, chat_id: Union[int, str], + document: Union[str, io.IOBase], document: str, file_ref: str = None, thumb: str = None, @@ -60,6 +61,8 @@ class SendDocument(BaseClient): Pass a file_id as string to send a file that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a file from the Internet, or pass a file path as string to upload a new file that exists on your local machine. + pass a readable file-like object with .name + file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -143,23 +146,35 @@ class SendDocument(BaseClient): file = None try: - if os.path.exists(document): + if isinstance(document, str): + if os.path.exists(document): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(document, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(document) or "application/zip", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) + ] + ) + elif document.startswith("http"): + media = types.InputMediaDocumentExternal( + url=document + ) + else: + media = utils.get_input_media_from_file_id(document, file_ref, 5) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(document) or "application/zip", + mime_type=self.guess_mime_type(document.name) or "application/zip", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) + types.DocumentAttributeFilename(file_name=document.name) ] ) - elif document.startswith("http"): - media = types.InputMediaDocumentExternal( - url=document - ) - else: - media = utils.get_input_media_from_file_id(document, file_ref, 5) while True: try: diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 4d6a18a3..7877c986 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendPhoto(BaseClient): def send_photo( self, chat_id: Union[int, str], + photo: Union[str, io.IOBase], photo: str, file_ref: str = None, caption: str = "", @@ -54,11 +55,12 @@ class SendPhoto(BaseClient): 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). - photo (``str``): + photo (``str`` | file-like object): Photo to send. Pass a file_id as string to send a photo that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a photo from the Internet, or pass a file path as string to upload a new photo that exists on your local machine. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -137,19 +139,26 @@ class SendPhoto(BaseClient): file = None try: - if os.path.exists(photo): + if isinstance(photo, str): + if os.path.exists(photo): + file = self.save_file(photo, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedPhoto( + file=file, + ttl_seconds=ttl_seconds + ) + elif photo.startswith("http"): + media = types.InputMediaPhotoExternal( + url=photo, + ttl_seconds=ttl_seconds + ) + else: + media = utils.get_input_media_from_file_id(photo, file_ref, 2) + elif hasattr(photo, "read"): file = self.save_file(photo, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedPhoto( file=file, ttl_seconds=ttl_seconds ) - elif photo.startswith("http"): - media = types.InputMediaPhotoExternal( - url=photo, - ttl_seconds=ttl_seconds - ) - else: - media = utils.get_input_media_from_file_id(photo, file_ref, 2) while True: try: diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 76a42d3d..98cc4425 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendSticker(BaseClient): def send_sticker( self, chat_id: Union[int, str], + sticker: Union[str, io.IOBase], sticker: str, file_ref: str = None, disable_notification: bool = None, @@ -56,6 +57,7 @@ class SendSticker(BaseClient): Pass a file_id as string to send a sticker that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or pass a file path as string to upload a new sticker that exists on your local machine. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -113,21 +115,31 @@ class SendSticker(BaseClient): file = None try: - if os.path.exists(sticker): + if isinstance(sticker, str): + if os.path.exists(sticker): + file = self.save_file(sticker, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(sticker) or "image/webp", + file=file, + attributes=[ + types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) + ] + ) + elif sticker.startswith("http"): + media = types.InputMediaDocumentExternal( + url=sticker + ) + else: + media = utils.get_input_media_from_file_id(sticker, file_ref, 8) + elif hasattr(sticker, "read"): file = self.save_file(sticker, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(sticker) or "image/webp", file=file, attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) + types.DocumentAttributeFilename(file_name=sticker.name) ] ) - elif sticker.startswith("http"): - media = types.InputMediaDocumentExternal( - url=sticker - ) - else: - media = utils.get_input_media_from_file_id(sticker, file_ref, 8) while True: try: diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index fc58aa98..6c1c2efe 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendVideo(BaseClient): def send_video( self, chat_id: Union[int, str], + video: Union[str, io.IOBase], video: str, file_ref: str = None, caption: str = "", @@ -64,6 +65,7 @@ class SendVideo(BaseClient): Pass a file_id as string to send a video that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a video from the Internet, or pass a file path as string to upload a new video that exists on your local machine. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -160,11 +162,35 @@ class SendVideo(BaseClient): file = None try: - if os.path.exists(video): + if isinstance(video, str): + if os.path.exists(video): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(video, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(video) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + supports_streaming=supports_streaming or None, + duration=duration, + w=width, + h=height + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) + ] + ) + elif video.startswith("http"): + media = types.InputMediaDocumentExternal( + url=video + ) + else: + media = utils.get_input_media_from_file_id(video, file_ref, 4) + elif hasattr(video, "read"): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video) or "video/mp4", + mime_type=self.guess_mime_type(video.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -174,15 +200,9 @@ class SendVideo(BaseClient): w=width, h=height ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) + types.DocumentAttributeFilename(file_name=video.name) ] ) - elif video.startswith("http"): - media = types.InputMediaDocumentExternal( - url=video - ) - else: - media = utils.get_input_media_from_file_id(video, file_ref, 4) while True: try: diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 64bde11b..a160567d 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendVideoNote(BaseClient): def send_video_note( self, chat_id: Union[int, str], + video_note: Union[str, io.IOBase], video_note: str, file_ref: str = None, duration: int = 0, @@ -54,11 +55,12 @@ class SendVideoNote(BaseClient): 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). - video_note (``str``): + video_note (``str``, file-like object): Video note to send. Pass a file_id as string to send a video note that exists on the Telegram servers, or pass a file path as string to upload a new video note that exists on your local machine. Sending video notes by a URL is currently unsupported. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -128,11 +130,30 @@ class SendVideoNote(BaseClient): file = None try: - if os.path.exists(video_note): + if isinstance(video_note, str): + if os.path.exists(video_note): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(video_note, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(video_note) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + round_message=True, + duration=duration, + w=length, + h=length + ) + ] + ) + else: + media = utils.get_input_media_from_file_id(video_note, file_ref, 13) + elif hasattr(video_note, "read"): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video_note) or "video/mp4", + mime_type=self.guess_mime_type(video_note.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -144,8 +165,6 @@ class SendVideoNote(BaseClient): ) ] ) - else: - media = utils.get_input_media_from_file_id(video_note, file_ref, 13) while True: try: diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 753e3806..e57a652e 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendVoice(BaseClient): def send_voice( self, chat_id: Union[int, str], + voice: Union[str, io.IOBase], voice: str, file_ref=None, caption: str = "", @@ -54,11 +55,12 @@ class SendVoice(BaseClient): 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). - voice (``str``): + voice (``str``, file-like object): Audio file to send. Pass a file_id as string to send an audio that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an audio from the Internet, or pass a file path as string to upload a new audio that exists on your local machine. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -132,11 +134,30 @@ class SendVoice(BaseClient): file = None try: - if os.path.exists(voice): + if isinstance(voice, str): + if os.path.exists(voice): + file = self.save_file(voice, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(voice) or "audio/mpeg", + file=file, + attributes=[ + types.DocumentAttributeAudio( + voice=True, + duration=duration + ) + ] + ) + elif voice.startswith("http"): + media = types.InputMediaDocumentExternal( + url=voice + ) + else: + media = utils.get_input_media_from_file_id(voice, file_ref, 3) + elif hasattr(voice, "read"): file = self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(voice) or "audio/mpeg", - file=file, + mime_type=self.guess_mime_type(voice.name) or "audio/mpeg", + file=file.name, attributes=[ types.DocumentAttributeAudio( voice=True, @@ -144,12 +165,6 @@ class SendVoice(BaseClient): ) ] ) - elif voice.startswith("http"): - media = types.InputMediaDocumentExternal( - url=voice - ) - else: - media = utils.get_input_media_from_file_id(voice, file_ref, 3) while True: try: From 173e08015a828a3fa50062a9747392832a1f3708 Mon Sep 17 00:00:00 2001 From: Yan Date: Sat, 20 Jul 2019 10:26:36 +0300 Subject: [PATCH 04/11] Annotate file-like objects in docstring of send_document --- pyrogram/client/methods/messages/send_document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index c4a7252b..be864b4b 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -56,7 +56,7 @@ class SendDocument(BaseClient): 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). - document (``str``): + document (``str`` | file-like object): File to send. Pass a file_id as string to send a file that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a file from the Internet, or From 4c9fee525e6b622645ea6f362ff7fe3365f65229 Mon Sep 17 00:00:00 2001 From: Yan Date: Sat, 20 Jul 2019 10:28:07 +0300 Subject: [PATCH 05/11] Annotate file-like objects in docstring of send_sticker --- pyrogram/client/methods/messages/send_sticker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 98cc4425..8cccfe01 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -52,7 +52,7 @@ class SendSticker(BaseClient): 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). - sticker (``str``): + sticker (``str`` | file-like object): Sticker to send. Pass a file_id as string to send a sticker that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or From 2e846f83ecef0f651ef47cd59efcd25e16fb0698 Mon Sep 17 00:00:00 2001 From: Yan Date: Sat, 20 Jul 2019 10:29:20 +0300 Subject: [PATCH 06/11] Annotate file-like objects in docstring of send_video --- pyrogram/client/methods/messages/send_video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 6c1c2efe..458d5148 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -60,7 +60,7 @@ class SendVideo(BaseClient): 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). - video (``str``): + video (``str`` | file-like object): Video to send. Pass a file_id as string to send a video that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a video from the Internet, or From 3ec5f76b104b8cf14bf1f4d8104595d7d618e485 Mon Sep 17 00:00:00 2001 From: Yan Date: Sat, 20 Jul 2019 10:41:15 +0300 Subject: [PATCH 07/11] Fix TypeError in send_sticker --- pyrogram/client/methods/messages/send_sticker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 8cccfe01..60b8609e 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -134,7 +134,7 @@ class SendSticker(BaseClient): elif hasattr(sticker, "read"): file = self.save_file(sticker, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(sticker) or "image/webp", + mime_type=self.guess_mime_type(sticker.name) or "image/webp", file=file, attributes=[ types.DocumentAttributeFilename(file_name=sticker.name) From 6b2d6ffacfdb3bd1e75d14cbb9c26df9950762d0 Mon Sep 17 00:00:00 2001 From: Octo Date: Sun, 4 Aug 2019 15:00:19 +0300 Subject: [PATCH 08/11] Fix send_voice --- pyrogram/client/methods/messages/send_voice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index e57a652e..e4d0e352 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -157,7 +157,7 @@ class SendVoice(BaseClient): file = self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(voice.name) or "audio/mpeg", - file=file.name, + file=file, attributes=[ types.DocumentAttributeAudio( voice=True, From 4a8e6fb855785588d79825f8ed6ade649fc1d6e8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 9 Jul 2020 00:20:46 +0200 Subject: [PATCH 09/11] Cleanup --- pyrogram/client/client.py | 289 ++++++++---------- .../client/methods/messages/download_media.py | 13 - .../methods/messages/send_animated_sticker.py | 153 ---------- .../client/methods/messages/send_animation.py | 45 +-- .../client/methods/messages/send_audio.py | 41 +-- .../client/methods/messages/send_document.py | 37 +-- .../client/methods/messages/send_photo.py | 29 +- .../client/methods/messages/send_sticker.py | 34 +-- .../client/methods/messages/send_video.py | 42 +-- .../methods/messages/send_video_note.py | 31 +- .../client/methods/messages/send_voice.py | 35 +-- .../types/messages_and_media/message.py | 11 +- 12 files changed, 216 insertions(+), 544 deletions(-) delete mode 100644 pyrogram/client/methods/messages/send_animated_sticker.py diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 1efb6f06..0e186d85 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import logging import math import os @@ -1231,9 +1231,9 @@ class Client(Methods, BaseClient): temp_file_path = "" final_file_path = "" - path = [None] + try: - data, done, progress, progress_args, out, path, to_file = packet + data, directory, file_name, done, progress, progress_args, path = packet temp_file_path = self.get_file( media_type=data.media_type, @@ -1250,15 +1250,13 @@ class Client(Methods, BaseClient): file_size=data.file_size, is_big=data.is_big, progress=progress, - progress_args=progress_args, - out=out + progress_args=progress_args ) - if to_file: - final_file_path = out.name - else: - final_file_path = '' - if to_file: - out.close() + + if temp_file_path: + 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: log.error(e, exc_info=True) @@ -1715,7 +1713,7 @@ class Client(Methods, BaseClient): def save_file( self, - path: Union[str, io.IOBase], + path: str, file_id: int = None, file_part: int = 0, progress: callable = None, @@ -1767,20 +1765,9 @@ class Client(Methods, BaseClient): Raises: RPCError: In case of a Telegram RPC error. - ValueError: if path is not str or file-like readable object """ part_size = 512 * 1024 - if isinstance(path, str): - fp = open(path, 'rb') - filename = os.path.basename(path) - elif hasattr(path, 'write'): - fp = path - filename = fp.name - else: - raise ValueError("Invalid path passed! Pass file pointer or path to file") - fp.seek(0, os.SEEK_END) - file_size = fp.tell() - fp.seek(0) + file_size = os.path.getsize(path) if file_size == 0: raise ValueError("File size equals to 0 B") @@ -1798,74 +1785,67 @@ class Client(Methods, BaseClient): session.start() try: - fp.seek(part_size * file_part) + with open(path, "rb") as f: + f.seek(part_size * file_part) - while True: - chunk = fp.read(part_size) + while True: + chunk = f.read(part_size) - if not chunk: - if not is_big: - md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) - break - - for _ in range(3): - if is_big: - rpc = functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk - ) - else: - rpc = functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk - ) - - if session.send(rpc): + if not chunk: + if not is_big: + md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) break - else: - raise AssertionError("Telegram didn't accept chunk #{} of {}".format(file_part, path)) - if is_missing_part: - return + for _ in range(3): + if is_big: + rpc = functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk + ) + else: + rpc = functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk + ) - if not is_big: - md5_sum.update(chunk) + if session.send(rpc): + break + else: + raise AssertionError("Telegram didn't accept chunk #{} of {}".format(file_part, path)) - file_part += 1 + if is_missing_part: + return - if progress: - progress(min(file_part * part_size, file_size), file_size, *progress_args) + if not is_big: + md5_sum.update(chunk) + + file_part += 1 + + if progress: + progress(min(file_part * part_size, file_size), file_size, *progress_args) except Client.StopTransmission: - if isinstance(path, str): - fp.close() raise except Exception as e: - if isinstance(path, str): - fp.close() log.error(e, exc_info=True) else: - if isinstance(path, str): - fp.close() if is_big: return types.InputFileBig( id=file_id, parts=file_total_parts, - name=filename, + name=os.path.basename(path), ) else: return types.InputFile( id=file_id, parts=file_total_parts, - name=filename, + name=os.path.basename(path), md5_checksum=md5_sum ) finally: - if isinstance(path, str): - fp.close() session.stop() def get_file( @@ -1884,8 +1864,7 @@ class Client(Methods, BaseClient): file_size: int, is_big: bool, progress: callable, - progress_args: tuple = (), - out: io.IOBase = None + progress_args: tuple = () ) -> str: with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) @@ -1971,10 +1950,7 @@ class Client(Methods, BaseClient): limit = 1024 * 1024 offset = 0 file_name = "" - if not out: - f = tempfile.NamedTemporaryFile("wb", delete=False) - else: - f = out + try: r = session.send( functions.upload.GetFile( @@ -1985,37 +1961,36 @@ class Client(Methods, BaseClient): ) if isinstance(r, types.upload.File): - if hasattr(f, "name"): + with tempfile.NamedTemporaryFile("wb", delete=False) as f: file_name = f.name - while True: - chunk = r.bytes + while True: + chunk = r.bytes - if not chunk: - break + if not chunk: + break - f.write(chunk) + f.write(chunk) - offset += limit + offset += limit - if progress: - progress( + if progress: + progress( + min(offset, file_size) + if file_size != 0 + else offset, + file_size, + *progress_args + ) - min(offset, file_size) - if file_size != 0 - else offset, - file_size, - *progress_args + r = session.send( + functions.upload.GetFile( + location=location, + offset=offset, + limit=limit + ) ) - r = session.send( - functions.upload.GetFile( - location=location, - offset=offset, - limit=limit - ) - ) - elif isinstance(r, types.upload.FileCdnRedirect): with self.media_sessions_lock: cdn_session = self.media_sessions.get(r.dc_id, None) @@ -2028,71 +2003,70 @@ class Client(Methods, BaseClient): self.media_sessions[r.dc_id] = cdn_session try: - if hasattr(f, "name"): + with tempfile.NamedTemporaryFile("wb", delete=False) as f: file_name = f.name - while True: - r2 = cdn_session.send( - functions.upload.GetCdnFile( - 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 - ) + while True: + r2 = cdn_session.send( + functions.upload.GetCdnFile( + file_token=r.file_token, + offset=offset, + limit=limit ) - except VolumeLocNotFound: + ) + + 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 + + chunk = r2.bytes + + # https://core.telegram.org/cdn#decrypting-files + decrypted_chunk = AES.ctr256_decrypt( + chunk, + r.encryption_key, + bytearray( + r.encryption_iv[:-4] + + (offset // 16).to_bytes(4, "big") + ) + ) + + hashes = session.send( + functions.upload.GetCdnFileHashes( + file_token=r.file_token, + offset=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) + + f.write(decrypted_chunk) + + offset += limit + + if progress: + progress( + min(offset, file_size) + if file_size != 0 + else offset, + file_size, + *progress_args + ) + + if len(chunk) < limit: break - else: - continue - - chunk = r2.bytes - - # https://core.telegram.org/cdn#decrypting-files - decrypted_chunk = AES.ctr256_decrypt( - chunk, - r.encryption_key, - bytearray( - r.encryption_iv[:-4] - + (offset // 16).to_bytes(4, "big") - ) - ) - - hashes = session.send( - functions.upload.GetCdnFileHashes( - file_token=r.file_token, - offset=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) - - f.write(decrypted_chunk) - - offset += limit - - if progress: - progress( - - min(offset, file_size) - if file_size != 0 - else offset, - file_size, - *progress_args - ) - - if len(chunk) < limit: - break except Exception as e: raise e except Exception as e: @@ -2100,8 +2074,7 @@ class Client(Methods, BaseClient): log.error(e, exc_info=True) try: - if out: - os.remove(file_name) + os.remove(file_name) except OSError: pass diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 2176e4aa..22054397 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -17,9 +17,7 @@ # along with Pyrogram. If not, see . import binascii -import io import os -import re import struct import time from datetime import datetime @@ -39,7 +37,6 @@ class DownloadMedia(BaseClient): message: Union["pyrogram.Message", str], file_ref: str = None, file_name: str = DEFAULT_DOWNLOAD_DIR, - out: io.IOBase = None, block: bool = True, progress: callable = None, progress_args: tuple = () @@ -61,9 +58,6 @@ class DownloadMedia(BaseClient): 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. - out (``io.IOBase``, *optional*): - A custom *file-like object* to be used when downloading file. Overrides file_name - block (``bool``, *optional*): Blocks the code execution until the file has been downloaded. Defaults to True. @@ -244,13 +238,6 @@ class DownloadMedia(BaseClient): extension ) - if not out: - out = open(os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))), 'wb') - os.makedirs(directory, exist_ok=True) - to_file = True - else: - to_file = False - self.download_queue.put((data, done, progress, progress_args, out, path, to_file)) # Cast to string because Path objects aren't supported by Python 3.5 self.download_queue.put((data, str(directory), str(file_name), done, progress, progress_args, path)) diff --git a/pyrogram/client/methods/messages/send_animated_sticker.py b/pyrogram/client/methods/messages/send_animated_sticker.py deleted file mode 100644 index b2959394..00000000 --- a/pyrogram/client/methods/messages/send_animated_sticker.py +++ /dev/null @@ -1,153 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2019 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . -import io -import os -from typing import Union - -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils -from pyrogram.errors import FilePartMissing - - -class SendAnimatedSticker(BaseClient): - def send_animated_sticker( - self, - chat_id: Union[int, str], - animated_sticker: Union[str, io.IOBase], - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None, - progress: callable = None, - progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: - """Send .tgs animated stickers. - - Parameters: - 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). - - animated_sticker (``str`` | file-like object): - Animated sticker to send. - Pass a file_id as string to send a animated sticker that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a .webp animated sticker file from the Internet, or - pass a file path as string to upload a new animated sticker that exists on your local machine. - pass a readable file-like object with .name - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message. - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). - - progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. - - Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - :obj:`Message` | ``None``: On success, the sent animated sticker message is returned, otherwise, in case the - upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. - """ - file = None - - try: - if isinstance(animated_sticker, str): - if os.path.exists(animated_sticker): - file = self.save_file(animated_sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animated_sticker) or "application/x-tgsticker", - file=file, - attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(animated_sticker)) - ] - ) - elif animated_sticker.startswith("http"): - media = types.InputMediaDocumentExternal( - url=animated_sticker - ) - else: - media = utils.get_input_media_from_file_id(animated_sticker, 5) - elif hasattr(animated_sticker, "read"): - file = self.save_file(animated_sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animated_sticker.name) or "application/x-tgsticker", - file=file, - attributes=[ - types.DocumentAttributeFilename(file_name=animated_sticker.name) - ] - ) - - - while True: - try: - r = self.send( - functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), - media=media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None, - message="" - ) - ) - except FilePartMissing as e: - self.save_file(animated_sticker, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message._parse( - self, i.message, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats} - ) - except BaseClient.StopTransmission: - return None diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index f8078a7c..288ed04e 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendAnimation(BaseClient): def send_animation( self, chat_id: Union[int, str], - animation: Union[str, io.IOBase], animation: str, file_ref: str = None, caption: str = "", @@ -60,13 +59,11 @@ class SendAnimation(BaseClient): 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). - animation (``str``| file-like object): + animation (``str``): Animation to send. Pass a file_id as string to send an animation that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an animation from the Internet, or pass a file path as string to upload a new animation that exists on your local machine. - pass a readable file-like object with .name - file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -166,36 +163,11 @@ class SendAnimation(BaseClient): file = None try: - if isinstance(animation, str): - if os.path.exists(animation): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(animation, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animation) or "video/mp4", - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeVideo( - supports_streaming=True, - duration=duration, - w=width, - h=height - ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), - types.DocumentAttributeAnimated() - ] - ) - elif animation.startswith("http"): - media = types.InputMediaDocumentExternal( - url=animation - ) - else: - media = utils.get_input_media_from_file_id(animation, file_ref, 10) - elif hasattr(animation, "read"): + if os.path.exists(animation): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animation.name) or "video/mp4", + mime_type=self.guess_mime_type(animation) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -205,10 +177,17 @@ class SendAnimation(BaseClient): w=width, h=height ), - types.DocumentAttributeFilename(file_name=animation.name), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), types.DocumentAttributeAnimated() ] ) + elif animation.startswith("http"): + media = types.InputMediaDocumentExternal( + url=animation + ) + else: + media = utils.get_input_media_from_file_id(animation, file_ref, 10) + while True: try: r = self.send( diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 0ff588d8..e271d96c 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendAudio(BaseClient): def send_audio( self, chat_id: Union[int, str], - audio: Union[str, io.IOBase], audio: str, file_ref: str = None, caption: str = "", @@ -61,12 +60,11 @@ class SendAudio(BaseClient): 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). - audio (``str``, file-like object): + audio (``str``): Audio file to send. Pass a file_id as string to send an audio file that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an audio file from the Internet, or pass a file path as string to upload a new audio file that exists on your local machine. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -165,34 +163,11 @@ class SendAudio(BaseClient): file = None try: - if isinstance(audio, str): - if os.path.exists(audio): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(audio, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(audio) or "audio/mpeg", - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeAudio( - duration=duration, - performer=performer, - title=title - ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) - ] - ) - elif audio.startswith("http"): - media = types.InputMediaDocumentExternal( - url=audio - ) - else: - media = utils.get_input_media_from_file_id(audio, file_ref, 9) - elif hasattr(audio, "read"): + if os.path.exists(audio): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(audio.name) or "audio/mpeg", + mime_type=self.guess_mime_type(audio) or "audio/mpeg", file=file, thumb=thumb, attributes=[ @@ -201,9 +176,15 @@ class SendAudio(BaseClient): performer=performer, title=title ), - types.DocumentAttributeFilename(file_name=os.path.basename(audio.name)) + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) ] ) + elif audio.startswith("http"): + media = types.InputMediaDocumentExternal( + url=audio + ) + else: + media = utils.get_input_media_from_file_id(audio, file_ref, 9) while True: try: diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index be864b4b..24a754f0 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendDocument(BaseClient): def send_document( self, chat_id: Union[int, str], - document: Union[str, io.IOBase], document: str, file_ref: str = None, thumb: str = None, @@ -56,13 +55,11 @@ class SendDocument(BaseClient): 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). - document (``str`` | file-like object): + document (``str``): File to send. Pass a file_id as string to send a file that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a file from the Internet, or pass a file path as string to upload a new file that exists on your local machine. - pass a readable file-like object with .name - file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -146,35 +143,23 @@ class SendDocument(BaseClient): file = None try: - if isinstance(document, str): - if os.path.exists(document): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(document, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(document) or "application/zip", - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) - ] - ) - elif document.startswith("http"): - media = types.InputMediaDocumentExternal( - url=document - ) - else: - media = utils.get_input_media_from_file_id(document, file_ref, 5) - else: + if os.path.exists(document): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(document.name) or "application/zip", + mime_type=self.guess_mime_type(document) or "application/zip", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeFilename(file_name=document.name) + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) ] ) + elif document.startswith("http"): + media = types.InputMediaDocumentExternal( + url=document + ) + else: + media = utils.get_input_media_from_file_id(document, file_ref, 5) while True: try: diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 7877c986..4d6a18a3 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendPhoto(BaseClient): def send_photo( self, chat_id: Union[int, str], - photo: Union[str, io.IOBase], photo: str, file_ref: str = None, caption: str = "", @@ -55,12 +54,11 @@ class SendPhoto(BaseClient): 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). - photo (``str`` | file-like object): + photo (``str``): Photo to send. Pass a file_id as string to send a photo that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a photo from the Internet, or pass a file path as string to upload a new photo that exists on your local machine. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -139,26 +137,19 @@ class SendPhoto(BaseClient): file = None try: - if isinstance(photo, str): - if os.path.exists(photo): - file = self.save_file(photo, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedPhoto( - file=file, - ttl_seconds=ttl_seconds - ) - elif photo.startswith("http"): - media = types.InputMediaPhotoExternal( - url=photo, - ttl_seconds=ttl_seconds - ) - else: - media = utils.get_input_media_from_file_id(photo, file_ref, 2) - elif hasattr(photo, "read"): + if os.path.exists(photo): file = self.save_file(photo, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedPhoto( file=file, ttl_seconds=ttl_seconds ) + elif photo.startswith("http"): + media = types.InputMediaPhotoExternal( + url=photo, + ttl_seconds=ttl_seconds + ) + else: + media = utils.get_input_media_from_file_id(photo, file_ref, 2) while True: try: diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 60b8609e..76a42d3d 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendSticker(BaseClient): def send_sticker( self, chat_id: Union[int, str], - sticker: Union[str, io.IOBase], sticker: str, file_ref: str = None, disable_notification: bool = None, @@ -52,12 +51,11 @@ class SendSticker(BaseClient): 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). - sticker (``str`` | file-like object): + sticker (``str``): Sticker to send. Pass a file_id as string to send a sticker that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or pass a file path as string to upload a new sticker that exists on your local machine. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -115,31 +113,21 @@ class SendSticker(BaseClient): file = None try: - if isinstance(sticker, str): - if os.path.exists(sticker): - file = self.save_file(sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(sticker) or "image/webp", - file=file, - attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) - ] - ) - elif sticker.startswith("http"): - media = types.InputMediaDocumentExternal( - url=sticker - ) - else: - media = utils.get_input_media_from_file_id(sticker, file_ref, 8) - elif hasattr(sticker, "read"): + if os.path.exists(sticker): file = self.save_file(sticker, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(sticker.name) or "image/webp", + mime_type=self.guess_mime_type(sticker) or "image/webp", file=file, attributes=[ - types.DocumentAttributeFilename(file_name=sticker.name) + types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) ] ) + elif sticker.startswith("http"): + media = types.InputMediaDocumentExternal( + url=sticker + ) + else: + media = utils.get_input_media_from_file_id(sticker, file_ref, 8) while True: try: diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 458d5148..fc58aa98 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendVideo(BaseClient): def send_video( self, chat_id: Union[int, str], - video: Union[str, io.IOBase], video: str, file_ref: str = None, caption: str = "", @@ -60,12 +59,11 @@ class SendVideo(BaseClient): 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). - video (``str`` | file-like object): + video (``str``): Video to send. Pass a file_id as string to send a video that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a video from the Internet, or pass a file path as string to upload a new video that exists on your local machine. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -162,35 +160,11 @@ class SendVideo(BaseClient): file = None try: - if isinstance(video, str): - if os.path.exists(video): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(video, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video) or "video/mp4", - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeVideo( - supports_streaming=supports_streaming or None, - duration=duration, - w=width, - h=height - ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) - ] - ) - elif video.startswith("http"): - media = types.InputMediaDocumentExternal( - url=video - ) - else: - media = utils.get_input_media_from_file_id(video, file_ref, 4) - elif hasattr(video, "read"): + if os.path.exists(video): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video.name) or "video/mp4", + mime_type=self.guess_mime_type(video) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -200,9 +174,15 @@ class SendVideo(BaseClient): w=width, h=height ), - types.DocumentAttributeFilename(file_name=video.name) + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) ] ) + elif video.startswith("http"): + media = types.InputMediaDocumentExternal( + url=video + ) + else: + media = utils.get_input_media_from_file_id(video, file_ref, 4) while True: try: diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index a160567d..64bde11b 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendVideoNote(BaseClient): def send_video_note( self, chat_id: Union[int, str], - video_note: Union[str, io.IOBase], video_note: str, file_ref: str = None, duration: int = 0, @@ -55,12 +54,11 @@ class SendVideoNote(BaseClient): 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). - video_note (``str``, file-like object): + video_note (``str``): Video note to send. Pass a file_id as string to send a video note that exists on the Telegram servers, or pass a file path as string to upload a new video note that exists on your local machine. Sending video notes by a URL is currently unsupported. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -130,30 +128,11 @@ class SendVideoNote(BaseClient): file = None try: - if isinstance(video_note, str): - if os.path.exists(video_note): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(video_note, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video_note) or "video/mp4", - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeVideo( - round_message=True, - duration=duration, - w=length, - h=length - ) - ] - ) - else: - media = utils.get_input_media_from_file_id(video_note, file_ref, 13) - elif hasattr(video_note, "read"): + if os.path.exists(video_note): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video_note.name) or "video/mp4", + mime_type=self.guess_mime_type(video_note) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -165,6 +144,8 @@ class SendVideoNote(BaseClient): ) ] ) + else: + media = utils.get_input_media_from_file_id(video_note, file_ref, 13) while True: try: diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index e4d0e352..753e3806 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendVoice(BaseClient): def send_voice( self, chat_id: Union[int, str], - voice: Union[str, io.IOBase], voice: str, file_ref=None, caption: str = "", @@ -55,12 +54,11 @@ class SendVoice(BaseClient): 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). - voice (``str``, file-like object): + voice (``str``): Audio file to send. Pass a file_id as string to send an audio that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an audio from the Internet, or pass a file path as string to upload a new audio that exists on your local machine. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -134,29 +132,10 @@ class SendVoice(BaseClient): file = None try: - if isinstance(voice, str): - if os.path.exists(voice): - file = self.save_file(voice, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(voice) or "audio/mpeg", - file=file, - attributes=[ - types.DocumentAttributeAudio( - voice=True, - duration=duration - ) - ] - ) - elif voice.startswith("http"): - media = types.InputMediaDocumentExternal( - url=voice - ) - else: - media = utils.get_input_media_from_file_id(voice, file_ref, 3) - elif hasattr(voice, "read"): + if os.path.exists(voice): file = self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(voice.name) or "audio/mpeg", + mime_type=self.guess_mime_type(voice) or "audio/mpeg", file=file, attributes=[ types.DocumentAttributeAudio( @@ -165,6 +144,12 @@ class SendVoice(BaseClient): ) ] ) + elif voice.startswith("http"): + media = types.InputMediaDocumentExternal( + url=voice + ) + else: + media = utils.get_input_media_from_file_id(voice, file_ref, 3) while True: try: diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index cff8c578..215f86d0 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + from functools import partial from typing import List, Match, Union @@ -2964,7 +2964,7 @@ class Message(Object, Update): chat_id=message.chat.id, message_id=message_id, ) - + Example: .. code-block:: python @@ -2985,7 +2985,6 @@ class Message(Object, Update): def download( self, file_name: str = "", - out: io.IOBase = None, block: bool = True, progress: callable = None, progress_args: tuple = () @@ -3010,9 +3009,6 @@ class Message(Object, Update): 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. - out (``io.IOBase``, *optional*): - A custom *file-like object* to be used when downloading file. Overrides file_name - block (``bool``, *optional*): Blocks the code execution until the file has been downloaded. Defaults to True. @@ -3049,7 +3045,6 @@ class Message(Object, Update): return self._client.download_media( message=self, file_name=file_name, - out=out, block=block, progress=progress, progress_args=progress_args, @@ -3079,7 +3074,7 @@ class Message(Object, Update): Parameters: option (``int``): Index of the poll option you want to vote for (0 to 9). - + Returns: :obj:`Poll`: On success, the poll with the chosen option is returned. From de8f784f7847d435e539ab5208dfff4568ea51fd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 9 Jul 2020 02:22:56 +0200 Subject: [PATCH 10/11] Use better checks for local and external files --- .../client/methods/chats/set_chat_photo.py | 2 +- .../methods/messages/edit_inline_media.py | 12 ++++++----- .../methods/messages/edit_message_media.py | 21 ++++++++++--------- .../client/methods/messages/send_animation.py | 5 +++-- .../client/methods/messages/send_audio.py | 5 +++-- .../client/methods/messages/send_document.py | 5 +++-- .../methods/messages/send_media_group.py | 11 +++++----- .../client/methods/messages/send_photo.py | 5 +++-- .../client/methods/messages/send_sticker.py | 5 +++-- .../client/methods/messages/send_video.py | 5 +++-- .../methods/messages/send_video_note.py | 2 +- .../client/methods/messages/send_voice.py | 5 +++-- 12 files changed, 46 insertions(+), 37 deletions(-) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 3a996711..d394322c 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -63,7 +63,7 @@ class SetChatPhoto(BaseClient): """ peer = self.resolve_peer(chat_id) - if os.path.exists(photo): + if os.path.isfile(photo): photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) else: photo = utils.get_input_media_from_file_id(photo, file_ref, 2) diff --git a/pyrogram/client/methods/messages/edit_inline_media.py b/pyrogram/client/methods/messages/edit_inline_media.py index 700804d9..f409ae06 100644 --- a/pyrogram/client/methods/messages/edit_inline_media.py +++ b/pyrogram/client/methods/messages/edit_inline_media.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import re + import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient, utils @@ -72,35 +74,35 @@ class EditInlineMedia(BaseClient): parse_mode = media.parse_mode if isinstance(media, InputMediaPhoto): - if media.media.startswith("http"): + if re.match("^https?://", media.media): media = types.InputMediaPhotoExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 2) elif isinstance(media, InputMediaVideo): - if media.media.startswith("http"): + if re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 4) elif isinstance(media, InputMediaAudio): - if media.media.startswith("http"): + if re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 9) elif isinstance(media, InputMediaAnimation): - if media.media.startswith("http"): + if re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 10) elif isinstance(media, InputMediaDocument): - if media.media.startswith("http"): + if re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index b6fbf933..3ab90744 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -78,7 +79,7 @@ class EditMessageMedia(BaseClient): parse_mode = media.parse_mode if isinstance(media, InputMediaPhoto): - if os.path.exists(media.media): + if os.path.isfile(media.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -95,14 +96,14 @@ class EditMessageMedia(BaseClient): file_reference=media.photo.file_reference ) ) - elif media.media.startswith("http"): + elif re.match("^https?://", media.media): media = types.InputMediaPhotoExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 2) elif isinstance(media, InputMediaVideo): - if os.path.exists(media.media): + if os.path.isfile(media.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -132,14 +133,14 @@ class EditMessageMedia(BaseClient): file_reference=media.document.file_reference ) ) - elif media.media.startswith("http"): + elif re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 4) elif isinstance(media, InputMediaAudio): - if os.path.exists(media.media): + if os.path.isfile(media.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -168,14 +169,14 @@ class EditMessageMedia(BaseClient): file_reference=media.document.file_reference ) ) - elif media.media.startswith("http"): + elif re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 9) elif isinstance(media, InputMediaAnimation): - if os.path.exists(media.media): + if os.path.isfile(media.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -206,14 +207,14 @@ class EditMessageMedia(BaseClient): file_reference=media.document.file_reference ) ) - elif media.media.startswith("http"): + elif re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 10) elif isinstance(media, InputMediaDocument): - if os.path.exists(media.media): + if os.path.isfile(media.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -237,7 +238,7 @@ class EditMessageMedia(BaseClient): file_reference=media.document.file_reference ) ) - elif media.media.startswith("http"): + elif re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 288ed04e..c84e0503 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -163,7 +164,7 @@ class SendAnimation(BaseClient): file = None try: - if os.path.exists(animation): + if os.path.isfile(animation): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( @@ -181,7 +182,7 @@ class SendAnimation(BaseClient): types.DocumentAttributeAnimated() ] ) - elif animation.startswith("http"): + elif re.match("^https?://", animation): media = types.InputMediaDocumentExternal( url=animation ) diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index e271d96c..49fd0e09 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -163,7 +164,7 @@ class SendAudio(BaseClient): file = None try: - if os.path.exists(audio): + if os.path.isfile(audio): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( @@ -179,7 +180,7 @@ class SendAudio(BaseClient): types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) ] ) - elif audio.startswith("http"): + elif re.match("^https?://", audio): media = types.InputMediaDocumentExternal( url=audio ) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 24a754f0..8f15c5ee 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -143,7 +144,7 @@ class SendDocument(BaseClient): file = None try: - if os.path.exists(document): + if os.path.isfile(document): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( @@ -154,7 +155,7 @@ class SendDocument(BaseClient): types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) ] ) - elif document.startswith("http"): + elif re.match("^https?://", document): media = types.InputMediaDocumentExternal( url=document ) diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 8571ef4f..9ca4473b 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -18,13 +18,12 @@ import logging import os -import time +import re from typing import Union, List import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient, utils -from pyrogram.errors import FloodWait log = logging.getLogger(__name__) @@ -77,7 +76,7 @@ class SendMediaGroup(BaseClient): for i in media: if isinstance(i, pyrogram.InputMediaPhoto): - if os.path.exists(i.media): + if os.path.isfile(i.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -94,7 +93,7 @@ class SendMediaGroup(BaseClient): file_reference=media.photo.file_reference ) ) - elif i.media.startswith("http"): + elif re.match("^https?://", i.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -114,7 +113,7 @@ class SendMediaGroup(BaseClient): else: media = utils.get_input_media_from_file_id(i.media, i.file_ref, 2) elif isinstance(i, pyrogram.InputMediaVideo): - if os.path.exists(i.media): + if os.path.isfile(i.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -142,7 +141,7 @@ class SendMediaGroup(BaseClient): file_reference=media.document.file_reference ) ) - elif i.media.startswith("http"): + elif re.match("^https?://", i.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 4d6a18a3..c21bb487 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -137,13 +138,13 @@ class SendPhoto(BaseClient): file = None try: - if os.path.exists(photo): + if os.path.isfile(photo): file = self.save_file(photo, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedPhoto( file=file, ttl_seconds=ttl_seconds ) - elif photo.startswith("http"): + elif re.match("^https?://", photo): media = types.InputMediaPhotoExternal( url=photo, ttl_seconds=ttl_seconds diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 76a42d3d..d9575885 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -113,7 +114,7 @@ class SendSticker(BaseClient): file = None try: - if os.path.exists(sticker): + if os.path.isfile(sticker): file = self.save_file(sticker, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(sticker) or "image/webp", @@ -122,7 +123,7 @@ class SendSticker(BaseClient): types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) ] ) - elif sticker.startswith("http"): + elif re.match("^https?://", sticker): media = types.InputMediaDocumentExternal( url=sticker ) diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index fc58aa98..9a67bbbb 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -160,7 +161,7 @@ class SendVideo(BaseClient): file = None try: - if os.path.exists(video): + if os.path.isfile(video): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( @@ -177,7 +178,7 @@ class SendVideo(BaseClient): types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) ] ) - elif video.startswith("http"): + elif re.match("^https?://", video): media = types.InputMediaDocumentExternal( url=video ) diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 64bde11b..b7acdc01 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -128,7 +128,7 @@ class SendVideoNote(BaseClient): file = None try: - if os.path.exists(video_note): + if os.path.isfile(video_note): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 753e3806..23492f53 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -132,7 +133,7 @@ class SendVoice(BaseClient): file = None try: - if os.path.exists(voice): + if os.path.isfile(voice): file = self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(voice) or "audio/mpeg", @@ -144,7 +145,7 @@ class SendVoice(BaseClient): ) ] ) - elif voice.startswith("http"): + elif re.match("^https?://", voice): media = types.InputMediaDocumentExternal( url=voice ) From b3faf21c95533232a71bbb7049d316ab18725c28 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 9 Jul 2020 02:56:09 +0200 Subject: [PATCH 11/11] Rework in-memory uploads --- pyrogram/client/client.py | 35 +++++++++---- .../client/methods/messages/send_animation.py | 52 +++++++++++++------ .../client/methods/messages/send_audio.py | 50 ++++++++++++------ .../client/methods/messages/send_document.py | 45 ++++++++++------ .../client/methods/messages/send_photo.py | 34 +++++++----- .../client/methods/messages/send_sticker.py | 39 +++++++++----- .../client/methods/messages/send_video.py | 51 ++++++++++++------ .../methods/messages/send_video_note.py | 40 ++++++++++---- .../client/methods/messages/send_voice.py | 40 +++++++++----- 9 files changed, 260 insertions(+), 126 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 0e186d85..c46f119a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import io import logging import math import os @@ -30,7 +31,7 @@ from importlib import import_module, reload from pathlib import Path from signal import signal, SIGINT, SIGTERM, SIGABRT from threading import Thread -from typing import Union, List +from typing import Union, List, BinaryIO from pyrogram.api import functions, types from pyrogram.api.core import TLObject @@ -39,9 +40,9 @@ from pyrogram.client.handlers.handler import Handler from pyrogram.client.methods.password.utils import compute_check from pyrogram.crypto import AES from pyrogram.errors import ( - PhoneMigrate, NetworkMigrate, SessionPasswordNeeded, - FloodWait, PeerIdInvalid, VolumeLocNotFound, UserMigrate, ChannelPrivate, AuthBytesInvalid, - BadRequest) + PhoneMigrate, NetworkMigrate, SessionPasswordNeeded, PeerIdInvalid, VolumeLocNotFound, UserMigrate, ChannelPrivate, + AuthBytesInvalid, BadRequest +) from pyrogram.session import Auth, Session from .ext import utils, Syncer, BaseClient, Dispatcher from .methods import Methods @@ -1713,7 +1714,7 @@ class Client(Methods, BaseClient): def save_file( self, - path: str, + path: Union[str, BinaryIO], file_id: int = None, file_part: int = 0, progress: callable = None, @@ -1767,7 +1768,19 @@ class Client(Methods, BaseClient): RPCError: In case of a Telegram RPC error. """ part_size = 512 * 1024 - file_size = os.path.getsize(path) + + if isinstance(path, str): + fp = open(path, "rb") + elif isinstance(path, io.IOBase): + fp = path + else: + raise ValueError("Invalid file. Expected a file path as string or a binary (not text) file pointer") + + file_name = fp.name + + fp.seek(0, os.SEEK_END) + file_size = fp.tell() + fp.seek(0) if file_size == 0: raise ValueError("File size equals to 0 B") @@ -1785,11 +1798,11 @@ class Client(Methods, BaseClient): session.start() try: - with open(path, "rb") as f: - f.seek(part_size * file_part) + with fp: + fp.seek(part_size * file_part) while True: - chunk = f.read(part_size) + chunk = fp.read(part_size) if not chunk: if not is_big: @@ -1835,14 +1848,14 @@ class Client(Methods, BaseClient): return types.InputFileBig( id=file_id, parts=file_total_parts, - name=os.path.basename(path), + name=file_name, ) else: return types.InputFile( id=file_id, parts=file_total_parts, - name=os.path.basename(path), + name=file_name, md5_checksum=md5_sum ) finally: diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index c84e0503..a38856a0 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,7 +30,7 @@ class SendAnimation(BaseClient): def send_animation( self, chat_id: Union[int, str], - animation: str, + animation: Union[str, BinaryIO], file_ref: str = None, caption: str = "", unsave: bool = False, @@ -38,7 +38,7 @@ class SendAnimation(BaseClient): duration: int = 0, width: int = 0, height: int = 0, - thumb: str = None, + thumb: Union[str, BinaryIO] = None, file_name: str = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -60,11 +60,12 @@ class SendAnimation(BaseClient): 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). - animation (``str``): + animation (``str`` | ``BinaryIO``): Animation to send. Pass a file_id as string to send an animation that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get an animation from the Internet, or - pass a file path as string to upload a new animation that exists on your local machine. + pass an HTTP URL as a string for Telegram to get an animation from the Internet, + pass a file path as string to upload a new animation that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -93,7 +94,7 @@ class SendAnimation(BaseClient): height (``int``, *optional*): Animation height. - thumb (``str``, *optional*): + thumb (``str`` | ``BinaryIO``, *optional*): Thumbnail of the animation file sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. @@ -164,11 +165,36 @@ class SendAnimation(BaseClient): file = None try: - if os.path.isfile(animation): + if isinstance(animation, str): + if os.path.isfile(animation): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(animation, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(animation) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + supports_streaming=True, + duration=duration, + w=width, + h=height + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), + types.DocumentAttributeAnimated() + ] + ) + elif re.match("^https?://", animation): + media = types.InputMediaDocumentExternal( + url=animation + ) + else: + media = utils.get_input_media_from_file_id(animation, file_ref, 10) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animation) or "video/mp4", + mime_type=self.guess_mime_type(animation.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -178,16 +204,10 @@ class SendAnimation(BaseClient): w=width, h=height ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), + types.DocumentAttributeFilename(file_name=animation.name), types.DocumentAttributeAnimated() ] ) - elif re.match("^https?://", animation): - media = types.InputMediaDocumentExternal( - url=animation - ) - else: - media = utils.get_input_media_from_file_id(animation, file_ref, 10) while True: try: diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 49fd0e09..08c03d07 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,14 +30,14 @@ class SendAudio(BaseClient): def send_audio( self, chat_id: Union[int, str], - audio: str, + audio: Union[str, BinaryIO], file_ref: str = None, caption: str = "", parse_mode: Union[str, None] = object, duration: int = 0, performer: str = None, title: str = None, - thumb: str = None, + thumb: Union[str, BinaryIO] = None, file_name: str = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -61,11 +61,12 @@ class SendAudio(BaseClient): 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). - audio (``str``): + audio (``str`` | ``BinaryIO``): Audio file to send. Pass a file_id as string to send an audio file that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get an audio file from the Internet, or - pass a file path as string to upload a new audio file that exists on your local machine. + pass an HTTP URL as a string for Telegram to get an audio file from the Internet, + pass a file path as string to upload a new audio file that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -90,7 +91,7 @@ class SendAudio(BaseClient): title (``str``, *optional*): Track name. - thumb (``str``, *optional*): + thumb (``str`` | ``BinaryIO``, *optional*): Thumbnail of the music file album cover. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. @@ -164,11 +165,34 @@ class SendAudio(BaseClient): file = None try: - if os.path.isfile(audio): + if isinstance(audio, str): + if os.path.isfile(audio): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(audio, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(audio) or "audio/mpeg", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeAudio( + duration=duration, + performer=performer, + title=title + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) + ] + ) + elif re.match("^https?://", audio): + media = types.InputMediaDocumentExternal( + url=audio + ) + else: + media = utils.get_input_media_from_file_id(audio, file_ref, 9) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(audio) or "audio/mpeg", + mime_type=self.guess_mime_type(audio.name) or "audio/mpeg", file=file, thumb=thumb, attributes=[ @@ -177,15 +201,9 @@ class SendAudio(BaseClient): performer=performer, title=title ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) + types.DocumentAttributeFilename(file_name=audio.name) ] ) - elif re.match("^https?://", audio): - media = types.InputMediaDocumentExternal( - url=audio - ) - else: - media = utils.get_input_media_from_file_id(audio, file_ref, 9) while True: try: diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 8f15c5ee..2241754b 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,9 +30,9 @@ class SendDocument(BaseClient): def send_document( self, chat_id: Union[int, str], - document: str, + document: Union[str, BinaryIO], file_ref: str = None, - thumb: str = None, + thumb: Union[str, BinaryIO] = None, caption: str = "", parse_mode: Union[str, None] = object, file_name: str = None, @@ -56,17 +56,18 @@ class SendDocument(BaseClient): 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). - document (``str``): + document (``str`` | ``BinaryIO``): File to send. Pass a file_id as string to send a file that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a file from the Internet, or - pass a file path as string to upload a new file that exists on your local machine. + pass an HTTP URL as a string for Telegram to get a file from the Internet, + pass a file path as string to upload a new file that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. To be used in combination with a file id in case a file reference is needed. - thumb (``str``, *optional*): + thumb (``str`` | ``BinaryIO``, *optional*): Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. @@ -144,23 +145,35 @@ class SendDocument(BaseClient): file = None try: - if os.path.isfile(document): + if isinstance(document, str): + if os.path.isfile(document): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(document, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(document) or "application/zip", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) + ] + ) + elif re.match("^https?://", document): + media = types.InputMediaDocumentExternal( + url=document + ) + else: + media = utils.get_input_media_from_file_id(document, file_ref, 5) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(document) or "application/zip", + mime_type=self.guess_mime_type(document.name) or "application/zip", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) + types.DocumentAttributeFilename(file_name=document.name) ] ) - elif re.match("^https?://", document): - media = types.InputMediaDocumentExternal( - url=document - ) - else: - media = utils.get_input_media_from_file_id(document, file_ref, 5) while True: try: diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index c21bb487..63101685 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,7 +30,7 @@ class SendPhoto(BaseClient): def send_photo( self, chat_id: Union[int, str], - photo: str, + photo: Union[str, BinaryIO], file_ref: str = None, caption: str = "", parse_mode: Union[str, None] = object, @@ -55,11 +55,12 @@ class SendPhoto(BaseClient): 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). - photo (``str``): + photo (``str`` | ``BinaryIO``): Photo to send. Pass a file_id as string to send a photo that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a photo from the Internet, or - pass a file path as string to upload a new photo that exists on your local machine. + pass an HTTP URL as a string for Telegram to get a photo from the Internet, + pass a file path as string to upload a new photo that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -138,19 +139,26 @@ class SendPhoto(BaseClient): file = None try: - if os.path.isfile(photo): + if isinstance(photo, str): + if os.path.isfile(photo): + file = self.save_file(photo, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedPhoto( + file=file, + ttl_seconds=ttl_seconds + ) + elif re.match("^https?://", photo): + media = types.InputMediaPhotoExternal( + url=photo, + ttl_seconds=ttl_seconds + ) + else: + media = utils.get_input_media_from_file_id(photo, file_ref, 2) + else: file = self.save_file(photo, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedPhoto( file=file, ttl_seconds=ttl_seconds ) - elif re.match("^https?://", photo): - media = types.InputMediaPhotoExternal( - url=photo, - ttl_seconds=ttl_seconds - ) - else: - media = utils.get_input_media_from_file_id(photo, file_ref, 2) while True: try: diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index d9575885..0de47d64 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,7 +30,7 @@ class SendSticker(BaseClient): def send_sticker( self, chat_id: Union[int, str], - sticker: str, + sticker: Union[str, BinaryIO], file_ref: str = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -52,11 +52,12 @@ class SendSticker(BaseClient): 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). - sticker (``str``): + sticker (``str`` | ``BinaryIO``): Sticker to send. Pass a file_id as string to send a sticker that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or - pass a file path as string to upload a new sticker that exists on your local machine. + pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, + pass a file path as string to upload a new sticker that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -114,21 +115,31 @@ class SendSticker(BaseClient): file = None try: - if os.path.isfile(sticker): + if isinstance(sticker, str): + if os.path.isfile(sticker): + file = self.save_file(sticker, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(sticker) or "image/webp", + file=file, + attributes=[ + types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) + ] + ) + elif re.match("^https?://", sticker): + media = types.InputMediaDocumentExternal( + url=sticker + ) + else: + media = utils.get_input_media_from_file_id(sticker, file_ref, 8) + else: file = self.save_file(sticker, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(sticker) or "image/webp", + mime_type=self.guess_mime_type(sticker.name) or "image/webp", file=file, attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) + types.DocumentAttributeFilename(file_name=sticker.name) ] ) - elif re.match("^https?://", sticker): - media = types.InputMediaDocumentExternal( - url=sticker - ) - else: - media = utils.get_input_media_from_file_id(sticker, file_ref, 8) while True: try: diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 9a67bbbb..1f46252f 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,14 +30,14 @@ class SendVideo(BaseClient): def send_video( self, chat_id: Union[int, str], - video: str, + video: Union[str, BinaryIO], file_ref: str = None, caption: str = "", parse_mode: Union[str, None] = object, duration: int = 0, width: int = 0, height: int = 0, - thumb: str = None, + thumb: Union[str, BinaryIO] = None, file_name: str = None, supports_streaming: bool = True, disable_notification: bool = None, @@ -60,11 +60,12 @@ class SendVideo(BaseClient): 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). - video (``str``): + video (``str`` | ``BinaryIO``): Video to send. Pass a file_id as string to send a video that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a video from the Internet, or - pass a file path as string to upload a new video that exists on your local machine. + pass an HTTP URL as a string for Telegram to get a video from the Internet, + pass a file path as string to upload a new video that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -89,7 +90,7 @@ class SendVideo(BaseClient): height (``int``, *optional*): Video height. - thumb (``str``, *optional*): + thumb (``str`` | ``BinaryIO``, *optional*): Thumbnail of the video sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. @@ -161,11 +162,35 @@ class SendVideo(BaseClient): file = None try: - if os.path.isfile(video): + if isinstance(video, str): + if os.path.isfile(video): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(video, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(video) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + supports_streaming=supports_streaming or None, + duration=duration, + w=width, + h=height + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) + ] + ) + elif re.match("^https?://", video): + media = types.InputMediaDocumentExternal( + url=video + ) + else: + media = utils.get_input_media_from_file_id(video, file_ref, 4) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video) or "video/mp4", + mime_type=self.guess_mime_type(video.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -175,15 +200,9 @@ class SendVideo(BaseClient): w=width, h=height ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) + types.DocumentAttributeFilename(file_name=video.name) ] ) - elif re.match("^https?://", video): - media = types.InputMediaDocumentExternal( - url=video - ) - else: - media = utils.get_input_media_from_file_id(video, file_ref, 4) while True: try: diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index b7acdc01..829f1459 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import os -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -29,11 +29,11 @@ class SendVideoNote(BaseClient): def send_video_note( self, chat_id: Union[int, str], - video_note: str, + video_note: Union[str, BinaryIO], file_ref: str = None, duration: int = 0, length: int = 1, - thumb: str = None, + thumb: Union[str, BinaryIO] = None, disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, @@ -54,10 +54,11 @@ class SendVideoNote(BaseClient): 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). - video_note (``str``): + video_note (``str`` | ``BinaryIO``): Video note to send. - Pass a file_id as string to send a video note that exists on the Telegram servers, or - pass a file path as string to upload a new video note that exists on your local machine. + Pass a file_id as string to send a video note that exists on the Telegram servers, + pass a file path as string to upload a new video note that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. Sending video notes by a URL is currently unsupported. file_ref (``str``, *optional*): @@ -70,7 +71,7 @@ class SendVideoNote(BaseClient): length (``int``, *optional*): Video width and height. - thumb (``str``, *optional*): + thumb (``str`` | ``BinaryIO``, *optional*): Thumbnail of the video sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. @@ -128,11 +129,30 @@ class SendVideoNote(BaseClient): file = None try: - if os.path.isfile(video_note): + if isinstance(video_note, str): + if os.path.isfile(video_note): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(video_note, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(video_note) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + round_message=True, + duration=duration, + w=length, + h=length + ) + ] + ) + else: + media = utils.get_input_media_from_file_id(video_note, file_ref, 13) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video_note) or "video/mp4", + mime_type=self.guess_mime_type(video_note.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -144,8 +164,6 @@ class SendVideoNote(BaseClient): ) ] ) - else: - media = utils.get_input_media_from_file_id(video_note, file_ref, 13) while True: try: diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 23492f53..f99b4236 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,7 +30,7 @@ class SendVoice(BaseClient): def send_voice( self, chat_id: Union[int, str], - voice: str, + voice: Union[str, BinaryIO], file_ref=None, caption: str = "", parse_mode: Union[str, None] = object, @@ -55,11 +55,12 @@ class SendVoice(BaseClient): 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). - voice (``str``): + voice (``str`` | ``BinaryIO``): Audio file to send. Pass a file_id as string to send an audio that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get an audio from the Internet, or - pass a file path as string to upload a new audio that exists on your local machine. + pass an HTTP URL as a string for Telegram to get an audio from the Internet, + pass a file path as string to upload a new audio that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -133,10 +134,29 @@ class SendVoice(BaseClient): file = None try: - if os.path.isfile(voice): + if isinstance(voice, str): + if os.path.isfile(voice): + file = self.save_file(voice, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(voice) or "audio/mpeg", + file=file, + attributes=[ + types.DocumentAttributeAudio( + voice=True, + duration=duration + ) + ] + ) + elif re.match("^https?://", voice): + media = types.InputMediaDocumentExternal( + url=voice + ) + else: + media = utils.get_input_media_from_file_id(voice, file_ref, 3) + else: file = self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(voice) or "audio/mpeg", + mime_type=self.guess_mime_type(voice.name) or "audio/mpeg", file=file, attributes=[ types.DocumentAttributeAudio( @@ -145,12 +165,6 @@ class SendVoice(BaseClient): ) ] ) - elif re.match("^https?://", voice): - media = types.InputMediaDocumentExternal( - url=voice - ) - else: - media = utils.get_input_media_from_file_id(voice, file_ref, 3) while True: try: