From 10251f978259ebf236f209a6b3657cf6053ff5ad Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 8 May 2019 18:41:40 +0200 Subject: [PATCH] Create a new Message.file property (#1168) --- readthedocs/extra/reference.rst | 26 ++++++ readthedocs/telethon.tl.custom.rst | 8 ++ telethon/tl/custom/file.py | 128 +++++++++++++++++++++++++++++ telethon/tl/custom/message.py | 23 ++++++ telethon/utils.py | 8 ++ 5 files changed, 193 insertions(+) create mode 100644 telethon/tl/custom/file.py diff --git a/readthedocs/extra/reference.rst b/readthedocs/extra/reference.rst index 3d73861c..34e093f8 100644 --- a/readthedocs/extra/reference.rst +++ b/readthedocs/extra/reference.rst @@ -216,6 +216,7 @@ Properties forward buttons button_count + file photo document web_preview @@ -255,6 +256,31 @@ Methods get_buttons +File +==== + +The `File ` type is a wrapper object +returned by `Message.file `, +and you can use it to easily access a document's attributes, such as +its name, bot-API style file ID, etc. + +.. currentmodule:: telethon.tl.custom.file.File + +.. autosummary:: + :nosignatures: + + id + name + width + height + size + duration + title + performer + emoji + sticker_set + + Conversation ============ diff --git a/readthedocs/telethon.tl.custom.rst b/readthedocs/telethon.tl.custom.rst index 3187e315..f005f209 100644 --- a/readthedocs/telethon.tl.custom.rst +++ b/readthedocs/telethon.tl.custom.rst @@ -18,6 +18,14 @@ telethon\.tl\.custom\.dialog module :undoc-members: :show-inheritance: +telethon\.tl\.custom\.file module +--------------------------------- + +.. automodule:: telethon.tl.custom.file + :members: + :undoc-members: + :show-inheritance: + telethon\.tl\.custom\.message module ------------------------------------ diff --git a/telethon/tl/custom/file.py b/telethon/tl/custom/file.py new file mode 100644 index 00000000..21e99d3e --- /dev/null +++ b/telethon/tl/custom/file.py @@ -0,0 +1,128 @@ +import mimetypes + +from ... import utils +from ...tl import types + + +class File: + """ + Convenience class over media like photos or documents, which + supports accessing the attributes in a more convenient way. + + If any of the attributes are not present in the current media, + the properties will be ``None``. + + The original media is available through the ``media`` attribute. + """ + def __init__(self, media): + self.media = media + + @property + def id(self): + """ + The bot-API style ``file_id`` representing this file. + """ + return utils.pack_bot_file_id(self.media) + + @property + def name(self): + """ + The file name of this document. + """ + return self._from_attr(types.DocumentAttributeFilename, 'file_name') + + @property + def ext(self): + """ + The extension from the mime type of this file. + """ + return mimetypes.guess_extension(self.mime_type) + + @property + def mime_type(self): + """ + The mime-type of this file. + """ + if isinstance(self.media, types.Photo): + return 'image/jpeg' + elif isinstance(self.media, types.Document): + return self.media.mime_type + + @property + def width(self): + """ + The width in pixels of this media if it's a photo or a video. + """ + return self._from_attr(( + types.DocumentAttributeImageSize, types.DocumentAttributeVideo), 'w') + + @property + def height(self): + """ + The height in pixels of this media if it's a photo or a video. + """ + return self._from_attr(( + types.DocumentAttributeImageSize, types.DocumentAttributeVideo), 'h') + + @property + def duration(self): + """ + The duration in seconds of the audio or video. + """ + return self._from_attr(( + types.DocumentAttributeAudio, types.DocumentAttributeVideo), 'duration') + + @property + def title(self): + """ + The title of the song. + """ + return self._from_attr(types.DocumentAttributeAudio, 'title') + + @property + def performer(self): + """ + The performer of the song. + """ + return self._from_attr(types.DocumentAttributeAudio, 'performer') + + @property + def emoji(self): + """ + A string with all emoji that represent the current sticker. + """ + return self._from_attr(types.DocumentAttributeSticker, 'alt') + + @property + def sticker_set(self): + """ + The :tl:`InputStickerSet` to which the sticker file belongs. + """ + return self._from_attr(types.DocumentAttributeSticker, 'stickerset') + + @property + def size(self): + """ + The size in bytes of this file. + """ + if isinstance(self.media, types.Photo): + return self._size_for(self.media.sizes[-1]) + elif isinstance(self.media, types.Document): + return self.media.size + + @staticmethod + def _size_for(kind): + if isinstance(kind, types.PhotoSize): + return kind.size + elif isinstance(kind, types.PhotoStrippedSize): + return utils._stripped_real_length(kind.bytes) + elif isinstance(kind, types.PhotoCachedSize): + return len(kind.bytes) + # elif isinstance(kind, types.PhotoSizeEmpty): + return 0 + + def _from_attr(self, cls, field): + if isinstance(self.media, types.Document): + for attr in self.media.attributes: + if isinstance(attr, cls): + return getattr(attr, field, None) diff --git a/telethon/tl/custom/message.py b/telethon/tl/custom/message.py index f105f615..d32129d6 100644 --- a/telethon/tl/custom/message.py +++ b/telethon/tl/custom/message.py @@ -3,9 +3,11 @@ from .chatgetter import ChatGetter from .sendergetter import SenderGetter from .messagebutton import MessageButton from .forward import Forward +from .file import File from .. import TLObject, types, functions from ... import utils, errors + # TODO Figure out a way to have the code generator error on missing fields # Maybe parsing the init function alone if that's possible. class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): @@ -174,8 +176,10 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): self.action = action # Convenient storage for custom functions + # TODO This is becoming a bit of bloat self._client = None self._text = None + self._file = None self._reply_message = None self._buttons = None self._buttons_flat = None @@ -370,6 +374,25 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): return self._buttons_count + @property + def file(self): + """ + Returns a `File ` wrapping the + `photo` or `document` in this message. If the media type is different + (polls, games, none, etc.), this property will be ``None``. + + This instance lets you easily access other properties, such as + `file.id `, + `file.name `, + etc., without having to manually inspect the ``document.attributes``. + """ + if not self._file: + media = self.photo or self.document + if media: + self._file = File(media) + + return self._file + @property def photo(self): """ diff --git a/telethon/utils.py b/telethon/utils.py index 1713c9e3..0a4f09bb 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -1162,6 +1162,7 @@ def stripped_photo_to_jpg(stripped): Ported from https://github.com/telegramdesktop/tdesktop/blob/bec39d89e19670eb436dc794a8f20b657cb87c71/Telegram/SourceFiles/ui/image/image.cpp#L225 """ + # NOTE: Changes here should update _stripped_real_length if len(stripped) < 3 or stripped[0] != 1: return stripped @@ -1170,3 +1171,10 @@ def stripped_photo_to_jpg(stripped): header[164] = stripped[1] header[166] = stripped[2] return bytes(header) + stripped[3:] + footer + + +def _stripped_real_length(stripped): + if len(stripped) < 3 or stripped[0] != 1: + return len(stripped) + + return len(stripped) + 622