From d58fa32827143e7b19f57e2d705994a8587c8a7c Mon Sep 17 00:00:00 2001 From: EntireMusic Date: Sat, 5 Oct 2024 11:34:13 +0300 Subject: [PATCH] Feature: Added the ability to apply formatted_entities when sending files. --- telethon/client/uploads.py | 59 ++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index 7245c098..7d99c1e2 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -6,11 +6,13 @@ import pathlib import re import typing from io import BytesIO +from typing import List, Any, Dict from ..crypto import AES from .. import utils, helpers, hints from ..tl import types, functions, custom +from ..tl.types import MessageEmpty, Message, MessageService try: import PIL @@ -18,7 +20,6 @@ try: except ImportError: PIL = None - if typing.TYPE_CHECKING: from .telegramclient import TelegramClient @@ -119,7 +120,11 @@ class UploadMethods: thumb: 'hints.FileLike' = None, allow_cache: bool = True, parse_mode: str = (), - formatting_entities: typing.Optional[typing.List[types.TypeMessageEntity]] = None, + formatting_entities: typing.Optional[ + typing.Union[ + typing.List[types.TypeMessageEntity], typing.List[typing.List[types.TypeMessageEntity]] + ] + ] = None, voice_note: bool = False, video_note: bool = False, buttons: typing.Optional['hints.MarkupLike'] = None, @@ -130,7 +135,7 @@ class UploadMethods: comment_to: 'typing.Union[int, types.Message]' = None, ttl: int = None, nosound_video: bool = None, - **kwargs) -> 'types.Message': + **kwargs) -> list[Any] | Any: """ Sends message with the given file to the specified entity. @@ -243,7 +248,11 @@ class UploadMethods: default. formatting_entities (`list`, optional): - A list of message formatting entities. When provided, the ``parse_mode`` is ignored. + Optional formatting entities for the sent media message. When sending an album, + `formatting_entities` can be a list of lists, where each inner list contains + `types.TypeMessageEntity`. Each inner list will be assigned to the corresponding + file in a pairwise manner with the caption. If provided, the ``parse_mode`` + parameter will be ignored. voice_note (`bool`, optional): If `True` the audio will be sent as a voice note. @@ -365,6 +374,9 @@ class UploadMethods: if not caption: caption = '' + if not formatting_entities: + formatting_entities = [] + entity = await self.get_input_entity(entity) if comment_to is not None: entity, reply_to = await self._get_comment_data(entity, comment_to) @@ -384,10 +396,22 @@ class UploadMethods: else: captions = [caption] + # Check that formatting_entities list is valid + if all(utils.is_list_like(obj) for obj in formatting_entities): + formatting_entities = formatting_entities + elif utils.is_list_like(formatting_entities): + formatting_entities = [formatting_entities] + else: + raise TypeError('The formatting_entities argument must be a list or a sequence of lists') + + # Check that all entities in all lists are of the correct type + if not all(isinstance(ent, types.TypeMessageEntity) for sublist in formatting_entities for ent in sublist): + raise TypeError('All entities must be instances of ') + result = [] while file: result += await self._send_album( - entity, file[:10], caption=captions[:10], + entity, file[:10], caption=captions[:10], formatting_entities=formatting_entities[:10], progress_callback=used_callback, reply_to=reply_to, parse_mode=parse_mode, silent=silent, schedule=schedule, supports_streaming=supports_streaming, clear_draft=clear_draft, @@ -395,6 +419,7 @@ class UploadMethods: ) file = file[10:] captions = captions[10:] + formatting_entities = formatting_entities[10:] sent_count += 10 return result @@ -409,7 +434,7 @@ class UploadMethods: file, force_document=force_document, file_size=file_size, progress_callback=progress_callback, - attributes=attributes, allow_cache=allow_cache, thumb=thumb, + attributes=attributes, allow_cache=allow_cache, thumb=thumb, voice_note=voice_note, video_note=video_note, supports_streaming=supports_streaming, ttl=ttl, nosound_video=nosound_video, @@ -430,6 +455,7 @@ class UploadMethods: return self._get_response_message(request, await self(request), entity) async def _send_album(self: 'TelegramClient', entity, files, caption='', + formatting_entities=None, progress_callback=None, reply_to=None, parse_mode=(), silent=None, schedule=None, supports_streaming=None, clear_draft=None, @@ -441,16 +467,25 @@ class UploadMethods: # cache only makes a difference for documents where the user may # want the attributes used on them to change. # - # In theory documents can be sent inside the albums but they appear + # In theory documents can be sent inside the albums, but they appear # as different messages (not inside the album), and the logic to set # the attributes/avoid cache is already written in .send_file(). entity = await self.get_input_entity(entity) if not utils.is_list_like(caption): caption = (caption,) + if not all(isinstance(obj, list) for obj in formatting_entities): + formatting_entities = (formatting_entities,) captions = [] - for c in reversed(caption): # Pop from the end (so reverse) - captions.append(await self._parse_message_text(c or '', parse_mode)) + # If the formatting_entities argument is provided, we don't use parse_mode + if formatting_entities: + # Pop from the end (so reverse) + capt_with_ent = itertools.zip_longest(reversed(caption), reversed(formatting_entities), fillvalue=None) + for msg_caption, msg_entities in capt_with_ent: + captions.append((msg_caption, msg_entities)) + else: + for c in reversed(caption): # Pop from the end (so reverse) + captions.append(await self._parse_message_text(c or '', parse_mode)) reply_to = utils.get_message_id(reply_to) @@ -482,7 +517,7 @@ class UploadMethods: )) fm = utils.get_input_media( - r.document, supports_streaming=supports_streaming) + r.document, supports_streaming=supports_streaming) if captions: caption, msg_entities = captions.pop() @@ -635,7 +670,7 @@ class UploadMethods: part_count = (file_size + part_size - 1) // part_size self._log[__name__].info('Uploading file of %d bytes in %d chunks of %d', - file_size, part_count, part_size) + file_size, part_count, part_size) pos = 0 for part_index in range(part_count): @@ -712,7 +747,7 @@ class UploadMethods: # `aiofiles` do not base `io.IOBase` but do have `read`, so we # just check for the read attribute to see if it's file-like. - if not isinstance(file, (str, bytes, types.InputFile, types.InputFileBig))\ + if not isinstance(file, (str, bytes, types.InputFile, types.InputFileBig)) \ and not hasattr(file, 'read'): # The user may pass a Message containing media (or the media, # or anything similar) that should be treated as a file. Try