From 0814a20ec4105dde9b25f014472c7aad5d9b0f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20J=C3=BCrgens?= Date: Fri, 21 Feb 2020 19:37:24 +0000 Subject: [PATCH 01/94] Fix macOS version parsing (again), bump v1.11.2 #1393 --- telethon/crypto/libssl.py | 2 +- telethon/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/telethon/crypto/libssl.py b/telethon/crypto/libssl.py index 0e759ffe..a11fdbc6 100644 --- a/telethon/crypto/libssl.py +++ b/telethon/crypto/libssl.py @@ -23,7 +23,7 @@ def _find_ssl_lib(): # https://www.shh.sh/2020/01/04/python-abort-trap-6.html if sys.platform == 'darwin': release, _version_info, _machine = platform.mac_ver() - major = release.split('.', maxsplit=1)[0] + ten, major, *minor = release.split('.') # macOS 10.14 "mojave" is the last known major release # to support unversioned libssl.dylib. Anything above # needs specific versions diff --git a/telethon/version.py b/telethon/version.py index 58bf4899..fe6bbf97 100644 --- a/telethon/version.py +++ b/telethon/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '1.11.1' +__version__ = '1.11.2' From 9a86447b6eca810d5c14211b154fc2ec3236cf58 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 24 Feb 2020 13:06:21 +0100 Subject: [PATCH 02/94] Fix get(_input)_users in ChatAction with no service msg --- telethon/events/chataction.py | 21 ++++++-- tests/telethon/crypto/__init__.py | 0 tests/telethon/events/__init__.py | 0 tests/telethon/events/test_chataction.py | 67 ++++++++++++++++++++++++ tests/telethon/extensions/__init__.py | 0 5 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 tests/telethon/crypto/__init__.py create mode 100644 tests/telethon/events/__init__.py create mode 100644 tests/telethon/events/test_chataction.py create mode 100644 tests/telethon/extensions/__init__.py diff --git a/telethon/events/chataction.py b/telethon/events/chataction.py index 7f9acb9d..7867d7e5 100644 --- a/telethon/events/chataction.py +++ b/telethon/events/chataction.py @@ -381,7 +381,8 @@ class ChatAction(EventBuilder): if not self._user_ids: return [] - if self._users is None or len(self._users) != len(self._user_ids): + # Note: we access the property first so that it fills if needed + if (self.users is None or len(self._users) != len(self._user_ids)) and self.action_message: await self.action_message._reload_message() self._users = [ u for u in self.action_message.action_entities @@ -397,19 +398,31 @@ class ChatAction(EventBuilder): if self._input_users is None and self._user_ids: self._input_users = [] for user_id in self._user_ids: + # First try to get it from our entities + try: + self._input_users.append(utils.get_input_peer(self._entities[user_id])) + continue + except (KeyError, TypeError) as e: + pass + + # If missing, try from the entity cache try: self._input_users.append(self._client._entity_cache[user_id]) + continue except KeyError: pass + return self._input_users or [] async def get_input_users(self): """ Returns `input_users` but will make an API call if necessary. """ - self._input_users = None - if self._input_users is None: - await self.action_message._reload_message() + if not self._user_ids: + return [] + + # Note: we access the property first so that it fills if needed + if (self.input_users is None or len(self._input_users) != len(self._user_ids)) and self.action_message: self._input_users = [ utils.get_input_peer(u) for u in self.action_message.action_entities diff --git a/tests/telethon/crypto/__init__.py b/tests/telethon/crypto/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/telethon/events/__init__.py b/tests/telethon/events/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/telethon/events/test_chataction.py b/tests/telethon/events/test_chataction.py new file mode 100644 index 00000000..24f60596 --- /dev/null +++ b/tests/telethon/events/test_chataction.py @@ -0,0 +1,67 @@ +import pytest + +from telethon import TelegramClient, events, types, utils + + +def get_client(): + return TelegramClient(None, 1, '1') + + +def get_user_456(): + return types.User( + id=456, + access_hash=789, + first_name='User 123' + ) + + +@pytest.mark.asyncio +async def test_get_input_users_no_action_message_no_entities(): + event = events.ChatAction.build(types.UpdateChatParticipantDelete( + chat_id=123, + user_id=456, + version=1 + )) + event._set_client(get_client()) + + assert await event.get_input_users() == [] + + +@pytest.mark.asyncio +async def test_get_input_users_no_action_message(): + user = get_user_456() + event = events.ChatAction.build(types.UpdateChatParticipantDelete( + chat_id=123, + user_id=456, + version=1 + )) + event._set_client(get_client()) + event._entities[user.id] = user + + assert await event.get_input_users() == [utils.get_input_peer(user)] + + +@pytest.mark.asyncio +async def test_get_users_no_action_message_no_entities(): + event = events.ChatAction.build(types.UpdateChatParticipantDelete( + chat_id=123, + user_id=456, + version=1 + )) + event._set_client(get_client()) + + assert await event.get_users() == [] + + +@pytest.mark.asyncio +async def test_get_users_no_action_message(): + user = get_user_456() + event = events.ChatAction.build(types.UpdateChatParticipantDelete( + chat_id=123, + user_id=456, + version=1 + )) + event._set_client(get_client()) + event._entities[user.id] = user + + assert await event.get_users() == [user] diff --git a/tests/telethon/extensions/__init__.py b/tests/telethon/extensions/__init__.py new file mode 100644 index 00000000..e69de29b From e9c5e719f19775b7a7e7f7dda5173b355583e7fc Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 24 Feb 2020 13:15:56 +0100 Subject: [PATCH 03/94] Minor docs update, bump v1.11.3 --- telethon/client/dialogs.py | 2 +- telethon/version.py | 2 +- telethon_generator/data/errors.csv | 1 + telethon_generator/data/methods.csv | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/telethon/client/dialogs.py b/telethon/client/dialogs.py index 34173c4c..4fd5f3da 100644 --- a/telethon/client/dialogs.py +++ b/telethon/client/dialogs.py @@ -245,7 +245,7 @@ class DialogMethods: # Getting only archived dialogs (both equivalent) archived = await client.get_dialogs(folder=1) - non_archived = await client.get_dialogs(archived=True) + archived = await client.get_dialogs(archived=True) """ return await self.iter_dialogs(*args, **kwargs).collect() diff --git a/telethon/version.py b/telethon/version.py index fe6bbf97..b7016736 100644 --- a/telethon/version.py +++ b/telethon/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '1.11.2' +__version__ = '1.11.3' diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index 302b0e6b..9c3c579e 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -68,6 +68,7 @@ CONNECTION_LAYER_INVALID,400,The very first request must always be InvokeWithLay CONNECTION_NOT_INITED,400,Connection not initialized CONNECTION_SYSTEM_EMPTY,400,Connection system empty CONTACT_ID_INVALID,400,The provided contact ID is invalid +CONTACT_NAME_EMPTY,400,The provided contact name cannot be empty DATA_INVALID,400,Encrypted data invalid DATA_JSON_INVALID,400,The provided JSON data is invalid DATE_EMPTY,400,Date empty diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index e1d04b86..38d8d61f 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -99,6 +99,7 @@ channels.togglePreHistoryHidden,user,CHAT_LINK_EXISTS channels.toggleSignatures,user,CHANNEL_INVALID channels.toggleSlowMode,user,SECONDS_INVALID channels.updateUsername,user,CHANNELS_ADMIN_PUBLIC_TOO_MUCH CHANNEL_INVALID CHAT_ADMIN_REQUIRED USERNAME_INVALID USERNAME_OCCUPIED +contacts.addContact,user,CONTACT_NAME_EMPTY contacts.block,user,CONTACT_ID_INVALID contacts.deleteByPhones,user, contacts.deleteContact,user,CONTACT_ID_INVALID From 673a2ecd5d08945ff79fa2809da45aaffa04aa90 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 25 Feb 2020 15:52:22 +0100 Subject: [PATCH 04/94] Document two new errors --- telethon_generator/data/errors.csv | 2 ++ telethon_generator/data/methods.csv | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index 9c3c579e..d96f398d 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -115,6 +115,7 @@ GROUPED_MEDIA_INVALID,400,Invalid grouped media HASH_INVALID,400,The provided hash is invalid HISTORY_GET_FAILED,500,Fetching of history failed IMAGE_PROCESS_FAILED,400,Failure while processing image +INLINE_BOT_REQUIRED,403,The action must be performed through an inline bot callback INLINE_RESULT_EXPIRED,400,The inline query expired INPUT_CONSTRUCTOR_INVALID,400,The provided constructor is invalid INPUT_FETCH_ERROR,,An error occurred while deserializing TL parameters @@ -302,6 +303,7 @@ VIDEO_CONTENT_TYPE_INVALID,400,The video content type is not supported with the WALLPAPER_FILE_INVALID,400,The given file cannot be used as a wallpaper WALLPAPER_INVALID,400,The input wallpaper was not valid WC_CONVERT_URL_INVALID,400,WC convert URL invalid +WEBDOCUMENT_URL_INVALID,400,The given URL cannot be used WEBPAGE_CURL_FAILED,400,Failure while fetching the webpage with cURL WEBPAGE_MEDIA_EMPTY,400,Webpage media empty WORKER_BUSY_TOO_LONG_RETRY,500,Telegram workers are too busy to respond immediately diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index 38d8d61f..f8eae625 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -167,7 +167,7 @@ messages.editChatDefaultBannedRights,both,BANNED_RIGHTS_INVALID messages.editChatPhoto,both,CHAT_ID_INVALID INPUT_CONSTRUCTOR_INVALID INPUT_FETCH_FAIL PEER_ID_INVALID PHOTO_EXT_INVALID messages.editChatTitle,both,CHAT_ID_INVALID NEED_CHAT_INVALID messages.editInlineBotMessage,both,MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED -messages.editMessage,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_WRITE_FORBIDDEN INPUT_USER_DEACTIVATED MEDIA_NEW_INVALID MEDIA_PREV_INVALID MESSAGE_AUTHOR_REQUIRED MESSAGE_EDIT_TIME_EXPIRED MESSAGE_EMPTY MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED PEER_ID_INVALID +messages.editMessage,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_WRITE_FORBIDDEN INLINE_BOT_REQUIRED INPUT_USER_DEACTIVATED MEDIA_NEW_INVALID MEDIA_PREV_INVALID MESSAGE_AUTHOR_REQUIRED MESSAGE_EDIT_TIME_EXPIRED MESSAGE_EMPTY MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED PEER_ID_INVALID messages.exportChatInvite,user,CHAT_ID_INVALID messages.faveSticker,user,STICKER_ID_INVALID messages.forwardMessages,both,BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_SEND_GIFS_FORBIDDEN CHAT_SEND_MEDIA_FORBIDDEN CHAT_SEND_STICKERS_FORBIDDEN CHAT_WRITE_FORBIDDEN GROUPED_MEDIA_INVALID INPUT_USER_DEACTIVATED MEDIA_EMPTY MESSAGE_IDS_EMPTY MESSAGE_ID_INVALID PEER_ID_INVALID PTS_CHANGE_EMPTY RANDOM_ID_DUPLICATE RANDOM_ID_INVALID SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER @@ -248,7 +248,7 @@ messages.setBotPrecheckoutResults,both,ERROR_TEXT_EMPTY messages.setBotShippingResults,both,QUERY_ID_INVALID messages.setEncryptedTyping,user,CHAT_ID_INVALID messages.setGameScore,bot,PEER_ID_INVALID USER_BOT_REQUIRED -messages.setInlineBotResults,bot,ARTICLE_TITLE_EMPTY BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID MESSAGE_EMPTY PHOTO_CONTENT_URL_EMPTY PHOTO_THUMB_URL_EMPTY QUERY_ID_INVALID REPLY_MARKUP_INVALID RESULT_TYPE_INVALID SEND_MESSAGE_MEDIA_INVALID SEND_MESSAGE_TYPE_INVALID START_PARAM_INVALID USER_BOT_INVALID +messages.setInlineBotResults,bot,ARTICLE_TITLE_EMPTY BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID MESSAGE_EMPTY PHOTO_CONTENT_URL_EMPTY PHOTO_THUMB_URL_EMPTY QUERY_ID_INVALID REPLY_MARKUP_INVALID RESULT_TYPE_INVALID SEND_MESSAGE_MEDIA_INVALID SEND_MESSAGE_TYPE_INVALID START_PARAM_INVALID USER_BOT_INVALID WEBDOCUMENT_URL_INVALID messages.setInlineGameScore,bot,MESSAGE_ID_INVALID USER_BOT_REQUIRED messages.setTyping,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ID_INVALID CHAT_WRITE_FORBIDDEN PEER_ID_INVALID USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT messages.startBot,user,BOT_INVALID PEER_ID_INVALID START_PARAM_EMPTY START_PARAM_INVALID From e451abbf20a5be37bb4da501b591a807518373ba Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 28 Feb 2020 10:42:23 +0100 Subject: [PATCH 05/94] Avoid another MemoryError --- telethon/tl/tlobject.py | 13 +++++++++++++ telethon_generator/generators/tlobject.py | 4 ++-- tests/telethon/test_utils.py | 2 ++ tests/telethon/tl/__init__.py | 0 tests/telethon/tl/test_serialization.py | 13 +++++++++++++ 5 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/telethon/tl/__init__.py create mode 100644 tests/telethon/tl/test_serialization.py diff --git a/telethon/tl/tlobject.py b/telethon/tl/tlobject.py index 451fad17..cd563857 100644 --- a/telethon/tl/tlobject.py +++ b/telethon/tl/tlobject.py @@ -186,6 +186,19 @@ class TLObject: return json.dumps(d, default=default, **kwargs) def __bytes__(self): + try: + return self._bytes() + except AttributeError: + # If a type is wrong (e.g. expected `TLObject` but `int` was + # provided) it will try to access `._bytes()`, which will fail + # with `AttributeError`. This occurs in fact because the type + # was wrong, so raise the correct error type. + raise TypeError('a TLObject was expected but found something else') + + # Custom objects will call `(...)._bytes()` and not `bytes(...)` so that + # if the wrong type is used (e.g. `int`) we won't try allocating a huge + # amount of data, which would cause a `MemoryError`. + def _bytes(self): raise NotImplementedError @classmethod diff --git a/telethon_generator/generators/tlobject.py b/telethon_generator/generators/tlobject.py index cd2b99d7..9ef23861 100644 --- a/telethon_generator/generators/tlobject.py +++ b/telethon_generator/generators/tlobject.py @@ -330,7 +330,7 @@ def _write_to_dict(tlobject, builder): def _write_to_bytes(tlobject, builder): - builder.writeln('def __bytes__(self):') + builder.writeln('def _bytes(self):') # Some objects require more than one flag parameter to be set # at the same time. In this case, add an assertion. @@ -509,7 +509,7 @@ def _write_arg_to_bytes(builder, arg, args, name=None): else: # Else it may be a custom type - builder.write('bytes({})', name) + builder.write('{}._bytes()', name) # If the type is not boxed (i.e. starts with lowercase) we should # not serialize the constructor ID (so remove its first 4 bytes). diff --git a/tests/telethon/test_utils.py b/tests/telethon/test_utils.py index 89391e36..b0cfb33f 100644 --- a/tests/telethon/test_utils.py +++ b/tests/telethon/test_utils.py @@ -1,6 +1,8 @@ import io import pathlib +import pytest + from telethon import utils from telethon.tl.types import ( MessageMediaGame, Game, PhotoEmpty diff --git a/tests/telethon/tl/__init__.py b/tests/telethon/tl/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/telethon/tl/test_serialization.py b/tests/telethon/tl/test_serialization.py new file mode 100644 index 00000000..7dbb067d --- /dev/null +++ b/tests/telethon/tl/test_serialization.py @@ -0,0 +1,13 @@ +import pytest + +from telethon.tl import types, functions + + +def test_nested_invalid_serialization(): + large_long = 2**62 + request = functions.account.SetPrivacyRequest( + key=types.InputPrivacyKeyChatInvite(), + rules=[types.InputPrivacyValueDisallowUsers(users=[large_long])] + ) + with pytest.raises(TypeError): + bytes(request) From 1ec38aa5b21d6d762806ee6f5aa799b958c741cb Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 28 Feb 2020 11:50:16 +0100 Subject: [PATCH 06/94] Update and clarify some docs cc #1388, #1396 --- telethon/client/downloads.py | 12 ++++++++++++ telethon/client/uploads.py | 11 +++++++++++ telethon/events/chataction.py | 4 ++-- telethon_generator/data/errors.csv | 1 + telethon_generator/data/methods.csv | 2 +- 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index c31be69a..6f1f0fdb 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -328,6 +328,13 @@ class DownloadMethods: # or path = await message.download_media() await message.download_media(filename) + + # Printing download progress + def callback(current, total): + print('Downloaded', current, 'out of', total, + 'bytes: {:.2%}'.format(current / total)) + + await client.download_media(message, progress_callback=callback) """ # TODO This won't work for messageService if isinstance(message, types.Message): @@ -373,6 +380,11 @@ class DownloadMethods: """ Low-level method to download files from their input location. + .. note:: + + Generally, you should instead use `download_media`. + This method is intended to be a bit more low-level. + Arguments input_location (:tl:`InputFileLocation`): The file location from which the file will be downloaded. diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index 923732e1..57262068 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -261,6 +261,13 @@ class UploadMethods: '/my/photos/holiday2.jpg', '/my/drawings/portrait.png' ]) + + # Printing upload progress + def callback(current, total): + print('Uploaded', current, 'out of', total, + 'bytes: {:.2%}'.format(current / total)) + + await client.send_file(chat, file, progress_callback=callback) """ # TODO Properly implement allow_cache to reuse the sha256 of the file # i.e. `None` was used @@ -436,6 +443,10 @@ class UploadMethods: """ Uploads a file to Telegram's servers, without sending it. + .. note:: + + Generally, you want to use `send_file` instead. + This method returns a handle (an instance of :tl:`InputFile` or :tl:`InputFileBig`, as required) which can be later used before it expires (they are usable during less than a day). diff --git a/telethon/events/chataction.py b/telethon/events/chataction.py index 7867d7e5..dd80b535 100644 --- a/telethon/events/chataction.py +++ b/telethon/events/chataction.py @@ -316,7 +316,7 @@ class ChatAction(EventBuilder): @property def user(self): """ - The first user that takes part in this action (e.g. joined). + The first user that takes part in this action. For example, who joined. Might be `None` if the information can't be retrieved or there is no user taking part. @@ -357,7 +357,7 @@ class ChatAction(EventBuilder): @property def users(self): """ - A list of users that take part in this action (e.g. joined). + A list of users that take part in this action. For example, who joined. Might be empty if the information can't be retrieved or there are no users taking part. diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index d96f398d..13d2e86a 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -236,6 +236,7 @@ RPC_MCGET_FAIL,,"Telegram is having internal issues, please try again later." RSA_DECRYPT_FAILED,400,Internal RSA decryption failed SCHEDULE_BOT_NOT_ALLOWED,400,Bots are not allowed to schedule messages SCHEDULE_DATE_TOO_LATE,400,The date you tried to schedule is too far in the future (last known limit of 1 year and a few hours) +SCHEDULE_STATUS_PRIVATE,400,You cannot schedule a message until the person comes online if their privacy does not show this information SCHEDULE_TOO_MUCH,400,You cannot schedule more messages in this chat (last known limit of 100 per chat) SEARCH_QUERY_EMPTY,400,The search query is empty SECONDS_INVALID,400,"Slow mode only supports certain values (e.g. 0, 10s, 30s, 1m, 5m, 15m and 1h)" diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index f8eae625..c03079c7 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -239,7 +239,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 BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID GAME_BOT_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_ANSWERS_INVALID POLL_OPTION_DUPLICATE POLL_QUESTION_INVALID QUIZ_CORRECT_ANSWERS_EMPTY QUIZ_CORRECT_ANSWERS_TOO_MUCH QUIZ_CORRECT_ANSWER_INVALID QUIZ_MULTIPLE_INVALID 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 MSG_ID_INVALID PEER_ID_INVALID POLL_OPTION_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 POLL_OPTION_INVALID RANDOM_ID_DUPLICATE REPLY_MARKUP_INVALID REPLY_MARKUP_TOO_LONG SCHEDULE_BOT_NOT_ALLOWED SCHEDULE_DATE_TOO_LATE SCHEDULE_STATUS_PRIVATE 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,MESSAGE_POLL_CLOSED OPTION_INVALID From 0e0052888f71763a0c604f06987c5af3db26efc9 Mon Sep 17 00:00:00 2001 From: painor Date: Wed, 4 Mar 2020 16:12:34 +0100 Subject: [PATCH 07/94] Expose key and iv parameter in downloads/uploads (#1397) --- telethon/client/downloads.py | 16 ++++++++++++++-- telethon/client/uploads.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index 6f1f0fdb..23441a18 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -5,6 +5,8 @@ import pathlib import typing import inspect +from ..crypto import AES + from .. import utils, helpers, errors, hints from ..requestiter import RequestIter from ..tl import TLObject, types, functions @@ -17,7 +19,6 @@ except ImportError: if typing.TYPE_CHECKING: from .telegramclient import TelegramClient - # Chunk sizes for upload.getFile must be multiples of the smallest size MIN_CHUNK_SIZE = 4096 MAX_CHUNK_SIZE = 512 * 1024 @@ -376,7 +377,9 @@ class DownloadMethods: part_size_kb: float = None, file_size: int = None, progress_callback: 'hints.ProgressCallback' = None, - dc_id: int = None) -> typing.Optional[bytes]: + dc_id: int = None, + key: bytes = None, + iv: bytes = None) -> typing.Optional[bytes]: """ Low-level method to download files from their input location. @@ -415,6 +418,13 @@ class DownloadMethods: The data center the library should connect to in order to download the file. You shouldn't worry about this. + key ('bytes', optional): + In case of an encrypted upload (secret chats) a key is supplied + + iv ('bytes', optional): + In case of an encrypted upload (secret chats) an iv is supplied + + Example .. code-block:: python @@ -446,6 +456,8 @@ class DownloadMethods: try: async for chunk in self.iter_download( input_location, request_size=part_size, dc_id=dc_id): + if iv and key: + chunk = AES.decrypt_ige(chunk, key, iv) r = f.write(chunk) if inspect.isawaitable(r): await r diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index 57262068..c5d2e3f4 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -8,6 +8,8 @@ import typing import inspect from io import BytesIO +from ..crypto import AES + from .. import utils, helpers, hints from ..tl import types, functions, custom @@ -439,6 +441,8 @@ class UploadMethods: part_size_kb: float = None, file_name: str = None, use_cache: type = None, + key: bytes = None, + iv: bytes = None, progress_callback: 'hints.ProgressCallback' = None) -> 'types.TypeInputFile': """ Uploads a file to Telegram's servers, without sending it. @@ -476,6 +480,12 @@ class UploadMethods: backward-compatibility (and it may get its use back in the future). + key ('bytes', optional): + In case of an encrypted upload (secret chats) a key is supplied + + iv ('bytes', optional): + In case of an encrypted upload (secret chats) an iv is supplied + progress_callback (`callable`, optional): A callback function accepting two parameters: ``(sent bytes, total)``. @@ -586,6 +596,10 @@ class UploadMethods: # Read the file by in chunks of size part_size part = stream.read(part_size) + # encryption part if needed + if key and iv: + part = AES.encrypt_ige(part, key, iv) + # The SavePartRequest is different depending on whether # the file is too large or not (over or less than 10MB) if is_large: From e3d81091100d331375fe2c7214a63aa72b6d7f85 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 11 Mar 2020 10:02:19 +0100 Subject: [PATCH 08/94] Fix a doc typo and update projects using the lib --- readthedocs/examples/projects-using-telethon.rst | 8 ++++++++ telethon/events/callbackquery.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/readthedocs/examples/projects-using-telethon.rst b/readthedocs/examples/projects-using-telethon.rst index 7ba4f998..481fa54b 100644 --- a/readthedocs/examples/projects-using-telethon.rst +++ b/readthedocs/examples/projects-using-telethon.rst @@ -96,3 +96,11 @@ a new ``Filter`` to define handlers more conveniently and utilities to run code on start and finish of the client. Be sure to check the project to learn about its latest features, since this description may be out of date. + +telethon-secret-chat +==================== + +`telethon-secret-chat `_ / +`painor's GitHub `_ + +This add-on can be used to work with secret chats in Telethon! diff --git a/telethon/events/callbackquery.py b/telethon/events/callbackquery.py index 5a6e1370..c35e348b 100644 --- a/telethon/events/callbackquery.py +++ b/telethon/events/callbackquery.py @@ -51,7 +51,7 @@ class CallbackQuery(EventBuilder): # Send a message with buttons users can click async def main(): await client.send_message(user, 'Yes or no?', buttons=[ - Button.inline('Yes!', b'yes') + Button.inline('Yes!', b'yes'), Button.inline('Nope', b'no') ]) """ From 68438f462127fa02021f40a0e2d686072978a264 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 11 Mar 2020 10:07:21 +0100 Subject: [PATCH 09/94] Don't store refs to files in cache File cache has been unused since file_reference were introduced, there's no point saving them to cache if they're never queried. Fixes #1400. --- telethon/client/uploads.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index c5d2e3f4..caf2dca4 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -357,10 +357,7 @@ class UploadMethods: entities=msg_entities, reply_markup=markup, silent=silent, schedule_date=schedule, clear_draft=clear_draft ) - msg = self._get_response_message(request, await self(request), entity) - await self._cache_media(msg, file, file_handle, image=image) - - return msg + return self._get_response_message(request, await self(request), entity) async def _send_album(self: 'TelegramClient', entity, files, caption='', progress_callback=None, reply_to=None, @@ -399,16 +396,12 @@ class UploadMethods: r = await self(functions.messages.UploadMediaRequest( entity, media=fm )) - self.session.cache_file( - fh.md5, fh.size, utils.get_input_photo(r.photo)) fm = utils.get_input_media(r.photo) elif isinstance(fm, types.InputMediaUploadedDocument): r = await self(functions.messages.UploadMediaRequest( entity, media=fm )) - self.session.cache_file( - fh.md5, fh.size, utils.get_input_document(r.document)) fm = utils.get_input_media( r.document, supports_streaming=supports_streaming) @@ -720,16 +713,4 @@ class UploadMethods: ) return file_handle, media, as_image - async def _cache_media(self: 'TelegramClient', msg, file, file_handle, image): - if file and msg and isinstance(file_handle, - custom.InputSizedFile): - # There was a response message and we didn't use cached - # version, so cache whatever we just sent to the database. - md5, size = file_handle.md5, file_handle.size - if image: - to_cache = utils.get_input_photo(msg.media.photo) - else: - to_cache = utils.get_input_document(msg.media.document) - self.session.cache_file(md5, size, to_cache) - # endregion From ccfd7a1015a5f525ddf97ef4f45a885a8d3cf93c Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 14 Mar 2020 12:12:40 +0100 Subject: [PATCH 10/94] Don't ignore thumb in send_file(input file) Fixes #1404 --- telethon/client/uploads.py | 19 +++++++++++-------- telethon/utils.py | 5 +++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index caf2dca4..3ec0990e 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -637,6 +637,14 @@ class UploadMethods: if as_image is None: as_image = utils.is_image(file) and not force_document + if as_image or not thumb: + # Images don't have thumb so don't bother uploading it + thumb = None + else: + if isinstance(thumb, pathlib.Path): + thumb = str(thumb.absolute()) + thumb = await self.upload_file(thumb) + # `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)) and not hasattr(file, 'read'): @@ -654,7 +662,8 @@ class UploadMethods: force_document=force_document, voice_note=voice_note, video_note=video_note, - supports_streaming=supports_streaming + supports_streaming=supports_streaming, + thumb=thumb, ), as_image) except TypeError: # Can't turn whatever was given into media @@ -699,17 +708,11 @@ class UploadMethods: supports_streaming=supports_streaming ) - input_kw = {} - if thumb: - if isinstance(thumb, pathlib.Path): - thumb = str(thumb.absolute()) - input_kw['thumb'] = await self.upload_file(thumb) - media = types.InputMediaUploadedDocument( file=file_handle, mime_type=mime_type, attributes=attributes, - **input_kw + thumb=thumb ) return file_handle, media, as_image diff --git a/telethon/utils.py b/telethon/utils.py index 0c63d143..b07cf875 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -427,7 +427,8 @@ def get_input_geo(geo): def get_input_media( media, *, is_photo=False, attributes=None, force_document=False, - voice_note=False, video_note=False, supports_streaming=False + voice_note=False, video_note=False, supports_streaming=False, + thumb=None, ): """ Similar to :meth:`get_input_peer`, but for media. @@ -481,7 +482,7 @@ def get_input_media( supports_streaming=supports_streaming ) return types.InputMediaUploadedDocument( - file=media, mime_type=mime, attributes=attrs) + file=media, mime_type=mime, attributes=attrs, thumb=thumb) if isinstance(media, types.MessageMediaGame): return types.InputMediaGame(id=types.InputGameID( From 3ab9986fc7be852cd9976b2bf5c72bc4b43906a5 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 14 Mar 2020 12:16:52 +0100 Subject: [PATCH 11/94] Slightly better flow in _file_to_media --- telethon/client/uploads.py | 26 ++++++++++++++------------ telethon/utils.py | 5 ++--- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index 3ec0990e..fb924595 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -637,17 +637,10 @@ class UploadMethods: if as_image is None: as_image = utils.is_image(file) and not force_document - if as_image or not thumb: - # Images don't have thumb so don't bother uploading it - thumb = None - else: - if isinstance(thumb, pathlib.Path): - thumb = str(thumb.absolute()) - thumb = await self.upload_file(thumb) - # `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)) and not hasattr(file, 'read'): + 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 # getting the input media for whatever they passed and send it. @@ -662,8 +655,7 @@ class UploadMethods: force_document=force_document, voice_note=voice_note, video_note=video_note, - supports_streaming=supports_streaming, - thumb=thumb, + supports_streaming=supports_streaming ), as_image) except TypeError: # Can't turn whatever was given into media @@ -671,7 +663,10 @@ class UploadMethods: media = None file_handle = None - if not isinstance(file, str) or os.path.isfile(file): + + if isinstance(file, (types.InputFile, types.InputFileBig)): + file_handle = file + elif not isinstance(file, str) or os.path.isfile(file): file_handle = await self.upload_file( _resize_photo_if_needed(file, as_image), progress_callback=progress_callback @@ -708,6 +703,13 @@ class UploadMethods: supports_streaming=supports_streaming ) + if not thumb: + thumb = None + else: + if isinstance(thumb, pathlib.Path): + thumb = str(thumb.absolute()) + thumb = await self.upload_file(thumb) + media = types.InputMediaUploadedDocument( file=file_handle, mime_type=mime_type, diff --git a/telethon/utils.py b/telethon/utils.py index b07cf875..0c63d143 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -427,8 +427,7 @@ def get_input_geo(geo): def get_input_media( media, *, is_photo=False, attributes=None, force_document=False, - voice_note=False, video_note=False, supports_streaming=False, - thumb=None, + voice_note=False, video_note=False, supports_streaming=False ): """ Similar to :meth:`get_input_peer`, but for media. @@ -482,7 +481,7 @@ def get_input_media( supports_streaming=supports_streaming ) return types.InputMediaUploadedDocument( - file=media, mime_type=mime, attributes=attrs, thumb=thumb) + file=media, mime_type=mime, attributes=attrs) if isinstance(media, types.MessageMediaGame): return types.InputMediaGame(id=types.InputGameID( From 65d8205eefeb909290cba1465d3478617d8eb076 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 28 Mar 2020 10:01:31 +0100 Subject: [PATCH 12/94] Update to layer 111 --- telethon_generator/data/api.tl | 41 +++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/telethon_generator/data/api.tl b/telethon_generator/data/api.tl index 15e82f30..8a87a442 100644 --- a/telethon_generator/data/api.tl +++ b/telethon_generator/data/api.tl @@ -72,6 +72,7 @@ inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia; inputMediaPoll#abe9ca25 flags:# poll:Poll correct_answers:flags.0?Vector = InputMedia; +inputMediaDice#aeffa807 = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; @@ -128,7 +129,7 @@ channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; -channelFull#2d895c74 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int pts:int = ChatFull; +channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -156,6 +157,7 @@ messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia; messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; +messageMediaDice#638fe46b value:int = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#a6638b9a title:string users:Vector = MessageAction; @@ -352,6 +354,9 @@ updateTheme#8216fba3 theme:Theme = Update; updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update; updateLoginToken#564fe691 = Update; updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector = Update; +updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; +updateDialogFilterOrder#a5d72105 order:Vector = Update; +updateDialogFilters#3504914f = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -502,7 +507,7 @@ messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMess webPageEmpty#eb1477e8 id:long = WebPage; webPagePending#c586da1c id:long date:int = WebPage; webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; -webPageNotModified#85849473 = WebPage; +webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage; authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; @@ -528,6 +533,7 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; +inputStickerSetDice#79e21a53 = InputStickerSet; stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet; @@ -823,7 +829,7 @@ phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3? phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; -phoneCallProtocol#a2bb35cb flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int = PhoneCallProtocol; +phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector = PhoneCallProtocol; phone.phoneCall#ec82e140 phone_call:PhoneCall users:Vector = phone.PhoneCall; @@ -1009,7 +1015,7 @@ pageListOrderedItemBlocks#98dd8936 num:string blocks:Vector = PageLis pageRelatedArticle#b390dc08 flags:# url:string webpage_id:long title:flags.0?string description:flags.1?string photo_id:flags.2?long author:flags.3?string published_date:flags.4?int = PageRelatedArticle; -page#ae891bec flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector photos:Vector documents:Vector = Page; +page#98657f0d flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector photos:Vector documents:Vector views:flags.3?int = Page; help.supportName#8c05f1c9 name:string = help.SupportName; @@ -1116,6 +1122,24 @@ bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl; payments.bankCardData#3e24e573 title:string open_urls:Vector = payments.BankCardData; +dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector exclude_peers:Vector = DialogFilter; + +dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested; + +statsDateRangeDays#b637edaf min_date:int max_date:int = StatsDateRangeDays; + +statsAbsValueAndPrev#cb43acde current:double previous:double = StatsAbsValueAndPrev; + +statsPercentValue#cbce2fe0 part:double total:double = StatsPercentValue; + +statsGraphAsync#4a27eb2d token:string = StatsGraph; +statsGraphError#bedc9822 error:string = StatsGraph; +statsGraph#8ea464b6 flags:# json:DataJSON zoom_token:flags.0?string = StatsGraph; + +messageInteractionCounters#ad4fc9bd msg_id:int views:int forwards:int = MessageInteractionCounters; + +stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueAndPrev views_per_post:StatsAbsValueAndPrev shares_per_post:StatsAbsValueAndPrev enabled_notifications:StatsPercentValue growth_graph:StatsGraph followers_graph:StatsGraph mute_graph:StatsGraph top_hours_graph:StatsGraph interactions_graph:StatsGraph iv_interactions_graph:StatsGraph views_by_source_graph:StatsGraph new_followers_by_source_graph:StatsGraph languages_graph:StatsGraph recent_message_interactions:Vector = stats.BroadcastStats; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1354,6 +1378,10 @@ messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector = Updates; messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector = Updates; messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList; messages.toggleStickerSets#b5052fea flags:# uninstall:flags.0?true archive:flags.1?true unarchive:flags.2?true stickersets:Vector = Bool; +messages.getDialogFilters#f19ed96d = Vector; +messages.getSuggestedDialogFilters#a29cd42c = Vector; +messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool; +messages.updateDialogFiltersOrder#c563c1e4 order:Vector = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1463,4 +1491,7 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; -// LAYER 110 +stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; +stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; + +// LAYER 111 From 1669d800822e93b4b00be2b161500aef4c54b6c2 Mon Sep 17 00:00:00 2001 From: YouTwitFace <32808683+YouTwitFace@users.noreply.github.com> Date: Sun, 29 Mar 2020 04:15:53 -0400 Subject: [PATCH 13/94] Remove call to `_cache_media` (#1419) Fixes #1418 --- telethon/client/messages.py | 1 - 1 file changed, 1 deletion(-) diff --git a/telethon/client/messages.py b/telethon/client/messages.py index 5c263edc..380bc1f8 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -1046,7 +1046,6 @@ class MessageMethods: schedule_date=schedule ) msg = self._get_response_message(request, await self(request), entity) - await self._cache_media(msg, file, file_handle, image=image) return msg async def delete_messages( From 0ec612d71a71536b5a0072ef5f5f1602a64ad35a Mon Sep 17 00:00:00 2001 From: "Dmitry D. Chernov" Date: Tue, 31 Mar 2020 19:18:57 +1000 Subject: [PATCH 14/94] utils: Style fix and simplify a bit the VALID_USERNAME_RE --- telethon/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/utils.py b/telethon/utils.py index 0c63d143..74f90011 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -64,7 +64,7 @@ TG_JOIN_RE = re.compile( # # See https://telegram.org/blog/inline-bots#how-does-it-work VALID_USERNAME_RE = re.compile( - r'^([a-z]((?!__)[\w\d]){3,30}[a-z\d]' + r'^([a-z](?:(?!__)\w){3,30}[a-z\d]' r'|gif|vid|pic|bing|wiki|imdb|bold|vote|like|coub)$', re.IGNORECASE ) From 15f30ed942273a7b8507beaedff986adb3208063 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 1 Apr 2020 19:55:14 +0200 Subject: [PATCH 15/94] Update documentation with some fixes and MongoDB sessions Closes #1403 and #1406. --- readthedocs/basic/signing-in.rst | 2 +- readthedocs/concepts/entities.rst | 2 +- readthedocs/concepts/sessions.rst | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/readthedocs/basic/signing-in.rst b/readthedocs/basic/signing-in.rst index 453d54fa..562d6b14 100644 --- a/readthedocs/basic/signing-in.rst +++ b/readthedocs/basic/signing-in.rst @@ -99,7 +99,7 @@ You will still need an API ID and hash, but the process is very similar: api_id = 12345 api_hash = '0123456789abcdef0123456789abcdef' - bot_token = '12345:0123456789abcdef0123456789abcdef + bot_token = '12345:0123456789abcdef0123456789abcdef' # We have to manually call "start" if we want an explicit bot token bot = TelegramClient('bot', api_id, api_hash).start(bot_token=bot_token) diff --git a/readthedocs/concepts/entities.rst b/readthedocs/concepts/entities.rst index 491c8c1e..d05d1040 100644 --- a/readthedocs/concepts/entities.rst +++ b/readthedocs/concepts/entities.rst @@ -178,7 +178,7 @@ exist, which just have the ID. You cannot get the hash out of them since you should not be needing it. The library probably has cached it before. Peers are enough to identify an entity, but they are not enough to make -a request with them use them. You need to know their hash before you can +a request with them. You need to know their hash before you can "use them", and to know the hash you need to "encounter" them, let it be in your dialogs, participants, message forwards, etc. diff --git a/readthedocs/concepts/sessions.rst b/readthedocs/concepts/sessions.rst index 03c3cbf0..a94bc773 100644 --- a/readthedocs/concepts/sessions.rst +++ b/readthedocs/concepts/sessions.rst @@ -100,6 +100,9 @@ There are other community-maintained implementations available: * `Redis `_: stores all sessions in a single Redis data store. +* `MongoDB `_: + stores the current session in a MongoDB database. + Creating your Own Storage ========================= From 3729fde5727861ebacf579ef86aee797da4af8ee Mon Sep 17 00:00:00 2001 From: Arne Beer Date: Fri, 3 Apr 2020 18:37:46 +0200 Subject: [PATCH 16/94] Fix editing of inline messages in some cases (#1427) --- telethon/client/messages.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/telethon/client/messages.py b/telethon/client/messages.py index 380bc1f8..e0ef93b4 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -1025,14 +1025,25 @@ class MessageMethods: force_document=force_document) if isinstance(entity, types.InputBotInlineMessageID): - return await self(functions.messages.EditInlineBotMessageRequest( + request = functions.messages.EditInlineBotMessageRequest( id=entity, message=text, no_webpage=not link_preview, entities=msg_entities, media=media, reply_markup=self.build_reply_markup(buttons) - )) + ) + # Invoke `messages.editInlineBotMessage` from the right datacenter. + # Otherwise, Telegram will error with `MESSAGE_ID_INVALID` and do nothing. + exported = self.session.dc_id != entity.dc_id + if exported: + try: + sender = await self._borrow_exported_sender(entity.dc_id) + return await sender.send(request) + finally: + await self._return_exported_sender(sender) + else: + return await self(request) entity = await self.get_input_entity(entity) request = functions.messages.EditMessageRequest( From d0f937bcb6f11aabea981889819bf4f7f633de39 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 5 Apr 2020 12:34:33 +0200 Subject: [PATCH 17/94] Don't disconnect borrowed senders immediately (#1364) --- telethon/client/telegrambaseclient.py | 92 +++++++++++++++++++++------ telethon/client/updates.py | 3 + 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/telethon/client/telegrambaseclient.py b/telethon/client/telegrambaseclient.py index eb571ea5..9fce65fe 100644 --- a/telethon/client/telegrambaseclient.py +++ b/telethon/client/telegrambaseclient.py @@ -29,6 +29,41 @@ __default_log__ = logging.getLogger(__base_name__) __default_log__.addHandler(logging.NullHandler()) +# In seconds, how long to wait before disconnecting a exported sender. +_DISCONNECT_EXPORTED_AFTER = 60 + + +class _ExportState: + def __init__(self): + # ``n`` is the amount of borrows a given sender has; + # once ``n`` reaches ``0``, disconnect the sender after a while. + self._n = 0 + self._zero_ts = 0 + self._connected = False + + def add_borrow(self): + self._n += 1 + self._connected = True + + def add_return(self): + self._n -= 1 + assert self._n >= 0, 'returned sender more than it was borrowed' + if self._n == 0: + self._zero_ts = time.time() + + def should_disconnect(self): + return (self._n == 0 + and self._connected + and (time.time() - self._zero_ts) > _DISCONNECT_EXPORTED_AFTER) + + def need_connect(self): + return not self._connected + + def mark_disconnected(self): + assert self.should_disconnect(), 'marked as disconnected when it was borrowed' + self._connected = False + + # TODO How hard would it be to support both `trio` and `asyncio`? class TelegramBaseClient(abc.ABC): """ @@ -314,9 +349,7 @@ class TelegramBaseClient(abc.ABC): # Remember flood-waited requests to avoid making them again self._flood_waited_requests = {} - # Cache ``{dc_id: (n, MTProtoSender)}`` for all borrowed senders, - # being ``n`` the amount of borrows a given sender has; once ``n`` - # reaches ``0`` it should be disconnected and removed. + # Cache ``{dc_id: (_ExportState, MTProtoSender)}`` for all borrowed senders self._borrowed_senders = {} self._borrow_sender_lock = asyncio.Lock(loop=self._loop) @@ -500,6 +533,15 @@ class TelegramBaseClient(abc.ABC): async def _disconnect_coro(self: 'TelegramClient'): await self._disconnect() + # Also clean-up all exported senders because we're done with them + async with self._borrow_sender_lock: + for state, sender in self._borrowed_senders.values(): + if state.should_disconnect(): + # disconnect should never raise + await sender.disconnect() + + self._borrowed_senders.clear() + # trio's nurseries would handle this for us, but this is asyncio. # All tasks spawned in the background should properly be terminated. if self._dispatching_updates_queue is None and self._updates_queue: @@ -598,8 +640,7 @@ class TelegramBaseClient(abc.ABC): loggers=self._log, proxy=self._proxy )) - self._log[__name__].info('Exporting authorization for data center %s', - dc) + self._log[__name__].info('Exporting auth for new borrowed sender in %s', dc) auth = await self(functions.auth.ExportAuthorizationRequest(dc_id)) req = self._init_with(functions.auth.ImportAuthorizationRequest( id=auth.id, bytes=auth.bytes @@ -616,11 +657,16 @@ class TelegramBaseClient(abc.ABC): Once its job is over it should be `_return_exported_sender`. """ async with self._borrow_sender_lock: - n, sender = self._borrowed_senders.get(dc_id, (0, None)) - if not sender: + self._log[__name__].debug('Borrowing sender for dc_id %d', dc_id) + state, sender = self._borrowed_senders.get(dc_id, (None, None)) + + if state is None: + state = _ExportState() sender = await self._create_exported_sender(dc_id) sender.dc_id = dc_id - elif not n: + self._borrowed_senders[dc_id] = (state, sender) + + elif state.need_connect(): dc = await self._get_dc(dc_id) await sender.connect(self._connection( dc.ip_address, @@ -631,9 +677,8 @@ class TelegramBaseClient(abc.ABC): proxy=self._proxy )) - self._borrowed_senders[dc_id] = (n + 1, sender) - - return sender + state.add_borrow() + return sender async def _return_exported_sender(self: 'TelegramClient', sender): """ @@ -641,14 +686,23 @@ class TelegramBaseClient(abc.ABC): been returned, the sender is cleanly disconnected. """ async with self._borrow_sender_lock: - dc_id = sender.dc_id - n, _ = self._borrowed_senders[dc_id] - n -= 1 - self._borrowed_senders[dc_id] = (n, sender) - if not n: - self._log[__name__].info( - 'Disconnecting borrowed sender for DC %d', dc_id) - await sender.disconnect() + self._log[__name__].debug('Returning borrowed sender for dc_id %d', sender.dc_id) + state, _ = self._borrowed_senders[sender.dc_id] + state.add_return() + + async def _clean_exported_senders(self: 'TelegramClient'): + """ + Cleans-up all unused exported senders by disconnecting them. + """ + async with self._borrow_sender_lock: + for dc_id, (state, sender) in self._borrowed_senders.items(): + if state.should_disconnect(): + self._log[__name__].info( + 'Disconnecting borrowed sender for DC %d', dc_id) + + # Disconnect should never raise + await sender.disconnect() + state.mark_disconnected() async def _get_cdn_client(self: 'TelegramClient', cdn_redirect): """Similar to ._borrow_exported_client, but for CDNs""" diff --git a/telethon/client/updates.py b/telethon/client/updates.py index fe19eaaf..854860fd 100644 --- a/telethon/client/updates.py +++ b/telethon/client/updates.py @@ -335,6 +335,9 @@ class UpdateMethods: except Exception: continue # Any disconnected exception should be ignored + # Check if we have any exported senders to clean-up periodically + await self._clean_exported_senders() + # Don't bother sending pings until the low-level connection is # ready, otherwise a lot of pings will be batched to be sent upon # reconnect, when we really don't care about that. From 8ea5fae61b9eddf9bef1c719cf874c44dcd26a07 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 6 Apr 2020 10:09:11 +0200 Subject: [PATCH 18/94] Add some missing layer 111 raw API methods --- telethon_generator/data/api.tl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/telethon_generator/data/api.tl b/telethon_generator/data/api.tl index 8a87a442..281f5ba4 100644 --- a/telethon_generator/data/api.tl +++ b/telethon_generator/data/api.tl @@ -1468,7 +1468,6 @@ payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.getBankCardData#2e79d779 number:string = payments.BankCardData; -stickers.createStickerSet#9bd86e6a flags:# masks:flags.0?true user_id:InputUser title:string short_name:string stickers:Vector = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet; stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet; @@ -1494,4 +1493,13 @@ folders.deleteFolder#1c295881 folder_id:int = Updates; stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; +// These are manually copied from tdlib because they're missing in tdesktop +// TODO look into automating this process (can be tricky because some conflict) + +---functions--- + +stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector = messages.StickerSet; +stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet; +bots.setBotCommands#805d46f6 commands:Vector = Bool; + // LAYER 111 From c0e523508b8567d9e3b4855cafa8912dc577c07c Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 6 Apr 2020 17:44:22 +0200 Subject: [PATCH 19/94] Update raw API method usability mapping --- telethon_generator/data/methods.csv | 51 ++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index c03079c7..3b4c46b5 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -5,24 +5,31 @@ account.changePhone,user,PHONE_NUMBER_INVALID account.checkUsername,user,USERNAME_INVALID account.confirmPasswordEmail,user, account.confirmPhone,user,CODE_HASH_INVALID PHONE_CODE_EMPTY +account.createTheme,user, account.deleteSecureValue,user, account.finishTakeoutSession,user, account.getAccountTTL,user, account.getAllSecureValues,user, account.getAuthorizationForm,user, account.getAuthorizations,user, +account.getAutoDownloadSettings,user, account.getContactSignUpNotification,user, +account.getContentSettings,user, +account.getMultiWallPapers,user, account.getNotifyExceptions,user, account.getNotifySettings,user,PEER_ID_INVALID account.getPassword,user, account.getPasswordSettings,user,PASSWORD_HASH_INVALID account.getPrivacy,user,PRIVACY_KEY_INVALID account.getSecureValue,user, +account.getTheme,user, +account.getThemes,user, account.getTmpPassword,user,PASSWORD_HASH_INVALID TMP_PASSWORD_DISABLED account.getWallPaper,user,WALLPAPER_INVALID account.getWallPapers,user, account.getWebAuthorizations,user, account.initTakeoutSession,user, +account.installTheme,user, account.installWallPaper,user,WALLPAPER_INVALID account.registerDevice,user,TOKEN_INVALID account.reportPeer,user,PEER_ID_INVALID @@ -32,7 +39,9 @@ account.resetNotifySettings,user, account.resetWallPapers,user, account.resetWebAuthorization,user, account.resetWebAuthorizations,user, +account.saveAutoDownloadSettings,user, account.saveSecureValue,user,PASSWORD_REQUIRED +account.saveTheme,user, account.saveWallPaper,user,WALLPAPER_INVALID account.sendChangePhoneCode,user,PHONE_NUMBER_INVALID account.sendConfirmPhoneCode,user,HASH_INVALID @@ -40,6 +49,7 @@ account.sendVerifyEmailCode,user,EMAIL_INVALID account.sendVerifyPhoneCode,user, account.setAccountTTL,user,TTL_DAYS_INVALID account.setContactSignUpNotification,user, +account.setContentSettings,user, account.setPrivacy,user,PRIVACY_KEY_INVALID account.unregisterDevice,user,TOKEN_INVALID account.updateDeviceLocked,user, @@ -47,17 +57,22 @@ account.updateNotifySettings,user,PEER_ID_INVALID account.updatePasswordSettings,user,EMAIL_UNCONFIRMED_X NEW_SALT_INVALID NEW_SETTINGS_INVALID PASSWORD_HASH_INVALID account.updateProfile,user,ABOUT_TOO_LONG FIRSTNAME_INVALID account.updateStatus,user,SESSION_PASSWORD_NEEDED +account.updateTheme,user, account.updateUsername,user,USERNAME_INVALID USERNAME_NOT_MODIFIED USERNAME_OCCUPIED +account.uploadTheme,user, account.uploadWallPaper,user,WALLPAPER_FILE_INVALID account.verifyEmail,user,EMAIL_INVALID account.verifyPhone,user, +auth.acceptLoginToken,user, auth.bindTempAuthKey,both,ENCRYPTED_MESSAGE_INVALID INPUT_REQUEST_TOO_LONG TEMP_AUTH_KEY_EMPTY Timeout auth.cancelCode,user,PHONE_NUMBER_INVALID auth.checkPassword,user,PASSWORD_HASH_INVALID auth.dropTempAuthKeys,both, auth.exportAuthorization,both,DC_ID_INVALID +auth.exportLoginToken,user, auth.importAuthorization,both,AUTH_BYTES_INVALID USER_ID_INVALID auth.importBotAuthorization,both,ACCESS_TOKEN_EXPIRED ACCESS_TOKEN_INVALID API_ID_INVALID +auth.importLoginToken,user, auth.logOut,both, auth.recoverPassword,user,CODE_EMPTY auth.requestPasswordRecovery,user,PASSWORD_EMPTY @@ -68,6 +83,7 @@ auth.signIn,user,PHONE_CODE_EMPTY PHONE_CODE_EXPIRED PHONE_CODE_INVALID PHONE_NU auth.signUp,user,FIRSTNAME_INVALID MEMBER_OCCUPY_PRIMARY_LOC_FAILED PHONE_CODE_EMPTY PHONE_CODE_EXPIRED PHONE_CODE_INVALID PHONE_NUMBER_FLOOD PHONE_NUMBER_INVALID PHONE_NUMBER_OCCUPIED REG_ID_GENERATE_FAILED bots.answerWebhookJSONQuery,bot,QUERY_ID_INVALID USER_BOT_INVALID bots.sendCustomRequest,bot,USER_BOT_INVALID +bots.setBotCommands,bot, channels.checkUsername,user,CHANNEL_INVALID CHAT_ID_INVALID USERNAME_INVALID channels.createChannel,user,CHAT_TITLE_EMPTY USER_RESTRICTED channels.deleteChannel,user,CHANNEL_INVALID CHANNEL_PRIVATE @@ -76,6 +92,8 @@ channels.deleteMessages,both,CHANNEL_INVALID CHANNEL_PRIVATE MESSAGE_DELETE_FORB channels.deleteUserHistory,user,CHANNEL_INVALID CHAT_ADMIN_REQUIRED channels.editAdmin,both,ADMINS_TOO_MUCH ADMIN_RANK_EMOJI_NOT_ALLOWED ADMIN_RANK_INVALID BOT_CHANNELS_NA CHANNEL_INVALID CHAT_ADMIN_INVITE_REQUIRED CHAT_ADMIN_REQUIRED FRESH_CHANGE_ADMINS_FORBIDDEN RIGHT_FORBIDDEN USER_CREATOR USER_ID_INVALID USER_NOT_MUTUAL_CONTACT USER_PRIVACY_RESTRICTED channels.editBanned,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ADMIN_INVALID USER_ID_INVALID +channels.editCreator,user, +channels.editLocation,user, channels.editPhoto,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED PHOTO_INVALID channels.editTitle,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED CHAT_NOT_MODIFIED channels.exportMessageLink,user,CHANNEL_INVALID @@ -83,6 +101,8 @@ channels.getAdminLog,user,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED channels.getAdminedPublicChannels,user, channels.getChannels,both,CHANNEL_INVALID CHANNEL_PRIVATE NEED_CHAT_INVALID channels.getFullChannel,both,CHANNEL_INVALID CHANNEL_PRIVATE CHANNEL_PUBLIC_GROUP_NA Timeout +channels.getGroupsForDiscussion,user, +channels.getInactiveChannels,user, channels.getLeftChannels,user, channels.getMessages,both,CHANNEL_INVALID CHANNEL_PRIVATE MESSAGE_IDS_EMPTY channels.getParticipant,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ID_INVALID USER_NOT_PARTICIPANT @@ -99,14 +119,15 @@ channels.togglePreHistoryHidden,user,CHAT_LINK_EXISTS channels.toggleSignatures,user,CHANNEL_INVALID channels.toggleSlowMode,user,SECONDS_INVALID channels.updateUsername,user,CHANNELS_ADMIN_PUBLIC_TOO_MUCH CHANNEL_INVALID CHAT_ADMIN_REQUIRED USERNAME_INVALID USERNAME_OCCUPIED +contacts.acceptContact,user, contacts.addContact,user,CONTACT_NAME_EMPTY contacts.block,user,CONTACT_ID_INVALID contacts.deleteByPhones,user, -contacts.deleteContact,user,CONTACT_ID_INVALID contacts.deleteContacts,user,NEED_MEMBER_INVALID Timeout contacts.getBlocked,user, contacts.getContactIDs,user, contacts.getContacts,user, +contacts.getLocated,user, contacts.getSaved,user,TAKEOUT_REQUIRED contacts.getStatuses,user, contacts.getTopPeers,user,TYPES_EMPTY @@ -117,9 +138,9 @@ contacts.resolveUsername,both,AUTH_KEY_PERM_EMPTY SESSION_PASSWORD_NEEDED USERNA contacts.search,user,QUERY_TOO_SHORT SEARCH_QUERY_EMPTY Timeout contacts.toggleTopPeers,user, contacts.unblock,user,CONTACT_ID_INVALID -contest.saveDeveloperInfo,both, folders.deleteFolder,user,FOLDER_ID_EMPTY folders.editPeerFolders,user,FOLDER_ID_INVALID +getFutureSalts,both, help.acceptTermsOfService,user, help.editUserInfo,user,USER_INVALID help.getAppChangelog,user, @@ -152,6 +173,7 @@ langpack.getLanguage,user, langpack.getLanguages,user,LANG_PACK_INVALID langpack.getStrings,user,LANG_PACK_INVALID messages.acceptEncryption,user,CHAT_ID_INVALID ENCRYPTION_ALREADY_ACCEPTED ENCRYPTION_ALREADY_DECLINED ENCRYPTION_OCCUPY_FAILED +messages.acceptUrlAuth,user, messages.addChatUser,user,CHAT_ADMIN_REQUIRED CHAT_ID_INVALID INPUT_USER_DEACTIVATED PEER_ID_INVALID USERS_TOO_MUCH USER_ALREADY_PARTICIPANT USER_ID_INVALID USER_NOT_MUTUAL_CONTACT USER_PRIVACY_RESTRICTED messages.checkChatInvite,user,INVITE_HASH_EMPTY INVITE_HASH_EXPIRED INVITE_HASH_INVALID messages.clearAllDrafts,user, @@ -160,6 +182,7 @@ messages.createChat,user,USERS_TOO_FEW USER_RESTRICTED messages.deleteChatUser,both,CHAT_ID_INVALID PEER_ID_INVALID USER_NOT_PARTICIPANT messages.deleteHistory,user,PEER_ID_INVALID messages.deleteMessages,both,MESSAGE_DELETE_FORBIDDEN +messages.deleteScheduledMessages,user, messages.discardEncryption,user,CHAT_ID_EMPTY ENCRYPTION_ALREADY_DECLINED ENCRYPTION_ID_INVALID messages.editChatAbout,both, messages.editChatAdmin,user,CHAT_ID_INVALID @@ -168,7 +191,7 @@ messages.editChatPhoto,both,CHAT_ID_INVALID INPUT_CONSTRUCTOR_INVALID INPUT_FETC messages.editChatTitle,both,CHAT_ID_INVALID NEED_CHAT_INVALID messages.editInlineBotMessage,both,MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED messages.editMessage,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_WRITE_FORBIDDEN INLINE_BOT_REQUIRED INPUT_USER_DEACTIVATED MEDIA_NEW_INVALID MEDIA_PREV_INVALID MESSAGE_AUTHOR_REQUIRED MESSAGE_EDIT_TIME_EXPIRED MESSAGE_EMPTY MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED PEER_ID_INVALID -messages.exportChatInvite,user,CHAT_ID_INVALID +messages.exportChatInvite,both,CHAT_ID_INVALID messages.faveSticker,user,STICKER_ID_INVALID messages.forwardMessages,both,BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_SEND_GIFS_FORBIDDEN CHAT_SEND_MEDIA_FORBIDDEN CHAT_SEND_STICKERS_FORBIDDEN CHAT_WRITE_FORBIDDEN GROUPED_MEDIA_INVALID INPUT_USER_DEACTIVATED MEDIA_EMPTY MESSAGE_IDS_EMPTY MESSAGE_ID_INVALID PEER_ID_INVALID PTS_CHANGE_EMPTY RANDOM_ID_DUPLICATE RANDOM_ID_INVALID SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER messages.getAllChats,user, @@ -180,9 +203,14 @@ messages.getBotCallbackAnswer,user,CHANNEL_INVALID DATA_INVALID MESSAGE_ID_INVAL messages.getChats,both,CHAT_ID_INVALID PEER_ID_INVALID messages.getCommonChats,user,USER_ID_INVALID messages.getDhConfig,user,RANDOM_LENGTH_INVALID +messages.getDialogFilters,user, messages.getDialogUnreadMarks,user, messages.getDialogs,user,INPUT_CONSTRUCTOR_INVALID OFFSET_PEER_ID_INVALID SESSION_PASSWORD_NEEDED Timeout messages.getDocumentByHash,both,SHA256_HASH_INVALID +messages.getEmojiKeywords,user, +messages.getEmojiKeywordsDifference,user, +messages.getEmojiKeywordsLanguages,user, +messages.getEmojiURL,user, messages.getFavedStickers,user, messages.getFeaturedStickers,user, messages.getFullChat,both,CHAT_ID_INVALID PEER_ID_INVALID @@ -199,17 +227,22 @@ messages.getPeerDialogs,user,CHANNEL_PRIVATE PEER_ID_INVALID messages.getPeerSettings,user,CHANNEL_INVALID PEER_ID_INVALID messages.getPinnedDialogs,user, messages.getPollResults,user, +messages.getPollVotes,user, messages.getRecentLocations,user, messages.getRecentStickers,user, messages.getSavedGifs,user, +messages.getScheduledHistory,user, +messages.getScheduledMessages,user, +messages.getSearchCounters,user, messages.getSplitRanges,user, messages.getStatsURL,user, messages.getStickerSet,both,STICKERSET_INVALID messages.getStickers,user,EMOTICON_EMPTY +messages.getSuggestedDialogFilters,user, messages.getUnreadMentions,user,PEER_ID_INVALID messages.getWebPage,user,WC_CONVERT_URL_INVALID messages.getWebPagePreview,user, -messages.hideReportSpam,user,PEER_ID_INVALID +messages.hidePeerSettingsBar,user, messages.importChatInvite,user,CHANNELS_TOO_MUCH INVITE_HASH_EMPTY INVITE_HASH_EXPIRED INVITE_HASH_INVALID SESSION_PASSWORD_NEEDED USERS_TOO_MUCH USER_ALREADY_PARTICIPANT messages.installStickerSet,user,STICKERSET_INVALID messages.markDialogUnread,user, @@ -227,6 +260,7 @@ messages.report,user, messages.reportEncryptedSpam,user,CHAT_ID_INVALID messages.reportSpam,user,PEER_ID_INVALID messages.requestEncryption,user,DH_G_A_INVALID USER_ID_INVALID +messages.requestUrlAuth,user, messages.saveDraft,user,PEER_ID_INVALID messages.saveGif,user,GIF_ID_INVALID messages.saveRecentSticker,user,STICKER_ID_INVALID @@ -241,7 +275,7 @@ messages.sendInlineBotResult,user,CHAT_SEND_INLINE_FORBIDDEN CHAT_WRITE_FORBIDDE messages.sendMedia,both,BOT_PAYMENTS_DISABLED BOT_POLLS_DISABLED BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID GAME_BOT_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_ANSWERS_INVALID POLL_OPTION_DUPLICATE POLL_QUESTION_INVALID QUIZ_CORRECT_ANSWERS_EMPTY QUIZ_CORRECT_ANSWERS_TOO_MUCH QUIZ_CORRECT_ANSWER_INVALID QUIZ_MULTIPLE_INVALID 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 MSG_ID_INVALID PEER_ID_INVALID POLL_OPTION_INVALID RANDOM_ID_DUPLICATE REPLY_MARKUP_INVALID REPLY_MARKUP_TOO_LONG SCHEDULE_BOT_NOT_ALLOWED SCHEDULE_DATE_TOO_LATE SCHEDULE_STATUS_PRIVATE 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.sendScheduledMessages,user, messages.sendVote,user,MESSAGE_POLL_CLOSED OPTION_INVALID messages.setBotCallbackAnswer,both,QUERY_ID_INVALID URL_INVALID messages.setBotPrecheckoutResults,both,ERROR_TEXT_EMPTY @@ -253,11 +287,15 @@ messages.setInlineGameScore,bot,MESSAGE_ID_INVALID USER_BOT_REQUIRED messages.setTyping,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ID_INVALID CHAT_WRITE_FORBIDDEN PEER_ID_INVALID USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT messages.startBot,user,BOT_INVALID PEER_ID_INVALID START_PARAM_EMPTY START_PARAM_INVALID messages.toggleDialogPin,user,PEER_ID_INVALID +messages.toggleStickerSets,user, messages.uninstallStickerSet,user,STICKERSET_INVALID +messages.updateDialogFilter,user, +messages.updateDialogFiltersOrder,user, messages.updatePinnedMessage,both, messages.uploadEncryptedFile,user, messages.uploadMedia,both,BOT_MISSING MEDIA_INVALID PEER_ID_INVALID payments.clearSavedInfo,user, +payments.getBankCardData,user, payments.getPaymentForm,user,MESSAGE_ID_INVALID payments.getPaymentReceipt,user,MESSAGE_ID_INVALID payments.getSavedInfo,user, @@ -281,10 +319,13 @@ reqPq,both, reqPqMulti,both, rpcDropAnswer,both, setClientDHParams,both, +stats.getBroadcastStats,user, +stats.loadAsyncGraph,user, stickers.addStickerToSet,bot,BOT_MISSING STICKERSET_INVALID stickers.changeStickerPosition,bot,BOT_MISSING STICKER_INVALID stickers.createStickerSet,bot,BOT_MISSING PACK_SHORT_NAME_INVALID PACK_SHORT_NAME_OCCUPIED PEER_ID_INVALID SHORTNAME_OCCUPY_FAILED STICKERS_EMPTY STICKER_EMOJI_INVALID STICKER_FILE_INVALID STICKER_PNG_DIMENSIONS STICKER_PNG_NOPNG USER_ID_INVALID stickers.removeStickerFromSet,bot,BOT_MISSING STICKER_INVALID +stickers.setStickerSetThumb,bot, updates.getChannelDifference,both,CHANNEL_INVALID CHANNEL_PRIVATE CHANNEL_PUBLIC_GROUP_NA HISTORY_GET_FAILED PERSISTENT_TIMESTAMP_EMPTY PERSISTENT_TIMESTAMP_INVALID PERSISTENT_TIMESTAMP_OUTDATED RANGES_INVALID Timeout updates.getDifference,both,AUTH_KEY_PERM_EMPTY CDN_METHOD_INVALID DATE_EMPTY NEED_MEMBER_INVALID PERSISTENT_TIMESTAMP_EMPTY PERSISTENT_TIMESTAMP_INVALID SESSION_PASSWORD_NEEDED STORE_INVALID_SCALAR_TYPE Timeout updates.getState,both,AUTH_KEY_DUPLICATED MSGID_DECREASE_RETRY SESSION_PASSWORD_NEEDED Timeout From 79fb1a54cb176993835ee0e83bb5af24722f8f7c Mon Sep 17 00:00:00 2001 From: ov7a Date: Sun, 12 Apr 2020 15:28:40 +0300 Subject: [PATCH 20/94] Switch to blocking connect when using proxy (#1432) Until a better fix is found, this should help proxy users. --- telethon/network/connection/connection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/telethon/network/connection/connection.py b/telethon/network/connection/connection.py index d7190c73..d6b9ea16 100644 --- a/telethon/network/connection/connection.py +++ b/telethon/network/connection/connection.py @@ -65,7 +65,7 @@ class Connection(abc.ABC): else: s.set_proxy(*self._proxy) - s.setblocking(False) + s.settimeout(timeout) await asyncio.wait_for( self._loop.sock_connect(s, address), timeout=timeout, @@ -78,14 +78,14 @@ class Connection(abc.ABC): 'without the SSL module being available' ) - s.settimeout(timeout) s = ssl_mod.wrap_socket( s, do_handshake_on_connect=True, ssl_version=ssl_mod.PROTOCOL_SSLv23, ciphers='ADH-AES256-SHA' ) - s.setblocking(False) + + s.setblocking(False) self._reader, self._writer = \ await asyncio.open_connection(sock=s, loop=self._loop) From 01cf4967a558658a830bafb9bf6faf5a215b193a Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 13 Apr 2020 11:31:39 +0200 Subject: [PATCH 21/94] Clarify send_read_acknowledge behaviour and add new error --- telethon/client/messages.py | 8 ++++++-- telethon_generator/data/errors.csv | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/telethon/client/messages.py b/telethon/client/messages.py index e0ef93b4..9ed86c95 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -1146,6 +1146,10 @@ class MessageMethods: If neither message nor maximum ID are provided, all messages will be marked as read by assuming that ``max_id = 0``. + If a message or maximum ID is provided, all the messages up to and + including such ID will be marked as read (for all messages whose ID + ≤ max_id). + See also `Message.mark_read() `. Arguments @@ -1156,8 +1160,8 @@ class MessageMethods: Either a list of messages or a single message. max_id (`int`): - Overrides messages, until which message should the - acknowledge should be sent. + Until which message should the read acknowledge be sent for. + This has priority over the ``message`` parameter. clear_mentions (`bool`): Whether the mention badge should be cleared (so that diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index 13d2e86a..652d06ed 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -250,6 +250,7 @@ SHORTNAME_OCCUPY_FAILED,400,An error occurred when trying to register the short- SLOWMODE_WAIT_X,420,A wait of {seconds} seconds is required before sending another message in this chat START_PARAM_EMPTY,400,The start parameter is empty START_PARAM_INVALID,400,Start parameter invalid +STATS_MIGRATE_X,303,The channel statistics must be fetched from DC {dc} STICKERSET_INVALID,400,The provided sticker set is invalid STICKERS_EMPTY,400,No sticker provided STICKER_EMOJI_INVALID,400,Sticker emoji invalid From 67a9718f9ea6431905766f2e6d3d89c0a1d30ca4 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 20 Apr 2020 15:12:00 +0200 Subject: [PATCH 22/94] Bump to v1.12.0 --- readthedocs/misc/changelog.rst | 30 ++++++++++++++++++++++++++++++ telethon/version.py | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/readthedocs/misc/changelog.rst b/readthedocs/misc/changelog.rst index f8c687ac..a75aa665 100644 --- a/readthedocs/misc/changelog.rst +++ b/readthedocs/misc/changelog.rst @@ -13,6 +13,36 @@ it can take advantage of new goodies! .. contents:: List of All Versions +Bug Fixes (v1.12) +================= + +*Published at 2020/04/20* + ++------------------------+ +| Scheme layer used: 111 | ++------------------------+ + +Once again nothing major, but a few bug fixes and primarily the new layer +deserves a new minor release. + +Bug fixes +~~~~~~~~~ + +These were already included in the ``v1.11.3`` patch: + +* ``libssl`` check was failing on macOS. +* Getting input users would sometimes fail on `events.ChatAction + `. + +These bug fixes are available in this release and beyond: + +* Avoid another occurrence of `MemoryError`. +* Sending large files in albums would fail because it tried to cache them. +* The ``thumb`` was being ignored when sending files from :tl:`InputFile`. +* Fixed editing inline messages from callback queries in some cases. +* Proxy connection is now blocking which should help avoid some errors. + + Bug Fixes (v1.11) ================= diff --git a/telethon/version.py b/telethon/version.py index b7016736..0f51216a 100644 --- a/telethon/version.py +++ b/telethon/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '1.11.3' +__version__ = '1.12.0' From 0c8a90f2a382db61e14d4f1933732656ddab70fe Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 23 Apr 2020 20:40:23 +0200 Subject: [PATCH 23/94] Fix delete_messages(None, ...) not working --- telethon/client/messages.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/telethon/client/messages.py b/telethon/client/messages.py index 9ed86c95..a18c3e08 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -1118,8 +1118,14 @@ class MessageMethods: else int(m) for m in message_ids ) - entity = await self.get_input_entity(entity) if entity else None - if helpers._entity_type(entity) == helpers._EntityType.CHANNEL: + if entity: + entity = await self.get_input_entity(entity) + ty = helpers._entity_type(entity) + else: + # no entity (None), set a value that's not a channel for private delete + ty = helpers._EntityType.USER + + if ty == helpers._EntityType.CHANNEL: return await self([functions.channels.DeleteMessagesRequest( entity, list(c)) for c in utils.chunks(message_ids)]) else: From c37dc695922228160aec739e955ec49e39572fa2 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 23 Apr 2020 21:01:29 +0200 Subject: [PATCH 24/94] Fix downloading thumb was using name inferred for video --- telethon/client/downloads.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index 23441a18..e4bb83a0 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -727,15 +727,15 @@ class DownloadMethods: if not isinstance(document, types.Document): return - kind, possible_names = self._get_kind_and_names(document.attributes) - file = self._get_proper_filename( - file, kind, utils.get_extension(document), - date=date, possible_names=possible_names - ) - if thumb is None: + kind, possible_names = self._get_kind_and_names(document.attributes) + file = self._get_proper_filename( + file, kind, utils.get_extension(document), + date=date, possible_names=possible_names + ) size = None else: + file = self._get_proper_filename(file, 'photo', '.jpg', date=date) size = self._get_thumb(document.thumbs, thumb) if isinstance(size, (types.PhotoCachedSize, types.PhotoStrippedSize)): return self._download_cached_photo_size(size, file) From a353ae3b6511592bdb25dbf47075eaeb75a2acdb Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 24 Apr 2020 12:17:33 +0200 Subject: [PATCH 25/94] Update to layer 112 --- telethon_generator/data/api.tl | 35 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/telethon_generator/data/api.tl b/telethon_generator/data/api.tl index 281f5ba4..244f2003 100644 --- a/telethon_generator/data/api.tl +++ b/telethon_generator/data/api.tl @@ -71,8 +71,8 @@ inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia; -inputMediaPoll#abe9ca25 flags:# poll:Poll correct_answers:flags.0?Vector = InputMedia; -inputMediaDice#aeffa807 = InputMedia; +inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; +inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; @@ -157,7 +157,7 @@ messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia; messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; -messageMediaDice#638fe46b value:int = MessageMedia; +messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#a6638b9a title:string users:Vector = MessageAction; @@ -533,7 +533,7 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; -inputStickerSetDice#79e21a53 = InputStickerSet; +inputStickerSetDice#e67f520e emoticon:string = InputStickerSet; stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet; @@ -692,8 +692,8 @@ contacts.topPeersDisabled#b52c939d = contacts.TopPeers; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; -messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers; -messages.featuredStickers#f89d88e5 hash:int sets:Vector unread:Vector = messages.FeaturedStickers; +messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; +messages.featuredStickers#b6abc341 hash:int count:int sets:Vector unread:Vector = messages.FeaturedStickers; messages.recentStickersNotModified#b17f890 = messages.RecentStickers; messages.recentStickers#22f3afb3 hash:int packs:Vector stickers:Vector dates:Vector = messages.RecentStickers; @@ -1024,11 +1024,11 @@ help.userInfo#1eb3758 message:string entities:Vector author:strin pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer; -poll#d5529d06 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector = Poll; +poll#86e18161 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector close_period:flags.4?int close_date:flags.5?int = Poll; pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters; -pollResults#c87024a2 flags:# min:flags.0?true results:flags.1?Vector total_voters:flags.2?int recent_voters:flags.3?Vector = PollResults; +pollResults#badcc1a3 flags:# min:flags.0?true results:flags.1?Vector total_voters:flags.2?int recent_voters:flags.3?Vector solution:flags.4?string solution_entities:flags.4?Vector = PollResults; chatOnlines#f041e250 onlines:int = ChatOnlines; @@ -1144,7 +1144,7 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector query:!X = X; -initConnection#785188b8 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X; +initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X; invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X; invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X; invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X; @@ -1382,6 +1382,7 @@ messages.getDialogFilters#f19ed96d = Vector; messages.getSuggestedDialogFilters#a29cd42c = Vector; messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool; messages.updateDialogFiltersOrder#c563c1e4 order:Vector = Bool; +messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = messages.FeaturedStickers; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1459,6 +1460,7 @@ channels.getInactiveChannels#11e831ee = messages.InactiveChats; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; +bots.setBotCommands#805d46f6 commands:Vector = Bool; payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm; payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt; @@ -1468,9 +1470,11 @@ payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.getBankCardData#2e79d779 number:string = payments.BankCardData; +stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet; stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet; +stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet; phone.getCallConfig#55451fa9 = DataJSON; phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; @@ -1490,16 +1494,7 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; -stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; +stats.getBroadcastStats#e6300dba flags:# dark:flags.0?true channel:InputChannel tz_offset:int = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; -// These are manually copied from tdlib because they're missing in tdesktop -// TODO look into automating this process (can be tricky because some conflict) - ----functions--- - -stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector = messages.StickerSet; -stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet; -bots.setBotCommands#805d46f6 commands:Vector = Bool; - -// LAYER 111 +// LAYER 112 From dcc450267f122ad75373a8a719930eee4ff2e318 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 24 Apr 2020 12:38:51 +0200 Subject: [PATCH 26/94] Document new EMOTICON_INVALID error --- telethon_generator/data/errors.csv | 1 + telethon_generator/data/methods.csv | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index 652d06ed..966b2324 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -78,6 +78,7 @@ EMAIL_HASH_EXPIRED,400,The email hash expired and cannot be used to verify it EMAIL_INVALID,400,The given email is invalid EMAIL_UNCONFIRMED_X,400,"Email unconfirmed, the length of the code must be {code_length}" EMOTICON_EMPTY,400,The emoticon field cannot be empty +EMOTICON_INVALID,400,The specified emoticon cannot be used or was not a emoticon ENCRYPTED_MESSAGE_INVALID,400,Encrypted message invalid ENCRYPTION_ALREADY_ACCEPTED,400,Secret chat already accepted ENCRYPTION_ALREADY_DECLINED,400,The secret chat was already declined diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index 3b4c46b5..635c4a16 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -272,7 +272,7 @@ messages.sendEncrypted,user,CHAT_ID_INVALID DATA_INVALID ENCRYPTION_DECLINED MSG 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 BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID GAME_BOT_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_ANSWERS_INVALID POLL_OPTION_DUPLICATE POLL_QUESTION_INVALID QUIZ_CORRECT_ANSWERS_EMPTY QUIZ_CORRECT_ANSWERS_TOO_MUCH QUIZ_CORRECT_ANSWER_INVALID QUIZ_MULTIPLE_INVALID 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.sendMedia,both,BOT_PAYMENTS_DISABLED BOT_POLLS_DISABLED BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EMOTICON_INVALID EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID GAME_BOT_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_ANSWERS_INVALID POLL_OPTION_DUPLICATE POLL_QUESTION_INVALID QUIZ_CORRECT_ANSWERS_EMPTY QUIZ_CORRECT_ANSWERS_TOO_MUCH QUIZ_CORRECT_ANSWER_INVALID QUIZ_MULTIPLE_INVALID 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 MSG_ID_INVALID PEER_ID_INVALID POLL_OPTION_INVALID RANDOM_ID_DUPLICATE REPLY_MARKUP_INVALID REPLY_MARKUP_TOO_LONG SCHEDULE_BOT_NOT_ALLOWED SCHEDULE_DATE_TOO_LATE SCHEDULE_STATUS_PRIVATE 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.sendScheduledMessages,user, From c487340f8ecc4f47c36bfcf36ef2b2eb10325e78 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 25 Apr 2020 16:28:13 +0200 Subject: [PATCH 27/94] Bump to v1.13.0 --- telethon/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/version.py b/telethon/version.py index 0f51216a..8846b60c 100644 --- a/telethon/version.py +++ b/telethon/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '1.12.0' +__version__ = '1.13.0' From a16c60c886142993621cfa0d7b0711422787cf4b Mon Sep 17 00:00:00 2001 From: TishSerg Date: Sun, 26 Apr 2020 12:00:00 +0300 Subject: [PATCH 28/94] Fix action 'song' should alias 'audio' (#1444) --- telethon/client/chats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/client/chats.py b/telethon/client/chats.py index bad8fc4b..dc28e7f8 100644 --- a/telethon/client/chats.py +++ b/telethon/client/chats.py @@ -30,13 +30,13 @@ class _ChatAction: 'audio': types.SendMessageUploadAudioAction(1), 'voice': types.SendMessageUploadAudioAction(1), # alias + 'song': types.SendMessageUploadAudioAction(1), # alias 'round': types.SendMessageUploadRoundAction(1), 'video': types.SendMessageUploadVideoAction(1), 'photo': types.SendMessageUploadPhotoAction(1), 'document': types.SendMessageUploadDocumentAction(1), 'file': types.SendMessageUploadDocumentAction(1), # alias - 'song': types.SendMessageUploadDocumentAction(1), # alias 'cancel': types.SendMessageCancelAction() } From eb58e60dd1cf928877cd5c0b07acb4ad9eff48d1 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 26 Apr 2020 13:33:12 +0200 Subject: [PATCH 29/94] Fix string formatting on events.Raw with bad input param --- telethon/events/raw.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telethon/events/raw.py b/telethon/events/raw.py index f7eebb6a..912a934d 100644 --- a/telethon/events/raw.py +++ b/telethon/events/raw.py @@ -29,12 +29,12 @@ class Raw(EventBuilder): self.types = None elif not utils.is_list_like(types): if not isinstance(types, type): - raise TypeError('Invalid input type given %s', types) + raise TypeError('Invalid input type given: {}'.format(types)) self.types = types else: if not all(isinstance(x, type) for x in types): - raise TypeError('Invalid input types given %s', types) + raise TypeError('Invalid input types given: {}'.format(types)) self.types = tuple(types) From bfa46f47ed700c3cb4afd56af5e4f6568dc9d0d4 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 26 Apr 2020 13:34:34 +0200 Subject: [PATCH 30/94] Register application/x-tgsticker to mimetypes --- telethon/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/telethon/utils.py b/telethon/utils.py index 74f90011..95bb65aa 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -51,6 +51,8 @@ mimetypes.add_type('audio/aac', '.aac') mimetypes.add_type('audio/ogg', '.ogg') mimetypes.add_type('audio/flac', '.flac') +mimetypes.add_type('application/x-tgsticker', '.tgs') + USERNAME_RE = re.compile( r'@|(?:https?://)?(?:www\.)?(?:telegram\.(?:me|dog)|t\.me)/(@|joinchat/)?' ) From 202a8a171b2c77b86b8defdea4fa8e7305914a64 Mon Sep 17 00:00:00 2001 From: Ali Gasymov Date: Mon, 27 Apr 2020 22:12:49 +0400 Subject: [PATCH 31/94] Update links to other MTProto libraries (#1442) --- readthedocs/developing/telegram-api-in-other-languages.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/readthedocs/developing/telegram-api-in-other-languages.rst b/readthedocs/developing/telegram-api-in-other-languages.rst index 3db1ec9f..cdb81eb2 100644 --- a/readthedocs/developing/telegram-api-in-other-languages.rst +++ b/readthedocs/developing/telegram-api-in-other-languages.rst @@ -29,10 +29,7 @@ published `here `__. JavaScript ========== -`@zerobias `__ is working on -`telegram-mtproto `__, -a work-in-progress JavaScript library installable via -`npm `__. +`Ali Gasymov `__ made the `@mtproto/core `__ library for the browser and nodejs installable via `npm `__. Kotlin ====== From 71ed1564cb8fa620445b0599fb7d1b402759e3bc Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 20:42:50 +0200 Subject: [PATCH 32/94] Add a new .dice property to Message --- telethon/tl/custom/message.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/telethon/tl/custom/message.py b/telethon/tl/custom/message.py index 8aa5bcb5..7460a487 100644 --- a/telethon/tl/custom/message.py +++ b/telethon/tl/custom/message.py @@ -552,6 +552,14 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): if isinstance(self.media, types.MessageMediaVenue): return self.media + @property + def dice(self): + """ + The :tl:`MessageMediaDice` in this message, if it's a dice roll. + """ + if isinstance(self.media, types.MessageMediaDice): + return self.media + @property def action_entities(self): """ From 7f3aa43ad4d4bff888e1136e9ed98b17a77cab3d Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 27 Apr 2020 21:16:45 +0200 Subject: [PATCH 33/94] Rely on types.UpdateChatPinnedMessage for chat unpins Fixes #1405, probably. --- telethon/events/chataction.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/telethon/events/chataction.py b/telethon/events/chataction.py index dd80b535..34d9c693 100644 --- a/telethon/events/chataction.py +++ b/telethon/events/chataction.py @@ -38,6 +38,10 @@ class ChatAction(EventBuilder): return cls.Event(types.PeerChannel(update.channel_id), unpin=True) + elif isinstance(update, types.UpdateChatPinnedMessage) and update.id == 0: + return cls.Event(types.PeerChat(update.chat_id), + unpin=True) + elif isinstance(update, types.UpdateChatParticipantAdd): return cls.Event(types.PeerChat(update.chat_id), added_by=update.inviter_id or True, @@ -104,8 +108,9 @@ class ChatAction(EventBuilder): return cls.Event(msg, users=msg.from_id, new_photo=True) - elif isinstance(action, types.MessageActionPinMessage): - # Telegram always sends this service message for new pins + elif isinstance(action, types.MessageActionPinMessage) and msg.reply_to_msg_id: + # Seems to not be reliable on unpins, but when pinning + # we prefer this because we know who caused it. return cls.Event(msg, users=msg.from_id, new_pin=msg.reply_to_msg_id) From 7ea4686d6c7a4234a834cffd464d3ed0414d5d00 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 28 Apr 2020 20:49:57 +0200 Subject: [PATCH 34/94] Handle FloodWaitError in client.download_media Fixes #1426. Not entirely happy with the new indirection layer, but the original __call__ method is already a mess anyway and the additional cost of an extra call should be neglible compared to the actual IO. --- telethon/client/downloads.py | 2 +- telethon/client/messages.py | 2 +- telethon/client/users.py | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index e4bb83a0..334f28a4 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -68,7 +68,7 @@ class _DirectDownloadIter(RequestIter): async def _request(self): try: - result = await self._sender.send(self.request) + result = await self.client._call(self._sender, self.request) if isinstance(result, types.upload.FileCdnRedirect): raise NotImplementedError # TODO Implement else: diff --git a/telethon/client/messages.py b/telethon/client/messages.py index a18c3e08..b0d76f07 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -1039,7 +1039,7 @@ class MessageMethods: if exported: try: sender = await self._borrow_exported_sender(entity.dc_id) - return await sender.send(request) + return await self._call(sender, request) finally: await self._return_exported_sender(sender) else: diff --git a/telethon/client/users.py b/telethon/client/users.py index 39d68aad..669650df 100644 --- a/telethon/client/users.py +++ b/telethon/client/users.py @@ -27,6 +27,9 @@ def _fmt_flood(delay, request, *, early=False, td=datetime.timedelta): class UserMethods: async def __call__(self: 'TelegramClient', request, ordered=False): + return await self._call(self._sender, request, ordered=ordered) + + async def _call(self: 'TelegramClient', sender, request, ordered=False): requests = (request if utils.is_list_like(request) else (request,)) for r in requests: if not isinstance(r, TLRequest): @@ -50,7 +53,7 @@ class UserMethods: self._last_request = time.time() for attempt in retry_range(self._request_retries): try: - future = self._sender.send(request, ordered=ordered) + future = sender.send(request, ordered=ordered) if isinstance(future, list): results = [] exceptions = [] From 74bced75b4175c67b31dac031f528c82e27f2ba4 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 28 Apr 2020 21:02:27 +0200 Subject: [PATCH 35/94] Check if the conversation was cancelled on send methods Fixes #1411. --- telethon/tl/custom/conversation.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/telethon/tl/custom/conversation.py b/telethon/tl/custom/conversation.py index 6d5023a7..c0d59e03 100644 --- a/telethon/tl/custom/conversation.py +++ b/telethon/tl/custom/conversation.py @@ -1,4 +1,5 @@ import asyncio +import functools import itertools import time @@ -11,6 +12,16 @@ from ... import helpers, utils, errors _EDIT_COLLISION_DELTA = 0.001 +def _checks_cancelled(f): + @functools.wraps(f) + def wrapper(self, *args, **kwargs): + if self._cancelled: + raise asyncio.CancelledError('The conversation was cancelled before') + + return f(self, *args, **kwargs) + return wrapper + + class Conversation(ChatGetter): """ Represents a conversation inside an specific chat. @@ -66,6 +77,7 @@ class Conversation(ChatGetter): self._edit_dates = {} + @_checks_cancelled async def send_message(self, *args, **kwargs): """ Sends a message in the context of this conversation. Shorthand @@ -81,6 +93,7 @@ class Conversation(ChatGetter): self._last_outgoing = ms[-1].id return sent + @_checks_cancelled async def send_file(self, *args, **kwargs): """ Sends a file in the context of this conversation. Shorthand @@ -96,6 +109,7 @@ class Conversation(ChatGetter): self._last_outgoing = ms[-1].id return sent + @_checks_cancelled def mark_read(self, message=None): """ Marks as read the latest received message if ``message is None``. @@ -379,10 +393,8 @@ class Conversation(ChatGetter): else: raise ValueError('No message was sent previously') + @_checks_cancelled def _get_result(self, future, start_time, timeout, pending, target_id): - if self._cancelled: - raise asyncio.CancelledError('The conversation was cancelled before') - due = self._total_due if timeout is None: timeout = self._timeout From c43e2a0a3a7af773af3f4342c99b44531e1b603a Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 29 Apr 2020 10:29:14 +0200 Subject: [PATCH 36/94] Return produced service message with pin_message Fixes #1394. --- telethon/client/messageparse.py | 9 ++++++++- telethon/client/messages.py | 17 +++++++++++++++-- telethon/events/album.py | 2 +- telethon/tl/custom/message.py | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/telethon/client/messageparse.py b/telethon/client/messageparse.py index 7bff5c06..1815f305 100644 --- a/telethon/client/messageparse.py +++ b/telethon/client/messageparse.py @@ -131,7 +131,14 @@ class MessageParseMethods: elif isinstance(update, ( types.UpdateNewChannelMessage, types.UpdateNewMessage)): update.message._finish_init(self, entities, input_chat) - id_to_message[update.message.id] = update.message + + # Pinning a message with `updatePinnedMessage` seems to + # always produce a service message we can't map so return + # it directly. + if hasattr(request, 'random_id'): + id_to_message[update.message.id] = update.message + else: + return update.message elif (isinstance(update, types.UpdateEditMessage) and helpers._entity_type(request.peer) != helpers._EntityType.CHANNEL): diff --git a/telethon/client/messages.py b/telethon/client/messages.py index b0d76f07..8237c380 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -1246,11 +1246,24 @@ class MessageMethods: """ message = utils.get_message_id(message) or 0 entity = await self.get_input_entity(entity) - await self(functions.messages.UpdatePinnedMessageRequest( + request = functions.messages.UpdatePinnedMessageRequest( peer=entity, id=message, silent=not notify - )) + ) + result = await self(request) + + # Unpinning does not produce a service message, and technically + # users can pass negative IDs which seem to behave as unpinning too. + if message <= 0: + return + + # Pinning in User chats (just with yourself really) does not produce a service message + if helpers._entity_type(entity) == helpers._EntityType.USER: + return + + # Pinning a message that doesn't exist would RPC-error earlier + return self._get_response_message(request, result, entity) # endregion diff --git a/telethon/events/album.py b/telethon/events/album.py index 2ba1e61b..f32dfc83 100644 --- a/telethon/events/album.py +++ b/telethon/events/album.py @@ -259,7 +259,7 @@ class Album(EventBuilder): `telethon.client.messages.MessageMethods.pin_message` with both ``entity`` and ``message`` already set. """ - await self.messages[0].pin(notify=notify) + return await self.messages[0].pin(notify=notify) def __len__(self): """ diff --git a/telethon/tl/custom/message.py b/telethon/tl/custom/message.py index 7460a487..4ece30d8 100644 --- a/telethon/tl/custom/message.py +++ b/telethon/tl/custom/message.py @@ -898,7 +898,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): # maybe just make it illegal to call messages from raw API? # That or figure out a way to always set it directly. if self._client: - await self._client.pin_message( + return await self._client.pin_message( await self.get_input_chat(), self.id, notify=notify) # endregion Public Methods From 0f1f655e5d45f3f15cc25b36511e13824f2f7663 Mon Sep 17 00:00:00 2001 From: Komron Aripov Date: Wed, 29 Apr 2020 15:42:59 +0200 Subject: [PATCH 37/94] Fix some missing things in the docs listing other libraries (#1445) --- readthedocs/developing/telegram-api-in-other-languages.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readthedocs/developing/telegram-api-in-other-languages.rst b/readthedocs/developing/telegram-api-in-other-languages.rst index cdb81eb2..63767c15 100644 --- a/readthedocs/developing/telegram-api-in-other-languages.rst +++ b/readthedocs/developing/telegram-api-in-other-languages.rst @@ -10,7 +10,7 @@ understand the official Telegram documentation) on several languages (even more Python too), listed below: C -* += Possibly the most well-known unofficial open source implementation out there by `@vysheng `__, @@ -31,6 +31,9 @@ JavaScript `Ali Gasymov `__ made the `@mtproto/core `__ library for the browser and nodejs installable via `npm `__. +`painor `__ is the primary author of `gramjs `__, +a Telegram client implementation in JavaScript. + Kotlin ====== From db16cf5548f3653df68b63aad671e97271741884 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 1 May 2020 14:10:43 +0200 Subject: [PATCH 38/94] Update to layer 113 --- telethon_generator/data/api.tl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/telethon_generator/data/api.tl b/telethon_generator/data/api.tl index 244f2003..728c4bd3 100644 --- a/telethon_generator/data/api.tl +++ b/telethon_generator/data/api.tl @@ -651,7 +651,7 @@ messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_off exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; -messageFwdHeader#ec338270 flags:# from_id:flags.0?int from_name:flags.5?string date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int = MessageFwdHeader; +messageFwdHeader#353a686b flags:# from_id:flags.0?int from_name:flags.5?string date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader; auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType; @@ -912,9 +912,6 @@ fileHash#6242c773 offset:int limit:int hash:bytes = FileHash; inputClientProxy#75588b3f address:string port:int = InputClientProxy; -help.proxyDataEmpty#e09e1fb8 expires:int = help.ProxyData; -help.proxyDataPromo#2bf7ee23 expires:int peer:Peer chats:Vector users:Vector = help.ProxyData; - help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate; help.termsOfServiceUpdate#28ecf961 expires:int terms_of_service:help.TermsOfService = help.TermsOfServiceUpdate; @@ -1140,6 +1137,9 @@ messageInteractionCounters#ad4fc9bd msg_id:int views:int forwards:int = MessageI stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueAndPrev views_per_post:StatsAbsValueAndPrev shares_per_post:StatsAbsValueAndPrev enabled_notifications:StatsPercentValue growth_graph:StatsGraph followers_graph:StatsGraph mute_graph:StatsGraph top_hours_graph:StatsGraph interactions_graph:StatsGraph iv_interactions_graph:StatsGraph views_by_source_graph:StatsGraph new_followers_by_source_graph:StatsGraph languages_graph:StatsGraph recent_message_interactions:Vector = stats.BroadcastStats; +help.promoDataEmpty#98f6ac75 expires:int = help.PromoData; +help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector users:Vector psa_type:flags.1?string psa_message:flags.2?string = help.PromoData; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1411,7 +1411,6 @@ help.getAppChangelog#9010ef6f prev_app_version:string = Updates; help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool; help.getCdnConfig#52029342 = CdnConfig; help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls; -help.getProxyData#3d7758e1 = help.ProxyData; help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate; help.acceptTermsOfService#ee72f79a id:DataJSON = Bool; help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo; @@ -1421,6 +1420,8 @@ help.getPassportConfig#c661ad08 hash:int = help.PassportConfig; help.getSupportName#d360e72c = help.SupportName; help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo; help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector = help.UserInfo; +help.getPromoData#c0977421 = help.PromoData; +help.hidePromoData#1e251c95 peer:InputPeer = Bool; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; @@ -1494,7 +1495,7 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; -stats.getBroadcastStats#e6300dba flags:# dark:flags.0?true channel:InputChannel tz_offset:int = stats.BroadcastStats; +stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; -// LAYER 112 +// LAYER 113 From 4393ec0b83d511b6a20d8a20334138730f084375 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 5 May 2020 09:28:37 +0200 Subject: [PATCH 39/94] Support dice autocast and update docs on send_file for dice --- telethon/client/uploads.py | 17 +++++++++++++++++ telethon/utils.py | 3 +++ 2 files changed, 20 insertions(+) diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index fb924595..c8564ad1 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -155,6 +155,10 @@ class UploadMethods: * A handle to an uploaded file (from `upload_file`). + * A :tl:`InputMedia` instance. For example, if you want to + send a dice use :tl:`InputMediaDice`, or if you want to + send a contact use :tl:`InputMediaContact`. + To send an album, you should provide a list in this parameter. If a list or similar is provided, the files in it will be @@ -270,6 +274,19 @@ class UploadMethods: 'bytes: {:.2%}'.format(current / total)) await client.send_file(chat, file, progress_callback=callback) + + # Dices, including dart and other future emoji + from telethon.tl import types + await client.send_file(chat, types.InputMediaDice('')) + await client.send_file(chat, types.InputMediaDice('🎯')) + + # Contacts + await client.send_file(chat, types.InputMediaContact( + phone_number='+34 123 456 789', + first_name='Example', + last_name='', + vcard='' + )) """ # TODO Properly implement allow_cache to reuse the sha256 of the file # i.e. `None` was used diff --git a/telethon/utils.py b/telethon/utils.py index 95bb65aa..b00644ca 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -512,6 +512,9 @@ def get_input_media( venue_type='' ) + if isinstance(media, types.MessageMediaDice): + return types.InputMediaDice(media.emoticon) + if isinstance(media, ( types.MessageMediaEmpty, types.MessageMediaUnsupported, types.ChatPhotoEmpty, types.UserProfilePhotoEmpty, From 393da7e57ab647140b8722e05d19640a2e57a2c0 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 9 May 2020 17:35:26 +0200 Subject: [PATCH 40/94] Expose missing embed_links param in edit_permissions --- telethon/client/chats.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/telethon/client/chats.py b/telethon/client/chats.py index dc28e7f8..7f540377 100644 --- a/telethon/client/chats.py +++ b/telethon/client/chats.py @@ -922,6 +922,7 @@ class ChatMethods: send_gifs: bool = True, send_games: bool = True, send_inline: bool = True, + embed_link_previews: bool = True, send_polls: bool = True, change_info: bool = True, invite_users: bool = True, @@ -986,6 +987,12 @@ class ChatMethods: send_inline (`bool`, optional): Whether the user is able to use inline bots or not. + embed_link_previews (`bool`, optional): + Whether the user is able to enable the link preview in the + messages they send. Note that the user will still be able to + send messages with links if this permission is removed, but + these links won't display a link preview. + send_polls (`bool`, optional): Whether the user is able to send polls or not. @@ -1031,6 +1038,7 @@ class ChatMethods: send_gifs=not send_gifs, send_games=not send_games, send_inline=not send_inline, + embed_links=not embed_link_previews, send_polls=not send_polls, change_info=not change_info, invite_users=not invite_users, From c45f2e7c390c44c1da5027a46d5b81ae37120b1d Mon Sep 17 00:00:00 2001 From: penn5 Date: Wed, 13 May 2020 17:50:56 +0100 Subject: [PATCH 41/94] Handle flood waits of 0 seconds more gracefully (#1460) --- telethon/client/users.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/telethon/client/users.py b/telethon/client/users.py index 669650df..49592494 100644 --- a/telethon/client/users.py +++ b/telethon/client/users.py @@ -92,6 +92,11 @@ class UserMethods: self._flood_waited_requests\ [request.CONSTRUCTOR_ID] = time.time() + e.seconds + # In test servers, FLOOD_WAIT_0 has been observed, and sleeping for + # such a short amount will cause retries very fast leading to issues. + if e.seconds == 0: + e.seconds = 1 + if e.seconds <= self.flood_sleep_threshold: self._log[__name__].info(*_fmt_flood(e.seconds, request)) await asyncio.sleep(e.seconds, loop=self._loop) From 634bc3a8bd6abcdceaad3d2d0a9039a57c98cbc0 Mon Sep 17 00:00:00 2001 From: JuniorJPDJ Date: Sat, 16 May 2020 09:58:37 +0200 Subject: [PATCH 42/94] Allow event's func to be async (#1461) Fixes #1344. --- telethon/client/updates.py | 6 +++++- telethon/events/callbackquery.py | 6 ++++-- telethon/events/common.py | 17 ++++++++++------- telethon/events/raw.py | 6 ++++-- telethon/tl/custom/conversation.py | 13 ++++++++++--- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/telethon/client/updates.py b/telethon/client/updates.py index 854860fd..10eb8a77 100644 --- a/telethon/client/updates.py +++ b/telethon/client/updates.py @@ -1,4 +1,5 @@ import asyncio +import inspect import itertools import random import time @@ -424,7 +425,10 @@ class UpdateMethods: if not builder.resolved: await builder.resolve(self) - if not builder.filter(event): + filter = builder.filter(event) + if inspect.isawaitable(filter): + filter = await filter + if not filter: continue try: diff --git a/telethon/events/callbackquery.py b/telethon/events/callbackquery.py index c35e348b..d1558d21 100644 --- a/telethon/events/callbackquery.py +++ b/telethon/events/callbackquery.py @@ -118,8 +118,10 @@ class CallbackQuery(EventBuilder): elif event.query.data != self.match: return - if not self.func or self.func(event): - return event + if self.func: + # Return the result of func directly as it may need to be awaited + return self.func(event) + return True class Event(EventCommon, SenderGetter): """ diff --git a/telethon/events/common.py b/telethon/events/common.py index 42586608..f5ff5b50 100644 --- a/telethon/events/common.py +++ b/telethon/events/common.py @@ -55,7 +55,7 @@ class EventBuilder(abc.ABC): which will be ignored if ``blacklist_chats=True``. func (`callable`, optional): - A callable function that should accept the event as input + A callable (async or not) function that should accept the event as input parameter, and return a value indicating whether the event should be dispatched or not (any truthy value will do, it does not need to be a `bool`). It works like a custom filter: @@ -105,13 +105,13 @@ class EventBuilder(abc.ABC): def filter(self, event): """ - If the ID of ``event._chat_peer`` isn't in the chats set (or it is - but the set is a blacklist) returns `None`, otherwise the event. + Returns a truthy value if the event passed the filter and should be + used, or falsy otherwise. The return value may need to be awaited. The events must have been resolved before this can be called. """ if not self.resolved: - return None + return if self.chats is not None: # Note: the `event.chat_id` property checks if it's `None` for us @@ -119,10 +119,13 @@ class EventBuilder(abc.ABC): if inside == self.blacklist_chats: # If this chat matches but it's a blacklist ignore. # If it doesn't match but it's a whitelist ignore. - return None + return - if not self.func or self.func(event): - return event + if not self.func: + return True + + # Return the result of func directly as it may need to be awaited + return self.func(event) class EventCommon(ChatGetter, abc.ABC): diff --git a/telethon/events/raw.py b/telethon/events/raw.py index 912a934d..84910778 100644 --- a/telethon/events/raw.py +++ b/telethon/events/raw.py @@ -46,6 +46,8 @@ class Raw(EventBuilder): return update def filter(self, event): - if ((not self.types or isinstance(event, self.types)) - and (not self.func or self.func(event))): + if not self.types or isinstance(event, self.types): + if self.func: + # Return the result of func directly as it may need to be awaited + return self.func(event) return event diff --git a/telethon/tl/custom/conversation.py b/telethon/tl/custom/conversation.py index c0d59e03..46b67aa8 100644 --- a/telethon/tl/custom/conversation.py +++ b/telethon/tl/custom/conversation.py @@ -1,5 +1,6 @@ import asyncio import functools +import inspect import itertools import time @@ -312,9 +313,15 @@ class Conversation(ChatGetter): for key, (ev, fut) in list(self._custom.items()): ev_type = type(ev) inst = built[ev_type] - if inst and ev.filter(inst): - fut.set_result(inst) - del self._custom[key] + + if inst: + filter = ev.filter(inst) + if inspect.isawaitable(filter): + filter = await filter + + if filter: + fut.set_result(inst) + del self._custom[key] def _on_new_message(self, response): response = response.message From 856538635d5ffda304b157ef73205acf2f1c5c2f Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 17 May 2020 09:27:44 +0200 Subject: [PATCH 43/94] Document FRESH_CHANGE_PHONE_FORBIDDEN Closes #1464. --- telethon_generator/data/errors.csv | 1 + telethon_generator/data/methods.csv | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index 966b2324..9a5197b0 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -109,6 +109,7 @@ FLOOD_WAIT_X,420,A wait of {seconds} seconds is required FOLDER_ID_EMPTY,400,The folder you tried to delete was already empty FOLDER_ID_INVALID,400,The folder you tried to use was not valid FRESH_CHANGE_ADMINS_FORBIDDEN,400,Recently logged-in users cannot add or change admins +FRESH_CHANGE_PHONE_FORBIDDEN,406,Recently logged-in users cannot use this request FRESH_RESET_AUTHORISATION_FORBIDDEN,406,The current session is too new and cannot be used to reset other authorisations yet GAME_BOT_INVALID,400,You cannot send that game with the current bot GIF_ID_INVALID,400,The provided GIF ID is invalid diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index 635c4a16..9896e64f 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -43,7 +43,7 @@ account.saveAutoDownloadSettings,user, account.saveSecureValue,user,PASSWORD_REQUIRED account.saveTheme,user, account.saveWallPaper,user,WALLPAPER_INVALID -account.sendChangePhoneCode,user,PHONE_NUMBER_INVALID +account.sendChangePhoneCode,user,FRESH_CHANGE_PHONE_FORBIDDEN PHONE_NUMBER_INVALID account.sendConfirmPhoneCode,user,HASH_INVALID account.sendVerifyEmailCode,user,EMAIL_INVALID account.sendVerifyPhoneCode,user, From 29eb90e503d9e81ef4d157201b42b159561d438c Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 17 May 2020 09:35:44 +0200 Subject: [PATCH 44/94] Fix get_pinned_message in Chat Closes #1458. --- telethon/events/chataction.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/telethon/events/chataction.py b/telethon/events/chataction.py index 34d9c693..52c7bbf8 100644 --- a/telethon/events/chataction.py +++ b/telethon/events/chataction.py @@ -1,5 +1,5 @@ from .common import EventBuilder, EventCommon, name_inner_event -from .. import utils +from .. import utils, helpers from ..tl import types, functions @@ -261,17 +261,8 @@ class ChatAction(EventBuilder): if isinstance(self._pinned_message, int)\ and await self.get_input_chat(): - r = await self._client(functions.channels.GetMessagesRequest( - self._input_chat, [self._pinned_message] - )) - try: - self._pinned_message = next( - x for x in r.messages - if isinstance(x, types.Message) - and x.id == self._pinned_message - ) - except StopIteration: - pass + self._pinned_message = await self._client.get_messages( + self._input_chat, ids=self._pinned_message) if isinstance(self._pinned_message, types.Message): return self._pinned_message From 165950169f10490a820b68a6bcb1dfc12a7873f4 Mon Sep 17 00:00:00 2001 From: apepenkov <39992738+apepenkov@users.noreply.github.com> Date: Thu, 21 May 2020 14:22:37 +0300 Subject: [PATCH 45/94] Add payment example (#1470) --- telethon_examples/README.md | 12 +++ telethon_examples/payment.py | 183 +++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 telethon_examples/payment.py diff --git a/telethon_examples/README.md b/telethon_examples/README.md index 29af0e4d..1e4d12b0 100644 --- a/telethon_examples/README.md +++ b/telethon_examples/README.md @@ -130,6 +130,18 @@ assumes some [`asyncio`] knowledge, but otherwise is easy to follow. ![Screenshot of the tkinter GUI][tkinter GUI] +### [`payment.py`](https://raw.githubusercontent.com/LonamiWebs/Telethon/master/telethon_examples/payment.py) + +* Usable as: **bot**. +* Difficulty: **medium**. + +This example shows how to make invoices (Telegram's way of requesting payments) via a bot account. The example does not include how to add shipping information, though. + +You'll need to obtain a "provider token" to use this example, so please read [Telegram's guide on payments](https://core.telegram.org/bots/payments) before using this example. + + +It makes use of the ["raw API"](https://tl.telethon.dev) (that is, no friendly `client.` methods), which can be helpful in understanding how it works and how it can be used. + [Telethon]: https://github.com/LonamiWebs/Telethon [CC0 License]: https://github.com/LonamiWebs/Telethon/blob/master/telethon_examples/LICENSE diff --git a/telethon_examples/payment.py b/telethon_examples/payment.py new file mode 100644 index 00000000..4586536a --- /dev/null +++ b/telethon_examples/payment.py @@ -0,0 +1,183 @@ +from telethon import TelegramClient, events, types, functions + +import asyncio +import logging +import tracemalloc +import os +import time +import sys + +loop = asyncio.get_event_loop() + +""" +Provider token can be obtained via @BotFather. more info at https://core.telegram.org/bots/payments#getting-a-token + +If you are using test token, set test=True in generate_invoice function, +If you are using real token, set test=False +""" +provider_token = '' + +tracemalloc.start() +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.WARNING) +logger = logging.getLogger(__name__) + + +def get_env(name, message, cast=str): + if name in os.environ: + return os.environ[name] + while True: + value = input(message) + try: + return cast(value) + except ValueError as e: + print(e, file=sys.stderr) + time.sleep(1) + + +bot = TelegramClient( + os.environ.get('TG_SESSION', 'payment'), + get_env('TG_API_ID', 'Enter your API ID: ', int), + get_env('TG_API_HASH', 'Enter your API hash: '), + proxy=None +) + + +# That event is handled when customer enters his card/etc, on final pre-checkout +# If we don't `SetBotPrecheckoutResultsRequest`, money won't be charged from buyer, and nothing will happen next. +@bot.on(events.Raw(types.UpdateBotPrecheckoutQuery)) +async def payment_pre_checkout_handler(event: types.UpdateBotPrecheckoutQuery): + if event.payload.decode('UTF-8') == 'product A': + # so we have to confirm payment + await bot( + functions.messages.SetBotPrecheckoutResultsRequest( + query_id=event.query_id, + success=True, + error=None + ) + ) + elif event.payload.decode('UTF-8') == 'product B': + # same for another + await bot( + functions.messages.SetBotPrecheckoutResultsRequest( + query_id=event.query_id, + success=True, + error=None + ) + ) + else: + # for example, something went wrong (whatever reason). We can tell customer about that: + await bot( + functions.messages.SetBotPrecheckoutResultsRequest( + query_id=event.query_id, + success=False, + error='Something went wrong' + ) + ) + + raise events.StopPropagation + + +# That event is handled at the end, when customer payed. +@bot.on(events.Raw(types.UpdateNewMessage)) +async def payment_received_handler(event): + if isinstance(event.message.action, types.MessageActionPaymentSentMe): + payment: types.MessageActionPaymentSentMe = event.message.action + # do something after payment was recieved + if payment.payload.decode('UTF-8') == 'product A': + await bot.send_message(event.message.from_id, 'Thank you for buying product A!') + elif payment.payload.decode('UTF-8') == 'product B': + await bot.send_message(event.message.from_id, 'Thank you for buying product B!') + raise events.StopPropagation + + +# let's put it in one function for more easier way +def generate_invoice(price_label: str, price_amount: int, currency: str, title: str, + description: str, payload: str, start_param: str) -> types.InputMediaInvoice: + price = types.LabeledPrice(label=price_label, amount=price_amount) # label - just a text, amount=10000 means 100.00 + invoice = types.Invoice( + currency=currency, # currency like USD + prices=[price], # there could be a couple of prices. + test=True, # if you're working with test token, else set test=False. + # More info at https://core.telegram.org/bots/payments + + # params for requesting specific fields + name_requested=False, + phone_requested=False, + email_requested=False, + shipping_address_requested=False, + + # if price changes depending on shipping + flexible=False, + + # send data to provider + phone_to_provider=False, + email_to_provider=False + ) + return types.InputMediaInvoice( + title=title, + description=description, + invoice=invoice, + payload=payload.encode('UTF-8'), # payload, which will be sent to next 2 handlers + provider=provider_token, + + provider_data=types.DataJSON('{}'), + # data about the invoice, which will be shared with the payment provider. A detailed description of + # required fields should be provided by the payment provider. + + start_param=start_param, + # Unique deep-linking parameter. May also be used in UpdateBotPrecheckoutQuery + # see: https://core.telegram.org/bots#deep-linking + # it may be the empty string if not needed + + ) + + +@bot.on(events.NewMessage(pattern='/start')) +async def start_handler(event: events.NewMessage.Event): + await event.respond('/product_a - product A\n/product_b - product B\n/product_c - product, shall cause an error') + + +@bot.on(events.NewMessage(pattern='/product_a')) +async def start_handler(event: events.NewMessage.Event): + await bot.send_message( + event.chat_id, 'Sending invoice A', + file=generate_invoice( + price_label='Pay', price_amount=10000, currency='RUB', title='Title A', description='description A', + payload='product A', start_param='abc' + ) + ) + + +@bot.on(events.NewMessage(pattern='/product_b')) +async def start_handler(event: events.NewMessage.Event): + await bot.send_message( + event.chat_id, 'Sending invoice B', + file=generate_invoice( + price_label='Pay', price_amount=20000, currency='RUB', title='Title B', description='description B', + payload='product B', start_param='abc' + ) + ) + + +@bot.on(events.NewMessage(pattern='/product_c')) +async def start_handler(event: events.NewMessage.Event): + await bot.send_message( + event.chat_id, 'Sending invoice C', + file=generate_invoice( + price_label='Pay', price_amount=50000, currency='RUB', title='Title C', + description='description c - shall cause an error', payload='product C', start_param='abc' + ) + ) + + +async def main(): + await bot.start() + await bot.run_until_disconnected() + + +if __name__ == '__main__': + if not provider_token: + logger.error("No provider token supplied.") + exit(1) + loop.run_until_complete(main()) From 88e7f0da654661e659e871ff99201fe477a01c4e Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 24 May 2020 18:35:06 +0200 Subject: [PATCH 46/94] Fix return value when fwding msgs if some are missing It was supposed to return None for the spots were it failed to fwd a message, but instead only those that were present were returned, because we were iterating over the wrong object (dict and not list). --- telethon/client/messageparse.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/telethon/client/messageparse.py b/telethon/client/messageparse.py index 1815f305..bb7c3a3b 100644 --- a/telethon/client/messageparse.py +++ b/telethon/client/messageparse.py @@ -211,13 +211,18 @@ class MessageParseMethods: # deleted or `WORKER_BUSY_TOO_LONG_RETRY` if there are issues at # Telegram), in which case we get some "missing" message mappings. # Log them with the hope that we can better work around them. + # + # This also happens when trying to forward messages that can't + # be forwarded because they don't exist (0, service, deleted) + # among others which could be (like deleted or existing). self._log[__name__].warning( 'Request %s had missing message mappings %s', request, result) return [ - mapping.get(random_to_id.get(rnd)) - or opposite.get(random_to_id.get(rnd)) - for rnd in random_to_id + (mapping.get(random_to_id[rnd]) or opposite.get(random_to_id[rnd])) + if rnd in random_to_id + else None + for rnd in random_id ] # endregion From 02d0cbcfabe99139b790809664143e19ddbc4afa Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 24 May 2020 18:40:40 +0200 Subject: [PATCH 47/94] Document FILE_REFERENCE_EMPTY --- telethon_generator/data/errors.csv | 1 + telethon_generator/data/methods.csv | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index 9a5197b0..045e0370 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -102,6 +102,7 @@ FILE_PART_LENGTH_INVALID,400,The length of a file part is invalid FILE_PART_SIZE_CHANGED,400,The file part size (chunk size) cannot change during upload FILE_PART_SIZE_INVALID,400,The provided file part size is invalid FILE_PART_X_MISSING,400,Part {which} of the file is missing from storage +FILE_REFERENCE_EMPTY,400,The file reference must exist to access the media and it cannot be empty FILEREF_UPGRADE_NEEDED,406,The file reference needs to be refreshed before being used again FIRSTNAME_INVALID,400,The first name is invalid FLOOD_TEST_PHONE_WAIT_X,420,A wait of {seconds} seconds is required in the test servers diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index 9896e64f..374d867c 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -272,7 +272,7 @@ messages.sendEncrypted,user,CHAT_ID_INVALID DATA_INVALID ENCRYPTION_DECLINED MSG 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 BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EMOTICON_INVALID EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID GAME_BOT_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_ANSWERS_INVALID POLL_OPTION_DUPLICATE POLL_QUESTION_INVALID QUIZ_CORRECT_ANSWERS_EMPTY QUIZ_CORRECT_ANSWERS_TOO_MUCH QUIZ_CORRECT_ANSWER_INVALID QUIZ_MULTIPLE_INVALID 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.sendMedia,both,BOT_PAYMENTS_DISABLED BOT_POLLS_DISABLED BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EMOTICON_INVALID EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID FILE_REFERENCE_EMPTY GAME_BOT_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_ANSWERS_INVALID POLL_OPTION_DUPLICATE POLL_QUESTION_INVALID QUIZ_CORRECT_ANSWERS_EMPTY QUIZ_CORRECT_ANSWERS_TOO_MUCH QUIZ_CORRECT_ANSWER_INVALID QUIZ_MULTIPLE_INVALID 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 MSG_ID_INVALID PEER_ID_INVALID POLL_OPTION_INVALID RANDOM_ID_DUPLICATE REPLY_MARKUP_INVALID REPLY_MARKUP_TOO_LONG SCHEDULE_BOT_NOT_ALLOWED SCHEDULE_DATE_TOO_LATE SCHEDULE_STATUS_PRIVATE 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.sendScheduledMessages,user, From a46ce053f1736dc4f0211b5b3db909bb48220ee2 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 24 May 2020 19:01:05 +0200 Subject: [PATCH 48/94] Fix another crash for return value when sending albums --- telethon/client/messageparse.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/telethon/client/messageparse.py b/telethon/client/messageparse.py index bb7c3a3b..67860a4a 100644 --- a/telethon/client/messageparse.py +++ b/telethon/client/messageparse.py @@ -135,7 +135,11 @@ class MessageParseMethods: # Pinning a message with `updatePinnedMessage` seems to # always produce a service message we can't map so return # it directly. - if hasattr(request, 'random_id'): + # + # It could also be a list (e.g. when sending albums). + # + # TODO this method is getting messier and messier as time goes on + if hasattr(request, 'random_id') or utils.is_list_like(request): id_to_message[update.message.id] = update.message else: return update.message From 8330635a725717390e1981848737b8988a419fe1 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 26 May 2020 09:31:36 +0200 Subject: [PATCH 49/94] Bump to v1.14.0 --- readthedocs/misc/changelog.rst | 62 ++++++++++++++++++++++++++++++++++ telethon/version.py | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/readthedocs/misc/changelog.rst b/readthedocs/misc/changelog.rst index a75aa665..eb1df48d 100644 --- a/readthedocs/misc/changelog.rst +++ b/readthedocs/misc/changelog.rst @@ -13,6 +13,68 @@ it can take advantage of new goodies! .. contents:: List of All Versions +Minor quality of life improvements (v1.14) +========================================== + +*Published at 2020/05/26* + ++------------------------+ +| Scheme layer used: 113 | ++------------------------+ + +Some nice things that were missing, along with the usual bug-fixes. + +Additions +~~~~~~~~~ + +* New `Message.dice ` property. +* The ``func=`` parameter of events can now be an ``async`` function. + +Bug fixes +~~~~~~~~~ + +* Fixed `client.action() ` + having an alias wrong. +* Fixed incorrect formatting of some errors. +* Probably more reliable detection of pin events in small groups. +* Fixed send methods on `client.conversation() + ` were not honoring + cancellation. +* Flood waits of zero seconds are handled better. +* Getting the pinned message in a chat was failing. +* Fixed the return value when forwarding messages if some were missing + and also the return value of albums. + +Enhancements +~~~~~~~~~~~~ + +* ``.tgs`` files are now recognised as animated stickers. +* The service message produced by `Message.pin() + ` is now returned. +* Sending a file with `client.send_file() + ` now works fine when + you pass an existing dice media (e.g. sending a message copy). +* `client.edit_permissions() ` + now has the ``embed_links`` parameter which was missing. + +Bug Fixes (v1.13) +================= + +*Published at 2020/04/25* + ++------------------------+ +| Scheme layer used: 112 | ++------------------------+ + +Bug fixes and layer bump. + +Bug fixes +~~~~~~~~~ + +* Passing ``None`` as the entity to `client.delete_messages() + ` would fail. +* When downloading a thumbnail, the name inferred was wrong. + Bug Fixes (v1.12) ================= diff --git a/telethon/version.py b/telethon/version.py index 8846b60c..12445eee 100644 --- a/telethon/version.py +++ b/telethon/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '1.13.0' +__version__ = '1.14.0' From 6c7cfd79b96262c6930698024e7f0f265022c4c8 Mon Sep 17 00:00:00 2001 From: Antoni Mur Date: Wed, 27 May 2020 20:34:27 +0300 Subject: [PATCH 50/94] Add Taas to the list of alternatives in other languages (#1474) --- readthedocs/developing/telegram-api-in-other-languages.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readthedocs/developing/telegram-api-in-other-languages.rst b/readthedocs/developing/telegram-api-in-other-languages.rst index 63767c15..943a4a1c 100644 --- a/readthedocs/developing/telegram-api-in-other-languages.rst +++ b/readthedocs/developing/telegram-api-in-other-languages.rst @@ -45,6 +45,11 @@ languages for `@badoualy `__, currently as a beta– yet working. +Language-Agnostic +================= + +`Taas `__ is a service that lets you use Telegram API with any HTTP client via API. Using tdlib under the hood, Taas is commercial service, but allows free access if you use under 5000 requests per month. + PHP === From 493f69f195380850ee73ceb04c5ca0d5a880c641 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 5 Jun 2020 09:28:52 +0200 Subject: [PATCH 51/94] Update to layer 114 --- telethon_generator/data/api.tl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/telethon_generator/data/api.tl b/telethon_generator/data/api.tl index 728c4bd3..c381558b 100644 --- a/telethon_generator/data/api.tl +++ b/telethon_generator/data/api.tl @@ -357,6 +357,7 @@ updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector = updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; +updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -421,7 +422,7 @@ inputDocumentEmpty#72f0eaae = InputDocument; inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument; documentEmpty#36f8c871 id:long = Document; -document#9ba29cc1 flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector dc_id:int attributes:Vector = Document; +document#1e87342b flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector video_thumbs:flags.1?Vector dc_id:int attributes:Vector = Document; help.support#17c6b5f6 phone_number:string user:User = help.Support; @@ -1140,6 +1141,8 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA help.promoDataEmpty#98f6ac75 expires:int = help.PromoData; help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector users:Vector psa_type:flags.1?string psa_message:flags.2?string = help.PromoData; +videoSize#435bb987 type:string location:FileLocation w:int h:int size:int = VideoSize; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1485,6 +1488,7 @@ phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool; phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates; phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates; phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; +phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -1498,4 +1502,4 @@ folders.deleteFolder#1c295881 folder_id:int = Updates; stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; -// LAYER 113 +// LAYER 114 From bfa995d52bf4cbb0c4b89db666732b4ff01932f6 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 5 Jun 2020 21:17:09 +0200 Subject: [PATCH 52/94] Don't crash when receiving updates prior to login Fixes #1467, and enables #1471. --- telethon/client/updates.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/telethon/client/updates.py b/telethon/client/updates.py index 10eb8a77..6f93351b 100644 --- a/telethon/client/updates.py +++ b/telethon/client/updates.py @@ -397,6 +397,9 @@ class UpdateMethods: # Some updates require our own ID, so we must make sure # that the event builder has offline access to it. Calling # `get_me()` will cache it under `self._self_input_peer`. + # + # It will return `None` if we haven't logged in yet which is + # fine, we will just retry next time anyway. await self.get_me(input_peer=True) built = EventBuilderDict(self, update, others) @@ -566,8 +569,15 @@ class EventBuilderDict: try: return self.__dict__[builder] except KeyError: + # Updates may arrive before login (like updateLoginToken) and we + # won't have our self ID yet (anyway only new messages need it). + self_id = ( + self.client._self_input_peer.user_id + if self.client._self_input_peer + else None + ) event = self.__dict__[builder] = builder.build( - self.update, self.others, self.client._self_input_peer.user_id) + self.update, self.others, self_id) if isinstance(event, EventCommon): event.original_update = self.update From c904b7ccd8a1dbc185bb614df51ebb124de109bd Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 5 Jun 2020 21:58:59 +0200 Subject: [PATCH 53/94] Add a friendly method for QR login Closes #1471. --- readthedocs/modules/custom.rst | 9 +++ telethon/client/auth.py | 38 +++++++++++ telethon/qrlogin.py | 119 +++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 telethon/qrlogin.py diff --git a/readthedocs/modules/custom.rst b/readthedocs/modules/custom.rst index 1f1329bf..8df74a24 100644 --- a/readthedocs/modules/custom.rst +++ b/readthedocs/modules/custom.rst @@ -136,6 +136,15 @@ MessageButton :show-inheritance: +QRLogin +======= + +.. automodule:: telethon.qrlogin + :members: + :undoc-members: + :show-inheritance: + + SenderGetter ============ diff --git a/telethon/client/auth.py b/telethon/client/auth.py index b96ee4dd..01752c1e 100644 --- a/telethon/client/auth.py +++ b/telethon/client/auth.py @@ -5,6 +5,7 @@ import sys import typing from .. import utils, helpers, errors, password as pwd_mod +from ..qrlogin import QRLogin from ..tl import types, functions if typing.TYPE_CHECKING: @@ -496,6 +497,43 @@ class AuthMethods: return result + async def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> QRLogin: + """ + Initiates the QR login procedure. + + Note that you must be connected before invoking this, as with any + other request. + + It is up to the caller to decide how to present the code to the user, + whether it's the URL, using the token bytes directly, or generating + a QR code and displaying it by other means. + + See the documentation for `QRLogin` to see how to proceed after this. + + Arguments + ignored_ids (List[`int`]): + List of already logged-in user IDs, to prevent logging in + twice with the same user. + + Returns + An instance of `QRLogin`. + + Example + .. code-block:: python + + def display_url_as_qr(url): + pass # do whatever to show url as a qr to the user + + qr_login = await client.qr_login() + display_url_as_qr(qr_login.url) + + # Important! You need to wait for the login to complete! + await qr_login.wait() + """ + qr_login = QRLogin(self, ignored_ids or []) + await qr_login.recreate() + return qr_login + async def log_out(self: 'TelegramClient') -> bool: """ Logs out Telegram and deletes the current ``*.session`` file. diff --git a/telethon/qrlogin.py b/telethon/qrlogin.py new file mode 100644 index 00000000..afb7caf7 --- /dev/null +++ b/telethon/qrlogin.py @@ -0,0 +1,119 @@ +import asyncio +import base64 +import datetime + +from telethon import events +from telethon.tl import types, functions + + +class QRLogin: + """ + QR login information. + + Most of the time, you will present the `url` as a QR code to the user, + and while it's being shown, call `wait`. + """ + def __init__(self, client, ignored_ids): + self._client = client + self._request = functions.auth.ExportLoginTokenRequest( + self._client.api_id, self._client.api_hash, ignored_ids) + self._resp = None + + async def recreate(self): + """ + Generates a new token and URL for a new QR code, useful if the code + has expired before it was imported. + """ + self._resp = await self._client(self._request) + + @property + def token(self) -> bytes: + """ + The binary data representing the token. + + It can be used by a previously-authorized client in a call to + :tl:`auth.importLoginToken` to log the client that originally + requested the QR login. + """ + return self._resp.token + + @property + def url(self) -> str: + """ + The ``tg://login`` URI with the token. When opened by a Telegram + application where the user is logged in, it will import the login + token. + + If you want to display a QR code to the user, this is the URL that + should be launched when the QR code is scanned (the URL that should + be contained in the QR code image you generate). + + Whether you generate the QR code image or not is up to you, and the + library can't do this for you due to the vast ways of generating and + displaying the QR code that exist. + + The URL simply consists of `token` base64-encoded. + """ + return 'tg://login?token={}'.format(base64.b64encode(self._resp.token)) + + @property + def expires(self) -> datetime.datetime: + """ + The `datetime` at which the QR code will expire. + + If you want to try again, you will need to call `recreate`. + """ + return self._resp.expires + + async def wait(self, timeout: float = None): + """ + Waits for the token to be imported by a previously-authorized client, + either by scanning the QR, launching the URL directly, or calling the + import method. + + This method **must** be called before the QR code is scanned, and + must be executing while the QR code is being scanned. Otherwise, the + login will not complete. + + Will raise `asyncio.TimeoutError` if the login doesn't complete on + time. + + Arguments + timeout (float): + The timeout, in seconds, to wait before giving up. By default + the library will wait until the token expires, which is often + what you want. + + Returns + On success, an instance of :tl:`User`. On failure it will raise. + """ + if timeout is None: + timeout = (self._resp.expires - datetime.datetime.now(tz=datetime.timezone.utc)).total_seconds() + + event = asyncio.Event() + + async def handler(_update): + event.set() + + self._client.add_event_handler(handler, events.Raw(types.UpdateLoginToken)) + + try: + # Will raise timeout error if it doesn't complete quick enough, + # which we want to let propagate + await asyncio.wait_for(event.wait(), timeout=timeout) + finally: + self._client.remove_event_handler(handler) + + # We got here without it raising timeout error, so we can proceed + resp = await self._client(self._request) + if isinstance(resp, types.auth.LoginTokenMigrateTo): + await self._client._switch_dc(resp.dc_id) + resp = await self._client(functions.auth.ImportLoginTokenRequest(resp.token)) + # resp should now be auth.loginTokenSuccess + + if isinstance(resp, types.auth.LoginTokenSuccess): + user = resp.authorization.user + self._client._on_login(user) + return user + + raise TypeError('Login token response was unexpected: {}'.format(resp)) From 8557effe1332b8a6c0a84a77835acf7c9f489ce5 Mon Sep 17 00:00:00 2001 From: penn5 Date: Sat, 6 Jun 2020 12:47:46 +0100 Subject: [PATCH 54/94] Fix docs in InlineQuery (#1425) --- telethon/events/inlinequery.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/telethon/events/inlinequery.py b/telethon/events/inlinequery.py index b23e3ac2..3ae3874e 100644 --- a/telethon/events/inlinequery.py +++ b/telethon/events/inlinequery.py @@ -79,11 +79,11 @@ class InlineQuery(EventBuilder): Represents the event of a new callback query. Members: - query (:tl:`UpdateBotCallbackQuery`): - The original :tl:`UpdateBotCallbackQuery`. + query (:tl:`UpdateBotInlineQuery`): + The original :tl:`UpdateBotInlineQuery`. - Make sure to access the `text` of the query if - that's what you want instead working with this. + Make sure to access the `text` property of the query if + you want the text rather than the actual query object. pattern_match (`obj`, optional): The resulting object from calling the passed ``pattern`` From bc03419902b3a369a3d68d7951f9b0933daa07ca Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 6 Jun 2020 13:01:02 +0200 Subject: [PATCH 55/94] Move doc page of projects using Telethon to the wiki New URL: https://github.com/LonamiWebs/Telethon/wiki/Projects-using-Telethon --- .../examples/projects-using-telethon.rst | 106 ------------------ readthedocs/examples/word-of-warning.rst | 3 +- readthedocs/index.rst | 1 - 3 files changed, 2 insertions(+), 108 deletions(-) delete mode 100644 readthedocs/examples/projects-using-telethon.rst diff --git a/readthedocs/examples/projects-using-telethon.rst b/readthedocs/examples/projects-using-telethon.rst deleted file mode 100644 index 481fa54b..00000000 --- a/readthedocs/examples/projects-using-telethon.rst +++ /dev/null @@ -1,106 +0,0 @@ -.. _telethon_projects: - -======================= -Projects using Telethon -======================= - -This page lists some **interesting and useful** real world -examples showcasing what can be built with the library. - -.. note:: - - Do you have an interesting project that uses the library or know of any - that's not listed here? Feel free to leave a comment at - `issue 744 `_ - so it can be included in the next revision of the documentation! - - You can also advertise your bot and its features, in the issue, although - it should be a big project which can be useful for others before being - included here, so please don't feel offended if it can't be here! - - -.. _projects-telegram-export: - -telethon_examples/ -================== - -`telethon_examples `_ / -`Lonami's site `_ - -This documentation is not the only place where you can find useful code -snippets using the library. The main repository also has a folder with -some cool examples (even a Tkinter GUI!) which you can download, edit -and run to learn and play with them. - -@TelethonSnippets -================= - -`@TelethonSnippets `_ - -You can find useful short snippets for Telethon here. - -telegram-export -=============== - -`telegram-export `_ / -`expectocode's GitHub `_ - -A tool to download Telegram data (users, chats, messages, and media) -into a database (and display the saved data). - -.. _projects-mautrix-telegram: - -mautrix-telegram -================ - -`mautrix-telegram `_ / -`maunium's site `_ - -A Matrix-Telegram hybrid puppeting/relaybot bridge. - -.. _projects-telegramtui: - -TelegramTUI -=========== - -`TelegramTUI `_ / -`bad-day's GitHub `_ - -A Telegram client on your terminal. - -tgcloud -======= - -`tgcloud `_ / -`tgcloud's site `_ - -Opensource Telegram based cloud storage. - -tgmount -======= - -`tgmount `_ / -`nktknshn's GitHub `_ - -Mount Telegram dialogs and channels as a Virtual File System. - -garnet -====== - -`garnet `_ / -`uwinx's GitHub `_ - -Pomegranate (or ``garnet`` for short) is a small telethon add-on which -features persistent conversations based on Finite State Machines (FSM), -a new ``Filter`` to define handlers more conveniently and utilities to -run code on start and finish of the client. Be sure to check the project -to learn about its latest features, since this description may be out of -date. - -telethon-secret-chat -==================== - -`telethon-secret-chat `_ / -`painor's GitHub `_ - -This add-on can be used to work with secret chats in Telethon! diff --git a/readthedocs/examples/word-of-warning.rst b/readthedocs/examples/word-of-warning.rst index 5501325f..de91741f 100644 --- a/readthedocs/examples/word-of-warning.rst +++ b/readthedocs/examples/word-of-warning.rst @@ -13,4 +13,5 @@ Full API **will** break between different minor versions of the library, since Telegram changes very often. The friendly methods will be kept compatible between major versions. -If you need to see real-world examples, please refer to :ref:`telethon_projects`. +If you need to see real-world examples, please refer to the +`wiki page of projects using Telethon `__. diff --git a/readthedocs/index.rst b/readthedocs/index.rst index 0776e3f2..eb786f10 100644 --- a/readthedocs/index.rst +++ b/readthedocs/index.rst @@ -83,7 +83,6 @@ You can also use the menu on the left to quickly skip over sections. examples/chats-and-channels examples/users examples/working-with-messages - examples/projects-using-telethon .. toctree:: :hidden: From 3f74f83964f797b95466eb22d8b560a4c0004a47 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 6 Jun 2020 13:07:36 +0200 Subject: [PATCH 56/94] Move qrlogin with the rest of custom types --- readthedocs/quick-references/client-reference.rst | 1 + telethon/client/auth.py | 7 +++---- telethon/tl/custom/__init__.py | 1 + telethon/{ => tl/custom}/qrlogin.py | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) rename telethon/{ => tl/custom}/qrlogin.py (98%) diff --git a/readthedocs/quick-references/client-reference.rst b/readthedocs/quick-references/client-reference.rst index 52e7d71a..69923efa 100644 --- a/readthedocs/quick-references/client-reference.rst +++ b/readthedocs/quick-references/client-reference.rst @@ -31,6 +31,7 @@ Auth start send_code_request sign_in + qr_login sign_up log_out edit_2fa diff --git a/telethon/client/auth.py b/telethon/client/auth.py index 01752c1e..ceeb3359 100644 --- a/telethon/client/auth.py +++ b/telethon/client/auth.py @@ -5,8 +5,7 @@ import sys import typing from .. import utils, helpers, errors, password as pwd_mod -from ..qrlogin import QRLogin -from ..tl import types, functions +from ..tl import types, functions, custom if typing.TYPE_CHECKING: from .telegramclient import TelegramClient @@ -497,7 +496,7 @@ class AuthMethods: return result - async def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> QRLogin: + async def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> custom.QRLogin: """ Initiates the QR login procedure. @@ -530,7 +529,7 @@ class AuthMethods: # Important! You need to wait for the login to complete! await qr_login.wait() """ - qr_login = QRLogin(self, ignored_ids or []) + qr_login = custom.QRLogin(self, ignored_ids or []) await qr_login.recreate() return qr_login diff --git a/telethon/tl/custom/__init__.py b/telethon/tl/custom/__init__.py index 6578fbf7..b5599c53 100644 --- a/telethon/tl/custom/__init__.py +++ b/telethon/tl/custom/__init__.py @@ -10,3 +10,4 @@ from .inlinebuilder import InlineBuilder from .inlineresult import InlineResult from .inlineresults import InlineResults from .conversation import Conversation +from .qrlogin import QRLogin diff --git a/telethon/qrlogin.py b/telethon/tl/custom/qrlogin.py similarity index 98% rename from telethon/qrlogin.py rename to telethon/tl/custom/qrlogin.py index afb7caf7..4355a070 100644 --- a/telethon/qrlogin.py +++ b/telethon/tl/custom/qrlogin.py @@ -2,8 +2,8 @@ import asyncio import base64 import datetime -from telethon import events -from telethon.tl import types, functions +from .. import types, functions +from ... import events class QRLogin: From 20a6d7b26b45186a380d0d6249576131e7e57f1c Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 6 Jun 2020 13:12:11 +0200 Subject: [PATCH 57/94] Document several new RPC errors --- telethon_generator/data/errors.csv | 5 +++++ telethon_generator/data/methods.csv | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index 045e0370..63967c51 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -15,9 +15,13 @@ AUTH_KEY_INVALID,401,The key is invalid AUTH_KEY_PERM_EMPTY,401,"The method is unavailable for temporary authorization key, not bound to permanent" AUTH_KEY_UNREGISTERED,401,The key is not registered in the system AUTH_RESTART,500,Restart the authorization process +AUTH_TOKEN_ALREADY_ACCEPTED,400,The authorization token was already used +AUTH_TOKEN_EXPIRED,400,The provided authorization token has expired and the updated QR-code must be re-scanned +AUTH_TOKEN_INVALID,400,An invalid authorization token was provided BANNED_RIGHTS_INVALID,400,"You cannot use that set of permissions in this request, i.e. restricting view_messages as a default" BOTS_TOO_MUCH,400,There are too many bots in this chat/channel BOT_CHANNELS_NA,400,Bots can't edit admin privileges +BOT_COMMAND_DESCRIPTION_INVALID,400,"The command description was empty, too long or had invalid characters used" BOT_GROUPS_BLOCKED,400,This bot can't be added to groups BOT_INLINE_DISABLED,400,This bot can't be used in inline mode BOT_INVALID,400,This is not a valid bot @@ -67,6 +71,7 @@ CONNECTION_LANG_PACK_INVALID,400,"The specified language pack is not valid. This CONNECTION_LAYER_INVALID,400,The very first request must always be InvokeWithLayerRequest CONNECTION_NOT_INITED,400,Connection not initialized CONNECTION_SYSTEM_EMPTY,400,Connection system empty +CONNECTION_SYSTEM_LANG_CODE_EMPTY,400,The system language string was empty during connection CONTACT_ID_INVALID,400,The provided contact ID is invalid CONTACT_NAME_EMPTY,400,The provided contact name cannot be empty DATA_INVALID,400,Encrypted data invalid diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index 374d867c..dfd19f58 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -72,7 +72,7 @@ auth.exportAuthorization,both,DC_ID_INVALID auth.exportLoginToken,user, auth.importAuthorization,both,AUTH_BYTES_INVALID USER_ID_INVALID auth.importBotAuthorization,both,ACCESS_TOKEN_EXPIRED ACCESS_TOKEN_INVALID API_ID_INVALID -auth.importLoginToken,user, +auth.importLoginToken,user,AUTH_TOKEN_ALREADY_ACCEPTED AUTH_TOKEN_EXPIRED AUTH_TOKEN_INVALID auth.logOut,both, auth.recoverPassword,user,CODE_EMPTY auth.requestPasswordRecovery,user,PASSWORD_EMPTY @@ -83,7 +83,7 @@ auth.signIn,user,PHONE_CODE_EMPTY PHONE_CODE_EXPIRED PHONE_CODE_INVALID PHONE_NU auth.signUp,user,FIRSTNAME_INVALID MEMBER_OCCUPY_PRIMARY_LOC_FAILED PHONE_CODE_EMPTY PHONE_CODE_EXPIRED PHONE_CODE_INVALID PHONE_NUMBER_FLOOD PHONE_NUMBER_INVALID PHONE_NUMBER_OCCUPIED REG_ID_GENERATE_FAILED bots.answerWebhookJSONQuery,bot,QUERY_ID_INVALID USER_BOT_INVALID bots.sendCustomRequest,bot,USER_BOT_INVALID -bots.setBotCommands,bot, +bots.setBotCommands,bot,BOT_COMMAND_DESCRIPTION_INVALID channels.checkUsername,user,CHANNEL_INVALID CHAT_ID_INVALID USERNAME_INVALID channels.createChannel,user,CHAT_TITLE_EMPTY USER_RESTRICTED channels.deleteChannel,user,CHANNEL_INVALID CHANNEL_PRIVATE From db3e7656e06a979e3c2d8b201b6eaacf64e9aa67 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 6 Jun 2020 13:54:19 +0200 Subject: [PATCH 58/94] Handle AssertionError when cancelling tasks Fixes #1478. --- telethon/helpers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/telethon/helpers.py b/telethon/helpers.py index 1b5f8843..664d504e 100644 --- a/telethon/helpers.py +++ b/telethon/helpers.py @@ -123,6 +123,8 @@ async def _cancel(log, **tasks): except RuntimeError: # Probably: RuntimeError: await wasn't used with future # + # See: https://github.com/python/cpython/blob/12d3061c7819a73d891dcce44327410eaf0e1bc2/Lib/asyncio/futures.py#L265 + # # Happens with _asyncio.Task instances (in "Task cancelling" state) # trying to SIGINT the program right during initial connection, on # _recv_loop coroutine (but we're creating its task explicitly with @@ -131,6 +133,12 @@ async def _cancel(log, **tasks): # Since we're aware of this error there's no point in logging it. # *May* be https://bugs.python.org/issue37172 pass + except AssertionError as e: + # In Python 3.6, the above RuntimeError is an AssertionError + # See https://github.com/python/cpython/blob/7df32f844efed33ca781a016017eab7050263b90/Lib/asyncio/futures.py#L328 + if e.args != ("yield from wasn't used with future",): + log.exception('Unhandled exception from %s after cancelling ' + '%s (%s)', name, type(task), task) except Exception: log.exception('Unhandled exception from %s after cancelling ' '%s (%s)', name, type(task), task) From faf7263d8f9a8457ad9cf4b117fb330f52a654bc Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 6 Jun 2020 14:04:14 +0200 Subject: [PATCH 59/94] Handle RPC errors on auto-get_difference Closes #1428. --- telethon/client/updates.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/telethon/client/updates.py b/telethon/client/updates.py index 6f93351b..70a3d9a1 100644 --- a/telethon/client/updates.py +++ b/telethon/client/updates.py @@ -392,6 +392,11 @@ class UpdateMethods: await self._get_difference(update, channel_id, pts_date) except OSError: pass # We were disconnected, that's okay + except errors.RPCError: + # There's a high chance the request fails because we lack + # the channel. Because these "happen sporadically" (#1428) + # we should be okay (no flood waits) even if more occur. + pass if not self._self_input_peer: # Some updates require our own ID, so we must make sure From 4b933069f1b3db187b0a5288a3eaf7491c2493bf Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 6 Jun 2020 21:01:02 +0200 Subject: [PATCH 60/94] Add hacks to properly handle events.Album from other DCs Fixes #1479. --- telethon/client/telegrambaseclient.py | 9 ++++ telethon/client/updates.py | 40 +++++++++++++++++ telethon/events/album.py | 64 +++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) diff --git a/telethon/client/telegrambaseclient.py b/telethon/client/telegrambaseclient.py index 9fce65fe..9f689427 100644 --- a/telethon/client/telegrambaseclient.py +++ b/telethon/client/telegrambaseclient.py @@ -379,6 +379,15 @@ class TelegramBaseClient(abc.ABC): # {chat_id: {Conversation}} self._conversations = collections.defaultdict(set) + # Hack to workaround the fact Telegram may send album updates as + # different Updates when being sent from a different data center. + # {grouped_id: AlbumHack} + # + # FIXME: We don't bother cleaning this up because it's not really + # worth it, albums are pretty rare and this only holds them + # for a second at most. + self._albums = {} + # Default parse mode self._parse_mode = markdown diff --git a/telethon/client/updates.py b/telethon/client/updates.py index 70a3d9a1..265ae850 100644 --- a/telethon/client/updates.py +++ b/telethon/client/updates.py @@ -459,6 +459,46 @@ class UpdateMethods: self._log[__name__].exception('Unhandled exception on %s', name) + async def _dispatch_event(self: 'TelegramClient', event): + """ + Dispatches a single, out-of-order event. Used by `AlbumHack`. + """ + # We're duplicating a most logic from `_dispatch_update`, but all in + # the name of speed; we don't want to make it worse for all updates + # just because albums may need it. + for builder, callback in self._event_builders: + if not isinstance(event, builder.Event): + continue + + if not builder.resolved: + await builder.resolve(self) + + filter = builder.filter(event) + if inspect.isawaitable(filter): + filter = await filter + if not filter: + continue + + try: + await callback(event) + except errors.AlreadyInConversationError: + name = getattr(callback, '__name__', repr(callback)) + self._log[__name__].debug( + 'Event handler "%s" already has an open conversation, ' + 'ignoring new one', name) + except events.StopPropagation: + name = getattr(callback, '__name__', repr(callback)) + self._log[__name__].debug( + 'Event handler "%s" stopped chain of propagation ' + 'for event %s.', name, type(event).__name__ + ) + break + except Exception as e: + if not isinstance(e, asyncio.CancelledError) or self.is_connected(): + name = getattr(callback, '__name__', repr(callback)) + self._log[__name__].exception('Unhandled exception on %s', + name) + async def _get_difference(self: 'TelegramClient', update, channel_id, pts_date): """ Get the difference for this `channel_id` if any, then load entities. diff --git a/telethon/events/album.py b/telethon/events/album.py index f32dfc83..473542bd 100644 --- a/telethon/events/album.py +++ b/telethon/events/album.py @@ -1,4 +1,6 @@ +import asyncio import time +import weakref from .common import EventBuilder, EventCommon, name_inner_event from .. import utils @@ -14,6 +16,54 @@ _IGNORE_MAX_AGE = 5 # seconds _IGNORE_DICT = {} +_HACK_DELAY = 0.5 + + +class AlbumHack: + """ + When receiving an album from a different data-center, they will come in + separate `Updates`, so we need to temporarily remember them for a while + and only after produce the event. + + Of course events are not designed for this kind of wizardy, so this is + a dirty hack that gets the job done. + + When cleaning up the code base we may want to figure out a better way + to do this, or just leave the album problem to the users; the update + handling code is bad enough as it is. + """ + def __init__(self, client, event): + # It's probably silly to use a weakref here because this object is + # very short-lived but might as well try to do "the right thing". + self._client = weakref.ref(client) + self._event = event # parent event + self._due = client.loop.time() + _HACK_DELAY + + client.loop.create_task(self.deliver_event()) + + def extend(self, messages): + client = self._client() + if client: # weakref may be dead + self._event.messages.extend(messages) + self._due = client.loop.time() + _HACK_DELAY + + async def deliver_event(self): + while True: + client = self._client() + if client is None: + return # weakref is dead, nothing to deliver + + diff = self._due - client.loop.time() + if diff <= 0: + # We've hit our due time, deliver event. It won't respect + # sequential updates but fixing that would just worsen this. + await client._dispatch_event(self._event) + return + + del client # Clear ref and sleep until our due time + await asyncio.sleep(diff) + + @name_inner_event class Album(EventBuilder): """ @@ -66,6 +116,7 @@ class Album(EventBuilder): return # Check if the ignore list is too big, and if it is clean it + # TODO time could technically go backwards; time is not monotonic now = time.time() if len(_IGNORE_DICT) > _IGNORE_MAX_SIZE: for i in [i for i, t in _IGNORE_DICT.items() if now - t > _IGNORE_MAX_AGE]: @@ -84,6 +135,11 @@ class Album(EventBuilder): and u.message.grouped_id == group) ]) + def filter(self, event): + # Albums with less than two messages require a few hacks to work. + if len(event.messages) > 1: + return super().filter(event) + class Event(EventCommon, SenderGetter): """ Represents the event of a new album. @@ -115,6 +171,14 @@ class Album(EventBuilder): for msg in self.messages: msg._finish_init(client, self._entities, None) + if len(self.messages) == 1: + # This will require hacks to be a proper album event + hack = client._albums.get(self.grouped_id) + if hack is None: + client._albums[self.grouped_id] = AlbumHack(client, self) + else: + hack.extend(self.messages) + @property def grouped_id(self): """ From 3e511484c785f430d233da481931d06256a51df1 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 6 Jun 2020 21:07:22 +0200 Subject: [PATCH 61/94] Support pathlib.Path on download_file Fixes #1379. --- telethon/client/downloads.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index 334f28a4..1b06bfe6 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -443,6 +443,9 @@ class DownloadMethods: raise ValueError( 'The part size must be evenly divisible by 4096.') + if isinstance(file, pathlib.Path): + file = str(file.absolute()) + in_memory = file is None or file is bytes if in_memory: f = io.BytesIO() From fc07e6bba78da2f3ed0fd9e94290aef4609dbd80 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 7 Jun 2020 09:50:33 +0200 Subject: [PATCH 62/94] Document some RPC errors for channels.editCreator --- telethon_generator/data/errors.csv | 3 +++ telethon_generator/data/methods.csv | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index 63967c51..abe45be0 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -183,7 +183,9 @@ PARTICIPANT_CALL_FAILED,500,Failure while making call PARTICIPANT_VERSION_OUTDATED,400,The other participant does not use an up to date telegram client with support for calls PASSWORD_EMPTY,400,The provided password is empty PASSWORD_HASH_INVALID,400,The password (and thus its hash value) you entered is invalid +PASSWORD_MISSING,400,The account must have 2-factor authentication enabled (a password) before this method can be used PASSWORD_REQUIRED,400,The account must have 2-factor authentication enabled (a password) before this method can be used +PASSWORD_TOO_FRESH_X,400,The password was added too recently and {seconds} seconds must pass before using the method PAYMENT_PROVIDER_INVALID,400,The payment provider was not recognised or its token was invalid PEER_FLOOD,,Too many requests PEER_ID_INVALID,400,An invalid Peer was used. Make sure to pass the right peer type @@ -253,6 +255,7 @@ SEND_MESSAGE_TYPE_INVALID,400,The message type is invalid SESSION_EXPIRED,401,The authorization has expired SESSION_PASSWORD_NEEDED,401,Two-steps verification is enabled and a password is required SESSION_REVOKED,401,"The authorization has been invalidated, because of the user terminating all sessions" +SESSION_TOO_FRESH_X,400,The session logged in too recently and {seconds} seconds must pass before calling the method SHA256_HASH_INVALID,400,The provided SHA256 hash is invalid SHORTNAME_OCCUPY_FAILED,400,An error occurred when trying to register the short-name used for the sticker pack. Try a different name SLOWMODE_WAIT_X,420,A wait of {seconds} seconds is required before sending another message in this chat diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index dfd19f58..71fd5ae7 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -92,7 +92,7 @@ channels.deleteMessages,both,CHANNEL_INVALID CHANNEL_PRIVATE MESSAGE_DELETE_FORB channels.deleteUserHistory,user,CHANNEL_INVALID CHAT_ADMIN_REQUIRED channels.editAdmin,both,ADMINS_TOO_MUCH ADMIN_RANK_EMOJI_NOT_ALLOWED ADMIN_RANK_INVALID BOT_CHANNELS_NA CHANNEL_INVALID CHAT_ADMIN_INVITE_REQUIRED CHAT_ADMIN_REQUIRED FRESH_CHANGE_ADMINS_FORBIDDEN RIGHT_FORBIDDEN USER_CREATOR USER_ID_INVALID USER_NOT_MUTUAL_CONTACT USER_PRIVACY_RESTRICTED channels.editBanned,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ADMIN_INVALID USER_ID_INVALID -channels.editCreator,user, +channels.editCreator,user,PASSWORD_MISSING PASSWORD_TOO_FRESH_X SESSION_TOO_FRESH_X channels.editLocation,user, channels.editPhoto,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED PHOTO_INVALID channels.editTitle,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED CHAT_NOT_MODIFIED From e0c314376321617d082206e065d382bbeebf66e9 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 22 Jun 2020 13:21:45 +0200 Subject: [PATCH 63/94] Update documentation with new errors and further clarifications --- telethon/client/downloads.py | 15 +++----- telethon/tl/custom/conversation.py | 53 ++++++++++++++++++++++------- telethon_generator/data/errors.csv | 2 ++ telethon_generator/data/methods.csv | 4 +-- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index 1b06bfe6..25910ffd 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -568,19 +568,12 @@ class DownloadMethods: # Fetching only the header of a file (32 bytes) # You should manually close the iterator in this case. # - # telethon.sync must be imported for this to work, - # and you must not be inside an "async def". + # "stream" is a common name for asynchronous generators, + # and iter_download will yield `bytes` (chunks of the file). stream = client.iter_download(media, request_size=32) - header = next(stream) - stream.close() + header = await stream.__anext__() # "manual" version of `async for` + await stream.close() assert len(header) == 32 - - # Fetching only the header, inside of an ``async def`` - async def main(): - stream = client.iter_download(media, request_size=32) - header = await stream.__anext__() - await stream.close() - assert len(header) == 32 """ info = utils._get_file_info(file) if info.dc_id is not None: diff --git a/telethon/tl/custom/conversation.py b/telethon/tl/custom/conversation.py index 46b67aa8..65ec2725 100644 --- a/telethon/tl/custom/conversation.py +++ b/telethon/tl/custom/conversation.py @@ -132,7 +132,8 @@ class Conversation(ChatGetter): def get_response(self, message=None, *, timeout=None): """ - Gets the next message that responds to a previous one. + Gets the next message that responds to a previous one. This is + the method you need most of the time, along with `get_edit`. Args: message (`Message ` | `int`, optional): @@ -142,6 +143,16 @@ class Conversation(ChatGetter): timeout (`int` | `float`, optional): If present, this `timeout` (in seconds) will override the per-action timeout defined for the conversation. + + .. code-block:: python + + async with client.conversation(...) as conv: + await conv.send_message('Hey, what is your name?') + + response = await conv.get_response() + name = response.text + + await conv.send_message('Nice to meet you, {}!'.format(name)) """ return self._get_message( message, self._response_indices, self._pending_responses, timeout, @@ -272,23 +283,41 @@ class Conversation(ChatGetter): .. note:: - Only use this if there isn't another method available! + **Only use this if there isn't another method available!** For example, don't use `wait_event` for new messages, since `get_response` already exists, etc. Unless you're certain that your code will run fast enough, generally you should get a "handle" of this special coroutine - before acting. Generally, you should do this: + before acting. In this example you will see how to wait for a user + to join a group with proper use of `wait_event`: - >>> from telethon import TelegramClient, events - >>> - >>> client = TelegramClient(...) - >>> - >>> async def main(): - >>> async with client.conversation(...) as conv: - >>> response = conv.wait_event(events.NewMessage(incoming=True)) - >>> await conv.send_message('Hi') - >>> response = await response + .. code-block:: python + + from telethon import TelegramClient, events + + client = TelegramClient(...) + group_id = ... + + async def main(): + # Could also get the user id from an event; this is just an example + user_id = ... + + async with client.conversation(user_id) as conv: + # Get a handle to the future event we'll wait for + handle = conv.wait_event(events.ChatAction( + group_id, + func=lambda e: e.user_joined and e.user_id == user_id + )) + + # Perform whatever action in between + await conv.send_message('Please join this group before speaking to me!') + + # Wait for the event we registered above to fire + event = await handle + + # Continue with the conversation + await conv.send_message('Thanks!') This way your event can be registered before acting, since the response may arrive before your event was diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index abe45be0..e93dced3 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -108,6 +108,7 @@ FILE_PART_SIZE_CHANGED,400,The file part size (chunk size) cannot change during FILE_PART_SIZE_INVALID,400,The provided file part size is invalid FILE_PART_X_MISSING,400,Part {which} of the file is missing from storage FILE_REFERENCE_EMPTY,400,The file reference must exist to access the media and it cannot be empty +FILE_REFERENCE_EXPIRED,400,The file reference has expired and is no longer valid or it belongs to self-destructing media and cannot be resent FILEREF_UPGRADE_NEEDED,406,The file reference needs to be refreshed before being used again FIRSTNAME_INVALID,400,The first name is invalid FLOOD_TEST_PHONE_WAIT_X,420,A wait of {seconds} seconds is required in the test servers @@ -163,6 +164,7 @@ MESSAGE_ID_INVALID,400,"The specified message ID is invalid or you can't do that MESSAGE_NOT_MODIFIED,400,Content of the message was not modified MESSAGE_POLL_CLOSED,400,The poll was closed and can no longer be voted on MESSAGE_TOO_LONG,400,Message was too long. Current maximum length is 4096 UTF-8 characters +METHOD_INVALID,400,The API method is invalid and cannot be used MSGID_DECREASE_RETRY,500,The request should be retried with a lower message ID MSG_ID_INVALID,400,The message ID used in the peer was invalid MSG_WAIT_FAILED,400,A waiting call returned an error diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index 71fd5ae7..20e50aae 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -265,14 +265,14 @@ messages.saveDraft,user,PEER_ID_INVALID messages.saveGif,user,GIF_ID_INVALID messages.saveRecentSticker,user,STICKER_ID_INVALID messages.search,user,CHAT_ADMIN_REQUIRED INPUT_CONSTRUCTOR_INVALID INPUT_USER_DEACTIVATED PEER_ID_INVALID PEER_ID_NOT_SUPPORTED SEARCH_QUERY_EMPTY USER_ID_INVALID -messages.searchGifs,user,SEARCH_QUERY_EMPTY +messages.searchGifs,user,METHOD_INVALID SEARCH_QUERY_EMPTY messages.searchGlobal,user,SEARCH_QUERY_EMPTY messages.searchStickerSets,user, messages.sendEncrypted,user,CHAT_ID_INVALID DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED 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 BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EMOTICON_INVALID EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID FILE_REFERENCE_EMPTY GAME_BOT_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_ANSWERS_INVALID POLL_OPTION_DUPLICATE POLL_QUESTION_INVALID QUIZ_CORRECT_ANSWERS_EMPTY QUIZ_CORRECT_ANSWERS_TOO_MUCH QUIZ_CORRECT_ANSWER_INVALID QUIZ_MULTIPLE_INVALID 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.sendMedia,both,BOT_PAYMENTS_DISABLED BOT_POLLS_DISABLED BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EMOTICON_INVALID EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID FILE_REFERENCE_EMPTY FILE_REFERENCE_EXPIRED GAME_BOT_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_ANSWERS_INVALID POLL_OPTION_DUPLICATE POLL_QUESTION_INVALID QUIZ_CORRECT_ANSWERS_EMPTY QUIZ_CORRECT_ANSWERS_TOO_MUCH QUIZ_CORRECT_ANSWER_INVALID QUIZ_MULTIPLE_INVALID 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 MSG_ID_INVALID PEER_ID_INVALID POLL_OPTION_INVALID RANDOM_ID_DUPLICATE REPLY_MARKUP_INVALID REPLY_MARKUP_TOO_LONG SCHEDULE_BOT_NOT_ALLOWED SCHEDULE_DATE_TOO_LATE SCHEDULE_STATUS_PRIVATE 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.sendScheduledMessages,user, From ba4f4c1f786a1868de1e1a875c4dcef0106670b9 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 24 Jun 2020 15:11:54 +0300 Subject: [PATCH 64/94] Fix url property in QRLogin (#1494) --- telethon/tl/custom/qrlogin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/tl/custom/qrlogin.py b/telethon/tl/custom/qrlogin.py index 4355a070..a92276c1 100644 --- a/telethon/tl/custom/qrlogin.py +++ b/telethon/tl/custom/qrlogin.py @@ -54,7 +54,7 @@ class QRLogin: The URL simply consists of `token` base64-encoded. """ - return 'tg://login?token={}'.format(base64.b64encode(self._resp.token)) + return 'tg://login?token={}'.format(base64.b64encode(self._resp.token).decode('utf-8')) @property def expires(self) -> datetime.datetime: From 0f8119c4003bb2de47a465b428cd6dd019b82b19 Mon Sep 17 00:00:00 2001 From: KnorpelSenf Date: Wed, 24 Jun 2020 14:30:41 +0200 Subject: [PATCH 65/94] Fix typo in docs (#1493) --- telethon/client/telegrambaseclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/client/telegrambaseclient.py b/telethon/client/telegrambaseclient.py index 9f689427..938cfb5d 100644 --- a/telethon/client/telegrambaseclient.py +++ b/telethon/client/telegrambaseclient.py @@ -417,7 +417,7 @@ class TelegramBaseClient(abc.ABC): .. code-block:: python # Download media in the background - task = client.loop_create_task(message.download_media()) + task = client.loop.create_task(message.download_media()) # Do some work ... From ab594ed0cbd0e7136e84e29b2a4a4feea40c718e Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 4 Jul 2020 12:18:39 +0200 Subject: [PATCH 66/94] Remove unused imports and variables --- telethon/client/telegrambaseclient.py | 3 +-- telethon/events/chataction.py | 6 +++--- telethon/events/common.py | 3 +-- telethon/events/newmessage.py | 1 - telethon/password.py | 1 - telethon/statecache.py | 1 - telethon/tl/core/gzippacked.py | 2 +- 7 files changed, 6 insertions(+), 11 deletions(-) diff --git a/telethon/client/telegrambaseclient.py b/telethon/client/telegrambaseclient.py index 938cfb5d..a7637768 100644 --- a/telethon/client/telegrambaseclient.py +++ b/telethon/client/telegrambaseclient.py @@ -3,7 +3,6 @@ import asyncio import collections import logging import platform -import sys import time import typing @@ -14,7 +13,7 @@ from ..extensions import markdown from ..network import MTProtoSender, Connection, ConnectionTcpFull, TcpMTProxy from ..sessions import Session, SQLiteSession, MemorySession from ..statecache import StateCache -from ..tl import TLObject, functions, types +from ..tl import functions, types from ..tl.alltlobjects import LAYER DEFAULT_DC_ID = 2 diff --git a/telethon/events/chataction.py b/telethon/events/chataction.py index 52c7bbf8..b7143d4f 100644 --- a/telethon/events/chataction.py +++ b/telethon/events/chataction.py @@ -1,6 +1,6 @@ from .common import EventBuilder, EventCommon, name_inner_event -from .. import utils, helpers -from ..tl import types, functions +from .. import utils +from ..tl import types @name_inner_event @@ -398,7 +398,7 @@ class ChatAction(EventBuilder): try: self._input_users.append(utils.get_input_peer(self._entities[user_id])) continue - except (KeyError, TypeError) as e: + except (KeyError, TypeError): pass # If missing, try from the entity cache diff --git a/telethon/events/common.py b/telethon/events/common.py index f5ff5b50..f95a1ac6 100644 --- a/telethon/events/common.py +++ b/telethon/events/common.py @@ -1,10 +1,9 @@ import abc import asyncio -import itertools import warnings from .. import utils -from ..tl import TLObject, types, functions +from ..tl import TLObject, types from ..tl.custom.chatgetter import ChatGetter diff --git a/telethon/events/newmessage.py b/telethon/events/newmessage.py index fc3eaaf2..b30a63c8 100644 --- a/telethon/events/newmessage.py +++ b/telethon/events/newmessage.py @@ -1,4 +1,3 @@ -import asyncio import re from .common import EventBuilder, EventCommon, name_inner_event, _into_id_set diff --git a/telethon/password.py b/telethon/password.py index 5afb53d1..0f950254 100644 --- a/telethon/password.py +++ b/telethon/password.py @@ -162,7 +162,6 @@ def compute_check(request: types.account.Password, password: str): def generate_and_check_random(): random_size = 256 - import time while True: random = os.urandom(random_size) a = int.from_bytes(random, 'big') diff --git a/telethon/statecache.py b/telethon/statecache.py index adb5099e..fbb505fa 100644 --- a/telethon/statecache.py +++ b/telethon/statecache.py @@ -1,4 +1,3 @@ -import datetime import inspect from .tl import types diff --git a/telethon/tl/core/gzippacked.py b/telethon/tl/core/gzippacked.py index 906c2c67..fb4094e4 100644 --- a/telethon/tl/core/gzippacked.py +++ b/telethon/tl/core/gzippacked.py @@ -1,7 +1,7 @@ import gzip import struct -from .. import TLObject, TLRequest +from .. import TLObject class GzipPacked(TLObject): From 7b852206f1023fbc90867772ab6cda6b388f952e Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 4 Jul 2020 12:30:15 +0200 Subject: [PATCH 67/94] Fix click timeout error is now different --- telethon/tl/custom/message.py | 2 +- telethon/tl/custom/messagebutton.py | 6 +++--- telethon_generator/data/errors.csv | 1 + telethon_generator/data/methods.csv | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/telethon/tl/custom/message.py b/telethon/tl/custom/message.py index 4ece30d8..56f4c725 100644 --- a/telethon/tl/custom/message.py +++ b/telethon/tl/custom/message.py @@ -844,7 +844,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): data=data ) ) - except errors.BotTimeout: + except errors.BotResponseTimeoutError: return None if sum(int(x is not None) for x in (i, text, filter)) >= 2: diff --git a/telethon/tl/custom/messagebutton.py b/telethon/tl/custom/messagebutton.py index 5c749ac3..2bd0a68d 100644 --- a/telethon/tl/custom/messagebutton.py +++ b/telethon/tl/custom/messagebutton.py @@ -1,5 +1,5 @@ from .. import types, functions -from ...errors import BotTimeout +from ...errors import BotResponseTimeoutError import webbrowser @@ -85,7 +85,7 @@ class MessageButton: ) try: return await self._client(req) - except BotTimeout: + except BotResponseTimeoutError: return None elif isinstance(self.button, types.KeyboardButtonSwitchInline): return await self._client(functions.messages.StartBotRequest( @@ -99,5 +99,5 @@ class MessageButton: ) try: return await self._client(req) - except BotTimeout: + except BotResponseTimeoutError: return None diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index e93dced3..94f98d95 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -29,6 +29,7 @@ BOT_METHOD_INVALID,400,The API access for bot users is restricted. The method yo BOT_MISSING,400,This method can only be run by a bot BOT_PAYMENTS_DISABLED,400,This method can only be run by a bot BOT_POLLS_DISABLED,400,You cannot create polls under a bot account +BOT_RESPONSE_TIMEOUT,400,The bot did not answer to the callback query in time BROADCAST_ID_INVALID,400,The channel is invalid BROADCAST_PUBLIC_VOTERS_FORBIDDEN,400,You cannot broadcast polls where the voters are public BUTTON_DATA_INVALID,400,The provided button data is invalid diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index 20e50aae..16a95026 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -199,7 +199,7 @@ messages.getAllDrafts,user, messages.getAllStickers,user, messages.getArchivedStickers,user, messages.getAttachedStickers,user, -messages.getBotCallbackAnswer,user,CHANNEL_INVALID DATA_INVALID MESSAGE_ID_INVALID PEER_ID_INVALID Timeout +messages.getBotCallbackAnswer,user,BOT_RESPONSE_TIMEOUT CHANNEL_INVALID DATA_INVALID MESSAGE_ID_INVALID PEER_ID_INVALID Timeout messages.getChats,both,CHAT_ID_INVALID PEER_ID_INVALID messages.getCommonChats,user,USER_ID_INVALID messages.getDhConfig,user,RANDOM_LENGTH_INVALID From 326f70b6788ab2bbb1ef15d68661ae80a4a5a2e8 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 4 Jul 2020 13:12:22 +0200 Subject: [PATCH 68/94] Support clicking on buttons asking for phone/location Closes #1492. --- telethon/tl/custom/message.py | 74 +++++++++++++++++++++-------- telethon/tl/custom/messagebutton.py | 38 ++++++++++++++- 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/telethon/tl/custom/message.py b/telethon/tl/custom/message.py index 56f4c725..5f286a40 100644 --- a/telethon/tl/custom/message.py +++ b/telethon/tl/custom/message.py @@ -764,7 +764,8 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): return await self._client.download_media(self, *args, **kwargs) async def click(self, i=None, j=None, - *, text=None, filter=None, data=None): + *, text=None, filter=None, data=None, share_phone=None, + share_geo=None): """ Calls `button.click ` on the specified button. @@ -813,6 +814,28 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): that if the message does not have this data, it will ``raise DataInvalidError``. + share_phone (`bool` | `str` | tl:`InputMediaContact`): + When clicking on a keyboard button requesting a phone number + (:tl:`KeyboardButtonRequestPhone`), this argument must be + explicitly set to avoid accidentally sharing the number. + + It can be `True` to automatically share the current user's + phone, a string to share a specific phone number, or a contact + media to specify all details. + + If the button is pressed without this, `ValueError` is raised. + + share_geo (`tuple` | `list` | tl:`InputMediaGeoPoint`): + When clicking on a keyboard button requesting a geo location + (:tl:`KeyboardButtonRequestGeoLocation`), this argument must + be explicitly set to avoid accidentally sharing the location. + + It must be a `tuple` of `float` as ``(longitude, latitude)``, + or a :tl:`InputGeoPoint` instance to avoid accidentally using + the wrong roder. + + If the button is pressed without this, `ValueError` is raised. + Example: .. code-block:: python @@ -828,6 +851,9 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): # Click by data await message.click(data=b'payload') + + # Click on a button requesting a phone + await message.click(0, share_phone=True) """ if not self._client: return @@ -853,29 +879,35 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): if not await self.get_buttons(): return # Accessing the property sets self._buttons[_flat] - if text is not None: - if callable(text): + def find_button(): + nonlocal i + if text is not None: + if callable(text): + for button in self._buttons_flat: + if text(button.text): + return button + else: + for button in self._buttons_flat: + if button.text == text: + return button + return + + if filter is not None: for button in self._buttons_flat: - if text(button.text): - return await button.click() + if filter(button): + return button + return + + if i is None: + i = 0 + if j is None: + return self._buttons_flat[i] else: - for button in self._buttons_flat: - if button.text == text: - return await button.click() - return + return self._buttons[i][j] - if filter is not None: - for button in self._buttons_flat: - if filter(button): - return await button.click() - return - - if i is None: - i = 0 - if j is None: - return await self._buttons_flat[i].click() - else: - return await self._buttons[i][j].click() + button = find_button() + if button: + return await button.click(share_phone=share_phone, share_geo=share_geo) async def mark_read(self): """ diff --git a/telethon/tl/custom/messagebutton.py b/telethon/tl/custom/messagebutton.py index 2bd0a68d..c99b8474 100644 --- a/telethon/tl/custom/messagebutton.py +++ b/telethon/tl/custom/messagebutton.py @@ -59,7 +59,7 @@ class MessageButton: if isinstance(self.button, types.KeyboardButtonUrl): return self.button.url - async def click(self): + async def click(self, share_phone=None, share_geo=None): """ Emulates the behaviour of clicking this button. @@ -75,6 +75,19 @@ class MessageButton: If it's a :tl:`KeyboardButtonUrl`, the URL of the button will be passed to ``webbrowser.open`` and return `True` on success. + + If it's a :tl:`KeyboardButtonRequestPhone`, you must indicate that you + want to ``share_phone=True`` in order to share it. Sharing it is not a + default because it is a privacy concern and could happen accidentally. + + You may also use ``share_phone=phone`` to share a specific number, in + which case either `str` or :tl:`InputMediaContact` should be used. + + If it's a :tl:`KeyboardButtonRequestGeoLocation`, you must pass a + tuple in ``share_geo=(longitude, latitude)``. Note that Telegram seems + to have some heuristics to determine impossible locations, so changing + this value a lot quickly may not work as expected. You may also pass a + :tl:`InputGeoPoint` if you find the order confusing. """ if isinstance(self.button, types.KeyboardButton): return await self._client.send_message( @@ -101,3 +114,26 @@ class MessageButton: return await self._client(req) except BotResponseTimeoutError: return None + elif isinstance(self.button, types.KeyboardButtonRequestPhone): + if not share_phone: + raise ValueError('cannot click on phone buttons unless share_phone=True') + + if share_phone == True or isinstance(share_phone, str): + me = await self._client.get_me() + share_phone = types.InputMediaContact( + phone_number=me.phone if share_phone == True else share_phone, + first_name=me.first_name or '', + last_name=me.last_name or '', + vcard='' + ) + + return await self._client.send_file(self._chat, share_phone) + elif isinstance(self.button, types.KeyboardButtonRequestGeoLocation): + if not share_geo: + raise ValueError('cannot click on geo buttons unless share_geo=(longitude, latitude)') + + if isinstance(share_geo, (tuple, list)): + long, lat = share_geo + share_geo = types.InputMediaGeoPoint(types.InputGeoPoint(lat=lat, long=long)) + + return await self._client.send_file(self._chat, share_geo) From e44926114a72dc52270c0cf6ff7feaa349cdbe3c Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 4 Jul 2020 13:29:19 +0200 Subject: [PATCH 69/94] Bump to v1.15 --- readthedocs/misc/changelog.rst | 38 ++++++++++++++++++++++++++++++++++ telethon/version.py | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/readthedocs/misc/changelog.rst b/readthedocs/misc/changelog.rst index eb1df48d..8b59c097 100644 --- a/readthedocs/misc/changelog.rst +++ b/readthedocs/misc/changelog.rst @@ -13,6 +13,44 @@ it can take advantage of new goodies! .. contents:: List of All Versions +QR login (v1.15) +================ + +*Published at 2020/07/04* + ++------------------------+ +| Scheme layer used: 114 | ++------------------------+ + +The library now has a friendly method to perform QR-login, as detailed in +https://core.telegram.org/api/qr-login. It won't generate QR images, but it +provides a way for you to easily do so with any other library of your choice. + +Additions +~~~~~~~~~ + +* New `client.qr_login() `. +* `message.click ` now lets you + click on buttons requesting phone or location. + +Enhancements +~~~~~~~~~~~~ + +* Updated documentation and list of known errors. +* `events.Album ` should now handle albums from + different data centers more gracefully. +* `client.download_file() + ` now supports + `pathlib.Path` as the destination. + +Bug fixes +~~~~~~~~~ + +* No longer crash on updates received prior to logging in. +* Server-side changes caused clicking on inline buttons to trigger a different + error, which is now handled correctly. + + Minor quality of life improvements (v1.14) ========================================== diff --git a/telethon/version.py b/telethon/version.py index 12445eee..b8e60970 100644 --- a/telethon/version.py +++ b/telethon/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '1.14.0' +__version__ = '1.15.0' From bfb8de2736959f586cb9f527faf1fd6eca08f742 Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Mon, 6 Jul 2020 23:41:40 +0530 Subject: [PATCH 70/94] Update upload file size limit to 2GB (#1499) Source: https://t.me/tginfo/2656 Closes #1498. --- telethon/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/utils.py b/telethon/utils.py index b00644ca..c8991724 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -1235,7 +1235,7 @@ def get_appropriated_part_size(file_size): return 128 if file_size <= 786432000: # 750MB return 256 - if file_size <= 1572864000: # 1500MB + if file_size <= 2097152000: # 2000MB return 512 raise ValueError('File size too large') From de17a19168d2a1c69c7bc431f46b2c47b61787d3 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 15 Jul 2020 14:35:42 +0200 Subject: [PATCH 71/94] Improve upload_file by properly supporting streaming files --- telethon/client/uploads.py | 121 ++++++++++++++++++++++++------------- telethon/helpers.py | 8 +++ 2 files changed, 87 insertions(+), 42 deletions(-) diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index c8564ad1..47fb4b44 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -5,7 +5,6 @@ import os import pathlib import re import typing -import inspect from io import BytesIO from ..crypto import AES @@ -95,6 +94,7 @@ class UploadMethods: *, caption: typing.Union[str, typing.Sequence[str]] = None, force_document: bool = False, + file_size: int = None, clear_draft: bool = False, progress_callback: 'hints.ProgressCallback' = None, reply_to: 'hints.MessageIDLike' = None, @@ -175,6 +175,13 @@ class UploadMethods: the extension of an image file or a video file, it will be sent as such. Otherwise always as a document. + file_size (`int`, optional): + The size of the file to be uploaded if it needs to be uploaded, + which will be determined automatically if not specified. + + If the file size can't be determined beforehand, the entire + file will be read in-memory to find out how large it is. + clear_draft (`bool`, optional): Whether the existing draft should be cleared or not. @@ -358,6 +365,7 @@ class UploadMethods: file_handle, media, image = await self._file_to_media( file, force_document=force_document, + file_size=file_size, progress_callback=progress_callback, attributes=attributes, allow_cache=allow_cache, thumb=thumb, voice_note=voice_note, video_note=video_note, @@ -449,6 +457,7 @@ class UploadMethods: file: 'hints.FileLike', *, part_size_kb: float = None, + file_size: int = None, file_name: str = None, use_cache: type = None, key: bytes = None, @@ -480,6 +489,13 @@ class UploadMethods: Chunk size when uploading files. The larger, the less requests will be made (up to 512KB maximum). + file_size (`int`, optional): + The size of the file to be uploaded, which will be determined + automatically if not specified. + + If the file size can't be determined beforehand, the entire + file will be read in-memory to find out how large it is. + file_name (`str`, optional): The file name which will be used on the resulting InputFile. If not specified, the name will be taken from the ``file`` @@ -527,34 +543,42 @@ class UploadMethods: if not file_name and getattr(file, 'name', None): file_name = file.name - if isinstance(file, str): + if file_size is not None: + pass # do nothing as it's already kwown + elif isinstance(file, str): file_size = os.path.getsize(file) + stream = open(file, 'rb') + close_stream = True elif isinstance(file, bytes): file_size = len(file) + stream = io.BytesIO(file) + close_stream = True else: - # `aiofiles` shouldn't base `IOBase` because they change the - # methods' definition. `seekable` would be `async` but since - # we won't get to check that, there's no need to maybe-await. - if isinstance(file, io.IOBase) and file.seekable(): - pos = file.tell() + if not callable(getattr(file, 'read', None)): + raise TypeError('file description should have a `read` method') + + if callable(getattr(file, 'seekable', None)): + seekable = await helpers._maybe_await(file.seekable()) else: - pos = None + seekable = False - # TODO Don't load the entire file in memory always - data = file.read() - if inspect.isawaitable(data): - data = await data + if seekable: + pos = await helpers._maybe_await(file.tell()) + await helpers._maybe_await(file.seek(0, os.SEEK_END)) + file_size = await helpers._maybe_await(file.tell()) + await helpers._maybe_await(file.seek(pos, os.SEEK_SET)) - if pos is not None: - file.seek(pos) + stream = file + close_stream = False + else: + self._log[__name__].warning( + 'Could not determine file size beforehand so the entire ' + 'file will be read in-memory') - if not isinstance(data, bytes): - raise TypeError( - 'file descriptor returned {}, not bytes (you must ' - 'open the file in bytes mode)'.format(type(data))) - - file = data - file_size = len(file) + data = await helpers._maybe_await(file.read()) + stream = io.BytesIO(data) + close_stream = True + file_size = len(data) # File will now either be a string or bytes if not part_size_kb: @@ -584,35 +608,46 @@ class UploadMethods: # Determine whether the file is too big (over 10MB) or not # Telegram does make a distinction between smaller or larger files - is_large = file_size > 10 * 1024 * 1024 + is_big = file_size > 10 * 1024 * 1024 hash_md5 = hashlib.md5() - if not is_large: - # Calculate the MD5 hash before anything else. - # As this needs to be done always for small files, - # might as well do it before anything else and - # check the cache. - if isinstance(file, str): - with open(file, 'rb') as stream: - file = stream.read() - hash_md5.update(file) 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) - with open(file, 'rb') if isinstance(file, str) else BytesIO(file)\ - as stream: + pos = 0 + try: for part_index in range(part_count): # Read the file by in chunks of size part_size - part = stream.read(part_size) + part = await helpers._maybe_await(stream.read(part_size)) - # encryption part if needed + if not isinstance(part, bytes): + raise TypeError( + 'file descriptor returned {}, not bytes (you must ' + 'open the file in bytes mode)'.format(type(part))) + + # `file_size` could be wrong in which case `part` may not be + # `part_size` before reaching the end. + if len(part) != part_size and part_index < part_count - 1: + raise ValueError( + 'read less than {} before reaching the end; either ' + '`file_size` or `read` are wrong'.format(part_size)) + + pos += len(part) + + if not is_big: + # Bit odd that MD5 is only needed for small files and not + # big ones with more chance for corruption, but that's + # what Telegram wants. + hash_md5.update(part) + + # Encryption part if needed if key and iv: part = AES.encrypt_ige(part, key, iv) # The SavePartRequest is different depending on whether # the file is too large or not (over or less than 10MB) - if is_large: + if is_big: request = functions.upload.SaveBigFilePartRequest( file_id, part_index, part_count, part) else: @@ -624,14 +659,15 @@ class UploadMethods: self._log[__name__].debug('Uploaded %d/%d', part_index + 1, part_count) if progress_callback: - r = progress_callback(stream.tell(), file_size) - if inspect.isawaitable(r): - await r + await helpers._maybe_await(progress_callback(pos, file_size)) else: raise RuntimeError( 'Failed to upload file part {}.'.format(part_index)) + finally: + if close_stream: + await helpers._maybe_await(stream.close()) - if is_large: + if is_big: return types.InputFileBig(file_id, part_count, file_name) else: return custom.InputSizedFile( @@ -641,7 +677,7 @@ class UploadMethods: # endregion async def _file_to_media( - self, file, force_document=False, + self, file, force_document=False, file_size=None, progress_callback=None, attributes=None, thumb=None, allow_cache=True, voice_note=False, video_note=False, supports_streaming=False, mime_type=None, as_image=None): @@ -686,6 +722,7 @@ class UploadMethods: elif not isinstance(file, str) or os.path.isfile(file): file_handle = await self.upload_file( _resize_photo_if_needed(file, as_image), + file_size=file_size, progress_callback=progress_callback ) elif re.match('https?://', file): @@ -725,7 +762,7 @@ class UploadMethods: else: if isinstance(thumb, pathlib.Path): thumb = str(thumb.absolute()) - thumb = await self.upload_file(thumb) + thumb = await self.upload_file(thumb, file_size=file_size) media = types.InputMediaUploadedDocument( file=file_handle, diff --git a/telethon/helpers.py b/telethon/helpers.py index 664d504e..55eb1b79 100644 --- a/telethon/helpers.py +++ b/telethon/helpers.py @@ -3,6 +3,7 @@ import asyncio import enum import os import struct +import inspect from hashlib import sha1 @@ -107,6 +108,13 @@ def retry_range(retries): yield 1 + attempt +async def _maybe_await(value): + if inspect.isawaitable(value): + return await value + else: + return value + + async def _cancel(log, **tasks): """ Helper to cancel one or more tasks gracefully, logging exceptions. From 1c3e7dda011dacf60bd5ef95e14c4026f402b001 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 25 Jul 2020 18:39:35 +0200 Subject: [PATCH 72/94] Avoid explicitly passing the loop to asyncio This behaviour is deprecated and will be removed in future versions of Python. Technically, it could be considered a bug (invalid usage causing different behaviour from the expected one), and in practice it should not break much code (because .get_event_loop() would likely be the same event loop anyway). --- readthedocs/concepts/asyncio.rst | 14 +++++---- telethon/client/dialogs.py | 2 +- telethon/client/telegrambaseclient.py | 20 ++++++------ telethon/client/updates.py | 2 +- telethon/client/users.py | 4 +-- telethon/events/common.py | 2 +- telethon/events/inlinequery.py | 11 +++---- telethon/extensions/messagepacker.py | 5 ++- telethon/network/connection/connection.py | 21 ++++++------- telethon/network/connection/tcpmtproxy.py | 4 +-- telethon/network/mtprotosender.py | 37 +++++++++++------------ telethon/network/requeststate.py | 4 +-- telethon/requestiter.py | 3 +- telethon/tl/custom/conversation.py | 3 +- telethon_examples/gui.py | 6 ++-- 15 files changed, 65 insertions(+), 73 deletions(-) diff --git a/readthedocs/concepts/asyncio.rst b/readthedocs/concepts/asyncio.rst index 03ffd198..eabb8f76 100644 --- a/readthedocs/concepts/asyncio.rst +++ b/readthedocs/concepts/asyncio.rst @@ -219,19 +219,21 @@ Can I use threads? ================== Yes, you can, but you must understand that the loops themselves are -not thread safe. and you must be sure to know what is happening. You -may want to create a loop in a new thread and make sure to pass it to -the client: +not thread safe. and you must be sure to know what is happening. The +easiest and cleanest option is to use `asyncio.run` to create and manage +the new event loop for you: .. code-block:: python import asyncio import threading - def go(): - loop = asyncio.new_event_loop() + async def actual_work(): client = TelegramClient(..., loop=loop) - ... + ... # can use `await` here + + def go(): + asyncio.run(actual_work()) threading.Thread(target=go).start() diff --git a/telethon/client/dialogs.py b/telethon/client/dialogs.py index 4fd5f3da..adb23851 100644 --- a/telethon/client/dialogs.py +++ b/telethon/client/dialogs.py @@ -378,7 +378,7 @@ class DialogMethods: entities = [await self.get_input_entity(entity)] else: entities = await asyncio.gather( - *(self.get_input_entity(x) for x in entity), loop=self.loop) + *(self.get_input_entity(x) for x in entity)) if folder is None: raise ValueError('You must specify a folder') diff --git a/telethon/client/telegrambaseclient.py b/telethon/client/telegrambaseclient.py index a7637768..162e9dcd 100644 --- a/telethon/client/telegrambaseclient.py +++ b/telethon/client/telegrambaseclient.py @@ -180,7 +180,8 @@ class TelegramBaseClient(abc.ABC): Defaults to `lang_code`. loop (`asyncio.AbstractEventLoop`, optional): - Asyncio event loop to use. Defaults to `asyncio.get_event_loop()` + Asyncio event loop to use. Defaults to `asyncio.get_event_loop()`. + This argument is ignored. base_logger (`str` | `logging.Logger`, optional): Base logger name or instance to use. @@ -227,7 +228,7 @@ class TelegramBaseClient(abc.ABC): "Refer to telethon.rtfd.io for more information.") self._use_ipv6 = use_ipv6 - self._loop = loop or asyncio.get_event_loop() + self._loop = asyncio.get_event_loop() if isinstance(base_logger, str): base_logger = logging.getLogger(base_logger) @@ -334,7 +335,7 @@ class TelegramBaseClient(abc.ABC): ) self._sender = MTProtoSender( - self.session.auth_key, self._loop, + self.session.auth_key, loggers=self._log, retries=self._connection_retries, delay=self._retry_delay, @@ -350,15 +351,15 @@ class TelegramBaseClient(abc.ABC): # Cache ``{dc_id: (_ExportState, MTProtoSender)}`` for all borrowed senders self._borrowed_senders = {} - self._borrow_sender_lock = asyncio.Lock(loop=self._loop) + self._borrow_sender_lock = asyncio.Lock() self._updates_handle = None self._last_request = time.time() self._channel_pts = {} if sequential_updates: - self._updates_queue = asyncio.Queue(loop=self._loop) - self._dispatching_updates_queue = asyncio.Event(loop=self._loop) + self._updates_queue = asyncio.Queue() + self._dispatching_updates_queue = asyncio.Event() else: # Use a set of pending instead of a queue so we can properly # terminate all pending updates on disconnect. @@ -481,7 +482,6 @@ class TelegramBaseClient(abc.ABC): self.session.server_address, self.session.port, self.session.dc_id, - loop=self._loop, loggers=self._log, proxy=self._proxy )): @@ -556,7 +556,7 @@ class TelegramBaseClient(abc.ABC): for task in self._updates_queue: task.cancel() - await asyncio.wait(self._updates_queue, loop=self._loop) + await asyncio.wait(self._updates_queue) self._updates_queue.clear() pts, date = self._state_cache[None] @@ -639,12 +639,11 @@ class TelegramBaseClient(abc.ABC): # # If one were to do that, Telegram would reset the connection # with no further clues. - sender = MTProtoSender(None, self._loop, loggers=self._log) + sender = MTProtoSender(None, loggers=self._log) await sender.connect(self._connection( dc.ip_address, dc.port, dc.id, - loop=self._loop, loggers=self._log, proxy=self._proxy )) @@ -680,7 +679,6 @@ class TelegramBaseClient(abc.ABC): dc.ip_address, dc.port, dc.id, - loop=self._loop, loggers=self._log, proxy=self._proxy )) diff --git a/telethon/client/updates.py b/telethon/client/updates.py index 265ae850..e609f9ab 100644 --- a/telethon/client/updates.py +++ b/telethon/client/updates.py @@ -326,7 +326,7 @@ class UpdateMethods: while self.is_connected(): try: await asyncio.wait_for( - self.disconnected, timeout=60, loop=self._loop + self.disconnected, timeout=60 ) continue # We actually just want to act upon timeout except asyncio.TimeoutError: diff --git a/telethon/client/users.py b/telethon/client/users.py index 49592494..816fa10a 100644 --- a/telethon/client/users.py +++ b/telethon/client/users.py @@ -44,7 +44,7 @@ class UserMethods: self._flood_waited_requests.pop(r.CONSTRUCTOR_ID, None) elif diff <= self.flood_sleep_threshold: self._log[__name__].info(*_fmt_flood(diff, r, early=True)) - await asyncio.sleep(diff, loop=self._loop) + await asyncio.sleep(diff) self._flood_waited_requests.pop(r.CONSTRUCTOR_ID, None) else: raise errors.FloodWaitError(request=r, capture=diff) @@ -99,7 +99,7 @@ class UserMethods: if e.seconds <= self.flood_sleep_threshold: self._log[__name__].info(*_fmt_flood(e.seconds, request)) - await asyncio.sleep(e.seconds, loop=self._loop) + await asyncio.sleep(e.seconds) else: raise except (errors.PhoneMigrateError, errors.NetworkMigrateError, diff --git a/telethon/events/common.py b/telethon/events/common.py index f95a1ac6..e8978f31 100644 --- a/telethon/events/common.py +++ b/telethon/events/common.py @@ -92,7 +92,7 @@ class EventBuilder(abc.ABC): return if not self._resolve_lock: - self._resolve_lock = asyncio.Lock(loop=client.loop) + self._resolve_lock = asyncio.Lock() async with self._resolve_lock: if not self.resolved: diff --git a/telethon/events/inlinequery.py b/telethon/events/inlinequery.py index 3ae3874e..7a789669 100644 --- a/telethon/events/inlinequery.py +++ b/telethon/events/inlinequery.py @@ -206,10 +206,9 @@ class InlineQuery(EventBuilder): return if results: - futures = [self._as_future(x, self._client.loop) - for x in results] + futures = [self._as_future(x) for x in results] - await asyncio.wait(futures, loop=self._client.loop) + await asyncio.wait(futures) # All futures will be in the `done` *set* that `wait` returns. # @@ -236,10 +235,10 @@ class InlineQuery(EventBuilder): ) @staticmethod - def _as_future(obj, loop): + def _as_future(obj): if inspect.isawaitable(obj): - return asyncio.ensure_future(obj, loop=loop) + return asyncio.ensure_future(obj) - f = loop.create_future() + f = asyncio.get_event_loop().create_future() f.set_result(obj) return f diff --git a/telethon/extensions/messagepacker.py b/telethon/extensions/messagepacker.py index 443a5f3e..c0f46f48 100644 --- a/telethon/extensions/messagepacker.py +++ b/telethon/extensions/messagepacker.py @@ -22,11 +22,10 @@ class MessagePacker: point where outgoing requests are put, and where ready-messages are get. """ - def __init__(self, state, loop, loggers): + def __init__(self, state, loggers): self._state = state - self._loop = loop self._deque = collections.deque() - self._ready = asyncio.Event(loop=loop) + self._ready = asyncio.Event() self._log = loggers[__name__] def append(self, state): diff --git a/telethon/network/connection/connection.py b/telethon/network/connection/connection.py index d6b9ea16..800ff02b 100644 --- a/telethon/network/connection/connection.py +++ b/telethon/network/connection/connection.py @@ -28,11 +28,10 @@ class Connection(abc.ABC): # should be one of `PacketCodec` implementations packet_codec = None - def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None): + def __init__(self, ip, port, dc_id, *, loggers, proxy=None): self._ip = ip self._port = port self._dc_id = dc_id # only for MTProxy, it's an abstraction leak - self._loop = loop self._log = loggers[__name__] self._proxy = proxy self._reader = None @@ -48,9 +47,8 @@ class Connection(abc.ABC): async def _connect(self, timeout=None, ssl=None): if not self._proxy: self._reader, self._writer = await asyncio.wait_for( - asyncio.open_connection( - self._ip, self._port, loop=self._loop, ssl=ssl), - loop=self._loop, timeout=timeout + asyncio.open_connection(self._ip, self._port, ssl=ssl), + timeout=timeout ) else: import socks @@ -67,9 +65,8 @@ class Connection(abc.ABC): s.settimeout(timeout) await asyncio.wait_for( - self._loop.sock_connect(s, address), - timeout=timeout, - loop=self._loop + asyncio.get_event_loop().sock_connect(s, address), + timeout=timeout ) if ssl: if ssl_mod is None: @@ -87,8 +84,7 @@ class Connection(abc.ABC): s.setblocking(False) - self._reader, self._writer = \ - await asyncio.open_connection(sock=s, loop=self._loop) + self._reader, self._writer = await asyncio.open_connection(sock=s) self._codec = self.packet_codec(self) self._init_conn() @@ -101,8 +97,9 @@ class Connection(abc.ABC): await self._connect(timeout=timeout, ssl=ssl) self._connected = True - self._send_task = self._loop.create_task(self._send_loop()) - self._recv_task = self._loop.create_task(self._recv_loop()) + loop = asyncio.get_event_loop() + self._send_task = loop.create_task(self._send_loop()) + self._recv_task = loop.create_task(self._recv_loop()) async def disconnect(self): """ diff --git a/telethon/network/connection/tcpmtproxy.py b/telethon/network/connection/tcpmtproxy.py index 2a9438ab..f034dfbe 100644 --- a/telethon/network/connection/tcpmtproxy.py +++ b/telethon/network/connection/tcpmtproxy.py @@ -95,12 +95,12 @@ class TcpMTProxy(ObfuscatedConnection): obfuscated_io = MTProxyIO # noinspection PyUnusedLocal - def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None): + def __init__(self, ip, port, dc_id, *, loggers, proxy=None): # connect to proxy's host and port instead of telegram's ones proxy_host, proxy_port = self.address_info(proxy) self._secret = bytes.fromhex(proxy[2]) super().__init__( - proxy_host, proxy_port, dc_id, loop=loop, loggers=loggers) + proxy_host, proxy_port, dc_id, loggers=loggers) async def _connect(self, timeout=None, ssl=None): await super()._connect(timeout=timeout, ssl=ssl) diff --git a/telethon/network/mtprotosender.py b/telethon/network/mtprotosender.py index 5483716a..48f6e238 100644 --- a/telethon/network/mtprotosender.py +++ b/telethon/network/mtprotosender.py @@ -40,12 +40,11 @@ class MTProtoSender: A new authorization key will be generated on connection if no other key exists yet. """ - def __init__(self, auth_key, loop, *, loggers, + def __init__(self, auth_key, *, loggers, retries=5, delay=1, auto_reconnect=True, connect_timeout=None, auth_key_callback=None, update_callback=None, auto_reconnect_callback=None): self._connection = None - self._loop = loop self._loggers = loggers self._log = loggers[__name__] self._retries = retries @@ -55,7 +54,7 @@ class MTProtoSender: self._auth_key_callback = auth_key_callback self._update_callback = update_callback self._auto_reconnect_callback = auto_reconnect_callback - self._connect_lock = asyncio.Lock(loop=loop) + self._connect_lock = asyncio.Lock() # Whether the user has explicitly connected or disconnected. # @@ -65,7 +64,7 @@ class MTProtoSender: # pending futures should be cancelled. self._user_connected = False self._reconnecting = False - self._disconnected = self._loop.create_future() + self._disconnected = asyncio.get_event_loop().create_future() self._disconnected.set_result(None) # We need to join the loops upon disconnection @@ -78,8 +77,7 @@ class MTProtoSender: # Outgoing messages are put in a queue and sent in a batch. # Note that here we're also storing their ``_RequestState``. - self._send_queue = MessagePacker(self._state, self._loop, - loggers=self._loggers) + self._send_queue = MessagePacker(self._state, loggers=self._loggers) # Sent states are remembered until a response is received. self._pending_state = {} @@ -171,7 +169,7 @@ class MTProtoSender: if not utils.is_list_like(request): try: - state = RequestState(request, self._loop) + state = RequestState(request) except struct.error as e: # "struct.error: required argument is not an integer" is not # very helpful; log the request to find out what wasn't int. @@ -186,7 +184,7 @@ class MTProtoSender: state = None for req in request: try: - state = RequestState(req, self._loop, after=ordered and state) + state = RequestState(req, after=ordered and state) except struct.error as e: self._log.error('Request caused struct.error: %s: %s', e, request) raise @@ -206,7 +204,7 @@ class MTProtoSender: Note that it may resolve in either a ``ConnectionError`` or any other unexpected error that could not be handled. """ - return asyncio.shield(self._disconnected, loop=self._loop) + return asyncio.shield(self._disconnected) # Private methods @@ -241,7 +239,7 @@ class MTProtoSender: # reconnect cleanly after. await self._connection.disconnect() connected = False - await asyncio.sleep(self._delay, loop=self._loop) + await asyncio.sleep(self._delay) continue # next iteration we will try to reconnect break # all steps done, break retry loop @@ -253,17 +251,18 @@ class MTProtoSender: await self._disconnect(error=e) raise e + loop = asyncio.get_event_loop() self._log.debug('Starting send loop') - self._send_loop_handle = self._loop.create_task(self._send_loop()) + self._send_loop_handle = loop.create_task(self._send_loop()) self._log.debug('Starting receive loop') - self._recv_loop_handle = self._loop.create_task(self._recv_loop()) + self._recv_loop_handle = loop.create_task(self._recv_loop()) # _disconnected only completes after manual disconnection # or errors after which the sender cannot continue such # as failing to reconnect or any unexpected error. if self._disconnected.done(): - self._disconnected = self._loop.create_future() + self._disconnected = loop.create_future() self._log.info('Connection to %s complete!', self._connection) @@ -378,7 +377,7 @@ class MTProtoSender: self._pending_state.clear() if self._auto_reconnect_callback: - self._loop.create_task(self._auto_reconnect_callback()) + asyncio.get_event_loop().create_task(self._auto_reconnect_callback()) break else: @@ -398,7 +397,7 @@ class MTProtoSender: # gets stuck. # TODO It still gets stuck? Investigate where and why. self._reconnecting = True - self._loop.create_task(self._reconnect(error)) + asyncio.get_event_loop().create_task(self._reconnect(error)) # Loops @@ -411,7 +410,7 @@ class MTProtoSender: """ while self._user_connected and not self._reconnecting: if self._pending_ack: - ack = RequestState(MsgsAck(list(self._pending_ack)), self._loop) + ack = RequestState(MsgsAck(list(self._pending_ack))) self._send_queue.append(ack) self._last_acks.append(ack) self._pending_ack.clear() @@ -564,7 +563,7 @@ class MTProtoSender: if rpc_result.error: error = rpc_message_to_error(rpc_result.error, state.request) self._send_queue.append( - RequestState(MsgsAck([state.msg_id]), loop=self._loop)) + RequestState(MsgsAck([state.msg_id]))) if not state.future.cancelled(): state.future.set_exception(error) @@ -751,8 +750,8 @@ class MTProtoSender: enqueuing a :tl:`MsgsStateInfo` to be sent at a later point. """ self._send_queue.append(RequestState(MsgsStateInfo( - req_msg_id=message.msg_id, info=chr(1) * len(message.obj.msg_ids)), - loop=self._loop)) + req_msg_id=message.msg_id, info=chr(1) * len(message.obj.msg_ids) + ))) async def _handle_msg_all(self, message): """ diff --git a/telethon/network/requeststate.py b/telethon/network/requeststate.py index eb598e24..21b5efd9 100644 --- a/telethon/network/requeststate.py +++ b/telethon/network/requeststate.py @@ -10,10 +10,10 @@ class RequestState: """ __slots__ = ('container_id', 'msg_id', 'request', 'data', 'future', 'after') - def __init__(self, request, loop, after=None): + def __init__(self, request, after=None): self.container_id = None self.msg_id = None self.request = request self.data = bytes(request) - self.future = asyncio.Future(loop=loop) + self.future = asyncio.Future() self.after = after diff --git a/telethon/requestiter.py b/telethon/requestiter.py index e51cfafe..fd28419d 100644 --- a/telethon/requestiter.py +++ b/telethon/requestiter.py @@ -65,8 +65,7 @@ class RequestIter(abc.ABC): # asyncio will handle times <= 0 to sleep 0 seconds if self.wait_time: await asyncio.sleep( - self.wait_time - (time.time() - self.last_load), - loop=self.client.loop + self.wait_time - (time.time() - self.last_load) ) self.last_load = time.time() diff --git a/telethon/tl/custom/conversation.py b/telethon/tl/custom/conversation.py index 65ec2725..79675b65 100644 --- a/telethon/tl/custom/conversation.py +++ b/telethon/tl/custom/conversation.py @@ -445,8 +445,7 @@ class Conversation(ChatGetter): # cleared when their futures are set to a result. return asyncio.wait_for( future, - timeout=None if due == float('inf') else due - time.time(), - loop=self._client.loop + timeout=None if due == float('inf') else due - time.time() ) def _cancel_all(self, exception=None): diff --git a/telethon_examples/gui.py b/telethon_examples/gui.py index 949d1eb9..bd241f60 100644 --- a/telethon_examples/gui.py +++ b/telethon_examples/gui.py @@ -341,8 +341,8 @@ class App(tkinter.Tk): self.chat.configure(bg='yellow') -async def main(loop, interval=0.05): - client = TelegramClient(SESSION, API_ID, API_HASH, loop=loop) +async def main(interval=0.05): + client = TelegramClient(SESSION, API_ID, API_HASH) try: await client.connect() except Exception as e: @@ -372,7 +372,7 @@ if __name__ == "__main__": # Some boilerplate code to set up the main method aio_loop = asyncio.get_event_loop() try: - aio_loop.run_until_complete(main(aio_loop)) + aio_loop.run_until_complete(main()) finally: if not aio_loop.is_closed(): aio_loop.close() From ec8bb8a06abcce7bde4179a6eb7232002fcf86ed Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 25 Jul 2020 18:42:12 +0200 Subject: [PATCH 73/94] Document PRIVACY_TOO_LONG --- telethon_generator/data/errors.csv | 1 + telethon_generator/data/methods.csv | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index 94f98d95..e686d3f2 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -223,6 +223,7 @@ POLL_OPTION_INVALID,400,A poll option used invalid data (the data may be too lon POLL_QUESTION_INVALID,400,The poll question was either empty or too long POLL_UNSUPPORTED,400,This layer does not support polls in the issued method PRIVACY_KEY_INVALID,400,The privacy key is invalid +PRIVACY_TOO_LONG,400,Cannot add that many entities in a single request PTS_CHANGE_EMPTY,500,No PTS change QUERY_ID_EMPTY,400,The query ID is empty QUERY_ID_INVALID,400,The query ID is invalid diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index 16a95026..9247d35f 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -50,7 +50,7 @@ account.sendVerifyPhoneCode,user, account.setAccountTTL,user,TTL_DAYS_INVALID account.setContactSignUpNotification,user, account.setContentSettings,user, -account.setPrivacy,user,PRIVACY_KEY_INVALID +account.setPrivacy,user,PRIVACY_KEY_INVALID PRIVACY_TOO_LONG account.unregisterDevice,user,TOKEN_INVALID account.updateDeviceLocked,user, account.updateNotifySettings,user,PEER_ID_INVALID From 57b38b24dd43bc0ea4899f8b64edaf0033c4a387 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 26 Jul 2020 12:55:29 +0200 Subject: [PATCH 74/94] Update to layer 116 --- telethon_generator/data/api.tl | 43 ++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/telethon_generator/data/api.tl b/telethon_generator/data/api.tl index c381558b..c667f1a2 100644 --- a/telethon_generator/data/api.tl +++ b/telethon_generator/data/api.tl @@ -62,10 +62,9 @@ inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector< inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia; inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia; inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia; -inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; +inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia; inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia; -inputMediaGifExternal#4843b0fd url:string q:string = InputMedia; inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; @@ -75,7 +74,7 @@ inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector s inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; -inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; +inputChatUploadedPhoto#c642724e flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = InputChatPhoto; inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto; inputGeoPointEmpty#e4c123d6 = InputGeoPoint; @@ -113,7 +112,7 @@ userEmpty#200250ba id:int = User; user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; -userProfilePhoto#ecd75d8c photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto; +userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto; userStatusEmpty#9d05049 = UserStatus; userStatusOnline#edb93949 expires:int = UserStatus; @@ -139,7 +138,7 @@ chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0? chatParticipants#3f460fed chat_id:int participants:Vector version:int = ChatParticipants; chatPhotoEmpty#37c1011c = ChatPhoto; -chatPhoto#475cdbd5 photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; +chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; messageEmpty#83e5de54 id:int = Message; message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; @@ -187,7 +186,7 @@ dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer t dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; photoEmpty#2331b22d id:long = Photo; -photo#d07504a5 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector dc_id:int = Photo; +photo#fb197a65 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector video_sizes:flags.1?Vector dc_id:int = Photo; photoSizeEmpty#e17e23c type:string = PhotoSize; photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; @@ -213,7 +212,7 @@ inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings; -peerSettings#818426cd flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true = PeerSettings; +peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true geo_distance:flags.6?int = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; @@ -358,6 +357,7 @@ updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; +updateChannelParticipant#65d2b464 flags:# channel_id:int date:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant qts:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -395,7 +395,7 @@ help.inviteText#18cb9f78 message:string = help.InviteText; encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat; encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat; -encryptedChatRequested#c878527e id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat; +encryptedChatRequested#62718a82 flags:# folder_id:flags.0?int id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat; encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat; encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat; @@ -529,6 +529,7 @@ chatInviteExported#fc2e05bc link:string = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; +chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; @@ -619,11 +620,6 @@ channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector min_age_confirm:flags.1?int = help.TermsOfService; -foundGif#162ecc1f url:string thumb_url:string content_url:string content_type:string w:int h:int = FoundGif; -foundGifCached#9c750409 url:string photo:Photo document:Document = FoundGif; - -messages.foundGifs#450a1c0a next_offset:int results:Vector = messages.FoundGifs; - messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs; messages.savedGifs#2e0709a5 hash:int gifs:Vector = messages.SavedGifs; @@ -1141,7 +1137,17 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA help.promoDataEmpty#98f6ac75 expires:int = help.PromoData; help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector users:Vector psa_type:flags.1?string psa_message:flags.2?string = help.PromoData; -videoSize#435bb987 type:string location:FileLocation w:int h:int size:int = VideoSize; +videoSize#e831c556 flags:# type:string location:FileLocation w:int h:int size:int video_start_ts:flags.0?double = VideoSize; + +statsGroupTopPoster#18f3d0f7 user_id:int messages:int avg_chars:int = StatsGroupTopPoster; + +statsGroupTopAdmin#6014f412 user_id:int deleted:int kicked:int banned:int = StatsGroupTopAdmin; + +statsGroupTopInviter#31962a4c user_id:int invitations:int = StatsGroupTopInviter; + +stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAndPrev messages:StatsAbsValueAndPrev viewers:StatsAbsValueAndPrev posters:StatsAbsValueAndPrev growth_graph:StatsGraph members_graph:StatsGraph new_members_by_source_graph:StatsGraph languages_graph:StatsGraph messages_graph:StatsGraph actions_graph:StatsGraph top_hours_graph:StatsGraph weekdays_graph:StatsGraph top_posters:Vector top_admins:Vector top_inviters:Vector users:Vector = stats.MegagroupStats; + +globalPrivacySettings#bea2f424 flags:# archive_and_mute_new_noncontact_peers:flags.0?Bool = GlobalPrivacySettings; ---functions--- @@ -1237,6 +1243,8 @@ account.getThemes#285946f8 format:string hash:int = account.Themes; account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool; account.getContentSettings#8b9b4dae = account.ContentSettings; account.getMultiWallPapers#65ad71dc wallpapers:Vector = Vector; +account.getGlobalPrivacySettings#eb2b4cf6 = GlobalPrivacySettings; +account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = GlobalPrivacySettings; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; @@ -1312,7 +1320,6 @@ messages.migrateChat#15a3b8e3 chat_id:int = Updates; messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; -messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs; messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; @@ -1392,7 +1399,7 @@ updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date: updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto; -photos.uploadProfilePhoto#4f32c098 file:InputFile = photos.Photo; +photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; @@ -1425,6 +1432,7 @@ help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo; help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector = help.UserInfo; help.getPromoData#c0977421 = help.PromoData; help.hidePromoData#1e251c95 peer:InputPeer = Bool; +help.dismissSuggestion#77fa99f suggestion:string = Bool; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; @@ -1501,5 +1509,6 @@ folders.deleteFolder#1c295881 folder_id:int = Updates; stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; +stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats; -// LAYER 114 +// LAYER 116 From 95ea2fb40c60eff3f79b6f36337a57548fa84c81 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 26 Jul 2020 12:59:10 +0200 Subject: [PATCH 75/94] Remove uses of gif external Since it has been removed in layer 116. --- telethon/client/uploads.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index 47fb4b44..19914ba5 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -728,8 +728,6 @@ class UploadMethods: elif re.match('https?://', file): if as_image: media = types.InputMediaPhotoExternal(file) - elif not force_document and utils.is_gif(file): - media = types.InputMediaGifExternal(file, '') else: media = types.InputMediaDocumentExternal(file) else: From e12f6c747fea4be9004be139eb51695b78ddd7ed Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 26 Jul 2020 13:03:59 +0200 Subject: [PATCH 76/94] Extend use of force_document to work on files This allows .webp files to be sent as documents and not stickers. --- telethon/client/uploads.py | 3 ++- telethon/utils.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index 19914ba5..2cf636dc 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -766,7 +766,8 @@ class UploadMethods: file=file_handle, mime_type=mime_type, attributes=attributes, - thumb=thumb + thumb=thumb, + force_file=force_document ) return file_handle, media, as_image diff --git a/telethon/utils.py b/telethon/utils.py index c8991724..c8faf5a6 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -483,7 +483,7 @@ def get_input_media( supports_streaming=supports_streaming ) return types.InputMediaUploadedDocument( - file=media, mime_type=mime, attributes=attrs) + file=media, mime_type=mime, attributes=attrs, force_file=force_document) if isinstance(media, types.MessageMediaGame): return types.InputMediaGame(id=types.InputGameID( From b1ea7572dd7aacba3b535195d330a2f1bcefc819 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 26 Jul 2020 13:27:45 +0200 Subject: [PATCH 77/94] Document new rpc errors in layer 116 --- telethon_generator/data/errors.csv | 3 +++ telethon_generator/data/methods.csv | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index e686d3f2..993ddec5 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -32,6 +32,7 @@ BOT_POLLS_DISABLED,400,You cannot create polls under a bot account BOT_RESPONSE_TIMEOUT,400,The bot did not answer to the callback query in time BROADCAST_ID_INVALID,400,The channel is invalid BROADCAST_PUBLIC_VOTERS_FORBIDDEN,400,You cannot broadcast polls where the voters are public +BROADCAST_REQUIRED,400,The request can only be used with a broadcast channel BUTTON_DATA_INVALID,400,The provided button data is invalid BUTTON_TYPE_INVALID,400,The type of one of the buttons you provided is invalid BUTTON_URL_INVALID,400,Button URL invalid @@ -154,6 +155,7 @@ MEDIA_NEW_INVALID,400,The new media to edit the message with is invalid (such as MEDIA_PREV_INVALID,400,The old media cannot be edited with anything else (such as stickers or voice notes) MEGAGROUP_ID_INVALID,400,The group is invalid MEGAGROUP_PREHISTORY_HIDDEN,400,You can't set this discussion group because it's history is hidden +MEGAGROUP_REQUIRED,400,The request can only be used with a megagroup channel MEMBER_NO_LOCATION,500,An internal failure occurred while fetching user info (couldn't find location) MEMBER_OCCUPY_PRIMARY_LOC_FAILED,500,Occupation of primary member location failed MESSAGE_AUTHOR_REQUIRED,403,Message author required @@ -317,6 +319,7 @@ USER_NOT_PARTICIPANT,400,The target user is not a member of the specified megagr USER_PRIVACY_RESTRICTED,403,The user's privacy settings do not allow you to do this USER_RESTRICTED,403,"You're spamreported, you can't create channels or chats." VIDEO_CONTENT_TYPE_INVALID,400,The video content type is not supported with the given parameters (i.e. supports_streaming) +VIDEO_FILE_INVALID,400,The given video cannot be used WALLPAPER_FILE_INVALID,400,The given file cannot be used as a wallpaper WALLPAPER_INVALID,400,The input wallpaper was not valid WC_CONVERT_URL_INVALID,400,WC convert URL invalid diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index 9247d35f..e2d58e6b 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -312,14 +312,15 @@ phone.setCallRating,user,CALL_PEER_INVALID photos.deletePhotos,user, photos.getUserPhotos,both,MAX_ID_INVALID USER_ID_INVALID photos.updateProfilePhoto,user, -photos.uploadProfilePhoto,user,FILE_PARTS_INVALID IMAGE_PROCESS_FAILED PHOTO_CROP_SIZE_SMALL PHOTO_EXT_INVALID +photos.uploadProfilePhoto,user,FILE_PARTS_INVALID IMAGE_PROCESS_FAILED PHOTO_CROP_SIZE_SMALL PHOTO_EXT_INVALID VIDEO_FILE_INVALID ping,both, reqDHParams,both, reqPq,both, reqPqMulti,both, rpcDropAnswer,both, setClientDHParams,both, -stats.getBroadcastStats,user, +stats.getBroadcastStats,user,BROADCAST_REQUIRED CHAT_ADMIN_REQUIRED STATS_MIGRATE_X +stats.getMegagroupStats,user,CHAT_ADMIN_REQUIRED MEGAGROUP_REQUIRED STATS_MIGRATE_X stats.loadAsyncGraph,user, stickers.addStickerToSet,bot,BOT_MISSING STICKERSET_INVALID stickers.changeStickerPosition,bot,BOT_MISSING STICKER_INVALID From f18ab08334c03ddbef4c86df7ac9fbbb3509c83a Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 26 Jul 2020 13:45:30 +0200 Subject: [PATCH 78/94] Add new friendly method to get channel stats --- .../quick-references/client-reference.rst | 1 + telethon/client/chats.py | 65 ++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/readthedocs/quick-references/client-reference.rst b/readthedocs/quick-references/client-reference.rst index 69923efa..a148f32c 100644 --- a/readthedocs/quick-references/client-reference.rst +++ b/readthedocs/quick-references/client-reference.rst @@ -139,6 +139,7 @@ Chats get_profile_photos edit_admin edit_permissions + get_stats action Parse Mode diff --git a/telethon/client/chats.py b/telethon/client/chats.py index 7f540377..5b43af36 100644 --- a/telethon/client/chats.py +++ b/telethon/client/chats.py @@ -4,7 +4,7 @@ import itertools import string import typing -from .. import helpers, utils, hints +from .. import helpers, utils, hints, errors from ..requestiter import RequestIter from ..tl import types, functions, custom @@ -1123,4 +1123,67 @@ class ChatMethods: else: raise ValueError('You must pass either a channel or a chat') + async def get_stats( + self: 'TelegramClient', + entity: 'hints.EntityLike', + ): + """ + Retrieves statistics from the given megagroup or broadcast channel. + + Note that some restrictions apply before being able to fetch statistics, + in particular the channel must have enough members (for megagroups, this + requires `at least 500 members`_). + + Arguments + entity (`entity`): + The channel from which to get statistics. + + Raises + If the given entity is not a channel (broadcast or megagroup), + a `TypeError` is raised. + + If there are not enough members (poorly named) errors such as + ``telethon.errors.ChatAdminRequiredError`` will appear. + + Returns + Either :tl:`BroadcastStats` or :tl:`MegagroupStats`, depending on + whether the input belonged to a broadcast channel or megagroup. + + Example + .. code-block:: python + + # Some megagroup or channel username or ID to fetch + channel = -100123 + stats = await client.get_stats(channel) + print('Stats from', stats.period.min_date, 'to', stats.period.max_date, ':') + print(stats.stringify()) + + .. _`at least 500 members`: https://telegram.org/blog/profile-videos-people-nearby-and-more + """ + entity = await self.get_input_entity(entity) + if helpers._entity_type(entity) != helpers._EntityType.CHANNEL: + raise TypeError('You must pass a user entity') + + # Don't bother fetching the Channel entity (costs a request), instead + # try to guess and if it fails we know it's the other one (best case + # no extra request, worst just one). + try: + req = functions.stats.GetBroadcastStatsRequest(entity) + return await self(req) + except errors.StatsMigrateError as e: + dc = e.dc + except errors.BroadcastRequiredError: + req = functions.stats.GetMegagroupStatsRequest(entity) + try: + return await self(req) + except errors.StatsMigrateError as e: + dc = e.dc + + sender = await self._borrow_exported_sender(dc) + try: + # req will be resolved to use the right types inside by now + return await sender.send(req) + finally: + await self._return_exported_sender(sender) + # endregion From 012cae051bc0a5a63f0dec8efe47b7a6501b74e2 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 26 Jul 2020 13:50:48 +0200 Subject: [PATCH 79/94] Add a script to update online documentation --- update-docs.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 update-docs.sh diff --git a/update-docs.sh b/update-docs.sh new file mode 100644 index 00000000..fe72cce7 --- /dev/null +++ b/update-docs.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +python setup.py gen docs +rm -rf /tmp/docs +mv docs/ /tmp/docs +git checkout gh-pages +# there's probably better ways but we know none has spaces +rm -rf $(ls /tmp/docs) +mv /tmp/docs/* . +git add constructors/ types/ methods/ index.html js/search.js css/ img/ +git commit --amend -m "Update documentation" +git push --force +git checkout master From 34861ad1bc3e57e1ba02202938d8aebc3969873d Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 28 Jul 2020 18:12:24 +0200 Subject: [PATCH 80/94] Update to v1.16 --- readthedocs/misc/changelog.rst | 32 ++++++++++++++++++++++++++++++++ telethon/version.py | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/readthedocs/misc/changelog.rst b/readthedocs/misc/changelog.rst index 8b59c097..e275533c 100644 --- a/readthedocs/misc/changelog.rst +++ b/readthedocs/misc/changelog.rst @@ -13,6 +13,38 @@ it can take advantage of new goodies! .. contents:: List of All Versions +Channel Statistics (v1.16) +========================== + ++------------------------+ +| Scheme layer used: 116 | ++------------------------+ + +The newest Telegram update has a new method to also retrieve megagroup +statistics, which can now be used with `client.get_stats() +`. This way you'll be able +to access the raw data about your channel or megagroup statistics. + +The maximum file size limit has also been increased to 2GB on the server, +so you can send even larger files. + +Breaking Changes +~~~~~~~~~~~~~~~~ + +* Besides the obvious layer change, the ``loop`` argument **is now ignored**. + It has been deprecated since Python 3.8 and will be removed in Python 3.10, + and also caused some annoying warning messages when using certain parts of + the library. If you were (incorrectly) relying on using a different loop + from the one that was set, things may break. + +Enhancements +~~~~~~~~~~~~ + +* `client.upload_file() ` + now works better when streaming files (anything that has a ``.read()``), + instead of reading it all into memory when possible. + + QR login (v1.15) ================ diff --git a/telethon/version.py b/telethon/version.py index b8e60970..19d6596d 100644 --- a/telethon/version.py +++ b/telethon/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '1.15.0' +__version__ = '1.16.0' From cb6ffeaabda7943fa7c7b98a21aac0dcdd7abb66 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 28 Jul 2020 19:46:50 +0200 Subject: [PATCH 81/94] Slightly improve docs heuristics for video files --- telethon_generator/parsers/tlobject/tlarg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/telethon_generator/parsers/tlobject/tlarg.py b/telethon_generator/parsers/tlobject/tlarg.py index bf33a3f6..b510f075 100644 --- a/telethon_generator/parsers/tlobject/tlarg.py +++ b/telethon_generator/parsers/tlobject/tlarg.py @@ -36,7 +36,8 @@ KNOWN_NAMED_EXAMPLES = { ('lang_pack', 'string'): "''", ('lang_code', 'string'): "'en'", ('chat_id', 'int'): '478614198', - ('client_id', 'long'): 'random.randrange(-2**63, 2**63)' + ('client_id', 'long'): 'random.randrange(-2**63, 2**63)', + ('video', 'InputFile'): "client.upload_file('/path/to/file.mp4')", } KNOWN_TYPED_EXAMPLES = { From 241c6c4ac8f1b371b93a2077954f04d8ff9c5580 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 3 Aug 2020 12:35:25 +0200 Subject: [PATCH 82/94] Auto-retry on interdc call (rich) error --- telethon/client/users.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/telethon/client/users.py b/telethon/client/users.py index 816fa10a..3c6faa6e 100644 --- a/telethon/client/users.py +++ b/telethon/client/users.py @@ -79,7 +79,8 @@ class UserMethods: self._entity_cache.add(result) return result except (errors.ServerError, errors.RpcCallFailError, - errors.RpcMcgetFailError) as e: + errors.RpcMcgetFailError, errors.InterdcCallErrorError, + errors.InterdcCallRichErrorError) as e: self._log[__name__].warning( 'Telegram is having internal issues %s: %s', e.__class__.__name__, e) From 1d71cdc9e0bbf46766014b825b77a4feddf8fb2b Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 7 Aug 2020 16:03:50 +0200 Subject: [PATCH 83/94] Support autocast of polls into input media when possible Closes #1516. --- telethon/utils.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/telethon/utils.py b/telethon/utils.py index c8faf5a6..f74ef880 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -525,6 +525,27 @@ def get_input_media( if isinstance(media, types.Message): return get_input_media(media.media, is_photo=is_photo) + if isinstance(media, types.MessageMediaPoll): + if media.poll.quiz: + if not media.results.results: + # A quiz has correct answers, which we don't know until answered. + # If the quiz hasn't been answered we can't reconstruct it properly. + raise TypeError('Cannot cast unanswered quiz to any kind of InputMedia.') + + correct_answers = [r.option for r in media.results.results if r.correct] + else: + correct_answers = None + + return types.InputMediaPoll( + poll=media.poll, + correct_answers=correct_answers, + solution=media.results.solution, + solution_entities=media.results.solution_entities, + ) + + if isinstance(media, types.Poll): + return types.InputMediaPoll(media) + _raise_cast_fail(media, 'InputMedia') From 958698bba73532ddb16e663e2dae5a6165a03a5f Mon Sep 17 00:00:00 2001 From: conetra <54206885+conetra@users.noreply.github.com> Date: Sat, 8 Aug 2020 11:16:01 +0000 Subject: [PATCH 84/94] Remove square bracket around IPv6 addresses (#1517) --- telethon/client/telegrambaseclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/client/telegrambaseclient.py b/telethon/client/telegrambaseclient.py index 162e9dcd..62faecae 100644 --- a/telethon/client/telegrambaseclient.py +++ b/telethon/client/telegrambaseclient.py @@ -18,7 +18,7 @@ from ..tl.alltlobjects import LAYER DEFAULT_DC_ID = 2 DEFAULT_IPV4_IP = '149.154.167.51' -DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]' +DEFAULT_IPV6_IP = '2001:67c:4e8:f002::a' DEFAULT_PORT = 443 if typing.TYPE_CHECKING: From ddeefff4319ad7e77e9ebd183990b926edf280ed Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 8 Aug 2020 17:47:58 +0200 Subject: [PATCH 85/94] Add a warning when trying to connect to a different account Closes #1172, and also fixed a typo. --- telethon/client/auth.py | 25 ++++++++++++++++++++++++- telethon/client/downloads.py | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/telethon/client/auth.py b/telethon/client/auth.py index ceeb3359..b0e5243e 100644 --- a/telethon/client/auth.py +++ b/telethon/client/auth.py @@ -3,6 +3,7 @@ import inspect import os import sys import typing +import warnings from .. import utils, helpers, errors, password as pwd_mod from ..tl import types, functions, custom @@ -138,7 +139,29 @@ class AuthMethods: if not self.is_connected(): await self.connect() - if await self.is_user_authorized(): + # Rather than using `is_user_authorized`, use `get_me`. While this is + # more expensive and needs to retrieve more data from the server, it + # enables the library to warn users trying to login to a different + # account. See #1172. + me = await self.get_me() + if me is not None: + # The warnings here are on a best-effort and may fail. + if bot_token: + # bot_token's first part has the bot ID, but it may be invalid + # so don't try to parse as int (instead cast our ID to string). + if bot_token[:bot_token.find(':')] != str(me.id): + warnings.warn( + 'the session already had an authorized user so it did ' + 'not login to the bot account using the provided ' + 'bot_token (it may not be using the user you expect)' + ) + elif not callable(phone) and phone != me.phone: + warnings.warn( + 'the session already had an authorized user so it did ' + 'not login to the user account using the provided ' + 'phone (it may not be using the user you expect)' + ) + return self if not bot_token: diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index 25910ffd..2a27f412 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -562,7 +562,7 @@ class DownloadMethods: # Streaming `media` to an output file # After the iteration ends, the sender is cleaned up with open('photo.jpg', 'wb') as fd: - async for chunk client.iter_download(media): + async for chunk in client.iter_download(media): fd.write(chunk) # Fetching only the header of a file (32 bytes) From 9a0d6b9931be92e7690c5d5c7c54c0fcd6a3d09a Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 10 Aug 2020 16:09:39 +0200 Subject: [PATCH 86/94] Don't set force_file on force_document with images Otherwise, Telegram won't analyze the image and won't add it the DocumentAttributeImageSize, causing some bots like t.me/Stickers to break. Closes #1507. --- telethon/client/uploads.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index 2cf636dc..c46c2899 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -687,8 +687,9 @@ class UploadMethods: if isinstance(file, pathlib.Path): file = str(file.absolute()) + is_image = utils.is_image(file) if as_image is None: - as_image = utils.is_image(file) and not force_document + as_image = is_image and not force_document # `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. @@ -749,7 +750,7 @@ class UploadMethods: file, mime_type=mime_type, attributes=attributes, - force_document=force_document, + force_document=force_document and not is_image, voice_note=voice_note, video_note=video_note, supports_streaming=supports_streaming @@ -767,7 +768,7 @@ class UploadMethods: mime_type=mime_type, attributes=attributes, thumb=thumb, - force_file=force_document + force_file=force_document and not is_image ) return file_handle, media, as_image From 3c56a6db4d54ddb2ae7a9063f08cc9c2a61080fc Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 10 Aug 2020 16:19:31 +0200 Subject: [PATCH 87/94] Update to v1.16.1 --- readthedocs/misc/changelog.rst | 30 ++++++++++++++++++++++++++++++ telethon/version.py | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/readthedocs/misc/changelog.rst b/readthedocs/misc/changelog.rst index e275533c..8d6484de 100644 --- a/readthedocs/misc/changelog.rst +++ b/readthedocs/misc/changelog.rst @@ -13,6 +13,36 @@ it can take advantage of new goodies! .. contents:: List of All Versions +Bug Fixes (v1.16.1) +=================== + +The last release added support to ``force_file`` on any media, including +things that were not possible before like ``.webp`` files. However, the +``force_document`` toggle commonly used for photos was applied "twice" +(one told the library to send it as a document, and then to send that +document as file), which prevented Telegram for analyzing the images. Long +story short, sending files to the stickers bot stopped working, but that's +been fixed now, and sending photos as documents include the size attribute +again as long as Telegram adds it. + +Enhancements +~~~~~~~~~~~~ + +* When trying to `client.start() ` to + another account if you were previously logged in, the library will now warn + you because this is probably not intended. To avoid the warning, make sure + you're logging in to the right account or logout from the other first. +* Sending a copy of messages with polls will now work when possible. +* The library now automatically retries on inter-dc call errors (which occur + when Telegram has internal issues). + +Bug Fixes +~~~~~~~~~ + +* The aforementioned issue with ``force_document``. +* Square brackets removed from IPv6 addresses. This may fix IPv6 support. + + Channel Statistics (v1.16) ========================== diff --git a/telethon/version.py b/telethon/version.py index 19d6596d..a05c72ca 100644 --- a/telethon/version.py +++ b/telethon/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '1.16.0' +__version__ = '1.16.1' From 0cefc7344849bc20629029c7a3d4aae4896b4be5 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 11 Aug 2020 22:31:12 +0200 Subject: [PATCH 88/94] Support both str and VideoSize as thumb on download_media --- telethon/client/downloads.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index 2a27f412..0a382ede 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -634,8 +634,10 @@ class DownloadMethods: return thumbs[-1] elif isinstance(thumb, int): return thumbs[thumb] + elif isinstance(thumb, str): + return next((t for t in thumbs if t.type == thumb), None) elif isinstance(thumb, (types.PhotoSize, types.PhotoCachedSize, - types.PhotoStrippedSize)): + types.PhotoStrippedSize, types.VideoSize)): return thumb else: return None @@ -670,11 +672,16 @@ class DownloadMethods: if not isinstance(photo, types.Photo): return - size = self._get_thumb(photo.sizes, thumb) + # Include video sizes here (but they may be None so provide an empty list) + size = self._get_thumb(photo.sizes + (photo.video_sizes or []), thumb) if not size or isinstance(size, types.PhotoSizeEmpty): return - file = self._get_proper_filename(file, 'photo', '.jpg', date=date) + if isinstance(size, types.VideoSize): + file = self._get_proper_filename(file, 'video', '.mp4', date=date) + else: + file = self._get_proper_filename(file, 'photo', '.jpg', date=date) + if isinstance(size, (types.PhotoCachedSize, types.PhotoStrippedSize)): return self._download_cached_photo_size(size, file) From e19aa44d5c23ebbcfb91a6f846a574eb9932abf5 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 11 Aug 2020 23:14:31 +0200 Subject: [PATCH 89/94] Sort thumbs to ensure -1 is largest Closes #1519. --- telethon/client/downloads.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index 0a382ede..ba612197 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -310,13 +310,20 @@ class DownloadMethods: The parameter should be an integer index between ``0`` and ``len(sizes)``. ``0`` will download the smallest thumbnail, and ``len(sizes) - 1`` will download the largest thumbnail. - You can also use negative indices. + You can also use negative indices, which work the same as + they do in Python's `list`. You can also pass the :tl:`PhotoSize` instance to use. + Alternatively, the thumb size type `str` may be used. In short, use ``thumb=0`` if you want the smallest thumbnail and ``thumb=-1`` if you want the largest thumbnail. + .. note:: + The largest thumbnail may be a video instead of a photo, + as they are available since layer 116 and are bigger than + any of the photos. + Returns `None` if no media was provided, or if it was Empty. On success the file path is returned since it may differ from the one given. @@ -630,6 +637,24 @@ class DownloadMethods: @staticmethod def _get_thumb(thumbs, thumb): + # Seems Telegram has changed the order and put `PhotoStrippedSize` + # last while this is the smallest (layer 116). Ensure we have the + # sizes sorted correctly with a custom function. + def sort_thumbs(thumb): + if isinstance(thumb, types.PhotoStrippedSize): + return 1, len(thumb.bytes) + if isinstance(thumb, types.PhotoCachedSize): + return 1, len(thumb.bytes) + if isinstance(thumb, types.PhotoSize): + return 1, thumb.size + if isinstance(thumb, types.VideoSize): + return 2, thumb.size + + # Empty size or invalid should go last + return 0, 0 + + thumbs = list(sorted(thumbs, key=sort_thumbs)) + if thumb is None: return thumbs[-1] elif isinstance(thumb, int): From e00496aa63dc586f6d6e29b7f6d86b742f69a2dd Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 11 Aug 2020 23:15:57 +0200 Subject: [PATCH 90/94] Update to v1.16.2 --- telethon/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/version.py b/telethon/version.py index a05c72ca..f6c0593f 100644 --- a/telethon/version.py +++ b/telethon/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '1.16.1' +__version__ = '1.16.2' From 73109eb81947be881c0daf5735900249a5be93d0 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 13 Aug 2020 15:13:29 +0200 Subject: [PATCH 91/94] Add a workaround for channels that claim have no photos but do --- telethon/client/chats.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/telethon/client/chats.py b/telethon/client/chats.py index 5b43af36..ee8dacfd 100644 --- a/telethon/client/chats.py +++ b/telethon/client/chats.py @@ -337,9 +337,25 @@ class _ProfilePhotoIter(RequestIter): else: self.request.offset += len(result.photos) else: + self.total = getattr(result, 'count', None) + if self.total == 0 and isinstance(result, types.messages.ChannelMessages): + # There are some broadcast channels that have a photo but this + # request doesn't retrieve it for some reason. Work around this + # issue by fetching the channel. + # + # We can't use the normal entity because that gives `ChatPhoto` + # but we want a proper `Photo`, so fetch full channel instead. + channel = await self.client(functions.channels.GetFullChannelRequest(self.request.peer)) + photo = channel.full_chat.chat_photo + if isinstance(photo, types.Photo): + self.buffer = [photo] + self.total = 1 + + self.left = len(self.buffer) + return + self.buffer = [x.action.photo for x in result.messages if isinstance(x.action, types.MessageActionChatEditPhoto)] - self.total = getattr(result, 'count', None) if len(result.messages) < self.request.limit: self.left = len(self.buffer) elif result.messages: From 26ff92caa9f61c4e37cc10035f73ec1f07336c52 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 15 Aug 2020 12:54:05 +0200 Subject: [PATCH 92/94] Update to layer 117 --- telethon_generator/data/api.tl | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/telethon_generator/data/api.tl b/telethon_generator/data/api.tl index c667f1a2..efe1a79f 100644 --- a/telethon_generator/data/api.tl +++ b/telethon_generator/data/api.tl @@ -109,7 +109,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#200250ba id:int = User; -user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; +user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto; @@ -128,7 +128,7 @@ channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; -channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; +channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -225,7 +225,7 @@ inputReportReasonOther#e1746d0a text:string = ReportReason; inputReportReasonCopyright#9b89f93a = ReportReason; inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; -userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; +userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; contact#f911c994 user_id:int mutual:Bool = Contact; @@ -818,13 +818,14 @@ inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_co inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall; phoneCallEmpty#5366c915 id:long = PhoneCall; -phoneCallWaiting#1b8f4ad1 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; -phoneCallRequested#87eabb53 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; -phoneCallAccepted#997c454a flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall; -phoneCall#8742ae7f flags:# p2p_allowed:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int = PhoneCall; -phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.5?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; +phoneCallWaiting#1b8f4ad1 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; +phoneCallRequested#87eabb53 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; +phoneCallAccepted#997c454a flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall; +phoneCall#8742ae7f flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int = PhoneCall; +phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; +phoneConnectionWebrtc#635fe375 flags:# turn:flags.0?true stun:flags.1?true id:long ip:string ipv6:string port:int username:string password:string = PhoneConnection; phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector = PhoneCallProtocol; @@ -1398,7 +1399,7 @@ updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; -photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto; +photos.updateProfilePhoto#72d4742c id:InputPhoto = photos.Photo; photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; @@ -1511,4 +1512,4 @@ stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats; -// LAYER 116 +// LAYER 117 From accb2142e790b95c9e2d9a300c7fd31bdf2069f2 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 23 Aug 2020 11:37:59 +0200 Subject: [PATCH 93/94] Document new known RPC error --- telethon_generator/data/errors.csv | 1 + telethon_generator/data/methods.csv | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/telethon_generator/data/errors.csv b/telethon_generator/data/errors.csv index 993ddec5..5687c0a0 100644 --- a/telethon_generator/data/errors.csv +++ b/telethon_generator/data/errors.csv @@ -30,6 +30,7 @@ BOT_MISSING,400,This method can only be run by a bot BOT_PAYMENTS_DISABLED,400,This method can only be run by a bot BOT_POLLS_DISABLED,400,You cannot create polls under a bot account BOT_RESPONSE_TIMEOUT,400,The bot did not answer to the callback query in time +BROADCAST_FORBIDDEN,403,The request cannot be used in broadcast channels BROADCAST_ID_INVALID,400,The channel is invalid BROADCAST_PUBLIC_VOTERS_FORBIDDEN,400,You cannot broadcast polls where the voters are public BROADCAST_REQUIRED,400,The request can only be used with a broadcast channel diff --git a/telethon_generator/data/methods.csv b/telethon_generator/data/methods.csv index e2d58e6b..83317861 100644 --- a/telethon_generator/data/methods.csv +++ b/telethon_generator/data/methods.csv @@ -227,7 +227,7 @@ messages.getPeerDialogs,user,CHANNEL_PRIVATE PEER_ID_INVALID messages.getPeerSettings,user,CHANNEL_INVALID PEER_ID_INVALID messages.getPinnedDialogs,user, messages.getPollResults,user, -messages.getPollVotes,user, +messages.getPollVotes,user,BROADCAST_FORBIDDEN messages.getRecentLocations,user, messages.getRecentStickers,user, messages.getSavedGifs,user, From bc799fd82c3ff6aa83040a82ec292e964fcccafe Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sun, 23 Aug 2020 11:45:45 +0200 Subject: [PATCH 94/94] Remove usage of the main group username in the docs --- readthedocs/basic/quick-start.rst | 2 +- readthedocs/concepts/asyncio.rst | 21 ++++++++++----------- readthedocs/concepts/entities.rst | 4 ++-- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/readthedocs/basic/quick-start.rst b/readthedocs/basic/quick-start.rst index 91481983..b3168701 100644 --- a/readthedocs/basic/quick-start.rst +++ b/readthedocs/basic/quick-start.rst @@ -41,7 +41,7 @@ use these if possible. # ...to your contacts await client.send_message('+34600123123', 'Hello, friend!') # ...or even to any username - await client.send_message('TelethonChat', 'Hello, Telethon!') + await client.send_message('username', 'Testing Telethon!') # You can, of course, use markdown in your messages: message = await client.send_message( diff --git a/readthedocs/concepts/asyncio.rst b/readthedocs/concepts/asyncio.rst index eabb8f76..ef7c3cd3 100644 --- a/readthedocs/concepts/asyncio.rst +++ b/readthedocs/concepts/asyncio.rst @@ -310,27 +310,26 @@ you can run requests in parallel: async def main(): last, sent, download_path = await asyncio.gather( - client.get_messages('TelethonChat', 10), - client.send_message('TelethonOfftopic', 'Hey guys!'), - client.download_profile_photo('TelethonChat') + client.get_messages('telegram', 10), + client.send_message('me', 'Using asyncio!'), + client.download_profile_photo('telegram') ) loop.run_until_complete(main()) -This code will get the 10 last messages from `@TelethonChat -`_, send one to `@TelethonOfftopic -`_, and also download the profile -photo of the main group. `asyncio` will run all these three tasks -at the same time. You can run all the tasks you want this way. +This code will get the 10 last messages from `@telegram +`_, send one to the chat with yourself, and also +download the profile photo of the channel. `asyncio` will run all these +three tasks at the same time. You can run all the tasks you want this way. A different way would be: .. code-block:: python - loop.create_task(client.get_messages('TelethonChat', 10)) - loop.create_task(client.send_message('TelethonOfftopic', 'Hey guys!')) - loop.create_task(client.download_profile_photo('TelethonChat')) + loop.create_task(client.get_messages('telegram', 10)) + loop.create_task(client.send_message('me', 'Using asyncio!')) + loop.create_task(client.download_profile_photo('telegram')) They will run in the background as long as the loop is running too. diff --git a/readthedocs/concepts/entities.rst b/readthedocs/concepts/entities.rst index d05d1040..4ee55d48 100644 --- a/readthedocs/concepts/entities.rst +++ b/readthedocs/concepts/entities.rst @@ -296,10 +296,10 @@ applications"? Now do the same with the library. Use what applies: await client.get_dialogs() # Are they participant of some group? Get them. - await client.get_participants('TelethonChat') + await client.get_participants('username') # Is the entity the original sender of a forwarded message? Get it. - await client.get_messages('TelethonChat', 100) + await client.get_messages('username', 100) # NOW you can use the ID, anywhere! await client.send_message(123456, 'Hi!')