diff --git a/telethon/client/chats.py b/telethon/client/chats.py index 08242f0e..bad8fc4b 100644 --- a/telethon/client/chats.py +++ b/telethon/client/chats.py @@ -108,8 +108,8 @@ class _ParticipantsIter(RequestIter): filter = filter() entity = await self.client.get_input_entity(entity) - if search and (filter - or not isinstance(entity, types.InputPeerChannel)): + ty = helpers._entity_type(entity) + if search and (filter or ty != helpers._EntityType.CHANNEL): # We need to 'search' ourselves unless we have a PeerChannel search = search.casefold() @@ -123,7 +123,7 @@ class _ParticipantsIter(RequestIter): # Only used for channels, but we should always set the attribute self.requests = [] - if isinstance(entity, types.InputPeerChannel): + if ty == helpers._EntityType.CHANNEL: self.total = (await self.client( functions.channels.GetFullChannelRequest(entity) )).full_chat.participants_count @@ -149,7 +149,7 @@ class _ParticipantsIter(RequestIter): hash=0 )) - elif isinstance(entity, types.InputPeerChat): + elif ty == helpers._EntityType.CHAT: full = await self.client( functions.messages.GetFullChatRequest(entity.chat_id)) if not isinstance( @@ -281,7 +281,8 @@ class _ProfilePhotoIter(RequestIter): self, entity, offset, max_id ): entity = await self.client.get_input_entity(entity) - if isinstance(entity, (types.InputPeerUser, types.InputPeerSelf)): + ty = helpers._entity_type(entity) + if ty == helpers._EntityType.USER: self.request = functions.photos.GetUserPhotosRequest( entity, offset=offset, @@ -864,7 +865,8 @@ class ChatMethods: """ entity = await self.get_input_entity(entity) user = await self.get_input_entity(user) - if not isinstance(user, (types.InputPeerUser, types.InputPeerSelf)): + ty = helpers._entity_type(user) + if ty != helpers._EntityType.USER: raise ValueError('You must pass a user entity') perm_names = ( @@ -872,7 +874,8 @@ class ChatMethods: 'ban_users', 'invite_users', 'pin_messages', 'add_admins' ) - if isinstance(entity, types.InputPeerChannel): + ty = helpers._entity_type(entity) + if ty == helpers._EntityType.CHANNEL: # If we try to set these permissions in a megagroup, we # would get a RIGHT_FORBIDDEN. However, it makes sense # that an admin can post messages, so we want to avoid the error @@ -894,7 +897,7 @@ class ChatMethods: for name in perm_names }), rank=title or '')) - elif isinstance(entity, types.InputPeerChat): + elif ty == helpers._EntityType.CHAT: # If the user passed any permission in a small # group chat, they must be a full admin to have it. if is_admin is None: @@ -1015,7 +1018,8 @@ class ChatMethods: await client.edit_permissions(chat, user) """ entity = await self.get_input_entity(entity) - if not isinstance(entity, types.InputPeerChannel): + ty = helpers._entity_type(entity) + if ty != helpers._EntityType.CHANNEL: raise ValueError('You must pass either a channel or a supergroup') rights = types.ChatBannedRights( @@ -1040,12 +1044,13 @@ class ChatMethods: )) user = await self.get_input_entity(user) + ty = helpers._entity_type(user) + if ty != helpers._EntityType.USER: + raise ValueError('You must pass a user entity') + if isinstance(user, types.InputPeerSelf): raise ValueError('You cannot restrict yourself') - if not isinstance(user, types.InputPeerUser): - raise ValueError('You must pass a user entity') - return await self(functions.channels.EditBannedRequest( channel=entity, user_id=user, @@ -1086,12 +1091,13 @@ class ChatMethods: """ entity = await self.get_input_entity(entity) user = await self.get_input_entity(user) - if not isinstance(user, (types.InputPeerUser, types.InputPeerSelf)): + if helpers._entity_type(user) != helpers._EntityType.USER: raise ValueError('You must pass a user entity') - if isinstance(entity, types.InputPeerChat): + ty = helpers._entity_type(entity) + if ty == helpers._EntityType.CHAT: await self(functions.messages.DeleteChatUserRequest(entity.chat_id, user)) - elif isinstance(entity, types.InputPeerChannel): + elif ty == helpers._EntityType.CHANNEL: if isinstance(user, types.InputPeerSelf): await self(functions.channels.LeaveChannelRequest(entity)) else: diff --git a/telethon/client/dialogs.py b/telethon/client/dialogs.py index 25bec5dd..4ddc726d 100644 --- a/telethon/client/dialogs.py +++ b/telethon/client/dialogs.py @@ -3,7 +3,7 @@ import inspect import itertools import typing -from .. import utils, hints +from .. import helpers, utils, hints from ..requestiter import RequestIter from ..tl import types, functions, custom @@ -436,10 +436,11 @@ class DialogMethods: await client.delete_dialog('username') """ entity = await self.get_input_entity(entity) - if isinstance(entity, types.InputPeerChannel): + ty = helpers._entity_type(entity) + if ty == helpers._EntityType.CHANNEL: return await self(functions.channels.LeaveChannelRequest(entity)) - if isinstance(entity, types.InputPeerChat): + if ty == helpers._EntityType.CHAT: result = await self(functions.messages.DeleteChatUserRequest( entity.chat_id, types.InputUserSelf())) else: diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index 48dad3b7..c6e13288 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -257,7 +257,8 @@ class DownloadMethods: # See issue #500, Android app fails as of v4.6.0 (1155). # The fix seems to be using the full channel chat photo. ie = await self.get_input_entity(entity) - if isinstance(ie, types.InputPeerChannel): + ty = helpers._entity_type(ie) + if ty == helpers._EntityType.CHANNEL: full = await self(functions.channels.GetFullChannelRequest(ie)) return await self._download_photo( full.full_chat.chat_photo, file, diff --git a/telethon/client/messageparse.py b/telethon/client/messageparse.py index 3a612c68..290e98cf 100644 --- a/telethon/client/messageparse.py +++ b/telethon/client/messageparse.py @@ -2,7 +2,7 @@ import itertools import re import typing -from .. import utils +from .. import helpers, utils from ..tl import types if typing.TYPE_CHECKING: @@ -134,7 +134,7 @@ class MessageParseMethods: id_to_message[update.message.id] = update.message elif (isinstance(update, types.UpdateEditMessage) - and not isinstance(request.peer, types.InputPeerChannel)): + and helpers._entity_type(request.peer) != helpers._EntityType.CHANNEL): if request.id == update.message.id: update.message._finish_init(self, entities, input_chat) return update.message diff --git a/telethon/client/messages.py b/telethon/client/messages.py index 97d11703..81be40d2 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -2,7 +2,7 @@ import inspect import itertools import typing -from .. import utils, errors, hints +from .. import helpers, utils, errors, hints from ..requestiter import RequestIter from ..tl import types, functions @@ -57,8 +57,8 @@ class _MessagesIter(RequestIter): if from_user: from_user = await self.client.get_input_entity(from_user) - if not isinstance(from_user, ( - types.InputPeerUser, types.InputPeerSelf)): + ty = helpers._entity_type(from_user) + if ty != helpers._EntityType.USER: from_user = None # Ignore from_user unless it's a user if from_user: @@ -86,8 +86,8 @@ class _MessagesIter(RequestIter): filter = types.InputMessagesFilterEmpty() # Telegram completely ignores `from_id` in private chats - if isinstance( - self.entity, (types.InputPeerUser, types.InputPeerSelf)): + ty = helpers._entity_type(self.entity) + if ty == helpers._EntityType.USER: # Don't bother sending `from_user` (it's ignored anyway), # but keep `from_id` defined above to check it locally. from_user = None @@ -246,6 +246,7 @@ class _IDsIter(RequestIter): self._ids = list(reversed(ids)) if self.reverse else ids self._offset = 0 self._entity = (await self.client.get_input_entity(entity)) if entity else None + self._ty = helpers._EntityType(self._entity) if self._entity else None # 30s flood wait every 300 messages (3 requests of 100 each, 30 of 10, etc.) if self.wait_time is None: @@ -259,7 +260,7 @@ class _IDsIter(RequestIter): self._offset += _MAX_CHUNK_SIZE from_id = None # By default, no need to validate from_id - if isinstance(self._entity, (types.InputChannel, types.InputPeerChannel)): + if self._ty == helpers._EntityType.CHANNEL: try: r = await self.client( functions.channels.GetMessagesRequest(self._entity, ids)) @@ -1108,7 +1109,7 @@ class MessageMethods: ) entity = await self.get_input_entity(entity) if entity else None - if isinstance(entity, types.InputPeerChannel): + if helpers._entity_type(entity) == helpers._EntityType.CHANNEL: return await self([functions.channels.DeleteMessagesRequest( entity, list(c)) for c in utils.chunks(message_ids)]) else: @@ -1181,7 +1182,7 @@ class MessageMethods: return True if max_id is not None: - if isinstance(entity, types.InputPeerChannel): + if helpers._entity_type(entity) == helpers._EntityType.CHANNEL: return await self(functions.channels.ReadHistoryRequest( utils.get_input_channel(entity), max_id=max_id)) else: diff --git a/telethon/client/users.py b/telethon/client/users.py index 15fcf771..0db2705b 100644 --- a/telethon/client/users.py +++ b/telethon/client/users.py @@ -4,7 +4,7 @@ import itertools import time import typing -from .. import errors, utils, hints +from .. import errors, helpers, utils, hints from ..errors import MultiError, RPCError from ..helpers import retry_range from ..tl import TLRequest, types, functions @@ -258,12 +258,20 @@ class UserMethods: else: inputs.append(await self.get_input_entity(x)) - users = [x for x in inputs - if isinstance(x, (types.InputPeerUser, types.InputPeerSelf))] - chats = [x.chat_id for x in inputs - if isinstance(x, types.InputPeerChat)] - channels = [x for x in inputs - if isinstance(x, types.InputPeerChannel)] + lists = { + helpers._EntityType.USER: [], + helpers._EntityType.CHAT: [], + helpers._EntityType.CHANNEL: [], + } + for x in inputs: + try: + lists[helpers._entity_type(x)].append(x) + except TypeError: + pass + + users = lists[helpers._EntityType.USER] + chats = lists[helpers._EntityType.CHAT] + channels = lists[helpers._EntityType.CHANNEL] if users: # GetUsersRequest has a limit of 200 per call tmp = [] diff --git a/telethon/helpers.py b/telethon/helpers.py index 0fdafbf9..190da04a 100644 --- a/telethon/helpers.py +++ b/telethon/helpers.py @@ -1,10 +1,17 @@ """Various helpers not related to the Telegram API itself""" import asyncio +import enum import os import struct from hashlib import sha1 +class _EntityType(enum.Enum): + USER = 0 + CHAT = 1 + CHANNEL = 2 + + # region Multiple utilities @@ -131,6 +138,41 @@ def _sync_exit(self, *args): return loop.run_until_complete(self.__aexit__(*args)) +def _entity_type(entity): + # This could be a `utils` method that just ran a few `isinstance` on + # `utils.get_peer(...)`'s result. However, there are *a lot* of auto + # casts going on, plenty of calls and temporary short-lived objects. + # + # So we just check if a string is in the class name. + # Still, assert that it's the right type to not return false results. + try: + if entity.SUBCLASS_OF_ID not in ( + 0x2d45687, # crc32(b'Peer') + 0xc91c90b6, # crc32(b'InputPeer') + 0xe669bf46, # crc32(b'InputUser') + 0x40f202fd, # crc32(b'InputChannel') + 0x2da17977, # crc32(b'User') + 0xc5af5d94, # crc32(b'Chat') + 0x1f4661b9, # crc32(b'UserFull') + 0xd49a2697, # crc32(b'ChatFull') + ): + raise TypeError('{} does not have any entity type'.format(entity)) + except AttributeError: + raise TypeError('{} is not a TLObject, cannot determine entity type'.format(entity)) + + name = entity.__class__.__name__ + if 'User' in name: + return _EntityType.USER + elif 'Chat' in name: + return _EntityType.CHAT + elif 'Channel' in name: + return _EntityType.CHANNEL + elif 'Self' in name: + return _EntityType.USER + + # 'Empty' in name or not found, we don't care, not a valid entity. + raise TypeError('{} does not have any entity type'.format(entity)) + # endregion # region Cryptographic related utils diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index b94f9b19..a49b0d36 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -149,6 +149,7 @@ MESSAGE_IDS_EMPTY,400,No message ids were provided MESSAGE_ID_INVALID,400,"The specified message ID is invalid or you can't do that operation on such message" MESSAGE_NOT_MODIFIED,400,Content of the message was not modified MESSAGE_TOO_LONG,400,Message was too long. Current maximum length is 4096 UTF-8 characters +MSG_ID_INVALID,400,The message ID used in the peer was invalid MSG_WAIT_FAILED,400,A waiting call returned an error MT_SEND_QUEUE_TOO_LONG,500, NEED_CHAT_INVALID,500,The provided chat is invalid diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index 3ecd2356..bedca42d 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -238,7 +238,7 @@ messages.sendEncryptedFile,user,MSG_WAIT_FAILED messages.sendEncryptedService,user,DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED USER_IS_BLOCKED messages.sendInlineBotResult,user,CHAT_SEND_INLINE_FORBIDDEN CHAT_WRITE_FORBIDDEN INLINE_RESULT_EXPIRED PEER_ID_INVALID QUERY_ID_EMPTY SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH WEBPAGE_CURL_FAILED WEBPAGE_MEDIA_EMPTY messages.sendMedia,both,BOT_PAYMENTS_DISABLED BOT_POLLS_DISABLED CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID INPUT_USER_DEACTIVATED MEDIA_CAPTION_TOO_LONG MEDIA_EMPTY PAYMENT_PROVIDER_INVALID PEER_ID_INVALID PHOTO_EXT_INVALID PHOTO_INVALID_DIMENSIONS PHOTO_SAVE_FILE_INVALID POLL_OPTION_DUPLICATE RANDOM_ID_DUPLICATE SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH STORAGE_CHECK_FAILED Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT VIDEO_CONTENT_TYPE_INVALID WEBPAGE_CURL_FAILED WEBPAGE_MEDIA_EMPTY -messages.sendMessage,both,AUTH_KEY_DUPLICATED BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_RESTRICTED CHAT_WRITE_FORBIDDEN ENTITIES_TOO_LONG ENTITY_MENTION_USER_INVALID INPUT_USER_DEACTIVATED MESSAGE_EMPTY MESSAGE_TOO_LONG PEER_ID_INVALID RANDOM_ID_DUPLICATE REPLY_MARKUP_INVALID REPLY_MARKUP_TOO_LONG SCHEDULE_BOT_NOT_ALLOWED SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER +messages.sendMessage,both,AUTH_KEY_DUPLICATED BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_RESTRICTED CHAT_WRITE_FORBIDDEN ENTITIES_TOO_LONG ENTITY_MENTION_USER_INVALID INPUT_USER_DEACTIVATED MESSAGE_EMPTY MESSAGE_TOO_LONG MSG_ID_INVALID PEER_ID_INVALID RANDOM_ID_DUPLICATE REPLY_MARKUP_INVALID REPLY_MARKUP_TOO_LONG SCHEDULE_BOT_NOT_ALLOWED SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER messages.sendMultiMedia,both,SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH messages.sendReaction,User,REACTION_INVALID messages.sendVote,user,