diff --git a/readthedocs/quick-references/objects-reference.rst b/readthedocs/quick-references/objects-reference.rst index b7ee3282..51ed4607 100644 --- a/readthedocs/quick-references/objects-reference.rst +++ b/readthedocs/quick-references/objects-reference.rst @@ -348,4 +348,6 @@ library a lot easier. Only the interesting ones will be listed here. get_inner_text get_peer_id resolve_id + pack_bot_file_id + resolve_bot_file_id resolve_invite_link diff --git a/telethon/client/chats.py b/telethon/client/chats.py index ef61d0d3..1137544b 100644 --- a/telethon/client/chats.py +++ b/telethon/client/chats.py @@ -484,38 +484,6 @@ class ChatMethods: get_participants.__signature__ = inspect.signature(iter_participants) - async def get_participant( - self: 'TelegramClient', - chat: 'hints.EntityLike', - entity: 'hints.EntityLike' - ): - """ - Get One Participant of Specific Channel or Chat. - - Arguments - - chat (`entity`): - The channel/chat entity from where to Get Participant. - - entity (`entity`): - Username/UserId of person to look for. - - Raises - UserNotParticipantError - User is Not Participant of Chat/Channel. - - Returns - The resulting :tl:`ChannelParticipant` object. - - Example - .. code-block:: python - await client.get_participant(chat, user) - """ - chat = await self.get_input_entity(chat) - user = await self.get_input_entity(entity) - return await self(functions.channels.GetParticipantRequest( - channel=chat, - participant=user) - ) def iter_admin_log( self: 'TelegramClient', @@ -1372,130 +1340,4 @@ class ChatMethods: finally: await self._return_exported_sender(sender) - async def modify_groupcall( - self: 'TelegramClient', - method: str, - entity: 'hints.EntityLike', - schedule: 'hints.DateLike' = None, - title: str = None, - reset_invite_hash: bool = None, - join_muted: bool = None - ): - """ - Stuff to do with Group Calls, Possible for Administrator and - Creator. - - will raise ValueError, if `method` is None or Invalid. - - Arguments - method (`str`): - Any of `create`, `discard`, `start_schedule` and - `edit_title` will work. Check Examples to know more Clearly. - - entity (`int` | `str`): - Username/ID of Channel or Chat, where to Modify Group Call. - - schedule (`hints.Datelike`, `optional`): - Used to Schedule the GroupCall. - - title (`str`, `optional`): - Edits the Group call Title. It should be used with - `edit_title` as method parameter. - - reset_invite_hash (`bool`, `optional`): - Reset the Invite Hash of Group Call of Specific Channel or - Chat. - - join_muted (`bool`, `optional`): - Whether the New Group Call Participant should be Muted or - Not. - - Method Parameter : - - `create` : `Create a Group Call or Schedule It.` - - `discard` : `Stop a Group Call.` - - `edit_title` : `Edit title of Group Call.` - - `edit_perms` : `Edit Permissions of Group Call.` - - `start_schedule` : `Start a Group Call which was Scheduled.` - - Returns - The resulting :tl:`Updates` object. - - Raises - ChatAdminRequiredError - You Should be Admin, in order to - Modify Group Call. - - Example - .. code-block:: python - - # Starting a Group Call - await client.modify_groupcall("create", -100123456789) - - # Scheduling a Group Call, within 5-Minutes - from datetime import timedelta - schedule_for = timedelta(minutes=5) - await client.modify_groupcall( - "create", - schedule=schedule_for - ) - - # Editing a Group Call Title - new_title = "Having Fun with Telethon" - await client.modify_groupcall( - "edit_title", - title=new_title - ) - - # Stopping/Closing a Group Call - await client.modify_groupcall("discard", -100123456789) - - # Force Start Scheduled Group Call - await client.modify_groupcall("start_schedule", chat) - - # Toggle Group Call Setting - await client.modify_groupcall( - "edit_perms", - reset_invite_hash=True, - join_muted=True) # Mute New Group call Participants - """ - if not method: - raise ValueError("Method Cant be None.") - - if method == "create": - return (await self( - functions.phone.CreateGroupCallRequest( - peer=entity, - schedule_date=schedule) - )).updates[0] - try: - Call = await self( - functions.messages.GetFullChatRequest(entity)) - except errors.rpcerrorlist.ChatIdInvalidError: - Call = await self( - functions.channels.GetFullChannelRequest(entity)) - - Call = Call.full_chat.call - - if method == "edit_title": - return await self(functions.phone.EditGroupCallTitleRequest( - Call, title=title if title else "")) - - elif method == "edit_perms": - return await self( - functions.phone.ToggleGroupCallSettingsRequest( - reset_invite_hash=reset_invite_hash, - call=Call, - join_muted=join_muted)) - - elif method == "discard": - return await self( - functions.phone.DiscardGroupCallRequest(Call)) - - elif method == "start_schedule": - return await self( - functions.phone.StartScheduledGroupCallRequest(Call)) - - else: - raise ValueError( - "Invalid Method Used while using Modifying GroupCall") - # endregion diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index be5229d0..62ba6332 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -395,6 +395,9 @@ class DownloadMethods: date = datetime.datetime.now() media = message + if isinstance(media, str): + media = utils.resolve_bot_file_id(media) + if isinstance(media, types.MessageService): if isinstance(message.action, types.MessageActionChatEditPhoto): diff --git a/telethon/client/messages.py b/telethon/client/messages.py index b09b1144..6bdb7f5d 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -784,7 +784,7 @@ class MessageMethods: return await self.send_file( entity, file, caption=message, reply_to=reply_to, attributes=attributes, parse_mode=parse_mode, - force_document=force_document,thumb=thumb, + force_document=force_document, thumb=thumb, buttons=buttons, clear_draft=clear_draft, silent=silent, schedule=schedule, supports_streaming=supports_streaming, formatting_entities=formatting_entities, diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index 473a87cd..2e1200c6 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -152,6 +152,9 @@ class UploadMethods: send the file as "external" media, and Telegram is the one that will fetch the media and send it. . + * A Bot API-like ``file_id``. You can convert previously + sent media to file IDs for later reusing with + `telethon.utils.pack_bot_file_id`. * A handle to an existing file (for example, if you sent a message with media before, you can use its ``message.media`` @@ -696,6 +699,10 @@ class UploadMethods: media = types.InputMediaPhotoExternal(file) else: media = types.InputMediaDocumentExternal(file) + else: + bot_file = utils.resolve_bot_file_id(file) + if bot_file: + media = utils.get_input_media(bot_file) if media: pass # Already have media, don't check the rest diff --git a/telethon/tl/custom/file.py b/telethon/tl/custom/file.py index 7758a5c5..210eb53d 100644 --- a/telethon/tl/custom/file.py +++ b/telethon/tl/custom/file.py @@ -18,6 +18,20 @@ class File: def __init__(self, media): self.media = media + @property + def id(self): + """ + The bot-API style ``file_id`` representing this file. + + .. note:: + + This file ID may not work under user accounts, + but should still be usable by bot accounts. + + You can, however, still use it to identify + a file in for example a database. + """ + return utils.pack_bot_file_id(self.media) @property def name(self): diff --git a/telethon/utils.py b/telethon/utils.py index 0dbb9afd..4c5c2e13 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -855,6 +855,8 @@ def is_image(file): match = re.match(r'\.(png|jpe?g)', _get_extension(file), re.IGNORECASE) if match: return True + else: + return isinstance(resolve_bot_file_id(file), types.Photo) def is_gif(file): @@ -1113,6 +1115,160 @@ def _encode_telegram_base64(string): return None # not valid base64, not valid ascii, not a string +def resolve_bot_file_id(file_id): + """ + Given a Bot API-style `file_id `, + returns the media it represents. If the `file_id ` + is not valid, `None` is returned instead. + + Note that the `file_id ` does not have information + such as image dimensions or file size, so these will be zero if present. + + For thumbnails, the photo ID and hash will always be zero. + """ + data = _rle_decode(_decode_telegram_base64(file_id)) + if not data: + return None + + # This isn't officially documented anywhere, but + # we assume the last byte is some kind of "version". + data, version = data[:-1], data[-1] + if version not in (2, 4): + return None + + if (version == 2 and len(data) == 24) or (version == 4 and len(data) == 25): + if version == 2: + file_type, dc_id, media_id, access_hash = struct.unpack('