From 357253b63e02ca923197a9fd98bdeb1d0948ced2 Mon Sep 17 00:00:00 2001 From: YoilyL Date: Sun, 1 Jul 2018 21:34:05 +0300 Subject: [PATCH 001/326] added option to reverse get_history order added an argument `reverse` to get_history which if set to True returns the messages from first to last instead of from newest to oldest. --- pyrogram/client/methods/messages/get_history.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 4089dde9..f55a64ed 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -27,7 +27,8 @@ class GetHistory(BaseClient): offset: int = 0, limit: int = 100, offset_id: int = 0, - offset_date: int = 0): + offset_date: int = 0, + reverse: bool = False): """Use this method to retrieve the history of a chat. You can get up to 100 messages at once. @@ -52,6 +53,9 @@ class GetHistory(BaseClient): offset_date (``int``, *optional*): Pass a date in Unix time as offset to retrieve only older messages starting from that date. + + reverse (``bool``, *optional*): + get the messages in order from first to last (instead of newest to oldest). Returns: On success, a :obj:`Messages ` object is returned. @@ -59,7 +63,9 @@ class GetHistory(BaseClient): Raises: :class:`Error ` """ - + if reverse: + offset = -limit*(offset + 1) + if offset_id: offset_id +=1 r = self.send( functions.messages.GetHistory( peer=self.resolve_peer(chat_id), @@ -107,5 +113,5 @@ class GetHistory(BaseClient): return pyrogram.Messages( total_count=getattr(r, "count", len(r.messages)), - messages=messages + messages=messages if not reverse else messages[::-1] ) From f7aae28ae9a7d4bf3bbcd9c15c22bea57b10c4bc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Jul 2018 02:48:58 +0200 Subject: [PATCH 002/326] Improve get_history --- pyrogram/client/methods/messages/get_history.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index f55a64ed..48619e9b 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -24,11 +24,12 @@ from ...ext import BaseClient, utils class GetHistory(BaseClient): def get_history(self, chat_id: int or str, - offset: int = 0, limit: int = 100, + offset: int = 0, offset_id: int = 0, offset_date: int = 0, - reverse: bool = False): + add_offset: int = 0, + downwards: bool = False): """Use this method to retrieve the history of a chat. You can get up to 100 messages at once. @@ -53,9 +54,6 @@ class GetHistory(BaseClient): offset_date (``int``, *optional*): Pass a date in Unix time as offset to retrieve only older messages starting from that date. - - reverse (``bool``, *optional*): - get the messages in order from first to last (instead of newest to oldest). Returns: On success, a :obj:`Messages ` object is returned. @@ -63,15 +61,13 @@ class GetHistory(BaseClient): Raises: :class:`Error ` """ - if reverse: - offset = -limit*(offset + 1) - if offset_id: offset_id +=1 + # TODO: Docstrings r = self.send( functions.messages.GetHistory( peer=self.resolve_peer(chat_id), offset_id=offset_id, offset_date=offset_date, - add_offset=offset, + add_offset=offset + add_offset - (limit if downwards else 0), limit=limit, max_id=0, min_id=0, @@ -113,5 +109,5 @@ class GetHistory(BaseClient): return pyrogram.Messages( total_count=getattr(r, "count", len(r.messages)), - messages=messages if not reverse else messages[::-1] + messages=messages[::-1] ) From 7bfa87c11bf44f64989fc1b7032886063b966f2a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 23 Aug 2018 20:42:42 +0200 Subject: [PATCH 003/326] Update API scheme to Layer 85 --- compiler/api/source/main_api.tl | 57 +++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 9ee01cb3..d3f227ce 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -191,6 +191,7 @@ inputReportReasonSpam#58dbcab8 = ReportReason; inputReportReasonViolence#1e22c78d = ReportReason; inputReportReasonPornography#2e59d922 = ReportReason; inputReportReasonOther#e1746d0a text:string = ReportReason; +inputReportReasonCopyright#9b89f93a = ReportReason; userFull#f220f3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true user:User about:flags.1?string link:contacts.Link profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo common_chats_count:int = UserFull; @@ -342,7 +343,7 @@ config#3213dbba flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:fla nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; -help.appUpdate#8987f311 id:int critical:Bool url:string text:string = help.AppUpdate; +help.appUpdate#1da7158f flags:# popup:flags.0?true id:int version:string text:string entities:Vector document:flags.1?Document url:flags.2?string = help.AppUpdate; help.noAppUpdate#c45a6536 = help.AppUpdate; help.inviteText#18cb9f78 message:string = help.InviteText; @@ -458,12 +459,11 @@ authorization#7bf2e6f6 hash:long flags:int device_model:string platform:string s account.authorizations#1250abde authorizations:Vector = account.Authorizations; -account.noPassword#5ea182f6 new_salt:bytes new_secure_salt:bytes secure_random:bytes email_unconfirmed_pattern:string = account.Password; -account.password#ca39b447 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true current_salt:bytes new_salt:bytes new_secure_salt:bytes secure_random:bytes hint:string email_unconfirmed_pattern:string = account.Password; +account.password#ad2641f8 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes = account.Password; -account.passwordSettings#7bd9c3f1 email:string secure_salt:bytes secure_secret:bytes secure_secret_id:long = account.PasswordSettings; +account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings; -account.passwordInputSettings#21ffa60d flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_salt:flags.2?bytes new_secure_secret:flags.2?bytes new_secure_secret_id:flags.2?long = account.PasswordInputSettings; +account.passwordInputSettings#c23727c9 flags:# new_algo:flags.0?PasswordKdfAlgo new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_settings:flags.2?SecureSecretSettings = account.PasswordInputSettings; auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; @@ -864,9 +864,9 @@ secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType; secureValueTypePhone#b320aadb = SecureValueType; secureValueTypeEmail#8e3ca7ee = SecureValueType; -secureValue#b4b4b699 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile files:flags.4?Vector plain_data:flags.5?SecurePlainData hash:bytes = SecureValue; +secureValue#187fa0ca flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile translation:flags.6?Vector files:flags.4?Vector plain_data:flags.5?SecurePlainData hash:bytes = SecureValue; -inputSecureValue#67872e8 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile files:flags.4?Vector plain_data:flags.5?SecurePlainData = InputSecureValue; +inputSecureValue#db21d0a7 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile translation:flags.6?Vector files:flags.4?Vector plain_data:flags.5?SecurePlainData = InputSecureValue; secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash; @@ -876,10 +876,13 @@ secureValueErrorReverseSide#868a2aa5 type:SecureValueType file_hash:bytes text:s secureValueErrorSelfie#e537ced6 type:SecureValueType file_hash:bytes text:string = SecureValueError; secureValueErrorFile#7a700873 type:SecureValueType file_hash:bytes text:string = SecureValueError; secureValueErrorFiles#666220e9 type:SecureValueType file_hash:Vector text:string = SecureValueError; +secureValueError#869d758f type:SecureValueType hash:bytes text:string = SecureValueError; +secureValueErrorTranslationFile#a1144770 type:SecureValueType file_hash:bytes text:string = SecureValueError; +secureValueErrorTranslationFiles#34636dd8 type:SecureValueType file_hash:Vector text:string = SecureValueError; secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted; -account.authorizationForm#cb976d53 flags:# selfie_required:flags.1?true required_types:Vector values:Vector errors:Vector users:Vector privacy_policy_url:flags.0?string = account.AuthorizationForm; +account.authorizationForm#ad2e1cd8 flags:# required_types:Vector values:Vector errors:Vector users:Vector privacy_policy_url:flags.0?string = account.AuthorizationForm; account.sentEmailCode#811f854f email_pattern:string length:int = account.SentEmailCode; @@ -890,6 +893,24 @@ savedPhoneContact#1142bd56 phone:string first_name:string last_name:string date: account.takeout#4dba4501 id:long = account.Takeout; +passwordKdfAlgoUnknown#d45ab096 = PasswordKdfAlgo; +passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow#3a912d4a salt1:bytes salt2:bytes g:int p:bytes = PasswordKdfAlgo; + +securePasswordKdfAlgoUnknown#4a8537 = SecurePasswordKdfAlgo; +securePasswordKdfAlgoPBKDF2HMACSHA512iter100000#bbf2dda0 salt:bytes = SecurePasswordKdfAlgo; +securePasswordKdfAlgoSHA512#86471d92 salt:bytes = SecurePasswordKdfAlgo; + +secureSecretSettings#1527bcac secure_algo:SecurePasswordKdfAlgo secure_secret:bytes secure_secret_id:long = SecureSecretSettings; + +inputCheckPasswordEmpty#9880f658 = InputCheckPasswordSRP; +inputCheckPasswordSRP#d27ff082 srp_id:long A:bytes M1:bytes = InputCheckPasswordSRP; + +secureRequiredType#829d99da flags:# native_names:flags.0?true selfie_required:flags.1?true translation_required:flags.2?true type:SecureValueType = SecureRequiredType; +secureRequiredTypeOneOf#27477b4 types:Vector = SecureRequiredType; + +help.passportConfigNotModified#bfb9f457 = help.PassportConfig; +help.passportConfig#a098d6af hash:int countries_langs:DataJSON = help.PassportConfig; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -909,7 +930,7 @@ auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization; auth.importAuthorization#e3ef9613 id:int bytes:bytes = auth.Authorization; auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool; auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization; -auth.checkPassword#a63011e password_hash:bytes = auth.Authorization; +auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization; auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery; auth.recoverPassword#4ea56e92 code:string = auth.Authorization; auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode; @@ -938,11 +959,11 @@ account.updateDeviceLocked#38df3532 period:int = Bool; account.getAuthorizations#e320c158 = account.Authorizations; account.resetAuthorization#df77f3bc hash:long = Bool; account.getPassword#548a30f5 = account.Password; -account.getPasswordSettings#bc8d11bb current_password_hash:bytes = account.PasswordSettings; -account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings:account.PasswordInputSettings = Bool; +account.getPasswordSettings#9cd4eaf9 password:InputCheckPasswordSRP = account.PasswordSettings; +account.updatePasswordSettings#a59b102f password:InputCheckPasswordSRP new_settings:account.PasswordInputSettings = Bool; account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode; account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool; -account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPassword; +account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword; account.getWebAuthorizations#182e6d6f = account.WebAuthorizations; account.resetWebAuthorization#2d01b9ef hash:long = Bool; account.resetWebAuthorizations#682d2594 = Bool; @@ -1080,6 +1101,7 @@ messages.searchStickerSets#c2b7d08b flags:# exclude_featured:flags.0?true q:stri messages.getSplitRanges#1cff7e08 = Vector; messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool; messages.getDialogUnreadMarks#22e24e22 = Vector; +messages.clearAllDrafts#7e58ee9c = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1101,7 +1123,7 @@ upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector = Bool; help.getInviteText#4d392343 = help.InviteText; help.getSupport#9cdf08cd = help.Support; @@ -1113,6 +1135,7 @@ help.getProxyData#3d7758e1 = help.ProxyData; help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate; help.acceptTermsOfService#ee72f79a id:DataJSON = Bool; help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo; +help.getPassportConfig#c661ad08 hash:int = help.PassportConfig; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; @@ -1172,9 +1195,9 @@ phone.discardCall#78d413a6 peer:InputPhoneCall duration:int reason:PhoneCallDisc phone.setCallRating#1c536a34 peer:InputPhoneCall rating:int comment:string = Updates; phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; -langpack.getLangPack#9ab5c58e lang_code:string = LangPackDifference; -langpack.getStrings#2e1ee318 lang_code:string keys:Vector = Vector; +langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; +langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; langpack.getDifference#b2e4d7d from_version:int = LangPackDifference; -langpack.getLanguages#800fd57d = Vector; +langpack.getLanguages#42c6978f lang_pack:string = Vector; -// LAYER 82 +// LAYER 85 From fafa3b513168c4b3ace7528a7fc6207f4776d66d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 10 Nov 2018 15:15:58 +0100 Subject: [PATCH 004/326] Fix some decorators not working when used in plugins --- pyrogram/client/methods/decorators/on_callback_query.py | 2 +- pyrogram/client/methods/decorators/on_deleted_messages.py | 2 +- pyrogram/client/methods/decorators/on_disconnect.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py index 8413515d..a79cbd10 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/client/methods/decorators/on_callback_query.py @@ -22,7 +22,7 @@ from ...ext import BaseClient class OnCallbackQuery(BaseClient): - def on_callback_query(self, filters=None, group: int = 0): + def on_callback_query(self=None, filters=None, group: int = 0): """Use this decorator to automatically register a function for handling callback queries. This does the same thing as :meth:`add_handler` using the :class:`CallbackQueryHandler`. diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index e4b2bc97..347deaf9 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.py @@ -22,7 +22,7 @@ from ...ext import BaseClient class OnDeletedMessages(BaseClient): - def on_deleted_messages(self, filters=None, group: int = 0): + def on_deleted_messages(self=None, filters=None, group: int = 0): """Use this decorator to automatically register a function for handling deleted messages. This does the same thing as :meth:`add_handler` using the :class:`DeletedMessagesHandler`. diff --git a/pyrogram/client/methods/decorators/on_disconnect.py b/pyrogram/client/methods/decorators/on_disconnect.py index a639471b..e2288619 100644 --- a/pyrogram/client/methods/decorators/on_disconnect.py +++ b/pyrogram/client/methods/decorators/on_disconnect.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class OnDisconnect(BaseClient): - def on_disconnect(self): + def on_disconnect(self=None): """Use this decorator to automatically register a function for handling disconnections. This does the same thing as :meth:`add_handler` using the :class:`DisconnectHandler`. From 2e164993693fe70c36c7cf41b22b95ecd22738ce Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 10 Nov 2018 15:21:52 +0100 Subject: [PATCH 005/326] Allow decorators to be stacked E.g: app1.on_message(...) app2.on_message(...) app3.on_message(...) def on_message(client, message): ... --- pyrogram/client/methods/decorators/on_callback_query.py | 3 +++ pyrogram/client/methods/decorators/on_deleted_messages.py | 3 +++ pyrogram/client/methods/decorators/on_message.py | 3 +++ pyrogram/client/methods/decorators/on_raw_update.py | 3 +++ pyrogram/client/methods/decorators/on_user_status.py | 3 +++ 5 files changed, 15 insertions(+) diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py index a79cbd10..a7079236 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/client/methods/decorators/on_callback_query.py @@ -37,6 +37,9 @@ class OnCallbackQuery(BaseClient): """ def decorator(func): + if isinstance(func, tuple): + func = func[0].callback + handler = pyrogram.CallbackQueryHandler(func, filters) if isinstance(self, Filter): diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index 347deaf9..2fd8f298 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.py @@ -37,6 +37,9 @@ class OnDeletedMessages(BaseClient): """ def decorator(func): + if isinstance(func, tuple): + func = func[0].callback + handler = pyrogram.DeletedMessagesHandler(func, filters) if isinstance(self, Filter): diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index 7a0d54a0..690f8368 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.py @@ -37,6 +37,9 @@ class OnMessage(BaseClient): """ def decorator(func): + if isinstance(func, tuple): + func = func[0].callback + handler = pyrogram.MessageHandler(func, filters) if isinstance(self, Filter): diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py index 7675a4f0..1391482f 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -32,6 +32,9 @@ class OnRawUpdate(BaseClient): """ def decorator(func): + if isinstance(func, tuple): + func = func[0].callback + handler = pyrogram.RawUpdateHandler(func) if isinstance(self, int): diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py index b49e63a8..5aa6f783 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/client/methods/decorators/on_user_status.py @@ -36,6 +36,9 @@ class OnUserStatus(BaseClient): """ def decorator(func): + if isinstance(func, tuple): + func = func[0].callback + handler = pyrogram.UserStatusHandler(func, filters) if isinstance(self, Filter): From 87fca98035c7e6428356ca60e5cfc75f76e8a394 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 13 Nov 2018 13:16:31 +0100 Subject: [PATCH 006/326] Update to v0.9.2 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index a98ac672..8471dd82 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès Date: Tue, 13 Nov 2018 13:21:34 +0100 Subject: [PATCH 007/326] Update version in docs --- docs/source/start/Installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/start/Installation.rst b/docs/source/start/Installation.rst index 37fedbdf..7886f1d4 100644 --- a/docs/source/start/Installation.rst +++ b/docs/source/start/Installation.rst @@ -82,7 +82,7 @@ If no error shows up you are good to go. >>> import pyrogram >>> pyrogram.__version__ - '0.9.1' + '0.9.2' .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto .. _develop: http://github.com/pyrogram/pyrogram From 9f8e05861fbebd6b805673bf321e01abb4affecf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 13 Nov 2018 13:22:23 +0100 Subject: [PATCH 008/326] Fix sphinx warning --- pyrogram/client/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index ee812c3a..7ef5c23f 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -391,7 +391,7 @@ class Message(Object): ) def edit(self, text: str, parse_mode: str = "", disable_web_page_preview: bool = None, reply_markup=None): - """Bound method *edit* of :obj:`Message + """Bound method *edit* of :obj:`Message ` Use as a shortcut for: From 5b10afa7a3ed29ff6d87667b48c51b8cb8d50d2d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 13 Nov 2018 13:30:26 +0100 Subject: [PATCH 009/326] Small style fix --- pyrogram/client/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 7ef5c23f..404ad39d 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -68,7 +68,7 @@ class Message(Object): new_chat_photo, delete_chat_photo, group_chat_created, supergroup_chat_created, channel_chat_created, migrate_to_chat_id, migrate_from_chat_id, pinned_message. - media (``bool``` *optional*): + media (``bool`` *optional*): The message is a media message. A media message has one and only one of these fields set: audio, document, photo, sticker, video, animation, voice, video_note, contact, location, venue. From e40064120464719b592a9abe90471cc8a7201a87 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 13 Nov 2018 20:31:53 +0100 Subject: [PATCH 010/326] Fix Dispatcher bad behaviours in case of multiple Clients running at the same time. --- pyrogram/client/client.py | 8 ++++---- pyrogram/client/dispatcher/dispatcher.py | 14 ++++++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 8cb19c47..119ecc11 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -851,7 +851,7 @@ class Client(Methods, BaseClient): if len(self.channels_pts[channel_id]) > 50: self.channels_pts[channel_id] = self.channels_pts[channel_id][25:] - self.dispatcher.updates.put((update, updates.users, updates.chats)) + self.dispatcher.updates_queue.put((update, updates.users, updates.chats)) elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)): diff = self.send( functions.updates.GetDifference( @@ -862,7 +862,7 @@ class Client(Methods, BaseClient): ) if diff.new_messages: - self.dispatcher.updates.put(( + self.dispatcher.updates_queue.put(( types.UpdateNewMessage( message=diff.new_messages[0], pts=updates.pts, @@ -872,9 +872,9 @@ class Client(Methods, BaseClient): diff.chats )) else: - self.dispatcher.updates.put((diff.other_updates[0], [], [])) + self.dispatcher.updates_queue.put((diff.other_updates[0], [], [])) elif isinstance(updates, types.UpdateShort): - self.dispatcher.updates.put((updates.update, [], [])) + self.dispatcher.updates_queue.put((updates.update, [], [])) elif isinstance(updates, types.UpdatesTooLong): log.warning(updates) except Exception as e: diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index fa65f987..a61e8077 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -52,17 +52,15 @@ class Dispatcher: MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES - UPDATES = None - def __init__(self, client, workers: int): self.client = client self.workers = workers self.workers_list = [] - self.updates = Queue() + self.updates_queue = Queue() self.groups = OrderedDict() - Dispatcher.UPDATES = { + self.update_parsers = { Dispatcher.MESSAGE_UPDATES: lambda upd, usr, cht: (utils.parse_messages(self.client, upd.message, usr, cht), MessageHandler), @@ -76,7 +74,7 @@ class Dispatcher: lambda upd, usr, cht: (utils.parse_user_status(upd.status, upd.user_id), UserStatusHandler) } - Dispatcher.UPDATES = {key: value for key_tuple, value in Dispatcher.UPDATES.items() for key in key_tuple} + self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple} def start(self): for i in range(self.workers): @@ -91,7 +89,7 @@ class Dispatcher: def stop(self): for _ in range(self.workers): - self.updates.put(None) + self.updates_queue.put(None) for worker in self.workers_list: worker.join() @@ -116,7 +114,7 @@ class Dispatcher: log.debug("{} started".format(name)) while True: - update = self.updates.get() + update = self.updates_queue.get() if update is None: break @@ -126,7 +124,7 @@ class Dispatcher: chats = {i.id: i for i in update[2]} update = update[0] - parser = Dispatcher.UPDATES.get(type(update), None) + parser = self.update_parsers.get(type(update), None) if parser is None: continue From e9ff43d71f348dbba468eb2c507b8276ee2c2c34 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 13 Nov 2018 20:33:55 +0100 Subject: [PATCH 011/326] Update reported version on docs --- docs/source/start/Installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/start/Installation.rst b/docs/source/start/Installation.rst index 7886f1d4..efebac5a 100644 --- a/docs/source/start/Installation.rst +++ b/docs/source/start/Installation.rst @@ -82,7 +82,7 @@ If no error shows up you are good to go. >>> import pyrogram >>> pyrogram.__version__ - '0.9.2' + '0.9.3' .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto .. _develop: http://github.com/pyrogram/pyrogram From 54e753986573f60e3706c1a158ff9b2050caa84f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 13 Nov 2018 20:34:33 +0100 Subject: [PATCH 012/326] Update to v0.9.3 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 8471dd82..b1a5430c 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès Date: Wed, 14 Nov 2018 23:21:19 +0300 Subject: [PATCH 013/326] fix RawUpdateHandler --- pyrogram/client/dispatcher/dispatcher.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index a61e8077..3ab704fc 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -129,7 +129,7 @@ class Dispatcher: if parser is None: continue - update, handler_type = parser(update, users, chats) + parsed_update, handler_type = parser(update, users, chats) for group in self.groups.values(): for handler in group: @@ -138,8 +138,8 @@ class Dispatcher: if isinstance(handler, RawUpdateHandler): args = (update, users, chats) elif isinstance(handler, handler_type): - if handler.check(update): - args = (update,) + if handler.check(parsed_update): + args = (parsed_update,) if args is None: continue From 08776619a78035da4ed5a82d6b191c159df4bab6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 20 Nov 2018 16:03:26 +0100 Subject: [PATCH 014/326] Change callback_data type to bytes for request_callback_answer Fixes #161 --- pyrogram/client/methods/bots/request_callback_answer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py index c2c7e312..470ad15a 100644 --- a/pyrogram/client/methods/bots/request_callback_answer.py +++ b/pyrogram/client/methods/bots/request_callback_answer.py @@ -24,7 +24,7 @@ class RequestCallbackAnswer(BaseClient): def request_callback_answer(self, chat_id: int or str, message_id: int, - callback_data: str): + callback_data: bytes): """Use this method to request a callback answer from bots. This is the equivalent of clicking an inline button containing callback data. @@ -37,7 +37,7 @@ class RequestCallbackAnswer(BaseClient): message_id (``int``): The message id the inline keyboard is attached on. - callback_data (``str``): + callback_data (``bytes``): Callback data associated with the inline button you want to get the answer from. Returns: @@ -52,7 +52,7 @@ class RequestCallbackAnswer(BaseClient): functions.messages.GetBotCallbackAnswer( peer=self.resolve_peer(chat_id), msg_id=message_id, - data=callback_data.encode() + data=callback_data ), retries=0, timeout=10 From b753e48732616614b99dd3bada8ad2bbb6cba3c1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 20 Nov 2018 16:04:03 +0100 Subject: [PATCH 015/326] Remove unnecessary check --- pyrogram/client/types/messages_and_media/message.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 404ad39d..3715ba1a 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -579,10 +579,7 @@ class Message(Object): ``TimeoutError``: If, after clicking an inline button, the bot fails to answer within 10 seconds """ if isinstance(self.reply_markup, ReplyKeyboardMarkup): - if quote is None: - quote = self.chat.type != "private" - - return self.reply(x, quote=quote) + return self.reply(x) elif isinstance(self.reply_markup, InlineKeyboardMarkup): if isinstance(x, int) and y is None: try: From 436c48d1c201731b279c3d2c1e0c66a4c0bbc467 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 20 Nov 2018 16:52:59 +0100 Subject: [PATCH 016/326] Allow Bots to edit and delete own messages w/ Message bound methods. This is some sort of a workaround because the server doesn't send full info about text messages originated by bots. Fixes #162 --- pyrogram/client/ext/utils.py | 7 +++---- pyrogram/client/methods/messages/send_message.py | 9 +++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index a543e6b5..15605bea 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -19,7 +19,6 @@ import logging from base64 import b64decode, b64encode from struct import pack -from weakref import proxy from pyrogram.client import types as pyrogram_types from ...api import types, functions @@ -624,7 +623,7 @@ def parse_messages( views=message.views, via_bot=parse_user(users.get(message.via_bot_id, None)), outgoing=message.out, - client=proxy(client), + client=client, reply_markup=reply_markup ) @@ -739,7 +738,7 @@ def parse_messages( migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None, group_chat_created=group_chat_created, channel_chat_created=channel_chat_created, - client=proxy(client) + client=client # TODO: supergroup_chat_created ) @@ -753,7 +752,7 @@ def parse_messages( except MessageIdsEmpty: pass else: - m = pyrogram_types.Message(message_id=message.id, client=proxy(client), empty=True) + m = pyrogram_types.Message(message_id=message.id, client=client, empty=True) parsed_messages.append(m) diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 2ad4b8e4..78ce27e5 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -67,6 +67,7 @@ class SendMessage(BaseClient): :class:`Error ` in case of a Telegram RPC error. """ style = self.html if parse_mode.lower() == "html" else self.markdown + message, entities = style.parse(text).values() r = self.send( functions.messages.SendMessage( @@ -76,16 +77,20 @@ class SendMessage(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(text) + message=message, + entities=entities ) ) if isinstance(r, types.UpdateShortSentMessage): return pyrogram_types.Message( message_id=r.id, + chat=pyrogram_types.Chat(id=list(self.resolve_peer(chat_id).__dict__.values())[0], type="private"), + text=message, date=r.date, outgoing=r.out, - entities=utils.parse_entities(r.entities, {}) or None + entities=entities, + client=self ) for i in r.updates: From 61f2d7c968a089caf714ed943cab0c8eae86b268 Mon Sep 17 00:00:00 2001 From: zeroone2numeral2 Date: Sun, 25 Nov 2018 16:56:39 +0000 Subject: [PATCH 017/326] Added web_page attribute to Message object --- pyrogram/client/ext/utils.py | 4 ++++ pyrogram/client/types/messages_and_media/message.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index a543e6b5..9cce212d 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -327,6 +327,7 @@ def parse_messages( video_note = None sticker = None document = None + web_page = None media = message.media @@ -574,6 +575,8 @@ def parse_messages( file_size=doc.size, date=doc.date ) + elif isinstance(media, types.MessageMediaWebPage): + web_page = True else: media = None @@ -621,6 +624,7 @@ def parse_messages( video_note=video_note, sticker=sticker, document=document, + web_page=web_page, views=message.views, via_bot=parse_user(users.get(message.via_bot_id, None)), outgoing=message.out, diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 404ad39d..19bdd067 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -134,6 +134,9 @@ class Message(Object): venue (:obj:`Venue `, *optional*): Message is a venue, information about the venue. + web_page (``bool``, *optional*): + Message was sent with a webpage preview. + new_chat_members (List of :obj:`User `, *optional*): New members that were added to the group or supergroup and information about them (the bot itself may be one of these members). @@ -246,6 +249,7 @@ class Message(Object): contact=None, location=None, venue=None, + web_page=None, new_chat_members: list = None, left_chat_member=None, new_chat_title: str = None, @@ -297,6 +301,7 @@ class Message(Object): self.contact = contact # flags.22?Contact self.location = location # flags.23?Location self.venue = venue # flags.24?Venue + self.web_page = web_page self.new_chat_members = new_chat_members # flags.25?Vector self.left_chat_member = left_chat_member # flags.26?User self.new_chat_title = new_chat_title # flags.27?string From 2d1a7871ea068165093b3d047be05a1dad2268c5 Mon Sep 17 00:00:00 2001 From: zeroone2numeral2 Date: Sun, 25 Nov 2018 17:05:59 +0000 Subject: [PATCH 018/326] Added Filters.web_page --- pyrogram/client/filters/filters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 6042173f..9ae15239 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -118,6 +118,9 @@ class Filters: venue = create("Venue", lambda _, m: bool(m.venue)) """Filter messages that contain :obj:`Venue ` objects.""" + web_page = create("WebPage", lambda _, m: m.web_page) + """Filter messages sent with a webpage preview.""" + private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private")) """Filter messages sent in private chats.""" From ac8fc58a06f80eb75c30a3d8db2a061f5eb34929 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 8 Dec 2018 16:41:30 +0100 Subject: [PATCH 019/326] Add a note hinting about basic support for web pages --- pyrogram/client/types/messages_and_media/message.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 19bdd067..2c7a97ff 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -136,6 +136,9 @@ class Message(Object): web_page (``bool``, *optional*): Message was sent with a webpage preview. + **Note:** Support for web pages is still basic; a simple boolean is set in case the message contains a + web page preview. In future versions this property could turn into a full web page object that contains + more details. new_chat_members (List of :obj:`User `, *optional*): New members that were added to the group or supergroup and information about them From 19ad70455fef1bc4328b23dbe905d604aa0671c1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 10 Dec 2018 15:15:21 +0100 Subject: [PATCH 020/326] Update welcome_bot.py example Delete the previous message in case of consecutive member joins and send a new one containing the names of all the previous new members --- examples/welcome_bot.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/examples/welcome_bot.py b/examples/welcome_bot.py index 5dbb44fb..2598a802 100644 --- a/examples/welcome_bot.py +++ b/examples/welcome_bot.py @@ -6,22 +6,40 @@ to make it only work for specific messages in a specific chat. from pyrogram import Client, Emoji, Filters -MENTION = "[{}](tg://user?id={})" -MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!" +USER = "**{}**" +MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {{}}!".format(Emoji.SPARKLES) -app = Client("my_account") +app = Client("dan_prod") + +enabled_groups = Filters.chat("PyrogramLounge") +last_welcomes = {} -@app.on_message(Filters.chat("PyrogramChat") & Filters.new_chat_members) +@app.on_message(enabled_groups & Filters.new_chat_members) def welcome(client, message): - # Build the new members list (with mentions) by using their first_name - new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members] + chat_id = message.chat.id - # Build the welcome message by using an emoji and the list we built above - text = MESSAGE.format(Emoji.SPARKLES, ", ".join(new_members)) + # Get the previous welcome message and members, if any + previous_welcome, previous_members = last_welcomes.pop(chat_id, (None, [])) - # Send the welcome message - message.reply(text, disable_web_page_preview=True) + # Delete the previous message, if exists + if previous_welcome: + previous_welcome.delete() + + # Build the new members list by using their first_name. Also append the previous members, if any + new_members = [USER.format(i.first_name) for i in message.new_chat_members] + previous_members + + # Build the welcome message by using an emoji and the list we created above + text = MESSAGE.format(", ".join(new_members)) + + # Actually send the welcome and save the new message and the new members list + last_welcomes[message.chat.id] = message.reply(text, disable_web_page_preview=True), new_members + + +@app.on_message(enabled_groups) +def reset(client, message): + # Don't make the bot delete the previous welcome in case someone talks in the middle + last_welcomes.pop(message.chat.id, None) app.run() # Automatically start() and idle() From 61b36898a4e96dc429efe7c5d6210f416e9df7d6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 10 Dec 2018 15:19:54 +0100 Subject: [PATCH 021/326] Fix chat username and session name --- examples/welcome_bot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/welcome_bot.py b/examples/welcome_bot.py index 2598a802..4326ed6c 100644 --- a/examples/welcome_bot.py +++ b/examples/welcome_bot.py @@ -9,11 +9,11 @@ from pyrogram import Client, Emoji, Filters USER = "**{}**" MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {{}}!".format(Emoji.SPARKLES) -app = Client("dan_prod") - -enabled_groups = Filters.chat("PyrogramLounge") +enabled_groups = Filters.chat("PyrogramChat") last_welcomes = {} +app = Client("my_account") + @app.on_message(enabled_groups & Filters.new_chat_members) def welcome(client, message): From 0e050b45e788623394a51c3a58c1213437a0b7a0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 08:06:18 +0100 Subject: [PATCH 022/326] Attempt srp --- .../methods/password/enable_cloud_password.py | 113 +++++++++++++++--- 1 file changed, 97 insertions(+), 16 deletions(-) diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index 80d527c4..1969453a 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -16,13 +16,29 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from hashlib import sha256, pbkdf2_hmac import os -from hashlib import sha256 from pyrogram.api import functions, types from ...ext import BaseClient +def compute_hash(algo: types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow, password: str): + hash1 = sha256(algo.salt1 + password.encode() + algo.salt1).digest() + hash2 = sha256(algo.salt2 + hash1 + algo.salt2).digest() + hash3 = pbkdf2_hmac("sha512", hash2, algo.salt1, 100000) + + return sha256(algo.salt2 + hash3 + algo.salt2).digest() + + +def btoi(b: bytes): + return int.from_bytes(b, "big") + + +def itob(i: int): + return i.to_bytes(256, "big") + + class EnableCloudPassword(BaseClient): def enable_cloud_password(self, password: str, hint: str = "", email: str = ""): """Use this method to enable the Two-Step Verification security feature (Cloud Password) on your account. @@ -46,21 +62,86 @@ class EnableCloudPassword(BaseClient): :class:`Error ` in case of a Telegram RPC error. """ r = self.send(functions.account.GetPassword()) + print(r) - if isinstance(r, types.account.NoPassword): - salt = r.new_salt + os.urandom(8) - password_hash = sha256(salt + password.encode() + salt).digest() + algo = r.new_algo - return self.send( - functions.account.UpdatePasswordSettings( - current_password_hash=salt, - new_settings=types.account.PasswordInputSettings( - new_salt=salt, - new_password_hash=password_hash, - hint=hint, - email=email - ) - ) + p_bytes = algo.p + p = btoi(algo.p) + + g_bytes = itob(algo.g) + g = algo.g + + B_bytes = r.srp_B or b"" + B = btoi(B_bytes) + + srp_id = r.srp_id or 0 + + x_bytes = compute_hash(algo, password) + x = btoi(x_bytes) + + g_x = pow(g, x, p) + + k_bytes = sha256(p_bytes + g_bytes).digest() + k = btoi(k_bytes) + + kg_x = (k * g_x) % p + + while True: + a_bytes = os.urandom(256) + a = btoi(a_bytes) + + A = pow(g, a, p) + A_bytes = itob(A) + + u = btoi(sha256(A_bytes + B_bytes).digest()) + + if u > 0: + break + + g_b = (B - kg_x) % p + + ux = u * x + a_ux = a + ux + S = pow(g_b, a_ux, p) + S_bytes = itob(S) + + K_bytes = sha256(S_bytes).digest() + M1_bytes = sha256( + b"".join([bytes([int(i) ^ int(j)]) for (i, j) in zip(sha256(p_bytes).digest(), sha256(g_bytes).digest())]) + + sha256(algo.salt1).digest() + + sha256(algo.salt2).digest() + + A_bytes + + B_bytes + + K_bytes + ).digest() + + input_check_password = types.InputCheckPasswordSRP(srp_id, A_bytes, M1_bytes) + + r2 = self.send(functions.account.UpdatePasswordSettings( + input_check_password, types.account.PasswordInputSettings( + new_algo=algo, + new_password_hash=b"", + hint="" ) - else: - return False + )) + + print(r2) + + # if isinstance(r, types.account.NoPassword): + # salt = r.new_salt + os.urandom(8) + # password_hash = sha256(salt + password.encode() + salt).digest() + # + # return self.send( + # functions.account.UpdatePasswordSettings( + # current_password_hash=salt, + # new_settings=types.account.PasswordInputSettings( + # new_salt=salt, + # new_password_hash=password_hash, + # hint=hint, + # email=email + # ) + # ) + # ) + # else: + # return False From c9ce95c9648c3288e5ea6d554fa748d11c3f9f8a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 08:14:04 +0100 Subject: [PATCH 023/326] Add 500 RANDOM_ID_DUPLICATE error --- compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv index 60d1b51a..d1c666c6 100644 --- a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv @@ -4,4 +4,5 @@ RPC_CALL_FAIL Telegram is having internal problems. Please try again later RPC_MCGET_FAIL Telegram is having internal problems. Please try again later PERSISTENT_TIMESTAMP_OUTDATED Telegram is having internal problems. Please try again later HISTORY_GET_FAILED Telegram is having internal problems. Please try again later -REG_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later \ No newline at end of file +REG_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later +RANDOM_ID_DUPLICATE Telegram is having internal problems. Please try again later \ No newline at end of file From 067b98853765ea6b18a078a1350288f1094ff544 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 08:33:27 +0100 Subject: [PATCH 024/326] Update 400 Bad Request errors --- compiler/error/source/400_BAD_REQUEST.tsv | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 382e521e..d330db1b 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -51,7 +51,7 @@ BOT_INLINE_DISABLED The inline feature of the bot is disabled INLINE_RESULT_EXPIRED The inline bot query expired INVITE_HASH_INVALID The invite link hash is invalid USER_ALREADY_PARTICIPANT The user is already a participant of this chat -TTL_MEDIA_INVALID This kind of media does not support self-destruction +TTL_MEDIA_INVALID The media does not support self-destruction MAX_ID_INVALID The max_id parameter is invalid CHANNEL_INVALID The channel parameter is invalid DC_ID_INVALID The dc_id parameter is invalid @@ -59,7 +59,7 @@ LIMIT_INVALID The limit parameter is invalid OFFSET_INVALID The offset parameter is invalid EMAIL_INVALID The email provided is invalid USER_IS_BOT A bot cannot send messages to other bots or to itself -WEBPAGE_CURL_FAILED Telegram could not fetch the provided URL +WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL STICKERSET_INVALID The requested sticker set is invalid PEER_FLOOD The method can't be used because your account is limited MEDIA_CAPTION_TOO_LONG The media caption is longer than 200 characters @@ -68,4 +68,11 @@ USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side USER_NOT_PARTICIPANT The user is not a member of this chat CHANNEL_PRIVATE The channel/supergroup is not accessible -MESSAGE_IDS_EMPTY The requested message doesn't exist \ No newline at end of file +MESSAGE_IDS_EMPTY The requested message doesn't exist +WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media +QUERY_ID_INVALID The callback query id is invalid +MEDIA_EMPTY The media is invalid +USER_IS_BLOCKED The user blocked you +YOU_BLOCKED_USER You blocked this user +ADMINS_TOO_MUCH The chat has too many administrators +BOTS_TOO_MUCH The chat has too many bots \ No newline at end of file From 49b18c600dd55b6217b90dface72b4111d783685 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 08:39:43 +0100 Subject: [PATCH 025/326] Add missing thumb for videos in albums. Fixes #169 --- pyrogram/client/methods/messages/send_media_group.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 5465c9f2..bc9f6971 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -108,6 +108,7 @@ class SendMediaGroup(BaseClient): peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( file=self.save_file(i.media), + thumb=None if i.thumb is None else self.save_file(i.thumb), mime_type=mimetypes.types_map[".mp4"], attributes=[ types.DocumentAttributeVideo( From 9c917201046e95c951612b624529d02ba59138a7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 08:50:08 +0100 Subject: [PATCH 026/326] Fix broken download_media progress args --- pyrogram/client/client.py | 2 +- pyrogram/client/methods/utilities/download_media.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 119ecc11..8b0acd22 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1214,7 +1214,7 @@ class Client(Methods, BaseClient): version: int = 0, size: int = None, progress: callable = None, - progress_args: tuple = None) -> str: + progress_args: tuple = ()) -> str: with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) diff --git a/pyrogram/client/methods/utilities/download_media.py b/pyrogram/client/methods/utilities/download_media.py index 1453539c..9b6c554a 100644 --- a/pyrogram/client/methods/utilities/download_media.py +++ b/pyrogram/client/methods/utilities/download_media.py @@ -28,7 +28,7 @@ class DownloadMedia(BaseClient): file_name: str = "", block: bool = True, progress: callable = None, - progress_args: tuple = None): + progress_args: tuple = ()): """Use this method to download the media from a Message. Args: From 4ba5e630349f693bf1a89995fde0617996b78209 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 08:51:20 +0100 Subject: [PATCH 027/326] Report offset instead of 0 in case file size is missing (for file_id) --- pyrogram/client/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 8b0acd22..2094eb9c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1296,7 +1296,7 @@ class Client(Methods, BaseClient): offset += limit if progress: - progress(self, min(offset, size), size, *progress_args) + progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) r = session.send( functions.upload.GetFile( @@ -1378,7 +1378,7 @@ class Client(Methods, BaseClient): offset += limit if progress: - progress(self, min(offset, size), size, *progress_args) + progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) if len(chunk) < limit: break From 2ae8730b22462eba509ee400107099eaf82dfd98 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 08:53:22 +0100 Subject: [PATCH 028/326] Add Filters.via_bot to filter messages sent via inline bots --- pyrogram/client/filters/filters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 9ae15239..fa9eace3 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -172,6 +172,9 @@ class Filters: mentioned = create("Mentioned", lambda _, m: bool(m.mentioned)) """Filter messages containing mentions""" + via_bot = create("ViaBot", lambda _, m: bool(m.via_bot)) + """Filter messages sent via inline bots""" + service = create("Service", lambda _, m: bool(m.service)) """Filter service messages. A service message contains any of the following fields set From 89983b75ca534f1e033a3071f044a7c8a8f06d6a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 08:59:26 +0100 Subject: [PATCH 029/326] Move relevant SRP-related code into another file These functions are going to be used by all *_cloud_password methods --- pyrogram/client/methods/password/utils.py | 103 ++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 pyrogram/client/methods/password/utils.py diff --git a/pyrogram/client/methods/password/utils.py b/pyrogram/client/methods/password/utils.py new file mode 100644 index 00000000..4bf0ddec --- /dev/null +++ b/pyrogram/client/methods/password/utils.py @@ -0,0 +1,103 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import hashlib +import os + +from pyrogram.api import types + + +def btoi(b: bytes) -> int: + return int.from_bytes(b, "big") + + +def itob(i: int) -> bytes: + return i.to_bytes(256, "big") + + +def sha256(data: bytes) -> bytes: + return hashlib.sha256(data).digest() + + +def xor(a: bytes, b: bytes) -> bytes: + return bytes(i ^ j for i, j in zip(a, b)) + + +def compute_hash(algo: types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow, password: str) -> bytes: + hash1 = sha256(algo.salt1 + password.encode() + algo.salt1) + hash2 = sha256(algo.salt2 + hash1 + algo.salt2) + hash3 = hashlib.pbkdf2_hmac("sha512", hash2, algo.salt1, 100000) + + return sha256(algo.salt2 + hash3 + algo.salt2) + + +def compute_check(r: types.account.Password, password: str) -> types.InputCheckPasswordSRP: + algo = r.current_algo + + p_bytes = algo.p + p = btoi(algo.p) + + g_bytes = itob(algo.g) + g = algo.g + + B_bytes = r.srp_B + B = btoi(B_bytes) + + srp_id = r.srp_id + + x_bytes = compute_hash(algo, password) + x = btoi(x_bytes) + + g_x = pow(g, x, p) + + k_bytes = sha256(p_bytes + g_bytes) + k = btoi(k_bytes) + + kg_x = (k * g_x) % p + + while True: + a_bytes = os.urandom(256) + a = btoi(a_bytes) + + A = pow(g, a, p) + A_bytes = itob(A) + + u = btoi(sha256(A_bytes + B_bytes)) + + if u > 0: + break + + g_b = (B - kg_x) % p + + ux = u * x + a_ux = a + ux + S = pow(g_b, a_ux, p) + S_bytes = itob(S) + + K_bytes = sha256(S_bytes) + + M1_bytes = sha256( + xor(sha256(p_bytes), sha256(g_bytes)) + + sha256(algo.salt1) + + sha256(algo.salt2) + + A_bytes + + B_bytes + + K_bytes + ) + + return types.InputCheckPasswordSRP(srp_id, A_bytes, M1_bytes) From e3459017efe74c13e9da5eef1eac03a718154c56 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 09:05:50 +0100 Subject: [PATCH 030/326] Re-implement enable_cloud_password using SRP --- .../methods/password/enable_cloud_password.py | 119 ++++-------------- 1 file changed, 21 insertions(+), 98 deletions(-) diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index 1969453a..e4341480 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -16,34 +16,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from hashlib import sha256, pbkdf2_hmac import os from pyrogram.api import functions, types +from .utils import compute_hash, btoi, itob from ...ext import BaseClient -def compute_hash(algo: types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow, password: str): - hash1 = sha256(algo.salt1 + password.encode() + algo.salt1).digest() - hash2 = sha256(algo.salt2 + hash1 + algo.salt2).digest() - hash3 = pbkdf2_hmac("sha512", hash2, algo.salt1, 100000) - - return sha256(algo.salt2 + hash3 + algo.salt2).digest() - - -def btoi(b: bytes): - return int.from_bytes(b, "big") - - -def itob(i: int): - return i.to_bytes(256, "big") - - class EnableCloudPassword(BaseClient): - def enable_cloud_password(self, password: str, hint: str = "", email: str = ""): + def enable_cloud_password(self, password: str, hint: str = "", email: str = None): """Use this method to enable the Two-Step Verification security feature (Cloud Password) on your account. - This password will be asked when you log in on a new device in addition to the SMS code. + This password will be asked when you log-in on a new device in addition to the SMS code. Args: password (``str``): @@ -56,92 +40,31 @@ class EnableCloudPassword(BaseClient): Recovery e-mail. Returns: - True on success, False otherwise. + True on success. Raises: :class:`Error ` in case of a Telegram RPC error. + ``ValueError`` in case there is already a cloud password enabled. """ r = self.send(functions.account.GetPassword()) - print(r) - algo = r.new_algo + if r.has_password: + raise ValueError("There is already a cloud password enabled") - p_bytes = algo.p - p = btoi(algo.p) + r.new_algo.salt1 += os.urandom(32) + new_hash = btoi(compute_hash(r.new_algo, password)) + new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p))) - g_bytes = itob(algo.g) - g = algo.g - - B_bytes = r.srp_B or b"" - B = btoi(B_bytes) - - srp_id = r.srp_id or 0 - - x_bytes = compute_hash(algo, password) - x = btoi(x_bytes) - - g_x = pow(g, x, p) - - k_bytes = sha256(p_bytes + g_bytes).digest() - k = btoi(k_bytes) - - kg_x = (k * g_x) % p - - while True: - a_bytes = os.urandom(256) - a = btoi(a_bytes) - - A = pow(g, a, p) - A_bytes = itob(A) - - u = btoi(sha256(A_bytes + B_bytes).digest()) - - if u > 0: - break - - g_b = (B - kg_x) % p - - ux = u * x - a_ux = a + ux - S = pow(g_b, a_ux, p) - S_bytes = itob(S) - - K_bytes = sha256(S_bytes).digest() - M1_bytes = sha256( - b"".join([bytes([int(i) ^ int(j)]) for (i, j) in zip(sha256(p_bytes).digest(), sha256(g_bytes).digest())]) - + sha256(algo.salt1).digest() - + sha256(algo.salt2).digest() - + A_bytes - + B_bytes - + K_bytes - ).digest() - - input_check_password = types.InputCheckPasswordSRP(srp_id, A_bytes, M1_bytes) - - r2 = self.send(functions.account.UpdatePasswordSettings( - input_check_password, types.account.PasswordInputSettings( - new_algo=algo, - new_password_hash=b"", - hint="" + self.send( + functions.account.UpdatePasswordSettings( + password=types.InputCheckPasswordEmpty(), + new_settings=types.account.PasswordInputSettings( + new_algo=r.new_algo, + new_password_hash=new_hash, + hint=hint, + email=email + ) ) - )) + ) - print(r2) - - # if isinstance(r, types.account.NoPassword): - # salt = r.new_salt + os.urandom(8) - # password_hash = sha256(salt + password.encode() + salt).digest() - # - # return self.send( - # functions.account.UpdatePasswordSettings( - # current_password_hash=salt, - # new_settings=types.account.PasswordInputSettings( - # new_salt=salt, - # new_password_hash=password_hash, - # hint=hint, - # email=email - # ) - # ) - # ) - # else: - # return False + return True From 6a9c7312ccf6daf56c8140e394b0cb7aeed0bc98 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 09:10:19 +0100 Subject: [PATCH 031/326] Document how decorated functions are modified --- pyrogram/client/methods/decorators/on_callback_query.py | 7 +++++++ pyrogram/client/methods/decorators/on_deleted_messages.py | 7 +++++++ pyrogram/client/methods/decorators/on_message.py | 7 +++++++ pyrogram/client/methods/decorators/on_raw_update.py | 7 +++++++ pyrogram/client/methods/decorators/on_user_status.py | 7 +++++++ 5 files changed, 35 insertions(+) diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py index a7079236..51a6df2e 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/client/methods/decorators/on_callback_query.py @@ -27,6 +27,13 @@ class OnCallbackQuery(BaseClient): callback queries. This does the same thing as :meth:`add_handler` using the :class:`CallbackQueryHandler`. + .. note:: + This decorator will wrap your defined function in a tuple consisting of *(Handler, group)*. + + To reference your own function after it has been decorated, you need to access + *my_function[0].callback*, that is, the *callback* field of Handler object which is the the + first element in the tuple. + Args: filters (:obj:`Filters `): Pass one or more filters to allow only a subset of callback queries to be passed diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index 2fd8f298..c23b7594 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.py @@ -27,6 +27,13 @@ class OnDeletedMessages(BaseClient): deleted messages. This does the same thing as :meth:`add_handler` using the :class:`DeletedMessagesHandler`. + .. note:: + This decorator will wrap your defined function in a tuple consisting of *(Handler, group)*. + + To reference your own function after it has been decorated, you need to access + *my_function[0].callback*, that is, the *callback* field of Handler object which is the the + first element in the tuple. + Args: filters (:obj:`Filters `): Pass one or more filters to allow only a subset of messages to be passed diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index 690f8368..a098cfa2 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.py @@ -27,6 +27,13 @@ class OnMessage(BaseClient): messages. This does the same thing as :meth:`add_handler` using the :class:`MessageHandler`. + .. note:: + This decorator will wrap your defined function in a tuple consisting of *(Handler, group)*. + + To reference your own function after it has been decorated, you need to access + *my_function[0].callback*, that is, the *callback* field of Handler object which is the the + first element in the tuple. + Args: filters (:obj:`Filters `): Pass one or more filters to allow only a subset of messages to be passed diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py index 1391482f..52728e1c 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -26,6 +26,13 @@ class OnRawUpdate(BaseClient): raw updates. This does the same thing as :meth:`add_handler` using the :class:`RawUpdateHandler`. + .. note:: + This decorator will wrap your defined function in a tuple consisting of *(Handler, group)*. + + To reference your own function after it has been decorated, you need to access + *my_function[0].callback*, that is, the *callback* field of Handler object which is the the + first element in the tuple. + Args: group (``int``, *optional*): The group identifier, defaults to 0. diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py index 5aa6f783..2ca41e56 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/client/methods/decorators/on_user_status.py @@ -27,6 +27,13 @@ class OnUserStatus(BaseClient): user status updates. This does the same thing as :meth:`add_handler` using the :class:`UserStatusHandler`. + .. note:: + This decorator will wrap your defined function in a tuple consisting of *(Handler, group)*. + + To reference your own function after it has been decorated, you need to access + *my_function[0].callback*, that is, the *callback* field of Handler object which is the the + first element in the tuple. + Args: filters (:obj:`Filters `): Pass one or more filters to allow only a subset of UserStatus updated to be passed in your function. From 001a067d82e9b31bfe853837371f49b2971cba96 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 09:50:35 +0100 Subject: [PATCH 032/326] Make start and stop methods return self to make chaining possible Suggestion by @CharlesBachman in Telegram --- pyrogram/client/client.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 2094eb9c..9235eb12 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -198,10 +198,9 @@ class Client(Methods, BaseClient): self.dispatcher = Dispatcher(self, workers) def __enter__(self): - self.start() - return self + return self.start() - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, *args): self.stop() @property @@ -294,6 +293,8 @@ class Client(Methods, BaseClient): mimetypes.init() Syncer.add(self) + return self + def stop(self): """Use this method to manually stop the Client. Requires no parameters. @@ -331,6 +332,8 @@ class Client(Methods, BaseClient): self.is_started = False self.session.stop() + return self + def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): """Blocks the program execution until one of the signals are received, then gently stop the Client by closing the underlying connection. From afb3f55d33f780d8ea65523e50ed09185fff0f78 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 11:10:03 +0100 Subject: [PATCH 033/326] Add USER_ADMIN_INVALID error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index d330db1b..08db4c4f 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -75,4 +75,5 @@ MEDIA_EMPTY The media is invalid USER_IS_BLOCKED The user blocked you YOU_BLOCKED_USER You blocked this user ADMINS_TOO_MUCH The chat has too many administrators -BOTS_TOO_MUCH The chat has too many bots \ No newline at end of file +BOTS_TOO_MUCH The chat has too many bots +USER_ADMIN_INVALID The action requires admin privileges \ No newline at end of file From 70470360b1d8167a20134e56728a345cda65e212 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 11:10:25 +0100 Subject: [PATCH 034/326] Print account name when logging in the first time --- pyrogram/client/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 9235eb12..bd132851 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -437,6 +437,8 @@ class Client(Methods, BaseClient): else: self.user_id = r.user.id + print("Logged in successfully as @{}".format(r.user.username)) + def authorize_user(self): phone_number_invalid_raises = self.phone_number is not None phone_code_invalid_raises = self.phone_code is not None @@ -621,7 +623,7 @@ class Client(Methods, BaseClient): self.password = None self.user_id = r.user.id - print("Login successful") + print("Logged in successfully as {}".format(r.user.first_name)) def fetch_peers(self, entities: list): for entity in entities: From 47b25b0e2df98f57ce2a196da0ca716d29a03ad0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 11:35:53 +0100 Subject: [PATCH 035/326] CallbackQuery must deal with bytes instead of strings --- pyrogram/client/ext/utils.py | 2 +- pyrogram/client/types/bots/callback_query.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index b803c077..fac611f3 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -905,7 +905,7 @@ def parse_callback_query(client, update, users): message=message, inline_message_id=inline_message_id, chat_instance=str(update.chat_instance), - data=update.data.decode(), + data=update.data, game_short_name=update.game_short_name, client=client ) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index 843a9bb8..447d13ea 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -60,7 +60,7 @@ class CallbackQuery(Object): client=None, message=None, inline_message_id: str = None, - data: str = None, + data: bytes = None, game_short_name: str = None ): self._client = client From 1bd41d01389aee7b7c02c765cd05835a60495596 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 11:37:27 +0100 Subject: [PATCH 036/326] Fix CallbackQuery docs --- pyrogram/client/types/bots/callback_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index 447d13ea..046981b7 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -43,7 +43,7 @@ class CallbackQuery(Object): Global identifier, uniquely corresponding to the chat to which the message with the callback button was sent. Useful for high scores in games. - data (``str``, *optional*): + data (``bytes``, *optional*): Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field. game_short_name (``str``, *optional*): From 40ecc082a65482072b9234c21e9beb9466413e03 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 12:22:33 +0100 Subject: [PATCH 037/326] Re-implement change_cloud_password using SRP --- .../methods/password/change_cloud_password.py | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index 4b5e86b3..2afc2fd7 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -17,9 +17,9 @@ # along with Pyrogram. If not, see . import os -from hashlib import sha256 from pyrogram.api import functions, types +from .utils import compute_hash, compute_check, btoi, itob from ...ext import BaseClient @@ -38,28 +38,30 @@ class ChangeCloudPassword(BaseClient): A new password hint. Returns: - True on success, False otherwise. + True on success. Raises: :class:`Error ` in case of a Telegram RPC error. + ``ValueError`` in case there is no cloud password to change. """ r = self.send(functions.account.GetPassword()) - if isinstance(r, types.account.Password): - current_password_hash = sha256(r.current_salt + current_password.encode() + r.current_salt).digest() + if not r.has_password: + raise ValueError("There is no cloud password to change") - new_salt = r.new_salt + os.urandom(8) - new_password_hash = sha256(new_salt + new_password.encode() + new_salt).digest() + r.new_algo.salt1 += os.urandom(32) + new_hash = btoi(compute_hash(r.new_algo, new_password)) + new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p))) - return self.send( - functions.account.UpdatePasswordSettings( - current_password_hash=current_password_hash, - new_settings=types.account.PasswordInputSettings( - new_salt=new_salt, - new_password_hash=new_password_hash, - hint=new_hint - ) + self.send( + functions.account.UpdatePasswordSettings( + password=compute_check(r, current_password), + new_settings=types.account.PasswordInputSettings( + new_algo=r.new_algo, + new_password_hash=new_hash, + hint=new_hint ) ) - else: - return False + ) + + return True From 37d063e569c6f451e9f7be45c65ff4bec9a6f30b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 12:35:09 +0100 Subject: [PATCH 038/326] Reorganize Message __init__ --- .../types/messages_and_media/message.py | 69 ++++--------------- 1 file changed, 13 insertions(+), 56 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 02d31590..e8e587d1 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +from pyrogram.api import types from pyrogram.api.core import Object from ..bots import InlineKeyboardMarkup, ReplyKeyboardMarkup @@ -217,62 +217,19 @@ class Message(Object): # TODO: Add game missing field. Also invoice, successful_payment, connected_website ID = 0xb0700003 - def __init__( - self, - message_id: int, - client=None, - date: int = None, - chat=None, - from_user=None, - forward_from=None, - forward_from_chat=None, - forward_from_message_id: int = None, - forward_signature: str = None, - forward_date: int = None, - reply_to_message=None, - mentioned=None, - empty=None, - service=None, - media=None, - edit_date: int = None, - media_group_id: str = None, - author_signature: str = None, - text: str = None, - entities: list = None, - caption_entities: list = None, - audio=None, - document=None, - photo=None, - sticker=None, - animation=None, - video=None, - voice=None, - video_note=None, - caption: str = None, - contact=None, - location=None, - venue=None, - web_page=None, - new_chat_members: list = None, - left_chat_member=None, - new_chat_title: str = None, - new_chat_photo=None, - delete_chat_photo: bool = None, - group_chat_created: bool = None, - supergroup_chat_created: bool = None, - channel_chat_created: bool = None, - migrate_to_chat_id: int = None, - migrate_from_chat_id: int = None, - pinned_message=None, - views: int = None, - via_bot=None, - outgoing: bool = None, - matches: list = None, - command: list = None, - reply_markup=None, - ): + def __init__(self, message_id: int, date: int = None, chat=None, from_user=None, forward_from=None, + forward_from_chat=None, forward_from_message_id: int = None, forward_signature: str = None, + forward_date: int = None, reply_to_message=None, mentioned=None, empty=None, service=None, media=None, + edit_date: int = None, media_group_id: str = None, author_signature: str = None, text: str = None, + entities: list = None, caption_entities: list = None, audio=None, document=None, photo=None, + sticker=None, animation=None, video=None, voice=None, video_note=None, caption: str = None, + contact=None, location=None, venue=None, web_page=None, new_chat_members: list = None, + left_chat_member=None, new_chat_title: str = None, new_chat_photo=None, delete_chat_photo: bool = None, + group_chat_created: bool = None, supergroup_chat_created: bool = None, + channel_chat_created: bool = None, migrate_to_chat_id: int = None, migrate_from_chat_id: int = None, + pinned_message=None, views: int = None, via_bot=None, outgoing: bool = None, matches: list = None, + command: list = None, reply_markup=None, client=None, raw=None): self.message_id = message_id # int - self._client = client self.date = date # int self.chat = chat # Chat self.from_user = from_user # flags.0?User From 9ac492e6447deab5e19d143992456b3eaada240d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 15:32:50 +0100 Subject: [PATCH 039/326] Remove Message generated comments --- .../types/messages_and_media/message.py | 88 ++++++++++--------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index e8e587d1..36c35837 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -15,9 +15,14 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . + from pyrogram.api import types from pyrogram.api.core import Object +from pyrogram.api.errors import MessageIdsEmpty from ..bots import InlineKeyboardMarkup, ReplyKeyboardMarkup +from ..messages_and_media.photo import Photo +from ..user_and_chats.chat import Chat +from ..user_and_chats.user import User class Message(Object): @@ -228,53 +233,54 @@ class Message(Object): group_chat_created: bool = None, supergroup_chat_created: bool = None, channel_chat_created: bool = None, migrate_to_chat_id: int = None, migrate_from_chat_id: int = None, pinned_message=None, views: int = None, via_bot=None, outgoing: bool = None, matches: list = None, - command: list = None, reply_markup=None, client=None, raw=None): - self.message_id = message_id # int - self.date = date # int - self.chat = chat # Chat - self.from_user = from_user # flags.0?User - self.forward_from = forward_from # flags.1?User - self.forward_from_chat = forward_from_chat # flags.2?Chat - self.forward_from_message_id = forward_from_message_id # flags.3?int - self.forward_signature = forward_signature # flags.4?string - self.forward_date = forward_date # flags.5?int - self.reply_to_message = reply_to_message # flags.6?Message + command: list = None, reply_markup=None, + client=None, raw=None): + self.message_id = message_id + self.date = date + self.chat = chat + self.from_user = from_user + self.forward_from = forward_from + self.forward_from_chat = forward_from_chat + self.forward_from_message_id = forward_from_message_id + self.forward_signature = forward_signature + self.forward_date = forward_date + self.reply_to_message = reply_to_message self.mentioned = mentioned self.empty = empty self.service = service self.media = media - self.edit_date = edit_date # flags.7?int - self.media_group_id = media_group_id # flags.8?string - self.author_signature = author_signature # flags.9?string - self.text = text # flags.10?string - self.entities = entities # flags.11?Vector - self.caption_entities = caption_entities # flags.12?Vector - self.audio = audio # flags.13?Audio - self.document = document # flags.14?Document - self.photo = photo # flags.16?Vector - self.sticker = sticker # flags.17?Sticker + self.edit_date = edit_date + self.media_group_id = media_group_id + self.author_signature = author_signature + self.text = text + self.entities = entities + self.caption_entities = caption_entities + self.audio = audio + self.document = document + self.photo = photo + self.sticker = sticker self.animation = animation - self.video = video # flags.18?Video - self.voice = voice # flags.19?Voice - self.video_note = video_note # flags.20?VideoNote - self.caption = caption # flags.21?string - self.contact = contact # flags.22?Contact - self.location = location # flags.23?Location - self.venue = venue # flags.24?Venue + self.video = video + self.voice = voice + self.video_note = video_note + self.caption = caption + self.contact = contact + self.location = location + self.venue = venue self.web_page = web_page - self.new_chat_members = new_chat_members # flags.25?Vector - self.left_chat_member = left_chat_member # flags.26?User - self.new_chat_title = new_chat_title # flags.27?string - self.new_chat_photo = new_chat_photo # flags.28?Vector - self.delete_chat_photo = delete_chat_photo # flags.29?true - self.group_chat_created = group_chat_created # flags.30?true - self.supergroup_chat_created = supergroup_chat_created # flags.31?true - self.channel_chat_created = channel_chat_created # flags.32?true - self.migrate_to_chat_id = migrate_to_chat_id # flags.33?int - self.migrate_from_chat_id = migrate_from_chat_id # flags.34?int - self.pinned_message = pinned_message # flags.35?Message - self.views = views # flags.39?int - self.via_bot = via_bot # flags.40?User + self.new_chat_members = new_chat_members + self.left_chat_member = left_chat_member + self.new_chat_title = new_chat_title + self.new_chat_photo = new_chat_photo + self.delete_chat_photo = delete_chat_photo + self.group_chat_created = group_chat_created + self.supergroup_chat_created = supergroup_chat_created + self.channel_chat_created = channel_chat_created + self.migrate_to_chat_id = migrate_to_chat_id + self.migrate_from_chat_id = migrate_from_chat_id + self.pinned_message = pinned_message + self.views = views + self.via_bot = via_bot self.outgoing = outgoing self.matches = matches self.command = command From b2f3e79ff7e53ac4970671b5dd9dc9f70c66e887 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 17:25:34 +0100 Subject: [PATCH 040/326] Refactor UserStatus --- .../types/user_and_chats/user_status.py | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index cc96df52..1a0ab872 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from pyrogram.api import types from pyrogram.api.core import Object @@ -63,17 +64,10 @@ class UserStatus(Object): ID = 0xb0700031 - def __init__( - self, - user_id: int = None, - online: bool = None, - offline: bool = None, - date: int = None, - recently: bool = None, - within_week: bool = None, - within_month: bool = None, - long_time_ago: bool = None - ): + def __init__(self, user_id: int, *, online: bool = None, offline: bool = None, date: int = None, + recently: bool = None, within_week: bool = None, within_month: bool = None, + long_time_ago: bool = None, + client=None, raw=None): self.user_id = user_id self.online = online self.offline = offline @@ -82,3 +76,31 @@ class UserStatus(Object): self.within_week = within_week self.within_month = within_month self.long_time_ago = long_time_ago + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, user: types.User): + if user.bot: + return None + + raw_status = user.status + status = UserStatus(user_id=user.id, client=client, raw=raw_status) + + if isinstance(raw_status, types.UserStatusOnline): + status.online = True + status.date = raw_status.expires + elif isinstance(raw_status, types.UserStatusOffline): + status.offline = True + status.date = raw_status.was_online + elif isinstance(raw_status, types.UserStatusRecently): + status.recently = True + elif isinstance(raw_status, types.UserStatusLastWeek): + status.within_week = True + elif isinstance(raw_status, types.UserStatusLastMonth): + status.within_month = True + else: + status.long_time_ago = True + + return status From 26b15f384d3669abcee84e4cc240692e20418551 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 17:26:24 +0100 Subject: [PATCH 041/326] Update UserStatus docs --- pyrogram/client/types/user_and_chats/user_status.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index 1a0ab872..920168b5 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -29,8 +29,8 @@ class UserStatus(Object): "recently", "within_week", "within_month" or "long_time_ago" fields set. Args: - user_id (``int``, *optional*): - User's id. Only available for incoming UserStatus updates. + user_id (``int``): + User's id. online (``bool``, *optional*): True if the user is online in this very moment, None otherwise. From 0d0fbdad272718a3c08c467965e8dd4e0425b46c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 17:28:52 +0100 Subject: [PATCH 042/326] Reformat UserStatus style --- pyrogram/client/types/user_and_chats/user_status.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index 920168b5..651d8db6 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -64,11 +64,12 @@ class UserStatus(Object): ID = 0xb0700031 - def __init__(self, user_id: int, *, online: bool = None, offline: bool = None, date: int = None, - recently: bool = None, within_week: bool = None, within_month: bool = None, - long_time_ago: bool = None, + def __init__(self, user_id: int, *, + online: bool = None, offline: bool = None, date: int = None, recently: bool = None, + within_week: bool = None, within_month: bool = None, long_time_ago: bool = None, client=None, raw=None): self.user_id = user_id + self.online = online self.offline = offline self.date = date From bf3609ec0a7b34342e48cbaaa97b4078798126ab Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 17:29:51 +0100 Subject: [PATCH 043/326] Refactor User --- pyrogram/client/types/user_and_chats/user.py | 54 +++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 06045b00..e460522b 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -16,7 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from pyrogram.api import types from pyrogram.api.core import Object +from .user_status import UserStatus +from .chat_photo import ChatPhoto class User(Object): @@ -68,23 +71,11 @@ class User(Object): ID = 0xb0700001 - def __init__( - self, - id: int, - is_self: bool, - is_contact: bool, - is_mutual_contact: bool, - is_deleted: bool, - is_bot: bool, - first_name: str, - status=None, - last_name: str = None, - username: str = None, - language_code: str = None, - phone_number: str = None, - photo=None, - restriction_reason: str = None - ): + def __init__(self, id: int, is_self: bool, is_contact: bool, is_mutual_contact: bool, is_deleted: bool, + is_bot: bool, first_name: str, *, + last_name: str = None, status=None, username: str = None, language_code: str = None, + phone_number: str = None, photo=None, restriction_reason: str = None, + client=None, raw=None): self.id = id self.is_self = is_self self.is_contact = is_contact @@ -92,10 +83,37 @@ class User(Object): self.is_deleted = is_deleted self.is_bot = is_bot self.first_name = first_name - self.status = status + self.last_name = last_name + self.status = status self.username = username self.language_code = language_code self.phone_number = phone_number self.photo = photo self.restriction_reason = restriction_reason + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, user: types.User) -> "User" or None: + if user is None: + return None + + return User( + id=user.id, + is_self=user.is_self, + is_contact=user.contact, + is_mutual_contact=user.mutual_contact, + is_deleted=user.deleted, + is_bot=user.bot, + first_name=user.first_name, + last_name=user.last_name, + status=UserStatus.parse(client, user), + username=user.username, + language_code=user.lang_code, + phone_number=user.phone, + photo=ChatPhoto.parse(client, user.photo), + restriction_reason=user.restriction_reason, + client=client, raw=user + ) From 5962f8dedc8438f58cbb2caf3c0a627fa1c304cb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 17:30:24 +0100 Subject: [PATCH 044/326] Refactor ChatPhoto --- .../client/types/user_and_chats/chat_photo.py | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index e5877309..485777e5 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -16,7 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from struct import pack + +from pyrogram.api import types from pyrogram.api.core import Object +from ...ext.utils import encode class ChatPhoto(Object): @@ -32,6 +36,42 @@ class ChatPhoto(Object): ID = 0xb0700015 - def __init__(self, small_file_id: str, big_file_id: str): - self.small_file_id = small_file_id # string - self.big_file_id = big_file_id # string + def __init__(self, small_file_id: str, big_file_id: str, *, + client=None, raw=None): + self.small_file_id = small_file_id + self.big_file_id = big_file_id + + self._client = client + self.raw = raw + + @staticmethod + def parse(client, chat_photo: types.UserProfilePhoto or types.ChatPhoto): + if not isinstance(chat_photo, (types.UserProfilePhoto, types.ChatPhoto)): + return None + + if not isinstance(chat_photo.photo_small, types.FileLocation): + return None + + if not isinstance(chat_photo.photo_big, types.FileLocation): + return None + + photo_id = getattr(chat_photo, "photo_id", 0) + loc_small = chat_photo.photo_small + loc_big = chat_photo.photo_big + + return ChatPhoto( + small_file_id=encode( + pack( + " Date: Sat, 15 Dec 2018 17:31:22 +0100 Subject: [PATCH 045/326] Refactor Chat --- pyrogram/client/types/user_and_chats/chat.py | 84 +++++++++++++++----- 1 file changed, 66 insertions(+), 18 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 68eaa775..75aa5eac 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -16,7 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from pyrogram.api import types from pyrogram.api.core import Object +from .chat_photo import ChatPhoto class Chat(Object): @@ -76,26 +78,15 @@ class Chat(Object): ID = 0xb0700002 - def __init__( - self, - id: int, - type: str, - title: str = None, - username: str = None, - first_name: str = None, - last_name: str = None, - all_members_are_administrators: bool = None, - photo=None, - description: str = None, - invite_link: str = None, - pinned_message=None, - sticker_set_name: str = None, - can_set_sticker_set: bool = None, - members_count: int = None, - restriction_reason: str = None - ): + def __init__(self, id: int, type: str, *, + title: str = None, username: str = None, first_name: str = None, last_name: str = None, + all_members_are_administrators: bool = None, photo=None, description: str = None, + invite_link: str = None, pinned_message=None, sticker_set_name: str = None, + can_set_sticker_set: bool = None, members_count: int = None, restriction_reason: str = None, + client=None, raw=None): self.id = id self.type = type + self.title = title self.username = username self.first_name = first_name @@ -109,3 +100,60 @@ class Chat(Object): self.can_set_sticker_set = can_set_sticker_set self.members_count = members_count self.restriction_reason = restriction_reason + + self._client = client + self._raw = raw + + @staticmethod + def parse_user_chat(client, user: types.User) -> "Chat": + return Chat( + id=user.id, + type="private", + username=user.username, + first_name=user.first_name, + last_name=user.last_name, + photo=ChatPhoto.parse(client, user.photo), + restriction_reason=user.restriction_reason, + client=client, + raw=user + ) + + @staticmethod + def parse_chat_chat(client, chat: types.Chat) -> "Chat": + admins_enabled = getattr(chat, "admins_enabled", None) + + if admins_enabled is not None: + admins_enabled = not admins_enabled + + return Chat( + id=-chat.id, + type="group", + title=chat.title, + all_members_are_administrators=admins_enabled, + photo=ChatPhoto.parse(client, getattr(chat, "photo", None)), + client=client, + raw=chat + ) + + @staticmethod + def parse_channel_chat(client, channel: types.Channel) -> "Chat": + return Chat( + id=int("-100" + str(channel.id)), + type="supergroup" if channel.megagroup else "channel", + title=channel.title, + username=getattr(channel, "username", None), + photo=ChatPhoto.parse(client, getattr(channel, "photo", None)), + restriction_reason=getattr(channel, "restriction_reason", None), + client=client, + raw=channel + ) + + @staticmethod + def parse(client, message: types.Message or types.MessageService, users: dict, chats: dict) -> "Chat": + if isinstance(message.to_id, types.PeerUser): + return Chat.parse_user_chat(client, users[message.to_id.user_id if message.out else message.from_id]) + + if isinstance(message.to_id, types.PeerChat): + return Chat.parse_chat_chat(chats[message.to_id.chat_id]) + + return Chat.parse_channel_chat(client, chats[message.to_id.channel_id]) From efc6023b08f1863a123c6c10b38384d498fa5a69 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 20:08:31 +0100 Subject: [PATCH 046/326] Re-implement remove_cloud_password using SRP --- .../methods/password/remove_cloud_password.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py index 5a9875ff..a9320c39 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -16,9 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from hashlib import sha256 - from pyrogram.api import functions, types +from .utils import compute_check from ...ext import BaseClient @@ -31,25 +30,26 @@ class RemoveCloudPassword(BaseClient): Your current password. Returns: - True on success, False otherwise. + True on success. Raises: :class:`Error ` in case of a Telegram RPC error. + ``ValueError`` in case there is no cloud password to remove. """ r = self.send(functions.account.GetPassword()) - if isinstance(r, types.account.Password): - password_hash = sha256(r.current_salt + password.encode() + r.current_salt).digest() + if not r.has_password: + raise ValueError("There is no cloud password to remove") - return self.send( - functions.account.UpdatePasswordSettings( - current_password_hash=password_hash, - new_settings=types.account.PasswordInputSettings( - new_salt=b"", - new_password_hash=b"", - hint="" - ) + self.send( + functions.account.UpdatePasswordSettings( + password=compute_check(r, password), + new_settings=types.account.PasswordInputSettings( + new_algo=types.PasswordKdfAlgoUnknown(), + new_password_hash=b"", + hint="" ) ) - else: - return False + ) + + return True From 905f4b8e62fde7bbf9cad7159ffe180b5bba4340 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 21:40:44 +0100 Subject: [PATCH 047/326] Refactor Voice and Audio --- .../client/types/messages_and_media/audio.py | 48 ++++++++++++++----- .../client/types/messages_and_media/voice.py | 38 +++++++++++---- 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 37f91992..3f2b3c20 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -16,7 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from struct import pack + +from pyrogram.api import types from pyrogram.api.core import Object +from .photo_size import PhotoSize +from ...ext.utils import encode class Audio(Object): @@ -53,18 +58,10 @@ class Audio(Object): ID = 0xb0700006 - def __init__( - self, - file_id: str, - duration: int, - thumb=None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None, - performer: str = None, - title: str = None - ): + def __init__(self, file_id: str, duration: int, *, + thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, + performer: str = None, title: str = None, + client=None, raw=None): self.file_id = file_id self.thumb = thumb self.file_name = file_name @@ -74,3 +71,30 @@ class Audio(Object): self.duration = duration self.performer = performer self.title = title + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, audio: types.Document, audio_attributes: types.DocumentAttributeAudio, file_name: str) -> "Audio": + return Audio( + file_id=encode( + pack( + ". +from pyrogram.api import types from pyrogram.api.core import Object +from ...ext.utils import encode +from struct import pack class Voice(Object): @@ -44,17 +47,36 @@ class Voice(Object): ID = 0xb0700009 - def __init__( - self, - file_id: str, - duration: int, - waveform: bytes = None, - mime_type: str = None, - file_size: int = None, - date: int = None): + def __init__(self, file_id: str, duration: int, *, + waveform: bytes = None, mime_type: str = None, file_size: int = None, date: int = None, + client=None, raw=None): self.file_id = file_id self.duration = duration self.waveform = waveform self.mime_type = mime_type self.file_size = file_size self.date = date + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, voice: types.Document, attributes: types.DocumentAttributeAudio) -> "Voice": + return Voice( + file_id=encode( + pack( + " Date: Sat, 15 Dec 2018 21:40:44 +0100 Subject: [PATCH 048/326] Refactor Animation, Video and VideoNote --- .../types/messages_and_media/animation.py | 48 ++++++++++++++----- .../client/types/messages_and_media/video.py | 45 ++++++++++++----- .../types/messages_and_media/video_note.py | 41 ++++++++++++---- 3 files changed, 100 insertions(+), 34 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index a8641e9e..0e36d5cf 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -16,7 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from struct import pack + +from pyrogram.api import types from pyrogram.api.core import Object +from .photo_size import PhotoSize +from ...ext.utils import encode class Animation(Object): @@ -53,18 +58,9 @@ class Animation(Object): ID = 0xb0700025 - def __init__( - self, - file_id: str, - width: int, - height: int, - duration: int, - thumb=None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None - ): + def __init__(self, file_id: str, width: int, height: int, duration: int, *, + thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, + client=None, raw=None): self.file_id = file_id self.thumb = thumb self.file_name = file_name @@ -74,3 +70,31 @@ class Animation(Object): self.width = width self.height = height self.duration = duration + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, animation: types.Document, video_attributes: types.DocumentAttributeVideo, + file_name: str) -> "Animation": + return Animation( + file_id=encode( + pack( + ". +from struct import pack + +from pyrogram.api import types from pyrogram.api.core import Object +from .photo_size import PhotoSize +from ...ext.utils import encode class Video(Object): @@ -53,18 +58,9 @@ class Video(Object): ID = 0xb0700008 - def __init__( - self, - file_id: str, - width: int, - height: int, - duration: int, - thumb=None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None - ): + def __init__(self, file_id: str, width: int, height: int, duration: int, *, + thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, + client=None, raw=None): self.file_id = file_id self.thumb = thumb self.file_name = file_name @@ -74,3 +70,28 @@ class Video(Object): self.width = width self.height = height self.duration = duration + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, video: types.Document, video_attributes: types.DocumentAttributeVideo, file_name: str) -> "Video": + return Video( + file_id=encode( + pack( + ". +from struct import pack + +from pyrogram.api import types from pyrogram.api.core import Object +from .photo_size import PhotoSize +from ...ext.utils import encode class VideoNote(Object): @@ -47,16 +52,9 @@ class VideoNote(Object): ID = 0xb0700010 - def __init__( - self, - file_id: str, - length: int, - duration: int, - thumb=None, - mime_type: str = None, - file_size: int = None, - date: int = None - ): + def __init__(self, file_id: str, length: int, duration: int, *, + thumb=None, mime_type: str = None, file_size: int = None, date: int = None, + client=None, raw=None): self.file_id = file_id self.thumb = thumb self.mime_type = mime_type @@ -64,3 +62,26 @@ class VideoNote(Object): self.date = date self.length = length self.duration = duration + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, video_note: types.Document, video_attributes: types.DocumentAttributeVideo) -> "VideoNote": + return VideoNote( + file_id=encode( + pack( + " Date: Sat, 15 Dec 2018 21:40:44 +0100 Subject: [PATCH 049/326] Refactor Photo and PhotoSize --- .../client/types/messages_and_media/photo.py | 61 ++++++++++++++++++- .../types/messages_and_media/photo_size.py | 37 ++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 4037025b..b1021d93 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -16,7 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from base64 import b64encode +from struct import pack + +from pyrogram.api import types from pyrogram.api.core import Object +from .photo_size import PhotoSize +from ...ext.utils import encode class Photo(Object): @@ -35,7 +41,60 @@ class Photo(Object): ID = 0xb0700027 - def __init__(self, id: str, date: int, sizes: list): + def __init__(self, id: str, date: int, sizes: list, *, + client=None, raw=None): self.id = id self.date = date self.sizes = sizes + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, photo: types.Photo): + if isinstance(photo, types.Photo): + raw_sizes = photo.sizes + sizes = [] + + for raw_size in raw_sizes: + if isinstance(raw_size, (types.PhotoSize, types.PhotoCachedSize)): + + if isinstance(raw_size, types.PhotoSize): + file_size = raw_size.size + elif isinstance(raw_size, types.PhotoCachedSize): + file_size = len(raw_size.bytes) + else: + file_size = 0 + + loc = raw_size.location + + if isinstance(loc, types.FileLocation): + size = PhotoSize( + file_id=encode( + pack( + " Date: Sat, 15 Dec 2018 21:40:44 +0100 Subject: [PATCH 050/326] Refactor Venue and Location --- .../types/messages_and_media/location.py | 17 ++++++++++- .../client/types/messages_and_media/venue.py | 28 +++++++++++++------ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index be8c839f..3ab9bfa7 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from pyrogram.api import types from pyrogram.api.core import Object @@ -32,6 +33,20 @@ class Location(Object): ID = 0xb0700012 - def __init__(self, longitude: float, latitude: float): + def __init__(self, longitude: float, latitude: float, *, + client=None, raw=None): self.longitude = longitude self.latitude = latitude + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, geo_point: types.GeoPoint) -> "Location": + if isinstance(geo_point, types.GeoPoint): + return Location( + longitude=geo_point.long, + latitude=geo_point.lat, + client=client, + raw=geo_point + ) diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index 3c5b2b05..8225c36b 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -16,7 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from pyrogram.api import types from pyrogram.api.core import Object +from .location import Location class Venue(Object): @@ -43,16 +45,26 @@ class Venue(Object): ID = 0xb0700013 - def __init__( - self, - location, - title: str, - address: str, - foursquare_id: str = None, - foursquare_type: str = None - ): + def __init__(self, location, title: str, address: str, *, + foursquare_id: str = None, foursquare_type: str = None, + client=None, raw=None): self.location = location self.title = title self.address = address self.foursquare_id = foursquare_id self.foursquare_type = foursquare_type + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, venue: types.MessageMediaVenue): + return Venue( + location=Location.parse(client, venue.geo), + title=venue.title, + address=venue.address, + foursquare_id=venue.venue_id or None, + foursquare_type=venue.venue_type, + client=client, + raw=venue + ) From 334fb8d0ba63d72fc6f9cf4c6683c86072af837a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 21:40:44 +0100 Subject: [PATCH 051/326] Refactor Sticker, Contact and Document --- .../types/messages_and_media/contact.py | 27 +++++++--- .../types/messages_and_media/document.py | 41 ++++++++++---- .../types/messages_and_media/sticker.py | 53 ++++++++++++++----- 3 files changed, 90 insertions(+), 31 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index 2569f03a..2c686a5f 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from pyrogram.api import types from pyrogram.api.core import Object @@ -41,16 +42,26 @@ class Contact(Object): ID = 0xb0700011 - def __init__( - self, - phone_number: str, - first_name: str, - last_name: str = None, - user_id: int = None, - vcard: str = None - ): + def __init__(self, phone_number: str, first_name: str, *, + last_name: str = None, user_id: int = None, vcard: str = None, + client=None, raw=None): self.phone_number = phone_number self.first_name = first_name self.last_name = last_name self.user_id = user_id self.vcard = vcard + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, contact: types.MessageMediaContact) -> "Contact": + return Contact( + phone_number=contact.phone_number, + first_name=contact.first_name, + last_name=contact.last_name or None, + vcard=contact.vcard or None, + user_id=contact.user_id or None, + client=client, + raw=contact + ) diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index d87fa666..5aa047cf 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -16,7 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from struct import pack + +from pyrogram.api import types from pyrogram.api.core import Object +from .photo_size import PhotoSize +from ...ext.utils import encode class Document(Object): @@ -44,18 +49,36 @@ class Document(Object): ID = 0xb0700007 - def __init__( - self, - file_id: str, - thumb=None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None - ): + def __init__(self, file_id: str, *, + thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, + client=None, raw=None): self.file_id = file_id self.thumb = thumb self.file_name = file_name self.mime_type = mime_type self.file_size = file_size self.date = date + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, document: types.Document, file_name: str) -> "Document": + return Document( + file_id=encode( + pack( + ". +from struct import pack + +from pyrogram.api import types from pyrogram.api.core import Object +from .photo_size import PhotoSize +from ...ext.utils import encode class Sticker(Object): @@ -57,20 +62,10 @@ class Sticker(Object): # TODO: Add mask position ID = 0xb0700017 - def __init__( - self, - file_id: str, - width: int, - height: int, - thumb=None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None, - emoji: str = None, - set_name: str = None, - mask_position=None - ): + def __init__(self, file_id: str, width: int, height: int, *, + thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, + emoji: str = None, set_name: str = None, mask_position=None, + client=None, raw=None): self.file_id = file_id self.thumb = thumb self.file_name = file_name @@ -82,3 +77,33 @@ class Sticker(Object): self.emoji = emoji self.set_name = set_name self.mask_position = mask_position + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, sticker: types.Document, image_size_attributes: types.DocumentAttributeImageSize, set_name: str, + sticker_attributes: types.DocumentAttributeSticker, file_name: str) -> "Sticker": + return Sticker( + file_id=encode( + pack( + " Date: Sat, 15 Dec 2018 21:40:44 +0100 Subject: [PATCH 052/326] Refactor Message and MessageEntity --- .../types/messages_and_media/message.py | 288 ++++++++++++++++-- .../messages_and_media/message_entity.py | 47 ++- 2 files changed, 310 insertions(+), 25 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 36c35837..0829c2db 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -16,13 +16,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import types +import pyrogram +from pyrogram.api import types, functions from pyrogram.api.core import Object -from pyrogram.api.errors import MessageIdsEmpty -from ..bots import InlineKeyboardMarkup, ReplyKeyboardMarkup +from pyrogram.api.errors import MessageIdsEmpty, StickersetInvalid +from .contact import Contact +from .location import Location +from .message_entity import MessageEntity from ..messages_and_media.photo import Photo from ..user_and_chats.chat import Chat from ..user_and_chats.user import User +from ...ext.utils import Str class Message(Object): @@ -222,18 +226,19 @@ class Message(Object): # TODO: Add game missing field. Also invoice, successful_payment, connected_website ID = 0xb0700003 - def __init__(self, message_id: int, date: int = None, chat=None, from_user=None, forward_from=None, - forward_from_chat=None, forward_from_message_id: int = None, forward_signature: str = None, - forward_date: int = None, reply_to_message=None, mentioned=None, empty=None, service=None, media=None, - edit_date: int = None, media_group_id: str = None, author_signature: str = None, text: str = None, - entities: list = None, caption_entities: list = None, audio=None, document=None, photo=None, - sticker=None, animation=None, video=None, voice=None, video_note=None, caption: str = None, - contact=None, location=None, venue=None, web_page=None, new_chat_members: list = None, - left_chat_member=None, new_chat_title: str = None, new_chat_photo=None, delete_chat_photo: bool = None, - group_chat_created: bool = None, supergroup_chat_created: bool = None, - channel_chat_created: bool = None, migrate_to_chat_id: int = None, migrate_from_chat_id: int = None, - pinned_message=None, views: int = None, via_bot=None, outgoing: bool = None, matches: list = None, - command: list = None, reply_markup=None, + def __init__(self, message_id: int, *, + date: int = None, chat=None, from_user=None, forward_from=None, forward_from_chat=None, + forward_from_message_id: int = None, forward_signature: str = None, forward_date: int = None, + reply_to_message=None, mentioned=None, empty=None, service=None, media=None, edit_date: int = None, + media_group_id: str = None, author_signature: str = None, text: str = None, entities: list = None, + caption_entities: list = None, audio=None, document=None, photo=None, sticker=None, animation=None, + video=None, voice=None, video_note=None, caption: str = None, contact=None, location=None, venue=None, + web_page=None, new_chat_members: list = None, left_chat_member=None, new_chat_title: str = None, + new_chat_photo=None, delete_chat_photo: bool = None, group_chat_created: bool = None, + supergroup_chat_created: bool = None, channel_chat_created: bool = None, + migrate_to_chat_id: int = None, migrate_from_chat_id: int = None, pinned_message=None, + views: int = None, via_bot=None, outgoing: bool = None, matches: list = None, command: list = None, + reply_markup=None, client=None, raw=None): self.message_id = message_id self.date = date @@ -286,6 +291,255 @@ class Message(Object): self.command = command self.reply_markup = reply_markup + self._client = client + self._raw = raw + + @staticmethod + def parse(client, message: types.Message or types.MessageService or types.MessageEmpty, users: dict, chats: dict, + replies: int = 1): + if isinstance(message, types.MessageEmpty): + return Message( + message_id=message.id, + client=client, + raw=message + ) + + if isinstance(message, types.MessageService): + action = message.action + + new_chat_members = None + left_chat_member = None + new_chat_title = None + delete_chat_photo = None + migrate_to_chat_id = None + migrate_from_chat_id = None + group_chat_created = None + channel_chat_created = None + new_chat_photo = None + + if isinstance(action, types.MessageActionChatAddUser): + new_chat_members = [User.parse(client, users[i]) for i in action.users] + elif isinstance(action, types.MessageActionChatJoinedByLink): + new_chat_members = [User.parse(client, users[message.from_id])] + elif isinstance(action, types.MessageActionChatDeleteUser): + left_chat_member = User.parse(client, users[action.user_id]) + elif isinstance(action, types.MessageActionChatEditTitle): + new_chat_title = action.title + elif isinstance(action, types.MessageActionChatDeletePhoto): + delete_chat_photo = True + elif isinstance(action, types.MessageActionChatMigrateTo): + migrate_to_chat_id = action.channel_id + elif isinstance(action, types.MessageActionChannelMigrateFrom): + migrate_from_chat_id = action.chat_id + elif isinstance(action, types.MessageActionChatCreate): + group_chat_created = True + elif isinstance(action, types.MessageActionChannelCreate): + channel_chat_created = True + elif isinstance(action, types.MessageActionChatEditPhoto): + new_chat_photo = Photo.parse(client, action.photo) + + parsed_message = Message( + message_id=message.id, + date=message.date, + chat=Chat.parse(client, message, users, chats), + from_user=User.parse(client, users.get(message.from_id, None)), + service=True, + new_chat_members=new_chat_members, + left_chat_member=left_chat_member, + new_chat_title=new_chat_title, + new_chat_photo=new_chat_photo, + delete_chat_photo=delete_chat_photo, + migrate_to_chat_id=int("-100" + str(migrate_to_chat_id)) if migrate_to_chat_id else None, + migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None, + group_chat_created=group_chat_created, + channel_chat_created=channel_chat_created, + client=client, + raw=message + # TODO: supergroup_chat_created + ) + + if isinstance(action, types.MessageActionPinMessage): + try: + parsed_message.pinned_message = client.get_messages( + parsed_message.chat.id, + reply_to_message_ids=message.id, + replies=0 + ) + except MessageIdsEmpty: + pass + + return parsed_message + + if isinstance(message, types.Message): + entities = [MessageEntity.parse(client, entity, users) for entity in message.entities] + entities = list(filter(lambda x: x is not None, entities)) + + forward_from = None + forward_from_chat = None + forward_from_message_id = None + forward_signature = None + forward_date = None + + forward_header = message.fwd_from + + if forward_header: + forward_date = forward_header.date + + if forward_header.from_id: + forward_from = User.parse(client, users[forward_header.from_id]) + else: + forward_from_chat = Chat.parse_channel_chat(client, chats[forward_header.channel_id]) + forward_from_message_id = forward_header.channel_post + forward_signature = forward_header.post_author + + photo = None + location = None + contact = None + venue = None + audio = None + voice = None + animation = None + video = None + video_note = None + sticker = None + document = None + web_page = None + + media = message.media + + if media: + if isinstance(media, types.MessageMediaPhoto): + photo = Photo.parse(client, media.photo) + elif isinstance(media, types.MessageMediaGeo): + location = Location.parse(client, media.geo) + elif isinstance(media, types.MessageMediaContact): + contact = Contact.parse(client, media) + elif isinstance(media, types.MessageMediaVenue): + venue = pyrogram.Venue.parse(client, media) + elif isinstance(media, types.MessageMediaDocument): + doc = media.document + + if isinstance(doc, types.Document): + attributes = {type(i): i for i in doc.attributes} + + file_name = getattr( + attributes.get( + types.DocumentAttributeFilename, None + ), "file_name", None + ) + + if types.DocumentAttributeAudio in attributes: + audio_attributes = attributes[types.DocumentAttributeAudio] + + if audio_attributes.voice: + voice = pyrogram.Voice.parse(client, doc, audio_attributes) + else: + audio = pyrogram.Audio.parse(client, doc, audio_attributes, file_name) + elif types.DocumentAttributeAnimated in attributes: + video_attributes = attributes.get(types.DocumentAttributeVideo, None) + + animation = pyrogram.Animation.parse(client, doc, video_attributes, file_name) + elif types.DocumentAttributeVideo in attributes: + video_attributes = attributes[types.DocumentAttributeVideo] + + if video_attributes.round_message: + video_note = pyrogram.VideoNote.parse(client, doc, video_attributes) + else: + video = pyrogram.Video.parse(client, doc, video_attributes, file_name) + elif types.DocumentAttributeSticker in attributes: + image_size_attributes = attributes.get(types.DocumentAttributeImageSize, None) + sticker_attribute = attributes[types.DocumentAttributeSticker] + + if isinstance(sticker_attribute.stickerset, types.InputStickerSetID): + try: + set_name = client.send( + functions.messages.GetStickerSet(sticker_attribute.stickerset) + ).set.short_name + except StickersetInvalid: + set_name = None + else: + set_name = None + + sticker = pyrogram.Sticker.parse(client, doc, image_size_attributes, + set_name, sticker_attribute, file_name) + else: + document = pyrogram.Document.parse(client, doc, file_name) + elif isinstance(media, types.MessageMediaWebPage): + web_page = True + else: + media = None + + reply_markup = message.reply_markup + + if reply_markup: + if isinstance(reply_markup, types.ReplyKeyboardForceReply): + reply_markup = pyrogram.ForceReply.read(reply_markup) + elif isinstance(reply_markup, types.ReplyKeyboardMarkup): + reply_markup = pyrogram.ReplyKeyboardMarkup.read(reply_markup) + elif isinstance(reply_markup, types.ReplyInlineMarkup): + reply_markup = pyrogram.InlineKeyboardMarkup.read(reply_markup) + elif isinstance(reply_markup, types.ReplyKeyboardHide): + reply_markup = pyrogram.ReplyKeyboardRemove.read(reply_markup) + else: + reply_markup = None + + parsed_message = Message( + message_id=message.id, + date=message.date, + chat=Chat.parse(client, message, users, chats), + from_user=User.parse(client, users.get(message.from_id, None)), + text=Str(message.message) or None if media is None else None, + caption=Str(message.message) or None if media is not None else None, + entities=entities or None if media is None else None, + caption_entities=entities or None if media is not None else None, + author_signature=message.post_author, + forward_from=forward_from, + forward_from_chat=forward_from_chat, + forward_from_message_id=forward_from_message_id, + forward_signature=forward_signature, + forward_date=forward_date, + mentioned=message.mentioned, + media=bool(media) or None, + edit_date=message.edit_date, + media_group_id=message.grouped_id, + photo=photo, + location=location, + contact=contact, + venue=venue, + audio=audio, + voice=voice, + animation=animation, + video=video, + video_note=video_note, + sticker=sticker, + document=document, + web_page=web_page, + views=message.views, + via_bot=User.parse(client, users.get(message.via_bot_id, None)), + outgoing=message.out, + reply_markup=reply_markup, + client=client, + raw=message + ) + + if parsed_message.text: + parsed_message.text.init(parsed_message._client, parsed_message.entities or []) + + if parsed_message.caption: + parsed_message.caption.init(parsed_message._client, parsed_message.caption_entities or []) + + if message.reply_to_msg_id and replies: + try: + parsed_message.reply_to_message = client.get_messages( + parsed_message.chat.id, + reply_to_message_ids=message.id, + replies=replies - 1 + ) + except MessageIdsEmpty: + pass + + return parsed_message + def reply(self, text: str, quote: bool = None, @@ -549,9 +803,9 @@ class Message(Object): ``ValueError``: If the provided index or position is out of range or the button label was not found ``TimeoutError``: If, after clicking an inline button, the bot fails to answer within 10 seconds """ - if isinstance(self.reply_markup, ReplyKeyboardMarkup): + if isinstance(self.reply_markup, pyrogram.ReplyKeyboardMarkup): return self.reply(x) - elif isinstance(self.reply_markup, InlineKeyboardMarkup): + elif isinstance(self.reply_markup, pyrogram.InlineKeyboardMarkup): if isinstance(x, int) and y is None: try: button = [ diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index f8f41734..9752bd28 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -16,7 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from pyrogram.api import types from pyrogram.api.core import Object +from ..user_and_chats.user import User class MessageEntity(Object): @@ -45,16 +47,45 @@ class MessageEntity(Object): ID = 0xb0700004 - def __init__( - self, - type: str, - offset: int, - length: int, - url: str = None, - user=None - ): + ENTITIES = { + types.MessageEntityMention.ID: "mention", + types.MessageEntityHashtag.ID: "hashtag", + types.MessageEntityCashtag.ID: "cashtag", + types.MessageEntityBotCommand.ID: "bot_command", + types.MessageEntityUrl.ID: "url", + types.MessageEntityEmail.ID: "email", + types.MessageEntityBold.ID: "bold", + types.MessageEntityItalic.ID: "italic", + types.MessageEntityCode.ID: "code", + types.MessageEntityPre.ID: "pre", + types.MessageEntityTextUrl.ID: "text_link", + types.MessageEntityMentionName.ID: "text_mention", + types.MessageEntityPhone.ID: "phone_number" + } + + def __init__(self, type: str, offset: int, length: int, *, + url: str = None, user=None, + client=None, raw=None): self.type = type self.offset = offset self.length = length self.url = url self.user = user + + self._client = client + self._raw = raw + + @staticmethod + def parse(client, entity, users: dict) -> "MessageEntity" or None: + type = MessageEntity.ENTITIES.get(entity.ID, None) + + if type is None: + return None + + return MessageEntity( + type=type, + offset=entity.offset, + length=entity.length, + url=getattr(entity, "url", None), + user=User.parse(client, users.get(getattr(entity, "user_id", None), None)) + ) From 6ab1c87050b990fee7250486c5fe190fbcd161ca Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 16 Dec 2018 15:24:51 +0100 Subject: [PATCH 053/326] Add PyrogramType --- .../types/messages_and_media/animation.py | 6 +-- .../client/types/messages_and_media/audio.py | 6 +-- .../types/messages_and_media/contact.py | 6 +-- .../types/messages_and_media/document.py | 6 +-- .../types/messages_and_media/location.py | 6 +-- .../types/messages_and_media/message.py | 7 ++- .../messages_and_media/message_entity.py | 6 +-- .../types/messages_and_media/messages.py | 6 +-- .../client/types/messages_and_media/photo.py | 6 +-- .../types/messages_and_media/photo_size.py | 6 +-- .../types/messages_and_media/sticker.py | 5 +-- .../messages_and_media/user_profile_photos.py | 6 +-- .../client/types/messages_and_media/venue.py | 6 +-- .../client/types/messages_and_media/video.py | 6 +-- .../types/messages_and_media/video_note.py | 6 +-- .../client/types/messages_and_media/voice.py | 11 +++-- pyrogram/client/types/pyrogram_type.py | 44 +++++++++++++++++++ pyrogram/client/types/user_and_chats/chat.py | 6 +-- .../client/types/user_and_chats/chat_photo.py | 2 +- pyrogram/client/types/user_and_chats/user.py | 8 ++-- .../types/user_and_chats/user_status.py | 6 +-- 21 files changed, 88 insertions(+), 79 deletions(-) create mode 100644 pyrogram/client/types/pyrogram_type.py diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index 0e36d5cf..7447f4ab 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -19,12 +19,12 @@ from struct import pack from pyrogram.api import types -from pyrogram.api.core import Object from .photo_size import PhotoSize +from ..pyrogram_type import PyrogramType from ...ext.utils import encode -class Animation(Object): +class Animation(PyrogramType): """This object represents an animation file (GIF or H.264/MPEG-4 AVC video without sound). Args: @@ -56,8 +56,6 @@ class Animation(Object): Date the animation was sent in Unix time. """ - ID = 0xb0700025 - def __init__(self, file_id: str, width: int, height: int, duration: int, *, thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, client=None, raw=None): diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 3f2b3c20..2cd3a1bd 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -19,12 +19,12 @@ from struct import pack from pyrogram.api import types -from pyrogram.api.core import Object from .photo_size import PhotoSize +from ..pyrogram_type import PyrogramType from ...ext.utils import encode -class Audio(Object): +class Audio(PyrogramType): """This object represents an audio file to be treated as music by the Telegram clients. Args: @@ -56,8 +56,6 @@ class Audio(Object): Title of the audio as defined by sender or by audio tags. """ - ID = 0xb0700006 - def __init__(self, file_id: str, duration: int, *, thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, performer: str = None, title: str = None, diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index 2c686a5f..2a3f8a21 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -17,10 +17,10 @@ # along with Pyrogram. If not, see . from pyrogram.api import types -from pyrogram.api.core import Object +from ..pyrogram_type import PyrogramType -class Contact(Object): +class Contact(PyrogramType): """This object represents a phone contact. Args: @@ -40,8 +40,6 @@ class Contact(Object): Additional data about the contact in the form of a vCard. """ - ID = 0xb0700011 - def __init__(self, phone_number: str, first_name: str, *, last_name: str = None, user_id: int = None, vcard: str = None, client=None, raw=None): diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index 5aa047cf..412954ef 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -19,12 +19,12 @@ from struct import pack from pyrogram.api import types -from pyrogram.api.core import Object from .photo_size import PhotoSize +from ..pyrogram_type import PyrogramType from ...ext.utils import encode -class Document(Object): +class Document(PyrogramType): """This object represents a general file (as opposed to photos, voice messages, audio files, ...). Args: @@ -47,8 +47,6 @@ class Document(Object): Date the document was sent in Unix time. """ - ID = 0xb0700007 - def __init__(self, file_id: str, *, thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, client=None, raw=None): diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index 3ab9bfa7..1276a783 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -17,10 +17,10 @@ # along with Pyrogram. If not, see . from pyrogram.api import types -from pyrogram.api.core import Object +from ..pyrogram_type import PyrogramType -class Location(Object): +class Location(PyrogramType): """This object represents a point on the map. Args: @@ -31,8 +31,6 @@ class Location(Object): Latitude as defined by sender. """ - ID = 0xb0700012 - def __init__(self, longitude: float, latitude: float, *, client=None, raw=None): self.longitude = longitude diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 0829c2db..8d84a513 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -18,18 +18,18 @@ import pyrogram from pyrogram.api import types, functions -from pyrogram.api.core import Object from pyrogram.api.errors import MessageIdsEmpty, StickersetInvalid from .contact import Contact from .location import Location from .message_entity import MessageEntity from ..messages_and_media.photo import Photo +from ..pyrogram_type import PyrogramType from ..user_and_chats.chat import Chat from ..user_and_chats.user import User from ...ext.utils import Str -class Message(Object): +class Message(PyrogramType): """This object represents a message. Args: @@ -224,7 +224,6 @@ class Message(Object): """ # TODO: Add game missing field. Also invoice, successful_payment, connected_website - ID = 0xb0700003 def __init__(self, message_id: int, *, date: int = None, chat=None, from_user=None, forward_from=None, forward_from_chat=None, @@ -461,7 +460,7 @@ class Message(Object): set_name = None sticker = pyrogram.Sticker.parse(client, doc, image_size_attributes, - set_name, sticker_attribute, file_name) + set_name, sticker_attribute, file_name) else: document = pyrogram.Document.parse(client, doc, file_name) elif isinstance(media, types.MessageMediaWebPage): diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 9752bd28..67f32806 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -17,11 +17,11 @@ # along with Pyrogram. If not, see . from pyrogram.api import types -from pyrogram.api.core import Object +from ..pyrogram_type import PyrogramType from ..user_and_chats.user import User -class MessageEntity(Object): +class MessageEntity(PyrogramType): """This object represents one special entity in a text message. For example, hashtags, usernames, URLs, etc. @@ -45,8 +45,6 @@ class MessageEntity(Object): For "text_mention" only, the mentioned user. """ - ID = 0xb0700004 - ENTITIES = { types.MessageEntityMention.ID: "mention", types.MessageEntityHashtag.ID: "hashtag", diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 7a2546a9..ef32f0b2 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -16,10 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object +from ..pyrogram_type import PyrogramType -class Messages(Object): +class Messages(PyrogramType): """This object represents a chat's messages. Args: @@ -30,8 +30,6 @@ class Messages(Object): Requested messages. """ - ID = 0xb0700026 - def __init__(self, total_count: int, messages: list): self.total_count = total_count self.messages = messages diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index b1021d93..970579af 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -20,12 +20,12 @@ from base64 import b64encode from struct import pack from pyrogram.api import types -from pyrogram.api.core import Object from .photo_size import PhotoSize +from ..pyrogram_type import PyrogramType from ...ext.utils import encode -class Photo(Object): +class Photo(PyrogramType): """This object represents a Photo. Args: @@ -39,8 +39,6 @@ class Photo(Object): Available sizes of this photo. """ - ID = 0xb0700027 - def __init__(self, id: str, date: int, sizes: list, *, client=None, raw=None): self.id = id diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index 39b9f756..cb94a6e6 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -19,11 +19,11 @@ from struct import pack from pyrogram.api import types -from pyrogram.api.core import Object from pyrogram.client.ext.utils import encode +from ..pyrogram_type import PyrogramType -class PhotoSize(Object): +class PhotoSize(PyrogramType): """This object represents one size of a photo or a file/sticker thumbnail. Args: @@ -40,8 +40,6 @@ class PhotoSize(Object): File size. """ - ID = 0xb0700005 - def __init__(self, file_id: str, width: int, height: int, file_size: int, *, client=None, raw=None): self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index a3dda2d5..7b5bfa70 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -19,12 +19,12 @@ from struct import pack from pyrogram.api import types -from pyrogram.api.core import Object from .photo_size import PhotoSize +from ..pyrogram_type import PyrogramType from ...ext.utils import encode -class Sticker(Object): +class Sticker(PyrogramType): """This object represents a sticker. Args: @@ -60,7 +60,6 @@ class Sticker(Object): """ # TODO: Add mask position - ID = 0xb0700017 def __init__(self, file_id: str, width: int, height: int, *, thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index c8ca9e39..bf53b13e 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -16,10 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object +from ..pyrogram_type import PyrogramType -class UserProfilePhotos(Object): +class UserProfilePhotos(PyrogramType): """This object represents a user's profile pictures. Args: @@ -30,8 +30,6 @@ class UserProfilePhotos(Object): Requested profile pictures. """ - ID = 0xb0700014 - def __init__(self, total_count: int, photos: list): self.total_count = total_count self.photos = photos diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index 8225c36b..662a416f 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -17,11 +17,11 @@ # along with Pyrogram. If not, see . from pyrogram.api import types -from pyrogram.api.core import Object from .location import Location +from ..pyrogram_type import PyrogramType -class Venue(Object): +class Venue(PyrogramType): """This object represents a venue. Args: @@ -43,8 +43,6 @@ class Venue(Object): """ - ID = 0xb0700013 - def __init__(self, location, title: str, address: str, *, foursquare_id: str = None, foursquare_type: str = None, client=None, raw=None): diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 4873730c..05d28f45 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -19,12 +19,12 @@ from struct import pack from pyrogram.api import types -from pyrogram.api.core import Object from .photo_size import PhotoSize +from ..pyrogram_type import PyrogramType from ...ext.utils import encode -class Video(Object): +class Video(PyrogramType): """This object represents a video file. Args: @@ -56,8 +56,6 @@ class Video(Object): Date the video was sent in Unix time. """ - ID = 0xb0700008 - def __init__(self, file_id: str, width: int, height: int, duration: int, *, thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, client=None, raw=None): diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index 0b07c00b..ddcd74f3 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -19,12 +19,12 @@ from struct import pack from pyrogram.api import types -from pyrogram.api.core import Object from .photo_size import PhotoSize +from ..pyrogram_type import PyrogramType from ...ext.utils import encode -class VideoNote(Object): +class VideoNote(PyrogramType): """This object represents a video message (available in Telegram apps as of v.4.0). Args: @@ -50,8 +50,6 @@ class VideoNote(Object): Date the video note was sent in Unix time. """ - ID = 0xb0700010 - def __init__(self, file_id: str, length: int, duration: int, *, thumb=None, mime_type: str = None, file_size: int = None, date: int = None, client=None, raw=None): diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index da9d6d51..c993ea5c 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -16,13 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import types -from pyrogram.api.core import Object -from ...ext.utils import encode from struct import pack +from pyrogram.api import types +from ..pyrogram_type import PyrogramType +from ...ext.utils import encode -class Voice(Object): + +class Voice(PyrogramType): """This object represents a voice note. Args: @@ -45,8 +46,6 @@ class Voice(Object): Date the voice was sent in Unix time. """ - ID = 0xb0700009 - def __init__(self, file_id: str, duration: int, *, waveform: bytes = None, mime_type: str = None, file_size: int = None, date: int = None, client=None, raw=None): diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py new file mode 100644 index 00000000..20b476a2 --- /dev/null +++ b/pyrogram/client/types/pyrogram_type.py @@ -0,0 +1,44 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from collections import OrderedDict +from json import dumps, JSONEncoder + + +class PyrogramType: + def __str__(self): + return dumps(self, cls=Encoder, indent=4) + + +def remove_none(obj): + if isinstance(obj, (list, tuple, set)): + return type(obj)(remove_none(x) for x in obj if x is not None) + elif isinstance(obj, dict): + return type(obj)((remove_none(k), remove_none(v)) for k, v in obj.items() if k is not None and v is not None) + else: + return obj + + +class Encoder(JSONEncoder): + def default(self, o: PyrogramType): + content = {i: getattr(o, i) for i in filter(lambda x: not x.startswith("_"), o.__dict__)} + + return OrderedDict( + [("_", "pyrogram:{}".format(o.__class__.__name__))] + + [i for i in remove_none(content).items()] + ) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 75aa5eac..6e1c675e 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -17,11 +17,11 @@ # along with Pyrogram. If not, see . from pyrogram.api import types -from pyrogram.api.core import Object from .chat_photo import ChatPhoto +from ..pyrogram_type import PyrogramType -class Chat(Object): +class Chat(PyrogramType): """This object represents a chat. Args: @@ -76,8 +76,6 @@ class Chat(Object): The reason why this chat might be unavailable to some users. """ - ID = 0xb0700002 - def __init__(self, id: int, type: str, *, title: str = None, username: str = None, first_name: str = None, last_name: str = None, all_members_are_administrators: bool = None, photo=None, description: str = None, diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index 485777e5..c7ef4c80 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -42,7 +42,7 @@ class ChatPhoto(Object): self.big_file_id = big_file_id self._client = client - self.raw = raw + self._raw = raw @staticmethod def parse(client, chat_photo: types.UserProfilePhoto or types.ChatPhoto): diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index e460522b..80660bcc 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -17,12 +17,12 @@ # along with Pyrogram. If not, see . from pyrogram.api import types -from pyrogram.api.core import Object -from .user_status import UserStatus from .chat_photo import ChatPhoto +from .user_status import UserStatus +from ..pyrogram_type import PyrogramType -class User(Object): +class User(PyrogramType): """This object represents a Telegram user or bot. Args: @@ -69,8 +69,6 @@ class User(Object): The reason why this bot might be unavailable to some users. """ - ID = 0xb0700001 - def __init__(self, id: int, is_self: bool, is_contact: bool, is_mutual_contact: bool, is_deleted: bool, is_bot: bool, first_name: str, *, last_name: str = None, status=None, username: str = None, language_code: str = None, diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index 651d8db6..f99d356d 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -17,10 +17,10 @@ # along with Pyrogram. If not, see . from pyrogram.api import types -from pyrogram.api.core import Object +from ..pyrogram_type import PyrogramType -class UserStatus(Object): +class UserStatus(PyrogramType): """This object represents a User status (Last Seen privacy). .. note:: @@ -62,8 +62,6 @@ class UserStatus(Object): always shown to blocked users), None otherwise. """ - ID = 0xb0700031 - def __init__(self, user_id: int, *, online: bool = None, offline: bool = None, date: int = None, recently: bool = None, within_week: bool = None, within_month: bool = None, long_time_ago: bool = None, From 7a8873029d3000a7572502381e09425af47a02c0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 16 Dec 2018 15:25:56 +0100 Subject: [PATCH 054/326] Don't parse custom types in the code generator --- compiler/api/compiler.py | 65 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index cacd9342..ac507537 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -475,38 +475,39 @@ def start(): f.write("\n 0x3072cfa1: \"pyrogram.api.core.GzipPacked\",") f.write("\n 0x5bb8e511: \"pyrogram.api.core.Message\",") - f.write("\n 0xb0700000: \"pyrogram.client.types.Update\",") - f.write("\n 0xb0700001: \"pyrogram.client.types.User\",") - f.write("\n 0xb0700002: \"pyrogram.client.types.Chat\",") - f.write("\n 0xb0700003: \"pyrogram.client.types.Message\",") - f.write("\n 0xb0700004: \"pyrogram.client.types.MessageEntity\",") - f.write("\n 0xb0700005: \"pyrogram.client.types.PhotoSize\",") - f.write("\n 0xb0700006: \"pyrogram.client.types.Audio\",") - f.write("\n 0xb0700007: \"pyrogram.client.types.Document\",") - f.write("\n 0xb0700008: \"pyrogram.client.types.Video\",") - f.write("\n 0xb0700009: \"pyrogram.client.types.Voice\",") - f.write("\n 0xb0700010: \"pyrogram.client.types.VideoNote\",") - f.write("\n 0xb0700011: \"pyrogram.client.types.Contact\",") - f.write("\n 0xb0700012: \"pyrogram.client.types.Location\",") - f.write("\n 0xb0700013: \"pyrogram.client.types.Venue\",") - f.write("\n 0xb0700014: \"pyrogram.client.types.UserProfilePhotos\",") - f.write("\n 0xb0700015: \"pyrogram.client.types.ChatPhoto\",") - f.write("\n 0xb0700016: \"pyrogram.client.types.ChatMember\",") - f.write("\n 0xb0700017: \"pyrogram.client.types.Sticker\",") - f.write("\n 0xb0700018: \"pyrogram.client.types.bots.ForceReply\",") - f.write("\n 0xb0700019: \"pyrogram.client.types.bots.InlineKeyboardButton\",") - f.write("\n 0xb0700020: \"pyrogram.client.types.bots.InlineKeyboardMarkup\",") - f.write("\n 0xb0700021: \"pyrogram.client.types.bots.KeyboardButton\",") - f.write("\n 0xb0700022: \"pyrogram.client.types.bots.ReplyKeyboardMarkup\",") - f.write("\n 0xb0700023: \"pyrogram.client.types.bots.ReplyKeyboardRemove\",") - f.write("\n 0xb0700024: \"pyrogram.client.types.CallbackQuery\",") - f.write("\n 0xb0700025: \"pyrogram.client.types.Animation\",") - f.write("\n 0xb0700026: \"pyrogram.client.types.Messages\",") - f.write("\n 0xb0700027: \"pyrogram.client.types.Photo\",") - f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",") - f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",") - f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",") - f.write("\n 0xb0700031: \"pyrogram.client.types.UserStatus\"") + # TODO Remove completely + # f.write("\n 0xb0700000: \"pyrogram.client.types.Update\",") + # f.write("\n 0xb0700001: \"pyrogram.client.types.User\",") + # f.write("\n 0xb0700002: \"pyrogram.client.types.Chat\",") + # f.write("\n 0xb0700003: \"pyrogram.client.types.Message\",") + # f.write("\n 0xb0700004: \"pyrogram.client.types.MessageEntity\",") + # f.write("\n 0xb0700005: \"pyrogram.client.types.PhotoSize\",") + # f.write("\n 0xb0700006: \"pyrogram.client.types.Audio\",") + # f.write("\n 0xb0700007: \"pyrogram.client.types.Document\",") + # f.write("\n 0xb0700008: \"pyrogram.client.types.Video\",") + # f.write("\n 0xb0700009: \"pyrogram.client.types.Voice\",") + # f.write("\n 0xb0700010: \"pyrogram.client.types.VideoNote\",") + # f.write("\n 0xb0700011: \"pyrogram.client.types.Contact\",") + # f.write("\n 0xb0700012: \"pyrogram.client.types.Location\",") + # f.write("\n 0xb0700013: \"pyrogram.client.types.Venue\",") + # f.write("\n 0xb0700014: \"pyrogram.client.types.UserProfilePhotos\",") + # f.write("\n 0xb0700015: \"pyrogram.client.types.ChatPhoto\",") + # f.write("\n 0xb0700016: \"pyrogram.client.types.ChatMember\",") + # f.write("\n 0xb0700017: \"pyrogram.client.types.Sticker\",") + # f.write("\n 0xb0700018: \"pyrogram.client.types.bots.ForceReply\",") + # f.write("\n 0xb0700019: \"pyrogram.client.types.bots.InlineKeyboardButton\",") + # f.write("\n 0xb0700020: \"pyrogram.client.types.bots.InlineKeyboardMarkup\",") + # f.write("\n 0xb0700021: \"pyrogram.client.types.bots.KeyboardButton\",") + # f.write("\n 0xb0700022: \"pyrogram.client.types.bots.ReplyKeyboardMarkup\",") + # f.write("\n 0xb0700023: \"pyrogram.client.types.bots.ReplyKeyboardRemove\",") + # f.write("\n 0xb0700024: \"pyrogram.client.types.CallbackQuery\",") + # f.write("\n 0xb0700025: \"pyrogram.client.types.Animation\",") + # f.write("\n 0xb0700026: \"pyrogram.client.types.Messages\",") + # f.write("\n 0xb0700027: \"pyrogram.client.types.Photo\",") + # f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",") + # f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",") + # f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",") + # f.write("\n 0xb0700031: \"pyrogram.client.types.UserStatus\"") f.write("\n}\n") From 3ecbb35470c9de9cc2ef46236cc495559971a467 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 16 Dec 2018 15:26:56 +0100 Subject: [PATCH 055/326] Delete Update --- pyrogram/__init__.py | 2 +- pyrogram/client/types/__init__.py | 1 - pyrogram/client/types/update.py | 94 ------------------------------- 3 files changed, 1 insertion(+), 96 deletions(-) delete mode 100644 pyrogram/client/types/update.py diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index b1a5430c..6ea6aa5c 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -29,7 +29,7 @@ from .api.errors import Error from .client.types import ( Audio, Chat, ChatMember, ChatMembers, ChatPhoto, Contact, Document, InputMediaPhoto, InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMediaAnimation, InputPhoneContact, - Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, Update, User, UserStatus, + Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove ) diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index 74c97ca1..8289a947 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -33,7 +33,6 @@ from .messages_and_media import ( Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos, Message, Messages, MessageEntity ) -from .update import Update from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, Dialog, Dialogs, User, UserStatus diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py deleted file mode 100644 index 748108de..00000000 --- a/pyrogram/client/types/update.py +++ /dev/null @@ -1,94 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.api.core import Object - - -class Update(Object): - """This object represents an incoming update. - At most one of the optional parameters can be present in any given update. - - Args: - message (:obj:`Message `, *optional*): - New incoming message of any kind — text, photo, sticker, etc. - - edited_message (:obj:`Message `, *optional*): - New version of a message that is known to the bot and was edited. - - deleted_messages (:obj:`Messages `, *optional*): - Deleted messages. - - channel_post (:obj:`Message `, *optional*): - New incoming channel post of any kind — text, photo, sticker, etc. - - edited_channel_post (:obj:`Message `, *optional*): - New version of a channel post that is known to the bot and was edited. - - deleted_channel_posts (:obj:`Messages `, *optional*): - Deleted channel posts. - - inline_query (:obj:`InlineQuery `, *optional*): - New incoming inline query. - - chosen_inline_result (:obj:`ChosenInlineResult `, *optional*): - The result of an inline query that was chosen by a user and sent to their chat partner. - Please see our documentation on the feedback collecting for details on how to enable these updates - for your bot. - - callback_query (:obj:`CallbackQuery `, *optional*): - New incoming callback query. - - shipping_query (:obj:`ShippingQuery `, *optional*): - New incoming shipping query. Only for invoices with flexible price. - - pre_checkout_query (:obj:`PreCheckoutQuery `, *optional*): - New incoming pre-checkout query. Contains full information about checkout. - - user_status (:obj:`UserStatus `, *optional*): - User status (last seen date) update. - """ - - ID = 0xb0700000 - - def __init__( - self, - message=None, - edited_message=None, - deleted_messages=None, - channel_post=None, - edited_channel_post=None, - deleted_channel_posts=None, - inline_query=None, - chosen_inline_result=None, - callback_query=None, - shipping_query=None, - pre_checkout_query=None, - user_status=None - ): - self.message = message - self.edited_message = edited_message - self.deleted_messages = deleted_messages - self.channel_post = channel_post - self.edited_channel_post = edited_channel_post - self.deleted_channel_posts = deleted_channel_posts - self.inline_query = inline_query - self.chosen_inline_result = chosen_inline_result - self.callback_query = callback_query - self.shipping_query = shipping_query - self.pre_checkout_query = pre_checkout_query - self.user_status = user_status From d62bc1d8ba80519f924c4813adaada833952c841 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 16 Dec 2018 16:22:46 +0100 Subject: [PATCH 056/326] Refactor ChatMember and ChatMembers --- .../types/user_and_chats/chat_member.py | 114 ++++++++++++------ .../types/user_and_chats/chat_members.py | 33 ++++- 2 files changed, 104 insertions(+), 43 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 71267d27..b05630da 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -16,10 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object +from pyrogram.api import types +from ..pyrogram_type import PyrogramType -class ChatMember(Object): +class ChatMember(PyrogramType): """This object contains information about one member of a chat. Args: @@ -78,40 +79,77 @@ class ChatMember(Object): Restricted only. True, if user may add web page previews to his messages, implies can_send_media_messages. """ - ID = 0xb0700016 + def __init__(self, *, user, status: str, until_date: int = None, can_be_edited: bool = None, + can_change_info: bool = None, can_post_messages: bool = None, can_edit_messages: bool = None, + can_delete_messages: bool = None, can_invite_users: bool = None, can_restrict_members: bool = None, + can_pin_messages: bool = None, can_promote_members: bool = None, can_send_messages: bool = None, + can_send_media_messages: bool = None, can_send_other_messages: bool = None, + can_add_web_page_previews: bool = None, client=None): + self.user = user + self.status = status + self.until_date = until_date + self.can_be_edited = can_be_edited + self.can_change_info = can_change_info + self.can_post_messages = can_post_messages + self.can_edit_messages = can_edit_messages + self.can_delete_messages = can_delete_messages + self.can_invite_users = can_invite_users + self.can_restrict_members = can_restrict_members + self.can_pin_messages = can_pin_messages + self.can_promote_members = can_promote_members + self.can_send_messages = can_send_messages + self.can_send_media_messages = can_send_media_messages + self.can_send_other_messages = can_send_other_messages + self.can_add_web_page_previews = can_add_web_page_previews - def __init__( - self, - user, - status: str, - until_date: int = None, - can_be_edited: bool = None, - can_change_info: bool = None, - can_post_messages: bool = None, - can_edit_messages: bool = None, - can_delete_messages: bool = None, - can_invite_users: bool = None, - can_restrict_members: bool = None, - can_pin_messages: bool = None, - can_promote_members: bool = None, - can_send_messages: bool = None, - can_send_media_messages: bool = None, - can_send_other_messages: bool = None, - can_add_web_page_previews: bool = None - ): - self.user = user # User - self.status = status # string - self.until_date = until_date # flags.0?int - self.can_be_edited = can_be_edited # flags.1?Bool - self.can_change_info = can_change_info # flags.2?Bool - self.can_post_messages = can_post_messages # flags.3?Bool - self.can_edit_messages = can_edit_messages # flags.4?Bool - self.can_delete_messages = can_delete_messages # flags.5?Bool - self.can_invite_users = can_invite_users # flags.6?Bool - self.can_restrict_members = can_restrict_members # flags.7?Bool - self.can_pin_messages = can_pin_messages # flags.8?Bool - self.can_promote_members = can_promote_members # flags.9?Bool - self.can_send_messages = can_send_messages # flags.10?Bool - self.can_send_media_messages = can_send_media_messages # flags.11?Bool - self.can_send_other_messages = can_send_other_messages # flags.12?Bool - self.can_add_web_page_previews = can_add_web_page_previews # flags.13?Bool + self.client = client + + @staticmethod + def parse(client, member, user) -> "ChatMember": + if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)): + return ChatMember(user=user, status="member", client=client) + + if isinstance(member, (types.ChannelParticipantCreator, types.ChatParticipantCreator)): + return ChatMember(user=user, status="creator", client=client) + + if isinstance(member, types.ChatParticipantAdmin): + return ChatMember(user=user, status="administrator", client=client) + + if isinstance(member, types.ChannelParticipantAdmin): + rights = member.admin_rights + + return ChatMember( + user=user, + status="administrator", + can_be_edited=member.can_edit, + can_change_info=rights.change_info, + can_post_messages=rights.post_messages, + can_edit_messages=rights.edit_messages, + can_delete_messages=rights.delete_messages, + can_invite_users=rights.invite_users or rights.invite_link, + can_restrict_members=rights.ban_users, + can_pin_messages=rights.pin_messages, + can_promote_members=rights.add_admins, + client=client + ) + + if isinstance(member, types.ChannelParticipantBanned): + rights = member.banned_rights + + chat_member = ChatMember( + user=user, + status="kicked" if rights.view_messages else "restricted", + until_date=0 if rights.until_date == (1 << 31) - 1 else rights.until_date, + client=client + ) + + if chat_member.status == "restricted": + chat_member.can_send_messages = not rights.send_messages + chat_member.can_send_media_messages = not rights.send_media + chat_member.can_send_other_messages = ( + not rights.send_stickers or not rights.send_gifs or + not rights.send_games or not rights.send_inline + ) + chat_member.can_add_web_page_previews = not rights.embed_links + + return chat_member diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index 622f8abc..e58a4168 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -16,10 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object +from pyrogram.api import types +from ..pyrogram_type import PyrogramType +from ..user_and_chats import ChatMember, User -class ChatMembers(Object): +class ChatMembers(PyrogramType): """This object contains information about the members list of a chat. Args: @@ -30,8 +32,29 @@ class ChatMembers(Object): Requested chat members. """ - ID = 0xb0700030 - - def __init__(self, total_count: int, chat_members: list): + def __init__(self, *, total_count: int, chat_members: list, client=None): self.total_count = total_count self.chat_members = chat_members + + self.client = client + + @staticmethod + def parse(client, members, users: dict): + if isinstance(members, types.channels.ChannelParticipants): + total_count = members.count + members = members.participants + else: + members = members.full_chat.participants.participants + total_count = len(members) + + chat_members = [] + + for member in members: + user = User.parse(client, users[member.user_id]) + chat_members.append(ChatMember.parse(client, member, user)) + + return ChatMembers( + total_count=total_count, + chat_members=chat_members, + client=client + ) From cffafa8c611db20469d81c641bf1eb18d43811b3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 16 Dec 2018 17:10:08 +0100 Subject: [PATCH 057/326] Small fixed to PyrogramType --- pyrogram/client/types/pyrogram_type.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py index 20b476a2..ee0cfea4 100644 --- a/pyrogram/client/types/pyrogram_type.py +++ b/pyrogram/client/types/pyrogram_type.py @@ -24,6 +24,9 @@ class PyrogramType: def __str__(self): return dumps(self, cls=Encoder, indent=4) + def __getitem__(self, item): + return getattr(self, item) + def remove_none(obj): if isinstance(obj, (list, tuple, set)): @@ -37,8 +40,4 @@ def remove_none(obj): class Encoder(JSONEncoder): def default(self, o: PyrogramType): content = {i: getattr(o, i) for i in filter(lambda x: not x.startswith("_"), o.__dict__)} - - return OrderedDict( - [("_", "pyrogram:{}".format(o.__class__.__name__))] - + [i for i in remove_none(content).items()] - ) + return remove_none(OrderedDict([("_", "pyrogram:" + o.__class__.__name__)] + [i for i in content.items()])) From 66cd896a998b8748e140d00162c0819bccf20d89 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 16 Dec 2018 17:10:55 +0100 Subject: [PATCH 058/326] Allow passing raw --- .../client/types/user_and_chats/chat_member.py | 15 ++++++++------- .../client/types/user_and_chats/chat_members.py | 17 ++++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index b05630da..b3e2916b 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -84,7 +84,7 @@ class ChatMember(PyrogramType): can_delete_messages: bool = None, can_invite_users: bool = None, can_restrict_members: bool = None, can_pin_messages: bool = None, can_promote_members: bool = None, can_send_messages: bool = None, can_send_media_messages: bool = None, can_send_other_messages: bool = None, - can_add_web_page_previews: bool = None, client=None): + can_add_web_page_previews: bool = None, client, raw): self.user = user self.status = status self.until_date = until_date @@ -102,18 +102,19 @@ class ChatMember(PyrogramType): self.can_send_other_messages = can_send_other_messages self.can_add_web_page_previews = can_add_web_page_previews - self.client = client + self._client = client + self._raw = raw @staticmethod def parse(client, member, user) -> "ChatMember": if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)): - return ChatMember(user=user, status="member", client=client) + return ChatMember(user=user, status="member", client=client, raw=member) if isinstance(member, (types.ChannelParticipantCreator, types.ChatParticipantCreator)): - return ChatMember(user=user, status="creator", client=client) + return ChatMember(user=user, status="creator", client=client, raw=member) if isinstance(member, types.ChatParticipantAdmin): - return ChatMember(user=user, status="administrator", client=client) + return ChatMember(user=user, status="administrator", client=client, raw=member) if isinstance(member, types.ChannelParticipantAdmin): rights = member.admin_rights @@ -130,7 +131,7 @@ class ChatMember(PyrogramType): can_restrict_members=rights.ban_users, can_pin_messages=rights.pin_messages, can_promote_members=rights.add_admins, - client=client + client=client, raw=member ) if isinstance(member, types.ChannelParticipantBanned): @@ -140,7 +141,7 @@ class ChatMember(PyrogramType): user=user, status="kicked" if rights.view_messages else "restricted", until_date=0 if rights.until_date == (1 << 31) - 1 else rights.until_date, - client=client + client=client, raw=member ) if chat_member.status == "restricted": diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index e58a4168..4f057309 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -17,8 +17,9 @@ # along with Pyrogram. If not, see . from pyrogram.api import types +from .chat_member import ChatMember +from .user import User from ..pyrogram_type import PyrogramType -from ..user_and_chats import ChatMember, User class ChatMembers(PyrogramType): @@ -32,14 +33,18 @@ class ChatMembers(PyrogramType): Requested chat members. """ - def __init__(self, *, total_count: int, chat_members: list, client=None): + def __init__(self, *, total_count: int, chat_members: list, client, raw): self.total_count = total_count self.chat_members = chat_members - self.client = client + self._client = client + self._raw = raw @staticmethod - def parse(client, members, users: dict): + def parse(client, members): + users = {i.id: i for i in members.users} + chat_members = [] + if isinstance(members, types.channels.ChannelParticipants): total_count = members.count members = members.participants @@ -47,8 +52,6 @@ class ChatMembers(PyrogramType): members = members.full_chat.participants.participants total_count = len(members) - chat_members = [] - for member in members: user = User.parse(client, users[member.user_id]) chat_members.append(ChatMember.parse(client, member, user)) @@ -56,5 +59,5 @@ class ChatMembers(PyrogramType): return ChatMembers( total_count=total_count, chat_members=chat_members, - client=client + client=client, raw=members ) From 31b046e5cc436fa223f604bae004997c1fe582f8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 16 Dec 2018 17:58:05 +0100 Subject: [PATCH 059/326] Use the refactored types in get_chat_member(s) --- pyrogram/client/methods/chats/get_chat_member.py | 8 +++++--- pyrogram/client/methods/chats/get_chat_members.py | 7 +++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index b9b23e54..b5e7cec7 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -17,7 +17,8 @@ # along with Pyrogram. If not, see . from pyrogram.api import functions, types, errors -from ...ext import BaseClient, utils +from ...ext import BaseClient +import pyrogram class GetChatMember(BaseClient): @@ -51,7 +52,7 @@ class GetChatMember(BaseClient): ) ) - for member in utils.parse_chat_members(full_chat).chat_members: + for member in pyrogram.ChatMembers.parse(self, full_chat).chat_members: if member.user.id == user_id.user_id: return member else: @@ -64,7 +65,8 @@ class GetChatMember(BaseClient): ) ) - return utils.parse_chat_members( + return pyrogram.ChatMembers.parse( + self, types.channels.ChannelParticipants( count=1, participants=[r.participant], diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index 5952a39d..df1ca8b1 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -18,6 +18,7 @@ from pyrogram.api import functions, types from ...ext import BaseClient, utils +import pyrogram class Filters: @@ -83,7 +84,8 @@ class GetChatMembers(BaseClient): peer = self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChat): - return utils.parse_chat_members( + return pyrogram.ChatMembers.parse( + self, self.send( functions.messages.GetFullChat( peer.chat_id @@ -108,7 +110,8 @@ class GetChatMembers(BaseClient): else: raise ValueError("Invalid filter \"{}\"".format(filter)) - return utils.parse_chat_members( + return pyrogram.ChatMembers.parse( + self, self.send( functions.channels.GetParticipants( channel=peer, From 5d64de10dc661aebac245c4bb9a64e88f36480fd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 16 Dec 2018 17:58:32 +0100 Subject: [PATCH 060/326] Add Chat.parse_full --- pyrogram/client/types/user_and_chats/chat.py | 48 +++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 6e1c675e..2169f1d7 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -112,8 +112,7 @@ class Chat(PyrogramType): last_name=user.last_name, photo=ChatPhoto.parse(client, user.photo), restriction_reason=user.restriction_reason, - client=client, - raw=user + client=client, raw=user ) @staticmethod @@ -129,8 +128,7 @@ class Chat(PyrogramType): title=chat.title, all_members_are_administrators=admins_enabled, photo=ChatPhoto.parse(client, getattr(chat, "photo", None)), - client=client, - raw=chat + client=client, raw=chat ) @staticmethod @@ -142,8 +140,7 @@ class Chat(PyrogramType): username=getattr(channel, "username", None), photo=ChatPhoto.parse(client, getattr(channel, "photo", None)), restriction_reason=getattr(channel, "restriction_reason", None), - client=client, - raw=channel + client=client, raw=channel ) @staticmethod @@ -152,6 +149,43 @@ class Chat(PyrogramType): return Chat.parse_user_chat(client, users[message.to_id.user_id if message.out else message.from_id]) if isinstance(message.to_id, types.PeerChat): - return Chat.parse_chat_chat(chats[message.to_id.chat_id]) + return Chat.parse_chat_chat(client, chats[message.to_id.chat_id]) return Chat.parse_channel_chat(client, chats[message.to_id.channel_id]) + + @staticmethod + def parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> "Chat": + if isinstance(chat_full, types.UserFull): + parsed_chat = Chat.parse_user_chat(client, chat_full.user) + parsed_chat.description = chat_full.about + else: + full_chat = chat_full.full_chat + chat = None + + for i in chat_full.chats: + if full_chat.id == i.id: + chat = i + + if isinstance(full_chat, types.ChatFull): + parsed_chat = Chat.parse_chat_chat(client, chat) + + if isinstance(full_chat.participants, types.ChatParticipants): + parsed_chat.members_count = len(full_chat.participants.participants) + else: + parsed_chat = Chat.parse_channel_chat(client, chat) + parsed_chat.members_count = full_chat.participants_count + parsed_chat.description = full_chat.about or None + # TODO: Add StickerSet type + parsed_chat.can_set_sticker_set = full_chat.can_set_stickers + parsed_chat.sticker_set_name = full_chat.stickerset + + if full_chat.pinned_msg_id: + parsed_chat.pinned_message = client.get_messages( + parsed_chat.id, + message_ids=full_chat.pinned_msg_id + ) + + if isinstance(full_chat.exported_invite, types.ChatInviteExported): + parsed_chat.invite_link = full_chat.exported_invite.link + + return parsed_chat From 7430529646164e09d84ddff862ffc92af86a6692 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 16 Dec 2018 17:59:34 +0100 Subject: [PATCH 061/326] Make use of the refactored Chat type in get_chat --- pyrogram/client/methods/chats/get_chat.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 8e41695a..7c191bf3 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types -from ...ext import BaseClient, utils +from ...ext import BaseClient class GetChat(BaseClient): @@ -44,4 +45,4 @@ class GetChat(BaseClient): else: r = self.send(functions.messages.GetFullChat(peer.chat_id)) - return utils.parse_chat_full(self, r) + return pyrogram.Chat.parse_full(self, r) From c6a0bf0791697b08b193d2664630ad45be72a4fa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 16 Dec 2018 22:34:23 +0100 Subject: [PATCH 062/326] Force named arguments on Pyrogram types --- .../types/messages_and_media/animation.py | 10 +++--- .../client/types/messages_and_media/audio.py | 12 +++---- .../types/messages_and_media/contact.py | 10 +++--- .../types/messages_and_media/document.py | 10 +++--- .../types/messages_and_media/location.py | 8 ++--- .../types/messages_and_media/message.py | 31 +++++++++---------- .../messages_and_media/message_entity.py | 13 ++++---- .../types/messages_and_media/messages.py | 4 ++- .../client/types/messages_and_media/photo.py | 8 ++--- .../types/messages_and_media/photo_size.py | 8 ++--- .../types/messages_and_media/sticker.py | 12 +++---- .../messages_and_media/user_profile_photos.py | 4 ++- .../client/types/messages_and_media/venue.py | 10 +++--- .../client/types/messages_and_media/video.py | 10 +++--- .../types/messages_and_media/video_note.py | 10 +++--- .../client/types/messages_and_media/voice.py | 10 +++--- pyrogram/client/types/pyrogram_type.py | 4 +++ pyrogram/client/types/user_and_chats/chat.py | 16 ++++------ .../types/user_and_chats/chat_member.py | 9 +++--- .../types/user_and_chats/chat_members.py | 10 +++--- .../client/types/user_and_chats/chat_photo.py | 6 +--- .../client/types/user_and_chats/dialog.py | 18 +++++------ .../client/types/user_and_chats/dialogs.py | 9 +++--- pyrogram/client/types/user_and_chats/user.py | 18 +++++------ .../types/user_and_chats/user_status.py | 13 +++----- 25 files changed, 118 insertions(+), 155 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index 7447f4ab..c270dfa9 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -56,9 +56,10 @@ class Animation(PyrogramType): Date the animation was sent in Unix time. """ - def __init__(self, file_id: str, width: int, height: int, duration: int, *, - thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, - client=None, raw=None): + def __init__(self, *, client, raw, file_id: str, width: int, height: int, duration: int, thumb=None, + file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None): + super().__init__(client, raw) + self.file_id = file_id self.thumb = thumb self.file_name = file_name @@ -69,9 +70,6 @@ class Animation(PyrogramType): self.height = height self.duration = duration - self._client = client - self._raw = raw - @staticmethod def parse(client, animation: types.Document, video_attributes: types.DocumentAttributeVideo, file_name: str) -> "Animation": diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 2cd3a1bd..bc5cf166 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -56,10 +56,11 @@ class Audio(PyrogramType): Title of the audio as defined by sender or by audio tags. """ - def __init__(self, file_id: str, duration: int, *, - thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, - performer: str = None, title: str = None, - client=None, raw=None): + def __init__(self, *, client, raw, file_id: str, duration: int, thumb=None, file_name: str = None, + mime_type: str = None, file_size: int = None, date: int = None, performer: str = None, + title: str = None): + super().__init__(client, raw) + self.file_id = file_id self.thumb = thumb self.file_name = file_name @@ -70,9 +71,6 @@ class Audio(PyrogramType): self.performer = performer self.title = title - self._client = client - self._raw = raw - @staticmethod def parse(client, audio: types.Document, audio_attributes: types.DocumentAttributeAudio, file_name: str) -> "Audio": return Audio( diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index 2a3f8a21..0525c463 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -40,18 +40,16 @@ class Contact(PyrogramType): Additional data about the contact in the form of a vCard. """ - def __init__(self, phone_number: str, first_name: str, *, - last_name: str = None, user_id: int = None, vcard: str = None, - client=None, raw=None): + def __init__(self, *, client, raw, phone_number: str, first_name: str, last_name: str = None, user_id: int = None, + vcard: str = None): + super().__init__(client, raw) + self.phone_number = phone_number self.first_name = first_name self.last_name = last_name self.user_id = user_id self.vcard = vcard - self._client = client - self._raw = raw - @staticmethod def parse(client, contact: types.MessageMediaContact) -> "Contact": return Contact( diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index 412954ef..0526a190 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -47,9 +47,10 @@ class Document(PyrogramType): Date the document was sent in Unix time. """ - def __init__(self, file_id: str, *, - thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, - client=None, raw=None): + def __init__(self, *, client, raw, file_id: str, thumb=None, file_name: str = None, mime_type: str = None, + file_size: int = None, date: int = None): + super().__init__(client, raw) + self.file_id = file_id self.thumb = thumb self.file_name = file_name @@ -57,9 +58,6 @@ class Document(PyrogramType): self.file_size = file_size self.date = date - self._client = client - self._raw = raw - @staticmethod def parse(client, document: types.Document, file_name: str) -> "Document": return Document( diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index 1276a783..853a4c90 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -31,14 +31,12 @@ class Location(PyrogramType): Latitude as defined by sender. """ - def __init__(self, longitude: float, latitude: float, *, - client=None, raw=None): + def __init__(self, *, client, raw, longitude: float, latitude: float, ): + super().__init__(client, raw) + self.longitude = longitude self.latitude = latitude - self._client = client - self._raw = raw - @staticmethod def parse(client, geo_point: types.GeoPoint) -> "Location": if isinstance(geo_point, types.GeoPoint): diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 8d84a513..f50dd30b 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -225,20 +225,20 @@ class Message(PyrogramType): # TODO: Add game missing field. Also invoice, successful_payment, connected_website - def __init__(self, message_id: int, *, - date: int = None, chat=None, from_user=None, forward_from=None, forward_from_chat=None, - forward_from_message_id: int = None, forward_signature: str = None, forward_date: int = None, - reply_to_message=None, mentioned=None, empty=None, service=None, media=None, edit_date: int = None, - media_group_id: str = None, author_signature: str = None, text: str = None, entities: list = None, - caption_entities: list = None, audio=None, document=None, photo=None, sticker=None, animation=None, - video=None, voice=None, video_note=None, caption: str = None, contact=None, location=None, venue=None, - web_page=None, new_chat_members: list = None, left_chat_member=None, new_chat_title: str = None, - new_chat_photo=None, delete_chat_photo: bool = None, group_chat_created: bool = None, - supergroup_chat_created: bool = None, channel_chat_created: bool = None, - migrate_to_chat_id: int = None, migrate_from_chat_id: int = None, pinned_message=None, - views: int = None, via_bot=None, outgoing: bool = None, matches: list = None, command: list = None, - reply_markup=None, - client=None, raw=None): + def __init__(self, *, client, raw, message_id: int, date: int = None, chat=None, from_user=None, forward_from=None, + forward_from_chat=None, forward_from_message_id: int = None, forward_signature: str = None, + forward_date: int = None, reply_to_message=None, mentioned=None, empty=None, service=None, media=None, + edit_date: int = None, media_group_id: str = None, author_signature: str = None, text: str = None, + entities: list = None, caption_entities: list = None, audio=None, document=None, photo=None, + sticker=None, animation=None, video=None, voice=None, video_note=None, caption: str = None, + contact=None, location=None, venue=None, web_page=None, new_chat_members: list = None, + left_chat_member=None, new_chat_title: str = None, new_chat_photo=None, delete_chat_photo: bool = None, + group_chat_created: bool = None, supergroup_chat_created: bool = None, + channel_chat_created: bool = None, migrate_to_chat_id: int = None, migrate_from_chat_id: int = None, + pinned_message=None, views: int = None, via_bot=None, outgoing: bool = None, matches: list = None, + command: list = None, reply_markup=None): + super().__init__(client, raw) + self.message_id = message_id self.date = date self.chat = chat @@ -290,9 +290,6 @@ class Message(PyrogramType): self.command = command self.reply_markup = reply_markup - self._client = client - self._raw = raw - @staticmethod def parse(client, message: types.Message or types.MessageService or types.MessageEmpty, users: dict, chats: dict, replies: int = 1): diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 67f32806..c72df95d 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -61,18 +61,15 @@ class MessageEntity(PyrogramType): types.MessageEntityPhone.ID: "phone_number" } - def __init__(self, type: str, offset: int, length: int, *, - url: str = None, user=None, - client=None, raw=None): + def __init__(self, *, client, raw, type: str, offset: int, length: int, url: str = None, user=None): + super().__init__(client, raw) + self.type = type self.offset = offset self.length = length self.url = url self.user = user - self._client = client - self._raw = raw - @staticmethod def parse(client, entity, users: dict) -> "MessageEntity" or None: type = MessageEntity.ENTITIES.get(entity.ID, None) @@ -85,5 +82,7 @@ class MessageEntity(PyrogramType): offset=entity.offset, length=entity.length, url=getattr(entity, "url", None), - user=User.parse(client, users.get(getattr(entity, "user_id", None), None)) + user=User.parse(client, users.get(getattr(entity, "user_id", None), None)), + client=client, + raw=entity ) diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index ef32f0b2..48907940 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -30,6 +30,8 @@ class Messages(PyrogramType): Requested messages. """ - def __init__(self, total_count: int, messages: list): + def __init__(self, *, client, raw, total_count: int, messages: list): + super().__init__(client, raw) + self.total_count = total_count self.messages = messages diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 970579af..96a1e19a 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -39,15 +39,13 @@ class Photo(PyrogramType): Available sizes of this photo. """ - def __init__(self, id: str, date: int, sizes: list, *, - client=None, raw=None): + def __init__(self, *, client, raw, id: str, date: int, sizes: list): + super().__init__(client, raw) + self.id = id self.date = date self.sizes = sizes - self._client = client - self._raw = raw - @staticmethod def parse(client, photo: types.Photo): if isinstance(photo, types.Photo): diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index cb94a6e6..1e37399c 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -40,16 +40,14 @@ class PhotoSize(PyrogramType): File size. """ - def __init__(self, file_id: str, width: int, height: int, file_size: int, *, - client=None, raw=None): + def __init__(self, *, client, raw, file_id: str, width: int, height: int, file_size: int): + super().__init__(client, raw) + self.file_id = file_id self.width = width self.height = height self.file_size = file_size - self._client = client - self._raw = raw - @staticmethod def parse(client, photo_size: types.PhotoSize or types.PhotoCachedSize): if isinstance(photo_size, (types.PhotoSize, types.PhotoCachedSize)): diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index 7b5bfa70..e6285895 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -61,10 +61,11 @@ class Sticker(PyrogramType): # TODO: Add mask position - def __init__(self, file_id: str, width: int, height: int, *, - thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, - emoji: str = None, set_name: str = None, mask_position=None, - client=None, raw=None): + def __init__(self, *, client, raw, file_id: str, width: int, height: int, thumb=None, file_name: str = None, + mime_type: str = None, file_size: int = None, date: int = None, emoji: str = None, + set_name: str = None, mask_position=None): + super().__init__(client, raw) + self.file_id = file_id self.thumb = thumb self.file_name = file_name @@ -77,9 +78,6 @@ class Sticker(PyrogramType): self.set_name = set_name self.mask_position = mask_position - self._client = client - self._raw = raw - @staticmethod def parse(client, sticker: types.Document, image_size_attributes: types.DocumentAttributeImageSize, set_name: str, sticker_attributes: types.DocumentAttributeSticker, file_name: str) -> "Sticker": diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index bf53b13e..8133cae4 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -30,6 +30,8 @@ class UserProfilePhotos(PyrogramType): Requested profile pictures. """ - def __init__(self, total_count: int, photos: list): + def __init__(self, *, client, raw, total_count: int, photos: list): + super().__init__(client, raw) + self.total_count = total_count self.photos = photos diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index 662a416f..ee533d59 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -43,18 +43,16 @@ class Venue(PyrogramType): """ - def __init__(self, location, title: str, address: str, *, - foursquare_id: str = None, foursquare_type: str = None, - client=None, raw=None): + def __init__(self, *, client, raw, location, title: str, address: str, foursquare_id: str = None, + foursquare_type: str = None): + super().__init__(client, raw) + self.location = location self.title = title self.address = address self.foursquare_id = foursquare_id self.foursquare_type = foursquare_type - self._client = client - self._raw = raw - @staticmethod def parse(client, venue: types.MessageMediaVenue): return Venue( diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 05d28f45..5115e651 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -56,9 +56,10 @@ class Video(PyrogramType): Date the video was sent in Unix time. """ - def __init__(self, file_id: str, width: int, height: int, duration: int, *, - thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, - client=None, raw=None): + def __init__(self, *, client, raw, file_id: str, width: int, height: int, duration: int, thumb=None, + file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None): + super().__init__(client, raw) + self.file_id = file_id self.thumb = thumb self.file_name = file_name @@ -69,9 +70,6 @@ class Video(PyrogramType): self.height = height self.duration = duration - self._client = client - self._raw = raw - @staticmethod def parse(client, video: types.Document, video_attributes: types.DocumentAttributeVideo, file_name: str) -> "Video": return Video( diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index ddcd74f3..61a86454 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -50,9 +50,10 @@ class VideoNote(PyrogramType): Date the video note was sent in Unix time. """ - def __init__(self, file_id: str, length: int, duration: int, *, - thumb=None, mime_type: str = None, file_size: int = None, date: int = None, - client=None, raw=None): + def __init__(self, *, client, raw, file_id: str, length: int, duration: int, thumb=None, mime_type: str = None, + file_size: int = None, date: int = None): + super().__init__(client, raw) + self.file_id = file_id self.thumb = thumb self.mime_type = mime_type @@ -61,9 +62,6 @@ class VideoNote(PyrogramType): self.length = length self.duration = duration - self._client = client - self._raw = raw - @staticmethod def parse(client, video_note: types.Document, video_attributes: types.DocumentAttributeVideo) -> "VideoNote": return VideoNote( diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index c993ea5c..88cc269e 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -46,9 +46,10 @@ class Voice(PyrogramType): Date the voice was sent in Unix time. """ - def __init__(self, file_id: str, duration: int, *, - waveform: bytes = None, mime_type: str = None, file_size: int = None, date: int = None, - client=None, raw=None): + def __init__(self, *, client, raw, file_id: str, duration: int, waveform: bytes = None, mime_type: str = None, + file_size: int = None, date: int = None): + super().__init__(client, raw) + self.file_id = file_id self.duration = duration self.waveform = waveform @@ -56,9 +57,6 @@ class Voice(PyrogramType): self.file_size = file_size self.date = date - self._client = client - self._raw = raw - @staticmethod def parse(client, voice: types.Document, attributes: types.DocumentAttributeAudio) -> "Voice": return Voice( diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py index ee0cfea4..f438a4ba 100644 --- a/pyrogram/client/types/pyrogram_type.py +++ b/pyrogram/client/types/pyrogram_type.py @@ -21,6 +21,10 @@ from json import dumps, JSONEncoder class PyrogramType: + def __init__(self, client, raw): + self._client = client + self._raw = raw + def __str__(self): return dumps(self, cls=Encoder, indent=4) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 2169f1d7..d98d57aa 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -76,15 +76,14 @@ class Chat(PyrogramType): The reason why this chat might be unavailable to some users. """ - def __init__(self, id: int, type: str, *, - title: str = None, username: str = None, first_name: str = None, last_name: str = None, - all_members_are_administrators: bool = None, photo=None, description: str = None, - invite_link: str = None, pinned_message=None, sticker_set_name: str = None, - can_set_sticker_set: bool = None, members_count: int = None, restriction_reason: str = None, - client=None, raw=None): + def __init__(self, *, client, raw, id: int, type: str, title: str = None, username: str = None, + first_name: str = None, last_name: str = None, all_members_are_administrators: bool = None, photo=None, + description: str = None, invite_link: str = None, pinned_message=None, sticker_set_name: str = None, + can_set_sticker_set: bool = None, members_count: int = None, restriction_reason: str = None): + super().__init__(client, raw) + self.id = id self.type = type - self.title = title self.username = username self.first_name = first_name @@ -99,9 +98,6 @@ class Chat(PyrogramType): self.members_count = members_count self.restriction_reason = restriction_reason - self._client = client - self._raw = raw - @staticmethod def parse_user_chat(client, user: types.User) -> "Chat": return Chat( diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index b3e2916b..11b2c9a0 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -79,12 +79,14 @@ class ChatMember(PyrogramType): Restricted only. True, if user may add web page previews to his messages, implies can_send_media_messages. """ - def __init__(self, *, user, status: str, until_date: int = None, can_be_edited: bool = None, + def __init__(self, *, client, raw, user, status: str, until_date: int = None, can_be_edited: bool = None, can_change_info: bool = None, can_post_messages: bool = None, can_edit_messages: bool = None, can_delete_messages: bool = None, can_invite_users: bool = None, can_restrict_members: bool = None, can_pin_messages: bool = None, can_promote_members: bool = None, can_send_messages: bool = None, can_send_media_messages: bool = None, can_send_other_messages: bool = None, - can_add_web_page_previews: bool = None, client, raw): + can_add_web_page_previews: bool = None): + super().__init__(client, raw) + self.user = user self.status = status self.until_date = until_date @@ -102,9 +104,6 @@ class ChatMember(PyrogramType): self.can_send_other_messages = can_send_other_messages self.can_add_web_page_previews = can_add_web_page_previews - self._client = client - self._raw = raw - @staticmethod def parse(client, member, user) -> "ChatMember": if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)): diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index 4f057309..5fa8275d 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -33,13 +33,12 @@ class ChatMembers(PyrogramType): Requested chat members. """ - def __init__(self, *, total_count: int, chat_members: list, client, raw): + def __init__(self, *, client, raw, total_count: int, chat_members: list): + super().__init__(client, raw) + self.total_count = total_count self.chat_members = chat_members - self._client = client - self._raw = raw - @staticmethod def parse(client, members): users = {i.id: i for i in members.users} @@ -59,5 +58,6 @@ class ChatMembers(PyrogramType): return ChatMembers( total_count=total_count, chat_members=chat_members, - client=client, raw=members + client=client, + raw=members ) diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index c7ef4c80..b998cf90 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -36,14 +36,10 @@ class ChatPhoto(Object): ID = 0xb0700015 - def __init__(self, small_file_id: str, big_file_id: str, *, - client=None, raw=None): + def __init__(self, *, client, raw, small_file_id: str, big_file_id: str): self.small_file_id = small_file_id self.big_file_id = big_file_id - self._client = client - self._raw = raw - @staticmethod def parse(client, chat_photo: types.UserProfilePhoto or types.ChatPhoto): if not isinstance(chat_photo, (types.UserProfilePhoto, types.ChatPhoto)): diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index 8107d363..3d4d0faf 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -16,10 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object +from ..pyrogram_type import PyrogramType -class Dialog(Object): +class Dialog(PyrogramType): """This object represents a dialog. Args: @@ -41,18 +41,14 @@ class Dialog(Object): is_pinned (``bool``): True, if the dialog is pinned. """ - ID = 0xb0700028 - def __init__(self, - chat, - top_message, - unread_messages_count: int, - unread_mentions_count: int, - unread_mark: bool, - is_pinned: bool): + def __init__(self, *, client, raw, chat, top_message, unread_messages_count: int, unread_mentions_count: int, + unread_mark: bool, is_pinned: bool): + super().__init__(client, raw) + self.chat = chat self.top_message = top_message self.unread_messages_count = unread_messages_count self.unread_mentions_count = unread_mentions_count self.unread_mark = unread_mark - self.is_pinned = is_pinned + self.is_pinned = is_pinned \ No newline at end of file diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py index cdf1d951..030018e4 100644 --- a/pyrogram/client/types/user_and_chats/dialogs.py +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -16,10 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object +from ..pyrogram_type import PyrogramType -class Dialogs(Object): +class Dialogs(PyrogramType): """This object represents a user's dialogs chunk Args: @@ -29,8 +29,9 @@ class Dialogs(Object): dialogs (List of :obj:`Dialog `): Requested dialogs. """ - ID = 0xb0700029 - def __init__(self, total_count: int, dialogs: list): + def __init__(self, *, client, raw, total_count: int, dialogs: list): + super().__init__(client, raw) + self.total_count = total_count self.dialogs = dialogs diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 80660bcc..1c1d41fe 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -69,11 +69,12 @@ class User(PyrogramType): The reason why this bot might be unavailable to some users. """ - def __init__(self, id: int, is_self: bool, is_contact: bool, is_mutual_contact: bool, is_deleted: bool, - is_bot: bool, first_name: str, *, - last_name: str = None, status=None, username: str = None, language_code: str = None, - phone_number: str = None, photo=None, restriction_reason: str = None, - client=None, raw=None): + def __init__(self, *, client, raw, id: int, is_self: bool, is_contact: bool, is_mutual_contact: bool, + is_deleted: bool, is_bot: bool, first_name: str, last_name: str = None, status=None, + username: str = None, language_code: str = None, phone_number: str = None, photo=None, + restriction_reason: str = None): + super().__init__(client, raw) + self.id = id self.is_self = is_self self.is_contact = is_contact @@ -81,7 +82,6 @@ class User(PyrogramType): self.is_deleted = is_deleted self.is_bot = is_bot self.first_name = first_name - self.last_name = last_name self.status = status self.username = username @@ -90,9 +90,6 @@ class User(PyrogramType): self.photo = photo self.restriction_reason = restriction_reason - self._client = client - self._raw = raw - @staticmethod def parse(client, user: types.User) -> "User" or None: if user is None: @@ -113,5 +110,6 @@ class User(PyrogramType): phone_number=user.phone, photo=ChatPhoto.parse(client, user.photo), restriction_reason=user.restriction_reason, - client=client, raw=user + client=client, + raw=user ) diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index f99d356d..d6bfa13d 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -62,12 +62,12 @@ class UserStatus(PyrogramType): always shown to blocked users), None otherwise. """ - def __init__(self, user_id: int, *, - online: bool = None, offline: bool = None, date: int = None, recently: bool = None, - within_week: bool = None, within_month: bool = None, long_time_ago: bool = None, - client=None, raw=None): - self.user_id = user_id + def __init__(self, *, client, raw, user_id: int, online: bool = None, offline: bool = None, date: int = None, + recently: bool = None, within_week: bool = None, within_month: bool = None, + long_time_ago: bool = None): + super().__init__(client, raw) + self.user_id = user_id self.online = online self.offline = offline self.date = date @@ -76,9 +76,6 @@ class UserStatus(PyrogramType): self.within_month = within_month self.long_time_ago = long_time_ago - self._client = client - self._raw = raw - @staticmethod def parse(client, user: types.User): if user.bot: From ac8258f4513a59c6501baf0eae01e1bbea3fb386 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 12:40:32 +0100 Subject: [PATCH 063/326] Move Str into message.py --- .../types/messages_and_media/message.py | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index f50dd30b..41766332 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -26,7 +26,6 @@ from ..messages_and_media.photo import Photo from ..pyrogram_type import PyrogramType from ..user_and_chats.chat import Chat from ..user_and_chats.user import User -from ...ext.utils import Str class Message(PyrogramType): @@ -484,8 +483,8 @@ class Message(PyrogramType): date=message.date, chat=Chat.parse(client, message, users, chats), from_user=User.parse(client, users.get(message.from_id, None)), - text=Str(message.message) or None if media is None else None, - caption=Str(message.message) or None if media is not None else None, + text=Str(message.message).init(client, entities) or None if media is None else None, + caption=Str(message.message).init(client, entities) or None if media is not None else None, entities=entities or None if media is None else None, caption_entities=entities or None if media is not None else None, author_signature=message.post_author, @@ -900,3 +899,29 @@ class Message(PyrogramType): progress=progress, progress_args=progress_args, ) + + +class Str(str): + def __init__(self, *args): + super().__init__() + + self.client = None + self.entities = None + + def init(self, client, entities): + self.client = client + self.entities = entities + + return self + + @property + def text(self): + return self + + @property + def markdown(self): + return self.client.markdown.unparse(self, self.entities) + + @property + def html(self): + return self.client.html.unparse(self, self.entities) From e9a362923f849bc80ce4dc8cb69efe1c19f56e36 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 12:42:29 +0100 Subject: [PATCH 064/326] Cache stickers --- .../types/messages_and_media/message.py | 31 +++++-------------- .../types/messages_and_media/sticker.py | 26 ++++++++++++++-- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 41766332..66072d22 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -17,8 +17,8 @@ # along with Pyrogram. If not, see . import pyrogram -from pyrogram.api import types, functions -from pyrogram.api.errors import MessageIdsEmpty, StickersetInvalid +from pyrogram.api import types +from pyrogram.api.errors import MessageIdsEmpty from .contact import Contact from .location import Location from .message_entity import MessageEntity @@ -442,21 +442,12 @@ class Message(PyrogramType): else: video = pyrogram.Video.parse(client, doc, video_attributes, file_name) elif types.DocumentAttributeSticker in attributes: - image_size_attributes = attributes.get(types.DocumentAttributeImageSize, None) - sticker_attribute = attributes[types.DocumentAttributeSticker] - - if isinstance(sticker_attribute.stickerset, types.InputStickerSetID): - try: - set_name = client.send( - functions.messages.GetStickerSet(sticker_attribute.stickerset) - ).set.short_name - except StickersetInvalid: - set_name = None - else: - set_name = None - - sticker = pyrogram.Sticker.parse(client, doc, image_size_attributes, - set_name, sticker_attribute, file_name) + sticker = pyrogram.Sticker.parse( + client, doc, + attributes.get(types.DocumentAttributeImageSize, None), + attributes[types.DocumentAttributeSticker], + file_name + ) else: document = pyrogram.Document.parse(client, doc, file_name) elif isinstance(media, types.MessageMediaWebPage): @@ -517,12 +508,6 @@ class Message(PyrogramType): raw=message ) - if parsed_message.text: - parsed_message.text.init(parsed_message._client, parsed_message.entities or []) - - if parsed_message.caption: - parsed_message.caption.init(parsed_message._client, parsed_message.caption_entities or []) - if message.reply_to_msg_id and replies: try: parsed_message.reply_to_message = client.get_messages( diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index e6285895..f2414311 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -16,9 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from functools import lru_cache from struct import pack -from pyrogram.api import types +from pyrogram.api import types, functions +from pyrogram.api.errors import StickersetInvalid from .photo_size import PhotoSize from ..pyrogram_type import PyrogramType from ...ext.utils import encode @@ -79,8 +81,28 @@ class Sticker(PyrogramType): self.mask_position = mask_position @staticmethod - def parse(client, sticker: types.Document, image_size_attributes: types.DocumentAttributeImageSize, set_name: str, + @lru_cache(maxsize=256) + def get_sticker_set_name(send, input_sticker_set_id): + try: + return send( + functions.messages.GetStickerSet( + types.InputStickerSetID(*input_sticker_set_id) + ) + ).set.short_name + except StickersetInvalid: + return None + + @staticmethod + def parse(client, sticker: types.Document, image_size_attributes: types.DocumentAttributeImageSize, sticker_attributes: types.DocumentAttributeSticker, file_name: str) -> "Sticker": + sticker_set = sticker_attributes.stickerset + + if isinstance(sticker_set, types.InputStickerSetID): + input_sticker_set_id = (sticker_set.id, sticker_set.access_hash) + set_name = Sticker.get_sticker_set_name(client.send, input_sticker_set_id) + else: + set_name = None + return Sticker( file_id=encode( pack( From 92118e36088e88e452671d427e52ab0d5b21ccfb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 12:45:35 +0100 Subject: [PATCH 065/326] Reformat __init__ parameters --- .../client/types/input_media/input_media_animation.py | 10 ++-------- .../client/types/input_media/input_media_audio.py | 10 ++-------- .../client/types/input_media/input_media_document.py | 6 +----- .../client/types/input_media/input_media_photo.py | 5 +---- .../client/types/input_media/input_media_video.py | 11 ++--------- 5 files changed, 8 insertions(+), 34 deletions(-) diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index 0e7b2433..cc3d9ad0 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -52,14 +52,8 @@ class InputMediaAnimation(InputMedia): Animation duration. """ - def __init__(self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = "", - width: int = 0, - height: int = 0, - duration: int = 0): + def __init__(self, media: str, thumb: str = None, caption: str = "", parse_mode: str = "", width: int = 0, + height: int = 0, duration: int = 0): super().__init__(media, caption, parse_mode) self.thumb = thumb diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index 455c2292..7c1b6072 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -53,14 +53,8 @@ class InputMediaAudio(InputMedia): Title of the audio """ - def __init__(self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - performer: int = "", - title: str = ""): + def __init__(self, media: str, thumb: str = None, caption: str = "", parse_mode: str = "", duration: int = 0, + performer: int = "", title: str = ""): super().__init__(media, caption, parse_mode) self.thumb = thumb diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index 08fcae5b..267abbde 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -43,11 +43,7 @@ class InputMediaDocument(InputMedia): Defaults to Markdown. """ - def __init__(self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = ""): + def __init__(self, media: str, thumb: str = None, caption: str = "", parse_mode: str = ""): super().__init__(media, caption, parse_mode) self.thumb = thumb diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py index c8cdccb8..f5af9c89 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -39,8 +39,5 @@ class InputMediaPhoto(InputMedia): Defaults to Markdown. """ - def __init__(self, - media: str, - caption: str = "", - parse_mode: str = ""): + def __init__(self, media: str, caption: str = "", parse_mode: str = ""): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index 955cf633..1884ea8d 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -57,15 +57,8 @@ class InputMediaVideo(InputMedia): Pass True, if the uploaded video is suitable for streaming. """ - def __init__(self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = "", - width: int = 0, - height: int = 0, - duration: int = 0, - supports_streaming: bool = True): + def __init__(self, media: str, thumb: str = None, caption: str = "", parse_mode: str = "", width: int = 0, + height: int = 0, duration: int = 0, supports_streaming: bool = True): super().__init__(media, caption, parse_mode) self.thumb = thumb From 52b9319734021f3d95df6502d166178c5c50453b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 13:01:41 +0100 Subject: [PATCH 066/326] Refactor CallbackQuery --- pyrogram/client/types/bots/callback_query.py | 79 ++++++++++++++------ 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index 046981b7..61ece31e 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -16,10 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object +from base64 import b64encode +from struct import pack + +from pyrogram.api import types +from ..pyrogram_type import PyrogramType +from ..user_and_chats import User -class CallbackQuery(Object): +class CallbackQuery(PyrogramType): """This object represents an incoming callback query from a callback button in an inline keyboard. If the button that originated the query was attached to a message sent by the bot, the field message will be present. If the button was attached to a message sent via the bot (in inline mode), @@ -50,27 +55,57 @@ class CallbackQuery(Object): Short name of a Game to be returned, serves as the unique identifier for the game. """ - ID = 0xb0700024 - def __init__( - self, - id: str, - from_user, - chat_instance: str, - client=None, - message=None, - inline_message_id: str = None, - data: bytes = None, - game_short_name: str = None - ): - self._client = client - self.id = id # string - self.from_user = from_user # User - self.message = message # flags.0?Message - self.inline_message_id = inline_message_id # flags.1?string - self.chat_instance = chat_instance # string - self.data = data # flags.2?string - self.game_short_name = game_short_name # flags.3?string + def __init__(self, *, client, raw, id: str, from_user, chat_instance: str, message=None, + inline_message_id: str = None, data: bytes = None, game_short_name: str = None): + super().__init__(client, raw) + + self.id = id + self.from_user = from_user + self.message = message + self.inline_message_id = inline_message_id + self.chat_instance = chat_instance + self.data = data + self.game_short_name = game_short_name + + @staticmethod + def parse(client, callback_query, users) -> "CallbackQuery": + message = None + inline_message_id = None + + if isinstance(callback_query, types.UpdateBotCallbackQuery): + peer = callback_query.peer + + if isinstance(peer, types.PeerUser): + peer_id = peer.user_id + elif isinstance(peer, types.PeerChat): + peer_id = -peer.chat_id + else: + peer_id = int("-100" + str(peer.channel_id)) + + message = client.get_messages(peer_id, callback_query.msg_id) + elif isinstance(callback_query, types.UpdateInlineBotCallbackQuery): + inline_message_id = b64encode( + pack( + "`. From d31a696f324b02e606d965590ad02b2e87e5d16f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 13:02:22 +0100 Subject: [PATCH 067/326] Fix encoder breaking with bytes --- pyrogram/client/types/pyrogram_type.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py index f438a4ba..18faa9e8 100644 --- a/pyrogram/client/types/pyrogram_type.py +++ b/pyrogram/client/types/pyrogram_type.py @@ -43,5 +43,9 @@ def remove_none(obj): class Encoder(JSONEncoder): def default(self, o: PyrogramType): - content = {i: getattr(o, i) for i in filter(lambda x: not x.startswith("_"), o.__dict__)} + try: + content = {i: getattr(o, i) for i in filter(lambda x: not x.startswith("_"), o.__dict__)} + except AttributeError: + return repr(o) + return remove_none(OrderedDict([("_", "pyrogram:" + o.__class__.__name__)] + [i for i in content.items()])) From 3f643242b1e3f039c161a6312137532226f102c0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 13:03:08 +0100 Subject: [PATCH 068/326] Reformat encoder style --- pyrogram/client/types/pyrogram_type.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py index 18faa9e8..880c452a 100644 --- a/pyrogram/client/types/pyrogram_type.py +++ b/pyrogram/client/types/pyrogram_type.py @@ -44,8 +44,16 @@ def remove_none(obj): class Encoder(JSONEncoder): def default(self, o: PyrogramType): try: - content = {i: getattr(o, i) for i in filter(lambda x: not x.startswith("_"), o.__dict__)} + content = { + i: getattr(o, i) + for i in filter(lambda x: not x.startswith("_"), o.__dict__) + } except AttributeError: return repr(o) - return remove_none(OrderedDict([("_", "pyrogram:" + o.__class__.__name__)] + [i for i in content.items()])) + return remove_none( + OrderedDict( + [("_", "pyrogram:" + o.__class__.__name__)] + + [i for i in content.items()] + ) + ) From a683e3e917dd2f911fdb76ea321b6f07aa38de21 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 13:16:05 +0100 Subject: [PATCH 069/326] Refactor bot keyboard types --- pyrogram/client/types/bots/force_reply.py | 8 ++++---- .../types/bots/inline_keyboard_button.py | 19 +++++-------------- .../types/bots/inline_keyboard_markup.py | 9 ++++----- pyrogram/client/types/bots/keyboard_button.py | 9 ++++----- .../types/bots/reply_keyboard_markup.py | 17 +++++------------ .../types/bots/reply_keyboard_remove.py | 8 ++++---- 6 files changed, 26 insertions(+), 44 deletions(-) diff --git a/pyrogram/client/types/bots/force_reply.py b/pyrogram/client/types/bots/force_reply.py index 2d3cc98f..6d91ff5f 100644 --- a/pyrogram/client/types/bots/force_reply.py +++ b/pyrogram/client/types/bots/force_reply.py @@ -16,11 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object from pyrogram.api.types import ReplyKeyboardForceReply +from ..pyrogram_type import PyrogramType -class ForceReply(Object): +class ForceReply(PyrogramType): """Upon receiving a message with this object, Telegram clients will display a reply interface to the user (act as if the user has selected the bot's message and tapped 'Reply'). This can be extremely useful if you want to create user-friendly step-by-step interfaces without having to @@ -33,9 +33,9 @@ class ForceReply(Object): 2) if the bot's message is a reply (has reply_to_message_id), sender of the original message. """ - ID = 0xb0700018 - def __init__(self, selective: bool = None): + super().__init__(None, None) + self.selective = selective @staticmethod diff --git a/pyrogram/client/types/bots/inline_keyboard_button.py b/pyrogram/client/types/bots/inline_keyboard_button.py index 3d6c7b6b..34718fd1 100644 --- a/pyrogram/client/types/bots/inline_keyboard_button.py +++ b/pyrogram/client/types/bots/inline_keyboard_button.py @@ -16,15 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object - from pyrogram.api.types import ( KeyboardButtonUrl, KeyboardButtonCallback, KeyboardButtonSwitchInline ) +from ..pyrogram_type import PyrogramType -class InlineKeyboardButton(Object): +class InlineKeyboardButton(PyrogramType): """This object represents one button of an inline keyboard. You must use exactly one of the optional fields. Args: @@ -54,18 +53,10 @@ class InlineKeyboardButton(Object): # TODO: Add callback_game and pay fields - ID = 0xb0700019 + def __init__(self, text: str, callback_data: bytes = None, url: str = None, + switch_inline_query: str = None, switch_inline_query_current_chat: str = None): + super().__init__(None, None) - def __init__( - self, - text: str, - callback_data: bytes = None, - url: str = None, - switch_inline_query: str = None, - switch_inline_query_current_chat: str = None, - # callback_game=None, - # pay: bool = None - ): self.text = text self.url = url self.callback_data = callback_data diff --git a/pyrogram/client/types/bots/inline_keyboard_markup.py b/pyrogram/client/types/bots/inline_keyboard_markup.py index 2a6993c4..bde31ec4 100644 --- a/pyrogram/client/types/bots/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots/inline_keyboard_markup.py @@ -16,13 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object - from pyrogram.api.types import ReplyInlineMarkup, KeyboardButtonRow from . import InlineKeyboardButton +from ..pyrogram_type import PyrogramType -class InlineKeyboardMarkup(Object): +class InlineKeyboardMarkup(PyrogramType): """This object represents an inline keyboard that appears right next to the message it belongs to. Args: @@ -30,9 +29,9 @@ class InlineKeyboardMarkup(Object): List of button rows, each represented by a List of InlineKeyboardButton objects. """ - ID = 0xb0700020 - def __init__(self, inline_keyboard: list): + super().__init__(None, None) + self.inline_keyboard = inline_keyboard @staticmethod diff --git a/pyrogram/client/types/bots/keyboard_button.py b/pyrogram/client/types/bots/keyboard_button.py index b9af3b46..0566fc20 100644 --- a/pyrogram/client/types/bots/keyboard_button.py +++ b/pyrogram/client/types/bots/keyboard_button.py @@ -16,13 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object - from pyrogram.api.types import KeyboardButton as RawKeyboardButton from pyrogram.api.types import KeyboardButtonRequestPhone, KeyboardButtonRequestGeoLocation +from ..pyrogram_type import PyrogramType -class KeyboardButton(Object): +class KeyboardButton(PyrogramType): """This object represents one button of the reply keyboard. For simple text buttons String can be used instead of this object to specify text of the button. Optional fields are mutually exclusive. @@ -41,9 +40,9 @@ class KeyboardButton(Object): Available in private chats only. """ - ID = 0xb0700021 - def __init__(self, text: str, request_contact: bool = None, request_location: bool = None): + super().__init__(None, None) + self.text = text self.request_contact = request_contact self.request_location = request_location diff --git a/pyrogram/client/types/bots/reply_keyboard_markup.py b/pyrogram/client/types/bots/reply_keyboard_markup.py index 29fc1081..77b72562 100644 --- a/pyrogram/client/types/bots/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots/reply_keyboard_markup.py @@ -16,15 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object - from pyrogram.api.types import KeyboardButtonRow from pyrogram.api.types import ReplyKeyboardMarkup as RawReplyKeyboardMarkup - from . import KeyboardButton +from ..pyrogram_type import PyrogramType -class ReplyKeyboardMarkup(Object): +class ReplyKeyboardMarkup(PyrogramType): """This object represents a custom keyboard with reply options. Args: @@ -49,15 +47,10 @@ class ReplyKeyboardMarkup(Object): select the new language. Other users in the group don't see the keyboard. """ - ID = 0xb0700022 + def __init__(self, keyboard: list, resize_keyboard: bool = None, one_time_keyboard: bool = None, + selective: bool = None): + super().__init__(None, None) - def __init__( - self, - keyboard: list, - resize_keyboard: bool = None, - one_time_keyboard: bool = None, - selective: bool = None - ): self.keyboard = keyboard self.resize_keyboard = resize_keyboard self.one_time_keyboard = one_time_keyboard diff --git a/pyrogram/client/types/bots/reply_keyboard_remove.py b/pyrogram/client/types/bots/reply_keyboard_remove.py index 3e2aebf5..82adb859 100644 --- a/pyrogram/client/types/bots/reply_keyboard_remove.py +++ b/pyrogram/client/types/bots/reply_keyboard_remove.py @@ -16,11 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Object from pyrogram.api.types import ReplyKeyboardHide +from ..pyrogram_type import PyrogramType -class ReplyKeyboardRemove(Object): +class ReplyKeyboardRemove(PyrogramType): """Upon receiving a message with this object, Telegram clients will remove the current custom keyboard and display the default letter-keyboard. By default, custom keyboards are displayed until a new keyboard is sent by a bot. An exception is made for one-time keyboards that are hidden immediately after the user presses a @@ -35,9 +35,9 @@ class ReplyKeyboardRemove(Object): keyboard for that user, while still showing the keyboard with poll options to users who haven't voted yet. """ - ID = 0xb0700023 - def __init__(self, selective: bool = None): + super().__init__(None, None) + self.selective = selective @staticmethod From 8cbb9c93161eafac8f15a2d6a7fe0417edc50202 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 13:51:08 +0100 Subject: [PATCH 070/326] Refactor UserProfilePhotos --- .../client/methods/users/get_user_profile_photos.py | 6 ++++-- .../types/messages_and_media/user_profile_photos.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/users/get_user_profile_photos.py b/pyrogram/client/methods/users/get_user_profile_photos.py index 552ebb9a..1403eb79 100644 --- a/pyrogram/client/methods/users/get_user_profile_photos.py +++ b/pyrogram/client/methods/users/get_user_profile_photos.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions -from ...ext import BaseClient, utils +from ...ext import BaseClient class GetUserProfilePhotos(BaseClient): @@ -47,7 +48,8 @@ class GetUserProfilePhotos(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ - return utils.parse_profile_photos( + return pyrogram.UserProfilePhotos.parse( + self, self.send( functions.photos.GetUserPhotos( user_id=self.resolve_peer(user_id), diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index 8133cae4..ca277a95 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from pyrogram.api import types +from .photo import Photo from ..pyrogram_type import PyrogramType @@ -35,3 +37,12 @@ class UserProfilePhotos(PyrogramType): self.total_count = total_count self.photos = photos + + @staticmethod + def parse(client, photos) -> "UserProfilePhotos": + return UserProfilePhotos( + total_count=len(photos.photos) if isinstance(photos, types.photos.Photos) else photos.count, + photos=[Photo.parse(client, photo) for photo in photos.photos], + client=client, + raw=photos + ) From 5bc9e0fc75d4c05b78a30903acea32ad320a52ed Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 14:17:57 +0100 Subject: [PATCH 071/326] Fix Video not having client and raw attributes set --- pyrogram/client/types/messages_and_media/video.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 5115e651..9ca54b69 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -89,5 +89,7 @@ class Video(PyrogramType): mime_type=video.mime_type, file_size=video.size, file_name=file_name, - date=video.date + date=video.date, + client=client, + raw=video ) From 7b90a0e7c56cd2566179c751f3f00be4ed0956a1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 14:18:15 +0100 Subject: [PATCH 072/326] Refactor Messages --- .../client/types/messages_and_media/messages.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 48907940..5619b968 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from pyrogram.api import types +from .message import Message from ..pyrogram_type import PyrogramType @@ -35,3 +37,17 @@ class Messages(PyrogramType): self.total_count = total_count self.messages = messages + + @staticmethod + def parse(client, messages: types.messages.Messages) -> "Messages": + users = {i.id: i for i in messages.users} + chats = {i.id: i for i in messages.chats} + + total_count = getattr(messages, "count", len(messages.messages)) + + return Messages( + total_count=total_count, + messages=[Message.parse(client, message, users, chats) for message in messages.messages], + client=client, + raw=messages + ) From 603bc88aa3b80991070ec1ce92e132e04d789518 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 14:18:41 +0100 Subject: [PATCH 073/326] Refactor get_history method --- .../client/methods/messages/get_history.py | 62 +++++-------------- 1 file changed, 14 insertions(+), 48 deletions(-) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 017c7f8b..bc272ce3 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -18,7 +18,7 @@ import pyrogram from pyrogram.api import functions -from ...ext import BaseClient, utils +from ...ext import BaseClient class GetHistory(BaseClient): @@ -59,52 +59,18 @@ class GetHistory(BaseClient): :class:`Error ` in case of a Telegram RPC error. """ - r = self.send( - functions.messages.GetHistory( - peer=self.resolve_peer(chat_id), - offset_id=offset_id, - offset_date=offset_date, - add_offset=offset, - limit=limit, - max_id=0, - min_id=0, - hash=0 + return pyrogram.Messages.parse( + self, + self.send( + functions.messages.GetHistory( + peer=self.resolve_peer(chat_id), + offset_id=offset_id, + offset_date=offset_date, + add_offset=offset, + limit=limit, + max_id=0, + min_id=0, + hash=0 + ) ) ) - - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - reply_to_messages = { - i.reply_to_msg_id: None - for i in r.messages - if i.reply_to_msg_id - } - - if reply_to_messages: - temp = self.get_messages( - chat_id, reply_to_messages, - replies=0 - ) - - assert len(temp) == len(reply_to_messages) - - for i in range(len(temp)): - reply_to_messages[temp[i].message_id] = temp[i] - - messages = utils.parse_messages( - self, r.messages, - users, chats, - replies=0 - ) - - assert len(messages) == len(r.messages) - - for i in range(len(messages)): - if r.messages[i].reply_to_msg_id: - messages[i].reply_to_message = reply_to_messages[r.messages[i].reply_to_msg_id] - - return pyrogram.Messages( - total_count=getattr(r, "count", len(r.messages)), - messages=messages - ) From 8c02a1553d1fd740a5e24842efcafb40b98feac8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:12:53 +0100 Subject: [PATCH 074/326] Add Chat.parse_dialog --- pyrogram/client/types/user_and_chats/chat.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index d98d57aa..a9564836 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -149,6 +149,15 @@ class Chat(PyrogramType): return Chat.parse_channel_chat(client, chats[message.to_id.channel_id]) + @staticmethod + def parse_dialog(client, peer, users: dict, chats: dict): + if isinstance(peer, types.PeerUser): + return Chat.parse_user_chat(client, users[peer.user_id]) + elif isinstance(peer, types.PeerChat): + return Chat.parse_chat_chat(client, chats[peer.chat_id]) + else: + return Chat.parse_channel_chat(client, chats[peer.channel_id]) + @staticmethod def parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> "Chat": if isinstance(chat_full, types.UserFull): From ccf677f3a026ec2071560e3893ea65c9769976f5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:13:28 +0100 Subject: [PATCH 075/326] Refactor Dialog and Dialogs --- .../client/types/user_and_chats/dialog.py | 26 ++++++++++++++- .../client/types/user_and_chats/dialogs.py | 32 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index 3d4d0faf..0fc17fe0 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -16,7 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from pyrogram.api import types from ..pyrogram_type import PyrogramType +from ..user_and_chats import Chat class Dialog(PyrogramType): @@ -51,4 +53,26 @@ class Dialog(PyrogramType): self.unread_messages_count = unread_messages_count self.unread_mentions_count = unread_mentions_count self.unread_mark = unread_mark - self.is_pinned = is_pinned \ No newline at end of file + self.is_pinned = is_pinned + + @staticmethod + def parse(client, dialog, messages, users, chats) -> "Dialog": + chat_id = dialog.peer + + if isinstance(chat_id, types.PeerUser): + chat_id = chat_id.user_id + elif isinstance(chat_id, types.PeerChat): + chat_id = -chat_id.chat_id + else: + chat_id = int("-100" + str(chat_id.channel_id)) + + return Dialog( + chat=Chat.parse_dialog(client, dialog.peer, users, chats), + top_message=messages.get(chat_id), + unread_messages_count=dialog.unread_count, + unread_mentions_count=dialog.unread_mentions_count, + unread_mark=dialog.unread_mark, + is_pinned=dialog.pinned, + client=client, + raw=dialog + ) diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py index 030018e4..8261e97c 100644 --- a/pyrogram/client/types/user_and_chats/dialogs.py +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from pyrogram.api import types +from .dialog import Dialog +from ..messages_and_media import Message from ..pyrogram_type import PyrogramType @@ -35,3 +38,32 @@ class Dialogs(PyrogramType): self.total_count = total_count self.dialogs = dialogs + + @staticmethod + def parse(client, dialogs) -> "Dialogs": + users = {i.id: i for i in dialogs.users} + chats = {i.id: i for i in dialogs.chats} + + messages = {} + + for message in dialogs.messages: + to_id = message.to_id + + if isinstance(to_id, types.PeerUser): + if message.out: + chat_id = to_id.user_id + else: + chat_id = message.from_id + elif isinstance(to_id, types.PeerChat): + chat_id = -to_id.chat_id + else: + chat_id = int("-100" + str(to_id.channel_id)) + + messages[chat_id] = Message.parse(client, message, users, chats) + + return Dialogs( + total_count=getattr(dialogs, "count", len(dialogs.dialogs)), + dialogs=[Dialog.parse(client, dialog, messages, users, chats) for dialog in dialogs.dialogs], + client=client, + raw=dialogs + ) From e6dced80cf5619e7ba510964037030649dd91173 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:13:57 +0100 Subject: [PATCH 076/326] Update get_dialogs to use the new refactored Dialogs type --- pyrogram/client/methods/chats/get_dialogs.py | 76 +++----------------- 1 file changed, 9 insertions(+), 67 deletions(-) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 045c541a..655bb431 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -18,12 +18,12 @@ import pyrogram from pyrogram.api import functions, types -from ...ext import BaseClient, utils +from ...ext import BaseClient class GetDialogs(BaseClient): def get_dialogs(self, - offset_dialogs=None, + offset_dialog=None, limit: int = 100, pinned_only: bool = False): """Use this method to get the user's dialogs @@ -31,18 +31,18 @@ class GetDialogs(BaseClient): You can get up to 100 dialogs at once. Args: + offset_dialog (:obj:`Dialog`): + Pass the last dialog object to retrieve the next dialogs chunk starting it. + Defaults to None (start from the beginning). + limit (``str``, *optional*): Limits the number of dialogs to be retrieved. - Defaults to 100 + Defaults to 100. pinned_only (``bool``, *optional*): Pass True if you want to get only pinned dialogs. Defaults to False. - offset_dialogs (:obj:`Dialogs`): - Pass the previous dialogs object to retrieve the next dialogs chunk starting from the last dialog. - Defaults to None (start from the beginning). - Returns: On success, a :obj:`Dialogs` object is returned. @@ -53,22 +53,9 @@ class GetDialogs(BaseClient): if pinned_only: r = self.send(functions.messages.GetPinnedDialogs()) else: - offset_date = 0 - - if offset_dialogs: - for dialog in reversed(offset_dialogs.dialogs): - top_message = dialog.top_message - - if top_message: - message_date = top_message.date - - if message_date: - offset_date = message_date - break - r = self.send( functions.messages.GetDialogs( - offset_date=offset_date, + offset_date=offset_dialog.top_message.date if offset_dialog else 0, offset_id=0, offset_peer=types.InputPeerEmpty(), limit=limit, @@ -77,49 +64,4 @@ class GetDialogs(BaseClient): ) ) - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - messages = {} - - for message in r.messages: - to_id = message.to_id - - if isinstance(to_id, types.PeerUser): - if message.out: - chat_id = to_id.user_id - else: - chat_id = message.from_id - elif isinstance(to_id, types.PeerChat): - chat_id = -to_id.chat_id - else: - chat_id = int("-100" + str(to_id.channel_id)) - - messages[chat_id] = utils.parse_messages(self, message, users, chats) - - dialogs = [] - - for dialog in r.dialogs: - chat_id = dialog.peer - - if isinstance(chat_id, types.PeerUser): - chat_id = chat_id.user_id - elif isinstance(chat_id, types.PeerChat): - chat_id = -chat_id.chat_id - else: - chat_id = int("-100" + str(chat_id.channel_id)) - - dialogs.append( - pyrogram.Dialog( - chat=utils.parse_dialog_chat(dialog.peer, users, chats), - top_message=messages.get(chat_id), - unread_messages_count=dialog.unread_count, - unread_mentions_count=dialog.unread_mentions_count, - unread_mark=dialog.unread_mark, - is_pinned=dialog.pinned - ) - ) - - return pyrogram.Dialogs( - total_count=getattr(r, "count", len(r.dialogs)), - dialogs=dialogs - ) + return pyrogram.Dialogs.parse(self, r) From 6bc2db7157c3ac24e2ccbef72e2e5e6ee27312c4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:14:32 +0100 Subject: [PATCH 077/326] Better way of parsing total_count for Messages and UserProfilePhotos --- pyrogram/client/types/messages_and_media/messages.py | 4 +--- .../client/types/messages_and_media/user_profile_photos.py | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 5619b968..cd53c38a 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -43,10 +43,8 @@ class Messages(PyrogramType): users = {i.id: i for i in messages.users} chats = {i.id: i for i in messages.chats} - total_count = getattr(messages, "count", len(messages.messages)) - return Messages( - total_count=total_count, + total_count=getattr(messages, "count", len(messages.messages)), messages=[Message.parse(client, message, users, chats) for message in messages.messages], client=client, raw=messages diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index ca277a95..2196608f 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -16,7 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import types from .photo import Photo from ..pyrogram_type import PyrogramType @@ -41,7 +40,7 @@ class UserProfilePhotos(PyrogramType): @staticmethod def parse(client, photos) -> "UserProfilePhotos": return UserProfilePhotos( - total_count=len(photos.photos) if isinstance(photos, types.photos.Photos) else photos.count, + total_count=getattr(photos, "count", len(photos.photos)), photos=[Photo.parse(client, photo) for photo in photos.photos], client=client, raw=photos From 187334446796dcdb078977b4f9c2bcf86e281d66 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:27:16 +0100 Subject: [PATCH 078/326] Update get_users to use the refactored User type --- pyrogram/client/methods/users/get_users.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py index 70c62568..7251476a 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/client/methods/users/get_users.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions -from ...ext import BaseClient, utils +from ...ext import BaseClient class GetUsers(BaseClient): @@ -52,6 +53,6 @@ class GetUsers(BaseClient): users = [] for i in r: - users.append(utils.parse_user(i)) + users.append(pyrogram.User.parse(self, i)) return users if is_iterable else users[0] From 6a0a271d24ee4efb7b64281c13603a83e2322004 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:28:16 +0100 Subject: [PATCH 079/326] Fix broken UpdateStatus in case of incoming updates --- pyrogram/client/types/user_and_chats/user.py | 2 +- .../types/user_and_chats/user_status.py | 21 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 1c1d41fe..ed3ea16d 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -104,7 +104,7 @@ class User(PyrogramType): is_bot=user.bot, first_name=user.first_name, last_name=user.last_name, - status=UserStatus.parse(client, user), + status=UserStatus.parse(client, user.status, user.id, user.bot), username=user.username, language_code=user.lang_code, phone_number=user.phone, diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index d6bfa13d..d9e27498 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -77,24 +77,23 @@ class UserStatus(PyrogramType): self.long_time_ago = long_time_ago @staticmethod - def parse(client, user: types.User): - if user.bot: + def parse(client, user_status, user_id: int, is_bot: bool = False): + if is_bot: return None - raw_status = user.status - status = UserStatus(user_id=user.id, client=client, raw=raw_status) + status = UserStatus(user_id=user_id, client=client, raw=user_status) - if isinstance(raw_status, types.UserStatusOnline): + if isinstance(user_status, types.UserStatusOnline): status.online = True - status.date = raw_status.expires - elif isinstance(raw_status, types.UserStatusOffline): + status.date = user_status.expires + elif isinstance(user_status, types.UserStatusOffline): status.offline = True - status.date = raw_status.was_online - elif isinstance(raw_status, types.UserStatusRecently): + status.date = user_status.was_online + elif isinstance(user_status, types.UserStatusRecently): status.recently = True - elif isinstance(raw_status, types.UserStatusLastWeek): + elif isinstance(user_status, types.UserStatusLastWeek): status.within_week = True - elif isinstance(raw_status, types.UserStatusLastMonth): + elif isinstance(user_status, types.UserStatusLastMonth): status.within_month = True else: status.long_time_ago = True From 2a1a6301a61dbb4884742876ae0622435cbf0edd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:37:58 +0100 Subject: [PATCH 080/326] Add Messages.parse_delete method --- .../types/messages_and_media/messages.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index cd53c38a..444c88ce 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -19,6 +19,7 @@ from pyrogram.api import types from .message import Message from ..pyrogram_type import PyrogramType +from ..user_and_chats import Chat class Messages(PyrogramType): @@ -49,3 +50,32 @@ class Messages(PyrogramType): client=client, raw=messages ) + + @staticmethod + def parse_deleted(client, update) -> "Messages": + messages = update.messages + channel_id = getattr(update, "channel_id", None) + + parsed_messages = [] + + for message in messages: + parsed_messages.append( + Message( + message_id=message, + chat=Chat( + id=int("-100" + str(channel_id)), + type="channel", + client=client, + raw=None + ) if channel_id is not None else None, + client=client, + raw=None + ) + ) + + return Messages( + total_count=len(parsed_messages), + messages=parsed_messages, + client=client, + raw=update + ) From 1344e95be6957dfea6c53c3447b0db8d9f32da84 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:40:06 +0100 Subject: [PATCH 081/326] Use the new parsers in dispatcher.py --- pyrogram/client/dispatcher/dispatcher.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 3ab704fc..4f3ae620 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -22,9 +22,9 @@ from collections import OrderedDict from queue import Queue from threading import Thread +import pyrogram from pyrogram.api import types -from ..ext import utils -from ..handlers import CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, UserStatusHandler, RawUpdateHandler +from ..handlers import CallbackQueryHandler, MessageHandler, RawUpdateHandler, UserStatusHandler, DeletedMessagesHandler log = logging.getLogger(__name__) @@ -40,7 +40,7 @@ class Dispatcher: types.UpdateEditChannelMessage ) - DELETE_MESSAGE_UPDATES = ( + DELETE_MESSAGES_UPDATES = ( types.UpdateDeleteMessages, types.UpdateDeleteChannelMessages ) @@ -62,16 +62,18 @@ class Dispatcher: self.update_parsers = { Dispatcher.MESSAGE_UPDATES: - lambda upd, usr, cht: (utils.parse_messages(self.client, upd.message, usr, cht), MessageHandler), + lambda upd, usr, cht: (pyrogram.Message.parse(self.client, upd.message, usr, cht), MessageHandler), - Dispatcher.DELETE_MESSAGE_UPDATES: - lambda upd, usr, cht: (utils.parse_deleted_messages(upd), DeletedMessagesHandler), + Dispatcher.DELETE_MESSAGES_UPDATES: + lambda upd, usr, cht: (pyrogram.Messages.parse_deleted(self.client, upd), DeletedMessagesHandler), Dispatcher.CALLBACK_QUERY_UPDATES: - lambda upd, usr, cht: (utils.parse_callback_query(self.client, upd, usr), CallbackQueryHandler), + lambda upd, usr, cht: (pyrogram.CallbackQuery.parse(self.client, upd, usr), CallbackQueryHandler), (types.UpdateUserStatus,): - lambda upd, usr, cht: (utils.parse_user_status(upd.status, upd.user_id), UserStatusHandler) + lambda upd, usr, cht: ( + pyrogram.UserStatus.parse(self.client, upd.status, upd.user_id), UserStatusHandler + ) } self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple} From 761a07bda884887aeda0c7203c4bd5ce5dcd1af4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 17 Dec 2018 16:41:06 +0100 Subject: [PATCH 082/326] Remove unneeded parts in utils.py --- pyrogram/client/ext/utils.py | 979 +---------------------------------- 1 file changed, 1 insertion(+), 978 deletions(-) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index fac611f3..3e67e3cd 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -16,233 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import logging from base64 import b64decode, b64encode -from struct import pack -from pyrogram.client import types as pyrogram_types -from ...api import types, functions -from ...api.errors import StickersetInvalid, MessageIdsEmpty - -log = logging.getLogger(__name__) - - -# TODO: Organize the code better? - -class Str(str): - __slots__ = "_client", "_entities" - - def __init__(self, *args): - super().__init__() - self._client = None - self._entities = None - - def init(self, client, entities): - self._client = client - self._entities = entities - - @property - def text(self): - return self - - @property - def markdown(self): - return self._client.markdown.unparse(self, self._entities) - - @property - def html(self): - return self._client.html.unparse(self, self._entities) - - -ENTITIES = { - types.MessageEntityMention.ID: "mention", - types.MessageEntityHashtag.ID: "hashtag", - types.MessageEntityCashtag.ID: "cashtag", - types.MessageEntityBotCommand.ID: "bot_command", - types.MessageEntityUrl.ID: "url", - types.MessageEntityEmail.ID: "email", - types.MessageEntityBold.ID: "bold", - types.MessageEntityItalic.ID: "italic", - types.MessageEntityCode.ID: "code", - types.MessageEntityPre.ID: "pre", - types.MessageEntityTextUrl.ID: "text_link", - types.MessageEntityMentionName.ID: "text_mention", - types.MessageEntityPhone.ID: "phone_number" -} - - -def parse_entities(entities: list, users: dict) -> list: - output_entities = [] - - for entity in entities: - entity_type = ENTITIES.get(entity.ID, None) - - if entity_type: - output_entities.append( - pyrogram_types.MessageEntity( - type=entity_type, - offset=entity.offset, - length=entity.length, - url=getattr(entity, "url", None), - user=parse_user( - users.get( - getattr(entity, "user_id", None), - None - ) - ) - ) - ) - - return output_entities - - -def parse_chat_photo(photo): - if not isinstance(photo, (types.UserProfilePhoto, types.ChatPhoto)): - return None - - if not isinstance(photo.photo_small, types.FileLocation): - return None - - if not isinstance(photo.photo_big, types.FileLocation): - return None - - photo_id = getattr(photo, "photo_id", 0) - loc_small = photo.photo_small - loc_big = photo.photo_big - - return pyrogram_types.ChatPhoto( - small_file_id=encode( - pack( - " pyrogram_types.UserStatus or None: - if is_bot: - return None - - status = pyrogram_types.UserStatus(user_id) - - if isinstance(user_status, types.UserStatusOnline): - status.online = True - status.date = user_status.expires - elif isinstance(user_status, types.UserStatusOffline): - status.offline = True - status.date = user_status.was_online - elif isinstance(user_status, types.UserStatusRecently): - status.recently = True - elif isinstance(user_status, types.UserStatusLastWeek): - status.within_week = True - elif isinstance(user_status, types.UserStatusLastMonth): - status.within_month = True - else: - status.long_time_ago = True - - return status - - -def parse_user(user: types.User) -> pyrogram_types.User or None: - return pyrogram_types.User( - id=user.id, - is_self=user.is_self, - is_contact=user.contact, - is_mutual_contact=user.mutual_contact, - is_deleted=user.deleted, - is_bot=user.bot, - first_name=user.first_name, - last_name=user.last_name, - username=user.username, - language_code=user.lang_code, - phone_number=user.phone, - photo=parse_chat_photo(user.photo), - status=parse_user_status(user.status, is_bot=user.bot), - restriction_reason=user.restriction_reason - ) if user else None - - -def parse_chat(message: types.Message, users: dict, chats: dict) -> pyrogram_types.Chat: - if isinstance(message.to_id, types.PeerUser): - return parse_user_chat(users[message.to_id.user_id if message.out else message.from_id]) - elif isinstance(message.to_id, types.PeerChat): - return parse_chat_chat(chats[message.to_id.chat_id]) - else: - return parse_channel_chat(chats[message.to_id.channel_id]) - - -def parse_user_chat(user: types.User) -> pyrogram_types.Chat: - return pyrogram_types.Chat( - id=user.id, - type="private", - username=user.username, - first_name=user.first_name, - last_name=user.last_name, - photo=parse_chat_photo(user.photo), - restriction_reason=user.restriction_reason - ) - - -def parse_chat_chat(chat: types.Chat) -> pyrogram_types.Chat: - admins_enabled = getattr(chat, "admins_enabled", None) - - if admins_enabled is not None: - admins_enabled = not admins_enabled - - return pyrogram_types.Chat( - id=-chat.id, - type="group", - title=chat.title, - all_members_are_administrators=admins_enabled, - photo=parse_chat_photo(getattr(chat, "photo", None)) - ) - - -def parse_channel_chat(channel: types.Channel) -> pyrogram_types.Chat: - return pyrogram_types.Chat( - id=int("-100" + str(channel.id)), - type="supergroup" if channel.megagroup else "channel", - title=channel.title, - username=getattr(channel, "username", None), - photo=parse_chat_photo(getattr(channel, "photo", None)), - restriction_reason=getattr(channel, "restriction_reason", None) - ) - - -def parse_thumb(thumb: types.PhotoSize or types.PhotoCachedSize) -> pyrogram_types.PhotoSize or None: - if isinstance(thumb, (types.PhotoSize, types.PhotoCachedSize)): - loc = thumb.location - - if isinstance(thumb, types.PhotoSize): - file_size = thumb.size - else: - file_size = len(thumb.bytes) - - if isinstance(loc, types.FileLocation): - return pyrogram_types.PhotoSize( - file_id=encode( - pack( - " bytes: @@ -281,507 +57,6 @@ def encode(s: bytes) -> str: return b64encode(r, b"-_").decode().rstrip("=") -# TODO: Reorganize code, maybe split parts as well -def parse_messages( - client, - messages: list or types.Message or types.MessageService or types.MessageEmpty, - users: dict, - chats: dict, - replies: int = 1 -) -> pyrogram_types.Message or list: - is_list = isinstance(messages, list) - messages = messages if is_list else [messages] - parsed_messages = [] - - for message in messages: - if isinstance(message, types.Message): - entities = parse_entities(message.entities, users) - - forward_from = None - forward_from_chat = None - forward_from_message_id = None - forward_signature = None - forward_date = None - - forward_header = message.fwd_from # type: types.MessageFwdHeader - - if forward_header: - forward_date = forward_header.date - - if forward_header.from_id: - forward_from = parse_user(users[forward_header.from_id]) - else: - forward_from_chat = parse_channel_chat(chats[forward_header.channel_id]) - forward_from_message_id = forward_header.channel_post - forward_signature = forward_header.post_author - - photo = None - location = None - contact = None - venue = None - audio = None - voice = None - animation = None - video = None - video_note = None - sticker = None - document = None - web_page = None - - media = message.media - - if media: - if isinstance(media, types.MessageMediaPhoto): - photo = media.photo - - if isinstance(photo, types.Photo): - sizes = photo.sizes - photo_sizes = [] - - for size in sizes: - if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)): - loc = size.location - - if isinstance(size, types.PhotoSize): - file_size = size.size - else: - file_size = len(size.bytes) - - if isinstance(loc, types.FileLocation): - photo_size = pyrogram_types.PhotoSize( - file_id=encode( - pack( - " pyrogram_types.Messages: - messages = update.messages - channel_id = getattr(update, "channel_id", None) - - parsed_messages = [] - - for message in messages: - parsed_messages.append( - pyrogram_types.Message( - message_id=message, - chat=(pyrogram_types.Chat(id=int("-100" + str(channel_id)), type="channel") - if channel_id is not None - else None) - ) - ) - - return pyrogram_types.Messages(len(parsed_messages), parsed_messages) - - def get_peer_id(input_peer) -> int: return ( input_peer.user_id if isinstance(input_peer, types.InputPeerUser) @@ -807,255 +82,3 @@ def get_offset_date(dialogs): return m.date else: return 0 - - -def parse_profile_photos(photos): - if isinstance(photos, types.photos.Photos): - total_count = len(photos.photos) - else: - total_count = photos.count - - user_profile_photos = [] - - for photo in photos.photos: - if isinstance(photo, types.Photo): - sizes = photo.sizes - photo_sizes = [] - - for size in sizes: - if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)): - loc = size.location - - if isinstance(size, types.PhotoSize): - file_size = size.size - else: - file_size = len(size.bytes) - - if isinstance(loc, types.FileLocation): - photo_size = pyrogram_types.PhotoSize( - file_id=encode( - pack( - " Date: Tue, 18 Dec 2018 08:48:20 +0100 Subject: [PATCH 083/326] Ditch raw attribute. There's no use for it now --- pyrogram/client/types/bots/callback_query.py | 7 ++--- pyrogram/client/types/bots/force_reply.py | 4 +-- .../types/bots/inline_keyboard_button.py | 28 +++++++++---------- .../types/bots/inline_keyboard_markup.py | 6 ++-- pyrogram/client/types/bots/keyboard_button.py | 16 +++++------ .../types/bots/reply_keyboard_markup.py | 4 +-- .../types/bots/reply_keyboard_remove.py | 4 +-- .../types/messages_and_media/animation.py | 7 ++--- .../client/types/messages_and_media/audio.py | 7 ++--- .../types/messages_and_media/contact.py | 7 ++--- .../types/messages_and_media/document.py | 7 ++--- .../types/messages_and_media/location.py | 7 ++--- .../types/messages_and_media/message.py | 16 ++++------- .../messages_and_media/message_entity.py | 7 ++--- .../types/messages_and_media/messages.py | 16 ++++------- .../client/types/messages_and_media/photo.py | 10 +++---- .../types/messages_and_media/photo_size.py | 7 ++--- .../types/messages_and_media/sticker.py | 7 ++--- .../messages_and_media/user_profile_photos.py | 7 ++--- .../client/types/messages_and_media/venue.py | 7 ++--- .../client/types/messages_and_media/video.py | 7 ++--- .../types/messages_and_media/video_note.py | 7 +++-- .../client/types/messages_and_media/voice.py | 7 ++--- pyrogram/client/types/pyrogram_type.py | 3 +- pyrogram/client/types/user_and_chats/chat.py | 10 +++---- .../types/user_and_chats/chat_member.py | 14 +++++----- .../types/user_and_chats/chat_members.py | 7 ++--- .../client/types/user_and_chats/chat_photo.py | 11 ++++---- .../client/types/user_and_chats/dialog.py | 7 ++--- .../client/types/user_and_chats/dialogs.py | 7 ++--- pyrogram/client/types/user_and_chats/user.py | 7 ++--- .../types/user_and_chats/user_status.py | 6 ++-- 32 files changed, 122 insertions(+), 152 deletions(-) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index 61ece31e..e586c736 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -56,9 +56,9 @@ class CallbackQuery(PyrogramType): """ - def __init__(self, *, client, raw, id: str, from_user, chat_instance: str, message=None, + def __init__(self, *, client, id: str, from_user, chat_instance: str, message=None, inline_message_id: str = None, data: bytes = None, game_short_name: str = None): - super().__init__(client, raw) + super().__init__(client) self.id = id self.from_user = from_user @@ -103,8 +103,7 @@ class CallbackQuery(PyrogramType): chat_instance=str(callback_query.chat_instance), data=callback_query.data, game_short_name=callback_query.game_short_name, - client=client, - raw=callback_query + client=client ) def answer(self, text: str = None, show_alert: bool = None, url: str = None, cache_time: int = 0): diff --git a/pyrogram/client/types/bots/force_reply.py b/pyrogram/client/types/bots/force_reply.py index 6d91ff5f..eb3a8729 100644 --- a/pyrogram/client/types/bots/force_reply.py +++ b/pyrogram/client/types/bots/force_reply.py @@ -34,12 +34,12 @@ class ForceReply(PyrogramType): """ def __init__(self, selective: bool = None): - super().__init__(None, None) + super().__init__(None) self.selective = selective @staticmethod - def read(o, *args): + def read(o): return ForceReply( selective=o.selective ) diff --git a/pyrogram/client/types/bots/inline_keyboard_button.py b/pyrogram/client/types/bots/inline_keyboard_button.py index 34718fd1..0fd1355a 100644 --- a/pyrogram/client/types/bots/inline_keyboard_button.py +++ b/pyrogram/client/types/bots/inline_keyboard_button.py @@ -55,7 +55,7 @@ class InlineKeyboardButton(PyrogramType): def __init__(self, text: str, callback_data: bytes = None, url: str = None, switch_inline_query: str = None, switch_inline_query_current_chat: str = None): - super().__init__(None, None) + super().__init__(None) self.text = text self.url = url @@ -66,29 +66,29 @@ class InlineKeyboardButton(PyrogramType): # self.pay = pay @staticmethod - def read(b, *args): - if isinstance(b, KeyboardButtonUrl): + def read(o): + if isinstance(o, KeyboardButtonUrl): return InlineKeyboardButton( - text=b.text, - url=b.url + text=o.text, + url=o.url ) - if isinstance(b, KeyboardButtonCallback): + if isinstance(o, KeyboardButtonCallback): return InlineKeyboardButton( - text=b.text, - callback_data=b.data + text=o.text, + callback_data=o.data ) - if isinstance(b, KeyboardButtonSwitchInline): - if b.same_peer: + if isinstance(o, KeyboardButtonSwitchInline): + if o.same_peer: return InlineKeyboardButton( - text=b.text, - switch_inline_query_current_chat=b.query + text=o.text, + switch_inline_query_current_chat=o.query ) else: return InlineKeyboardButton( - text=b.text, - switch_inline_query=b.query + text=o.text, + switch_inline_query=o.query ) def write(self): diff --git a/pyrogram/client/types/bots/inline_keyboard_markup.py b/pyrogram/client/types/bots/inline_keyboard_markup.py index bde31ec4..b4eb936f 100644 --- a/pyrogram/client/types/bots/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots/inline_keyboard_markup.py @@ -30,15 +30,15 @@ class InlineKeyboardMarkup(PyrogramType): """ def __init__(self, inline_keyboard: list): - super().__init__(None, None) + super().__init__(None) self.inline_keyboard = inline_keyboard @staticmethod - def read(kb, *args): + def read(o): inline_keyboard = [] - for i in kb.rows: + for i in o.rows: row = [] for j in i.buttons: diff --git a/pyrogram/client/types/bots/keyboard_button.py b/pyrogram/client/types/bots/keyboard_button.py index 0566fc20..ceb7a2b7 100644 --- a/pyrogram/client/types/bots/keyboard_button.py +++ b/pyrogram/client/types/bots/keyboard_button.py @@ -41,26 +41,26 @@ class KeyboardButton(PyrogramType): """ def __init__(self, text: str, request_contact: bool = None, request_location: bool = None): - super().__init__(None, None) + super().__init__(None) self.text = text self.request_contact = request_contact self.request_location = request_location @staticmethod - def read(b, *args): - if isinstance(b, RawKeyboardButton): - return b.text + def read(o): + if isinstance(o, RawKeyboardButton): + return o.text - if isinstance(b, KeyboardButtonRequestPhone): + if isinstance(o, KeyboardButtonRequestPhone): return KeyboardButton( - text=b.text, + text=o.text, request_contact=True ) - if isinstance(b, KeyboardButtonRequestGeoLocation): + if isinstance(o, KeyboardButtonRequestGeoLocation): return KeyboardButton( - text=b.text, + text=o.text, request_location=True ) diff --git a/pyrogram/client/types/bots/reply_keyboard_markup.py b/pyrogram/client/types/bots/reply_keyboard_markup.py index 77b72562..fb0817ac 100644 --- a/pyrogram/client/types/bots/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots/reply_keyboard_markup.py @@ -49,7 +49,7 @@ class ReplyKeyboardMarkup(PyrogramType): def __init__(self, keyboard: list, resize_keyboard: bool = None, one_time_keyboard: bool = None, selective: bool = None): - super().__init__(None, None) + super().__init__(None) self.keyboard = keyboard self.resize_keyboard = resize_keyboard @@ -57,7 +57,7 @@ class ReplyKeyboardMarkup(PyrogramType): self.selective = selective @staticmethod - def read(kb, *args): + def read(kb): keyboard = [] for i in kb.rows: diff --git a/pyrogram/client/types/bots/reply_keyboard_remove.py b/pyrogram/client/types/bots/reply_keyboard_remove.py index 82adb859..7f82fd6e 100644 --- a/pyrogram/client/types/bots/reply_keyboard_remove.py +++ b/pyrogram/client/types/bots/reply_keyboard_remove.py @@ -36,12 +36,12 @@ class ReplyKeyboardRemove(PyrogramType): """ def __init__(self, selective: bool = None): - super().__init__(None, None) + super().__init__(None) self.selective = selective @staticmethod - def read(o, *args): + def read(o): return ReplyKeyboardRemove( selective=o.selective ) diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index c270dfa9..45beefc3 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -56,9 +56,9 @@ class Animation(PyrogramType): Date the animation was sent in Unix time. """ - def __init__(self, *, client, raw, file_id: str, width: int, height: int, duration: int, thumb=None, + def __init__(self, *, client, file_id: str, width: int, height: int, duration: int, thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None): - super().__init__(client, raw) + super().__init__(client) self.file_id = file_id self.thumb = thumb @@ -91,6 +91,5 @@ class Animation(PyrogramType): file_size=animation.size, file_name=file_name, date=animation.date, - client=client, - raw=animation + client=client ) diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index bc5cf166..9d001615 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -56,10 +56,10 @@ class Audio(PyrogramType): Title of the audio as defined by sender or by audio tags. """ - def __init__(self, *, client, raw, file_id: str, duration: int, thumb=None, file_name: str = None, + def __init__(self, *, client, file_id: str, duration: int, thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, performer: str = None, title: str = None): - super().__init__(client, raw) + super().__init__(client) self.file_id = file_id self.thumb = thumb @@ -91,6 +91,5 @@ class Audio(PyrogramType): thumb=PhotoSize.parse(client, audio.thumb), file_name=file_name, date=audio.date, - client=client, - raw=audio + client=client ) diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index 0525c463..2195df45 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -40,9 +40,9 @@ class Contact(PyrogramType): Additional data about the contact in the form of a vCard. """ - def __init__(self, *, client, raw, phone_number: str, first_name: str, last_name: str = None, user_id: int = None, + def __init__(self, *, client, phone_number: str, first_name: str, last_name: str = None, user_id: int = None, vcard: str = None): - super().__init__(client, raw) + super().__init__(client) self.phone_number = phone_number self.first_name = first_name @@ -58,6 +58,5 @@ class Contact(PyrogramType): last_name=contact.last_name or None, vcard=contact.vcard or None, user_id=contact.user_id or None, - client=client, - raw=contact + client=client ) diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index 0526a190..6d124e2a 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -47,9 +47,9 @@ class Document(PyrogramType): Date the document was sent in Unix time. """ - def __init__(self, *, client, raw, file_id: str, thumb=None, file_name: str = None, mime_type: str = None, + def __init__(self, *, client, file_id: str, thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None): - super().__init__(client, raw) + super().__init__(client) self.file_id = file_id self.thumb = thumb @@ -75,6 +75,5 @@ class Document(PyrogramType): mime_type=document.mime_type, file_size=document.size, date=document.date, - client=client, - raw=document + client=client ) diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index 853a4c90..3f5bb059 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -31,8 +31,8 @@ class Location(PyrogramType): Latitude as defined by sender. """ - def __init__(self, *, client, raw, longitude: float, latitude: float, ): - super().__init__(client, raw) + def __init__(self, *, client, longitude: float, latitude: float, ): + super().__init__(client) self.longitude = longitude self.latitude = latitude @@ -43,6 +43,5 @@ class Location(PyrogramType): return Location( longitude=geo_point.long, latitude=geo_point.lat, - client=client, - raw=geo_point + client=client ) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 66072d22..c0c7edd1 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -224,7 +224,7 @@ class Message(PyrogramType): # TODO: Add game missing field. Also invoice, successful_payment, connected_website - def __init__(self, *, client, raw, message_id: int, date: int = None, chat=None, from_user=None, forward_from=None, + def __init__(self, *, client, message_id: int, date: int = None, chat=None, from_user=None, forward_from=None, forward_from_chat=None, forward_from_message_id: int = None, forward_signature: str = None, forward_date: int = None, reply_to_message=None, mentioned=None, empty=None, service=None, media=None, edit_date: int = None, media_group_id: str = None, author_signature: str = None, text: str = None, @@ -236,7 +236,7 @@ class Message(PyrogramType): channel_chat_created: bool = None, migrate_to_chat_id: int = None, migrate_from_chat_id: int = None, pinned_message=None, views: int = None, via_bot=None, outgoing: bool = None, matches: list = None, command: list = None, reply_markup=None): - super().__init__(client, raw) + super().__init__(client) self.message_id = message_id self.date = date @@ -293,11 +293,7 @@ class Message(PyrogramType): def parse(client, message: types.Message or types.MessageService or types.MessageEmpty, users: dict, chats: dict, replies: int = 1): if isinstance(message, types.MessageEmpty): - return Message( - message_id=message.id, - client=client, - raw=message - ) + return Message(message_id=message.id, empty=True, client=client) if isinstance(message, types.MessageService): action = message.action @@ -348,8 +344,7 @@ class Message(PyrogramType): migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None, group_chat_created=group_chat_created, channel_chat_created=channel_chat_created, - client=client, - raw=message + client=client # TODO: supergroup_chat_created ) @@ -504,8 +499,7 @@ class Message(PyrogramType): via_bot=User.parse(client, users.get(message.via_bot_id, None)), outgoing=message.out, reply_markup=reply_markup, - client=client, - raw=message + client=client ) if message.reply_to_msg_id and replies: diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index c72df95d..9f894f75 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -61,8 +61,8 @@ class MessageEntity(PyrogramType): types.MessageEntityPhone.ID: "phone_number" } - def __init__(self, *, client, raw, type: str, offset: int, length: int, url: str = None, user=None): - super().__init__(client, raw) + def __init__(self, *, client, type: str, offset: int, length: int, url: str = None, user=None): + super().__init__(client) self.type = type self.offset = offset @@ -83,6 +83,5 @@ class MessageEntity(PyrogramType): length=entity.length, url=getattr(entity, "url", None), user=User.parse(client, users.get(getattr(entity, "user_id", None), None)), - client=client, - raw=entity + client=client ) diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 444c88ce..6d2d666a 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -33,8 +33,8 @@ class Messages(PyrogramType): Requested messages. """ - def __init__(self, *, client, raw, total_count: int, messages: list): - super().__init__(client, raw) + def __init__(self, *, client, total_count: int, messages: list): + super().__init__(client) self.total_count = total_count self.messages = messages @@ -47,8 +47,7 @@ class Messages(PyrogramType): return Messages( total_count=getattr(messages, "count", len(messages.messages)), messages=[Message.parse(client, message, users, chats) for message in messages.messages], - client=client, - raw=messages + client=client ) @staticmethod @@ -65,17 +64,14 @@ class Messages(PyrogramType): chat=Chat( id=int("-100" + str(channel_id)), type="channel", - client=client, - raw=None + client=client ) if channel_id is not None else None, - client=client, - raw=None + client=client ) ) return Messages( total_count=len(parsed_messages), messages=parsed_messages, - client=client, - raw=update + client=client ) diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 96a1e19a..01edd0fc 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -39,8 +39,8 @@ class Photo(PyrogramType): Available sizes of this photo. """ - def __init__(self, *, client, raw, id: str, date: int, sizes: list): - super().__init__(client, raw) + def __init__(self, *, client, id: str, date: int, sizes: list): + super().__init__(client) self.id = id self.date = date @@ -74,8 +74,7 @@ class Photo(PyrogramType): width=raw_size.w, height=raw_size.h, file_size=file_size, - client=client, - raw=raw_size + client=client ) sizes.append(size) @@ -91,6 +90,5 @@ class Photo(PyrogramType): ).decode().rstrip("="), date=photo.date, sizes=sizes, - client=client, - raw=photo + client=client ) diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index 1e37399c..efc9def9 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -40,8 +40,8 @@ class PhotoSize(PyrogramType): File size. """ - def __init__(self, *, client, raw, file_id: str, width: int, height: int, file_size: int): - super().__init__(client, raw) + def __init__(self, *, client, file_id: str, width: int, height: int, file_size: int): + super().__init__(client) self.file_id = file_id self.width = width @@ -71,6 +71,5 @@ class PhotoSize(PyrogramType): width=photo_size.w, height=photo_size.h, file_size=file_size, - client=client, - raw=photo_size + client=client ) diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index f2414311..9892694a 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -63,10 +63,10 @@ class Sticker(PyrogramType): # TODO: Add mask position - def __init__(self, *, client, raw, file_id: str, width: int, height: int, thumb=None, file_name: str = None, + def __init__(self, *, client, file_id: str, width: int, height: int, thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, emoji: str = None, set_name: str = None, mask_position=None): - super().__init__(client, raw) + super().__init__(client) self.file_id = file_id self.thumb = thumb @@ -123,6 +123,5 @@ class Sticker(PyrogramType): mime_type=sticker.mime_type, file_name=file_name, date=sticker.date, - client=client, - raw=sticker + client=client ) diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index 2196608f..23b3200c 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -31,8 +31,8 @@ class UserProfilePhotos(PyrogramType): Requested profile pictures. """ - def __init__(self, *, client, raw, total_count: int, photos: list): - super().__init__(client, raw) + def __init__(self, *, client, total_count: int, photos: list): + super().__init__(client) self.total_count = total_count self.photos = photos @@ -42,6 +42,5 @@ class UserProfilePhotos(PyrogramType): return UserProfilePhotos( total_count=getattr(photos, "count", len(photos.photos)), photos=[Photo.parse(client, photo) for photo in photos.photos], - client=client, - raw=photos + client=client ) diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index ee533d59..c206a2a0 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -43,9 +43,9 @@ class Venue(PyrogramType): """ - def __init__(self, *, client, raw, location, title: str, address: str, foursquare_id: str = None, + def __init__(self, *, client, location, title: str, address: str, foursquare_id: str = None, foursquare_type: str = None): - super().__init__(client, raw) + super().__init__(client) self.location = location self.title = title @@ -61,6 +61,5 @@ class Venue(PyrogramType): address=venue.address, foursquare_id=venue.venue_id or None, foursquare_type=venue.venue_type, - client=client, - raw=venue + client=client ) diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 9ca54b69..7d995ea8 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -56,9 +56,9 @@ class Video(PyrogramType): Date the video was sent in Unix time. """ - def __init__(self, *, client, raw, file_id: str, width: int, height: int, duration: int, thumb=None, + def __init__(self, *, client, file_id: str, width: int, height: int, duration: int, thumb=None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None): - super().__init__(client, raw) + super().__init__(client) self.file_id = file_id self.thumb = thumb @@ -90,6 +90,5 @@ class Video(PyrogramType): file_size=video.size, file_name=file_name, date=video.date, - client=client, - raw=video + client=client ) diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index 61a86454..688bca19 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -50,9 +50,9 @@ class VideoNote(PyrogramType): Date the video note was sent in Unix time. """ - def __init__(self, *, client, raw, file_id: str, length: int, duration: int, thumb=None, mime_type: str = None, + def __init__(self, *, client, file_id: str, length: int, duration: int, thumb=None, mime_type: str = None, file_size: int = None, date: int = None): - super().__init__(client, raw) + super().__init__(client) self.file_id = file_id self.thumb = thumb @@ -79,5 +79,6 @@ class VideoNote(PyrogramType): thumb=PhotoSize.parse(client, video_note.thumb), file_size=video_note.size, mime_type=video_note.mime_type, - date=video_note.date + date=video_note.date, + client=client ) diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index 88cc269e..cce70a31 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -46,9 +46,9 @@ class Voice(PyrogramType): Date the voice was sent in Unix time. """ - def __init__(self, *, client, raw, file_id: str, duration: int, waveform: bytes = None, mime_type: str = None, + def __init__(self, *, client, file_id: str, duration: int, waveform: bytes = None, mime_type: str = None, file_size: int = None, date: int = None): - super().__init__(client, raw) + super().__init__(client) self.file_id = file_id self.duration = duration @@ -74,6 +74,5 @@ class Voice(PyrogramType): file_size=voice.size, waveform=attributes.waveform, date=voice.date, - client=client, - raw=voice + client=client ) diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py index 880c452a..3c2babf8 100644 --- a/pyrogram/client/types/pyrogram_type.py +++ b/pyrogram/client/types/pyrogram_type.py @@ -21,9 +21,8 @@ from json import dumps, JSONEncoder class PyrogramType: - def __init__(self, client, raw): + def __init__(self, client): self._client = client - self._raw = raw def __str__(self): return dumps(self, cls=Encoder, indent=4) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index a9564836..b9d0d796 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -76,11 +76,11 @@ class Chat(PyrogramType): The reason why this chat might be unavailable to some users. """ - def __init__(self, *, client, raw, id: int, type: str, title: str = None, username: str = None, + def __init__(self, *, client, id: int, type: str, title: str = None, username: str = None, first_name: str = None, last_name: str = None, all_members_are_administrators: bool = None, photo=None, description: str = None, invite_link: str = None, pinned_message=None, sticker_set_name: str = None, can_set_sticker_set: bool = None, members_count: int = None, restriction_reason: str = None): - super().__init__(client, raw) + super().__init__(client) self.id = id self.type = type @@ -108,7 +108,7 @@ class Chat(PyrogramType): last_name=user.last_name, photo=ChatPhoto.parse(client, user.photo), restriction_reason=user.restriction_reason, - client=client, raw=user + client=client ) @staticmethod @@ -124,7 +124,7 @@ class Chat(PyrogramType): title=chat.title, all_members_are_administrators=admins_enabled, photo=ChatPhoto.parse(client, getattr(chat, "photo", None)), - client=client, raw=chat + client=client ) @staticmethod @@ -136,7 +136,7 @@ class Chat(PyrogramType): username=getattr(channel, "username", None), photo=ChatPhoto.parse(client, getattr(channel, "photo", None)), restriction_reason=getattr(channel, "restriction_reason", None), - client=client, raw=channel + client=client ) @staticmethod diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 11b2c9a0..27eeb897 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -79,13 +79,13 @@ class ChatMember(PyrogramType): Restricted only. True, if user may add web page previews to his messages, implies can_send_media_messages. """ - def __init__(self, *, client, raw, user, status: str, until_date: int = None, can_be_edited: bool = None, + def __init__(self, *, client, user, status: str, until_date: int = None, can_be_edited: bool = None, can_change_info: bool = None, can_post_messages: bool = None, can_edit_messages: bool = None, can_delete_messages: bool = None, can_invite_users: bool = None, can_restrict_members: bool = None, can_pin_messages: bool = None, can_promote_members: bool = None, can_send_messages: bool = None, can_send_media_messages: bool = None, can_send_other_messages: bool = None, can_add_web_page_previews: bool = None): - super().__init__(client, raw) + super().__init__(client) self.user = user self.status = status @@ -107,13 +107,13 @@ class ChatMember(PyrogramType): @staticmethod def parse(client, member, user) -> "ChatMember": if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)): - return ChatMember(user=user, status="member", client=client, raw=member) + return ChatMember(user=user, status="member", client=client) if isinstance(member, (types.ChannelParticipantCreator, types.ChatParticipantCreator)): - return ChatMember(user=user, status="creator", client=client, raw=member) + return ChatMember(user=user, status="creator", client=client) if isinstance(member, types.ChatParticipantAdmin): - return ChatMember(user=user, status="administrator", client=client, raw=member) + return ChatMember(user=user, status="administrator", client=client) if isinstance(member, types.ChannelParticipantAdmin): rights = member.admin_rights @@ -130,7 +130,7 @@ class ChatMember(PyrogramType): can_restrict_members=rights.ban_users, can_pin_messages=rights.pin_messages, can_promote_members=rights.add_admins, - client=client, raw=member + client=client ) if isinstance(member, types.ChannelParticipantBanned): @@ -140,7 +140,7 @@ class ChatMember(PyrogramType): user=user, status="kicked" if rights.view_messages else "restricted", until_date=0 if rights.until_date == (1 << 31) - 1 else rights.until_date, - client=client, raw=member + client=client ) if chat_member.status == "restricted": diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index 5fa8275d..ba28b23d 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -33,8 +33,8 @@ class ChatMembers(PyrogramType): Requested chat members. """ - def __init__(self, *, client, raw, total_count: int, chat_members: list): - super().__init__(client, raw) + def __init__(self, *, client, total_count: int, chat_members: list): + super().__init__(client) self.total_count = total_count self.chat_members = chat_members @@ -58,6 +58,5 @@ class ChatMembers(PyrogramType): return ChatMembers( total_count=total_count, chat_members=chat_members, - client=client, - raw=members + client=client ) diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index b998cf90..91c77b06 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -19,11 +19,11 @@ from struct import pack from pyrogram.api import types -from pyrogram.api.core import Object +from ..pyrogram_type import PyrogramType from ...ext.utils import encode -class ChatPhoto(Object): +class ChatPhoto(PyrogramType): """This object represents a chat photo. Args: @@ -34,9 +34,9 @@ class ChatPhoto(Object): Unique file identifier of big (640x640) chat photo. This file_id can be used only for photo download. """ - ID = 0xb0700015 + def __init__(self, *, client, small_file_id: str, big_file_id: str): + super().__init__(client) - def __init__(self, *, client, raw, small_file_id: str, big_file_id: str): self.small_file_id = small_file_id self.big_file_id = big_file_id @@ -68,6 +68,5 @@ class ChatPhoto(Object): 1, loc_big.dc_id, photo_id, 0, loc_big.volume_id, loc_big.secret, loc_big.local_id ) ), - client=client, - raw=chat_photo + client=client ) diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index 0fc17fe0..d617c11e 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -44,9 +44,9 @@ class Dialog(PyrogramType): True, if the dialog is pinned. """ - def __init__(self, *, client, raw, chat, top_message, unread_messages_count: int, unread_mentions_count: int, + def __init__(self, *, client, chat, top_message, unread_messages_count: int, unread_mentions_count: int, unread_mark: bool, is_pinned: bool): - super().__init__(client, raw) + super().__init__(client) self.chat = chat self.top_message = top_message @@ -73,6 +73,5 @@ class Dialog(PyrogramType): unread_mentions_count=dialog.unread_mentions_count, unread_mark=dialog.unread_mark, is_pinned=dialog.pinned, - client=client, - raw=dialog + client=client ) diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py index 8261e97c..21eda2f0 100644 --- a/pyrogram/client/types/user_and_chats/dialogs.py +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -33,8 +33,8 @@ class Dialogs(PyrogramType): Requested dialogs. """ - def __init__(self, *, client, raw, total_count: int, dialogs: list): - super().__init__(client, raw) + def __init__(self, *, client, total_count: int, dialogs: list): + super().__init__(client) self.total_count = total_count self.dialogs = dialogs @@ -64,6 +64,5 @@ class Dialogs(PyrogramType): return Dialogs( total_count=getattr(dialogs, "count", len(dialogs.dialogs)), dialogs=[Dialog.parse(client, dialog, messages, users, chats) for dialog in dialogs.dialogs], - client=client, - raw=dialogs + client=client ) diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index ed3ea16d..66d68121 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -69,11 +69,11 @@ class User(PyrogramType): The reason why this bot might be unavailable to some users. """ - def __init__(self, *, client, raw, id: int, is_self: bool, is_contact: bool, is_mutual_contact: bool, + def __init__(self, *, client, id: int, is_self: bool, is_contact: bool, is_mutual_contact: bool, is_deleted: bool, is_bot: bool, first_name: str, last_name: str = None, status=None, username: str = None, language_code: str = None, phone_number: str = None, photo=None, restriction_reason: str = None): - super().__init__(client, raw) + super().__init__(client) self.id = id self.is_self = is_self @@ -110,6 +110,5 @@ class User(PyrogramType): phone_number=user.phone, photo=ChatPhoto.parse(client, user.photo), restriction_reason=user.restriction_reason, - client=client, - raw=user + client=client ) diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index d9e27498..8102ffe0 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -62,10 +62,10 @@ class UserStatus(PyrogramType): always shown to blocked users), None otherwise. """ - def __init__(self, *, client, raw, user_id: int, online: bool = None, offline: bool = None, date: int = None, + def __init__(self, *, client, user_id: int, online: bool = None, offline: bool = None, date: int = None, recently: bool = None, within_week: bool = None, within_month: bool = None, long_time_ago: bool = None): - super().__init__(client, raw) + super().__init__(client) self.user_id = user_id self.online = online @@ -81,7 +81,7 @@ class UserStatus(PyrogramType): if is_bot: return None - status = UserStatus(user_id=user_id, client=client, raw=user_status) + status = UserStatus(user_id=user_id, client=client) if isinstance(user_status, types.UserStatusOnline): status.online = True From 27052e31ca8f853bebab7f19727d59e3347db08a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 18 Dec 2018 09:45:49 +0100 Subject: [PATCH 084/326] Fix all those unresolved references caused by the refactor --- .../client/methods/chats/get_chat_members.py | 4 ++-- .../client/methods/chats/kick_chat_member.py | 4 ++-- .../methods/messages/edit_message_caption.py | 6 ++++-- .../methods/messages/edit_message_media.py | 3 ++- .../messages/edit_message_reply_markup.py | 5 +++-- .../methods/messages/edit_message_text.py | 5 +++-- .../methods/messages/forward_messages.py | 5 +++-- .../client/methods/messages/get_messages.py | 21 +++++++------------ .../client/methods/messages/send_animation.py | 3 ++- .../client/methods/messages/send_audio.py | 3 ++- .../client/methods/messages/send_contact.py | 5 +++-- .../client/methods/messages/send_document.py | 3 ++- .../client/methods/messages/send_location.py | 5 +++-- .../client/methods/messages/send_message.py | 14 ++++++++----- .../client/methods/messages/send_photo.py | 3 ++- .../client/methods/messages/send_sticker.py | 3 ++- .../client/methods/messages/send_venue.py | 5 +++-- .../client/methods/messages/send_video.py | 3 ++- .../methods/messages/send_video_note.py | 3 ++- .../client/methods/messages/send_voice.py | 3 ++- pyrogram/client/methods/users/get_me.py | 6 ++++-- .../methods/utilities/download_media.py | 9 +++++--- 22 files changed, 70 insertions(+), 51 deletions(-) diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index df1ca8b1..71728676 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -16,9 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import functions, types -from ...ext import BaseClient, utils import pyrogram +from pyrogram.api import functions, types +from ...ext import BaseClient class Filters: diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index 5bac80b8..292fdfd6 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types -from pyrogram.client.ext import utils from ...ext import BaseClient @@ -86,7 +86,7 @@ class KickChatMember(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index 0e12f72b..a79c21f4 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -16,8 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram + from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.ext import BaseClient class EditMessageCaption(BaseClient): @@ -68,7 +70,7 @@ class EditMessageCaption(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index ccbacac7..5d06c31b 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -21,6 +21,7 @@ import mimetypes import os import struct +import pyrogram from pyrogram.api import functions, types from pyrogram.api.errors import FileIdInvalid from pyrogram.client.ext import BaseClient, utils @@ -333,7 +334,7 @@ class EditMessageMedia(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py index 72796306..3b301dcb 100644 --- a/pyrogram/client/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.ext import BaseClient class EditMessageReplyMarkup(BaseClient): @@ -57,7 +58,7 @@ class EditMessageReplyMarkup(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index 144f5413..991ff049 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.ext import BaseClient class EditMessageText(BaseClient): @@ -73,7 +74,7 @@ class EditMessageText(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index a8783044..e2e10c93 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types -from ...ext import BaseClient, utils +from ...ext import BaseClient class ForwardMessages(BaseClient): @@ -77,7 +78,7 @@ class ForwardMessages(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): messages.append( - utils.parse_messages( + pyrogram.Message.parse( self, i.message, users, chats ) diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index 7411e22f..2e3930ae 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types -from ...ext import BaseClient, utils +from ...ext import BaseClient class GetMessages(BaseClient): @@ -48,10 +49,9 @@ class GetMessages(BaseClient): The number of subsequent replies to get for each message. Defaults to 1. Returns: - On success and in case *message_ids* or *reply_to_message_ids* was a list, the returned value will be a - list of the requested :obj:`Messages ` even if a list contains just one element, - otherwise if *message_ids* or *reply_to_message_ids* was an integer, the single requested - :obj:`Message ` is returned. + On success and in case *message_ids* or *reply_to_message_ids* was an iterable, the returned value will be a + :obj:`Messages ` even if a list contains just one element. Otherwise, if *message_ids* or + *reply_to_message_ids* was an integer, the single requested :obj:`Message ` is returned. Raises: :class:`Error ` in case of a Telegram RPC error. @@ -76,13 +76,6 @@ class GetMessages(BaseClient): else: rpc = functions.messages.GetMessages(id=ids) - r = self.send(rpc) + messages = pyrogram.Messages.parse(self, self.send(rpc)) - messages = utils.parse_messages( - self, r.messages, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats}, - replies=replies - ) - - return messages if is_iterable else messages[0] + return messages if is_iterable else messages.messages[0] diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index dbd29575..c86fb867 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -21,6 +21,7 @@ import mimetypes import os import struct +import pyrogram from pyrogram.api import functions, types from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.client.ext import BaseClient, utils @@ -184,7 +185,7 @@ class SendAnimation(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 613ec90c..12c65dec 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -21,6 +21,7 @@ import mimetypes import os import struct +import pyrogram from pyrogram.api import functions, types from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.client.ext import BaseClient, utils @@ -184,7 +185,7 @@ class SendAudio(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index b40321be..a5d88f97 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.ext import BaseClient class SendContact(BaseClient): @@ -86,7 +87,7 @@ class SendContact(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index f3f52178..66a65e9d 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -21,6 +21,7 @@ import mimetypes import os import struct +import pyrogram from pyrogram.api import functions, types from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.client.ext import BaseClient, utils @@ -165,7 +166,7 @@ class SendDocument(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py index 2bd3eff3..baa84966 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/client/methods/messages/send_location.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.ext import BaseClient class SendLocation(BaseClient): @@ -78,7 +79,7 @@ class SendLocation(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 78ce27e5..77af4db8 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -16,9 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types -from pyrogram.client import types as pyrogram_types -from ...ext import utils, BaseClient +from ...ext import BaseClient class SendMessage(BaseClient): @@ -83,9 +83,13 @@ class SendMessage(BaseClient): ) if isinstance(r, types.UpdateShortSentMessage): - return pyrogram_types.Message( + return pyrogram.Message( message_id=r.id, - chat=pyrogram_types.Chat(id=list(self.resolve_peer(chat_id).__dict__.values())[0], type="private"), + chat=pyrogram.Chat( + id=list(self.resolve_peer(chat_id).__dict__.values())[0], + type="private", + client=self + ), text=message, date=r.date, outgoing=r.out, @@ -95,7 +99,7 @@ class SendMessage(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 7f0c2d07..323c2fa5 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -20,6 +20,7 @@ import binascii import os import struct +import pyrogram from pyrogram.api import functions, types from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.client.ext import BaseClient, utils @@ -160,7 +161,7 @@ class SendPhoto(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 0b8f8073..fccfb0b5 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -20,6 +20,7 @@ import binascii import os import struct +import pyrogram from pyrogram.api import functions, types from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.client.ext import BaseClient, utils @@ -144,7 +145,7 @@ class SendSticker(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/client/methods/messages/send_venue.py index 30b848ea..1155ca79 100644 --- a/pyrogram/client/methods/messages/send_venue.py +++ b/pyrogram/client/methods/messages/send_venue.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.ext import BaseClient class SendVenue(BaseClient): @@ -100,7 +101,7 @@ class SendVenue(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index e8e4627a..47b15d59 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -21,6 +21,7 @@ import mimetypes import os import struct +import pyrogram from pyrogram.api import functions, types from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.client.ext import BaseClient, utils @@ -187,7 +188,7 @@ class SendVideo(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index de539a81..61ae0017 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -21,6 +21,7 @@ import mimetypes import os import struct +import pyrogram from pyrogram.api import functions, types from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.client.ext import BaseClient, utils @@ -163,7 +164,7 @@ class SendVideoNote(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 4be702d0..4882a87f 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -21,6 +21,7 @@ import mimetypes import os import struct +import pyrogram from pyrogram.api import functions, types from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.client.ext import BaseClient, utils @@ -163,7 +164,7 @@ class SendVoice(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return pyrogram.Message.parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index 009ef71e..c7d32968 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types -from ...ext import BaseClient, utils +from ...ext import BaseClient class GetMe(BaseClient): @@ -30,7 +31,8 @@ class GetMe(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ - return utils.parse_user( + return pyrogram.User.parse( + self, self.send( functions.users.GetFullUser( types.InputPeerSelf() diff --git a/pyrogram/client/methods/utilities/download_media.py b/pyrogram/client/methods/utilities/download_media.py index 9b6c554a..f0ad0396 100644 --- a/pyrogram/client/methods/utilities/download_media.py +++ b/pyrogram/client/methods/utilities/download_media.py @@ -84,7 +84,8 @@ class DownloadMedia(BaseClient): file_id=message.photo.sizes[-1].file_id, file_size=message.photo.sizes[-1].file_size, mime_type="", - date=message.photo.date + date=message.photo.date, + client=self ) elif message.audio: media = message.audio @@ -118,7 +119,8 @@ class DownloadMedia(BaseClient): file_id=message.sizes[-1].file_id, file_size=message.sizes[-1].file_size, mime_type="", - date=message.date + date=message.date, + client=self ) else: media = message @@ -126,7 +128,8 @@ class DownloadMedia(BaseClient): media = pyrogram_types.Document( file_id=message, file_size=0, - mime_type="" + mime_type="", + client=self ) else: raise ValueError(error_message) From 914dfca5749375ca2c6fe3c040e2b33aac6219b9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 18 Dec 2018 09:50:39 +0100 Subject: [PATCH 085/326] Reformat code and optimize imports --- pyrogram/client/client.py | 6 +++--- pyrogram/client/methods/chats/get_chat_member.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index bd132851..7003823d 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1135,13 +1135,13 @@ class Client(Methods, BaseClient): progress_args: tuple = ()): part_size = 512 * 1024 file_size = os.path.getsize(path) - + if file_size == 0: raise ValueError("File size equals to 0 B") - + if file_size > 1500 * 1024 * 1024: raise ValueError("Telegram doesn't support uploading files bigger than 1500 MiB") - + file_total_parts = int(math.ceil(file_size / part_size)) is_big = True if file_size > 10 * 1024 * 1024 else False is_missing_part = True if file_id is not None else False diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index b5e7cec7..b304be17 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -16,9 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types, errors from ...ext import BaseClient -import pyrogram class GetChatMember(BaseClient): From e203a5500f65c52c46c551fea5232092a888f9c5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 18 Dec 2018 10:07:40 +0100 Subject: [PATCH 086/326] Don't treat messages containing web page previews as media --- pyrogram/client/types/messages_and_media/message.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index c0c7edd1..00567491 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -447,6 +447,7 @@ class Message(PyrogramType): document = pyrogram.Document.parse(client, doc, file_name) elif isinstance(media, types.MessageMediaWebPage): web_page = True + media = None else: media = None From c9bcf93cf7ddf8667427ab5cacd00a787152598e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 16 Dec 2018 23:42:31 +0100 Subject: [PATCH 087/326] Add extra checks in case sign-in or sign-up fails --- pyrogram/client/client.py | 40 ++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index bd132851..1560da9d 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -45,7 +45,7 @@ from pyrogram.api.errors import ( PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty, PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, - VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate) + VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied) from pyrogram.client.handlers import DisconnectHandler from pyrogram.client.handlers.handler import Handler from pyrogram.crypto import AES @@ -529,13 +529,18 @@ class Client(Methods, BaseClient): try: if phone_registered: - r = self.send( - functions.auth.SignIn( - self.phone_number, - phone_code_hash, - self.phone_code + try: + r = self.send( + functions.auth.SignIn( + self.phone_number, + phone_code_hash, + self.phone_code + ) ) - ) + except PhoneNumberUnoccupied: + print("PHONE NON-OCCUPIED") + phone_registered = False + continue else: try: self.send( @@ -551,15 +556,20 @@ class Client(Methods, BaseClient): self.first_name = self.first_name if self.first_name is not None else input("First name: ") self.last_name = self.last_name if self.last_name is not None else input("Last name: ") - r = self.send( - functions.auth.SignUp( - self.phone_number, - phone_code_hash, - self.phone_code, - self.first_name, - self.last_name + try: + r = self.send( + functions.auth.SignUp( + self.phone_number, + phone_code_hash, + self.phone_code, + self.first_name, + self.last_name + ) ) - ) + except PhoneNumberOccupied: + print("PHONE OCCUPIED") + phone_registered = True + continue except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e: if phone_code_invalid_raises: raise From bc824f738c370c463c01903a7b495c13d1ddca0c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 18 Dec 2018 11:21:37 +0100 Subject: [PATCH 088/326] Add extra warnings when sign-ins or sign-ups fail --- pyrogram/client/client.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 1560da9d..16f015d9 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -538,21 +538,10 @@ class Client(Methods, BaseClient): ) ) except PhoneNumberUnoccupied: - print("PHONE NON-OCCUPIED") + log.warning("Phone number unregistered") phone_registered = False continue else: - try: - self.send( - functions.auth.SignIn( - self.phone_number, - phone_code_hash, - self.phone_code - ) - ) - except PhoneNumberUnoccupied: - pass - self.first_name = self.first_name if self.first_name is not None else input("First name: ") self.last_name = self.last_name if self.last_name is not None else input("Last name: ") @@ -567,7 +556,7 @@ class Client(Methods, BaseClient): ) ) except PhoneNumberOccupied: - print("PHONE OCCUPIED") + log.warning("Phone number already registered") phone_registered = True continue except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e: From 510b2456b6799f25aaac9cab3d25d5b37160b303 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Dec 2018 10:27:47 +0100 Subject: [PATCH 089/326] Reword offset_dialog docstrings in Dialog --- pyrogram/client/methods/chats/get_dialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 655bb431..f8bed7c8 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -32,7 +32,7 @@ class GetDialogs(BaseClient): Args: offset_dialog (:obj:`Dialog`): - Pass the last dialog object to retrieve the next dialogs chunk starting it. + Sequential Dialog of the first dialog to be returned. Defaults to None (start from the beginning). limit (``str``, *optional*): From a13707a3b5d75ade4dc5e7d0654f48e50a1b9cdd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Dec 2018 10:28:47 +0100 Subject: [PATCH 090/326] Completely remove obsolete commented code --- compiler/api/compiler.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index ac507537..d31353c6 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -475,40 +475,6 @@ def start(): f.write("\n 0x3072cfa1: \"pyrogram.api.core.GzipPacked\",") f.write("\n 0x5bb8e511: \"pyrogram.api.core.Message\",") - # TODO Remove completely - # f.write("\n 0xb0700000: \"pyrogram.client.types.Update\",") - # f.write("\n 0xb0700001: \"pyrogram.client.types.User\",") - # f.write("\n 0xb0700002: \"pyrogram.client.types.Chat\",") - # f.write("\n 0xb0700003: \"pyrogram.client.types.Message\",") - # f.write("\n 0xb0700004: \"pyrogram.client.types.MessageEntity\",") - # f.write("\n 0xb0700005: \"pyrogram.client.types.PhotoSize\",") - # f.write("\n 0xb0700006: \"pyrogram.client.types.Audio\",") - # f.write("\n 0xb0700007: \"pyrogram.client.types.Document\",") - # f.write("\n 0xb0700008: \"pyrogram.client.types.Video\",") - # f.write("\n 0xb0700009: \"pyrogram.client.types.Voice\",") - # f.write("\n 0xb0700010: \"pyrogram.client.types.VideoNote\",") - # f.write("\n 0xb0700011: \"pyrogram.client.types.Contact\",") - # f.write("\n 0xb0700012: \"pyrogram.client.types.Location\",") - # f.write("\n 0xb0700013: \"pyrogram.client.types.Venue\",") - # f.write("\n 0xb0700014: \"pyrogram.client.types.UserProfilePhotos\",") - # f.write("\n 0xb0700015: \"pyrogram.client.types.ChatPhoto\",") - # f.write("\n 0xb0700016: \"pyrogram.client.types.ChatMember\",") - # f.write("\n 0xb0700017: \"pyrogram.client.types.Sticker\",") - # f.write("\n 0xb0700018: \"pyrogram.client.types.bots.ForceReply\",") - # f.write("\n 0xb0700019: \"pyrogram.client.types.bots.InlineKeyboardButton\",") - # f.write("\n 0xb0700020: \"pyrogram.client.types.bots.InlineKeyboardMarkup\",") - # f.write("\n 0xb0700021: \"pyrogram.client.types.bots.KeyboardButton\",") - # f.write("\n 0xb0700022: \"pyrogram.client.types.bots.ReplyKeyboardMarkup\",") - # f.write("\n 0xb0700023: \"pyrogram.client.types.bots.ReplyKeyboardRemove\",") - # f.write("\n 0xb0700024: \"pyrogram.client.types.CallbackQuery\",") - # f.write("\n 0xb0700025: \"pyrogram.client.types.Animation\",") - # f.write("\n 0xb0700026: \"pyrogram.client.types.Messages\",") - # f.write("\n 0xb0700027: \"pyrogram.client.types.Photo\",") - # f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",") - # f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",") - # f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",") - # f.write("\n 0xb0700031: \"pyrogram.client.types.UserStatus\"") - f.write("\n}\n") for k, v in namespaces.items(): From 26ef8ea9534c9f9ca8d8763dd268fefeb75e38b1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Dec 2018 10:59:24 +0100 Subject: [PATCH 091/326] Revert to the old style --- pyrogram/client/types/bots/callback_query.py | 12 +++- pyrogram/client/types/bots/force_reply.py | 3 +- .../types/bots/inline_keyboard_button.py | 8 ++- .../types/bots/inline_keyboard_markup.py | 3 +- pyrogram/client/types/bots/keyboard_button.py | 5 +- .../types/bots/reply_keyboard_markup.py | 5 +- .../types/bots/reply_keyboard_remove.py | 3 +- .../client/types/input_media/input_media.py | 5 +- .../input_media/input_media_animation.py | 10 ++- .../types/input_media/input_media_audio.py | 10 ++- .../types/input_media/input_media_document.py | 6 +- .../types/input_media/input_media_photo.py | 5 +- .../types/input_media/input_media_video.py | 11 +++- .../types/input_media/input_phone_contact.py | 10 ++- .../types/messages_and_media/animation.py | 14 +++- .../client/types/messages_and_media/audio.py | 13 +++- .../types/messages_and_media/contact.py | 8 ++- .../types/messages_and_media/document.py | 11 +++- .../types/messages_and_media/location.py | 2 +- .../types/messages_and_media/message.py | 65 +++++++++++++++---- .../messages_and_media/message_entity.py | 9 ++- .../types/messages_and_media/messages.py | 6 +- .../client/types/messages_and_media/photo.py | 7 +- .../types/messages_and_media/photo_size.py | 8 ++- .../types/messages_and_media/sticker.py | 17 ++++- .../messages_and_media/user_profile_photos.py | 6 +- .../client/types/messages_and_media/venue.py | 8 ++- .../client/types/messages_and_media/video.py | 14 +++- .../types/messages_and_media/video_note.py | 12 +++- .../client/types/messages_and_media/voice.py | 11 +++- pyrogram/client/types/user_and_chats/chat.py | 22 +++++-- .../types/user_and_chats/chat_member.py | 23 +++++-- .../types/user_and_chats/chat_members.py | 6 +- .../client/types/user_and_chats/chat_photo.py | 6 +- .../client/types/user_and_chats/dialog.py | 11 +++- .../client/types/user_and_chats/dialogs.py | 6 +- pyrogram/client/types/user_and_chats/user.py | 19 +++++- .../types/user_and_chats/user_status.py | 12 +++- 38 files changed, 338 insertions(+), 74 deletions(-) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index e586c736..c6c3ee66 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -56,8 +56,16 @@ class CallbackQuery(PyrogramType): """ - def __init__(self, *, client, id: str, from_user, chat_instance: str, message=None, - inline_message_id: str = None, data: bytes = None, game_short_name: str = None): + def __init__(self, + *, + client, + id: str, + from_user, + chat_instance: str, + message=None, + inline_message_id: str = None, + data: bytes = None, + game_short_name: str = None): super().__init__(client) self.id = id diff --git a/pyrogram/client/types/bots/force_reply.py b/pyrogram/client/types/bots/force_reply.py index eb3a8729..0c15b92a 100644 --- a/pyrogram/client/types/bots/force_reply.py +++ b/pyrogram/client/types/bots/force_reply.py @@ -33,7 +33,8 @@ class ForceReply(PyrogramType): 2) if the bot's message is a reply (has reply_to_message_id), sender of the original message. """ - def __init__(self, selective: bool = None): + def __init__(self, + selective: bool = None): super().__init__(None) self.selective = selective diff --git a/pyrogram/client/types/bots/inline_keyboard_button.py b/pyrogram/client/types/bots/inline_keyboard_button.py index 0fd1355a..655e78f4 100644 --- a/pyrogram/client/types/bots/inline_keyboard_button.py +++ b/pyrogram/client/types/bots/inline_keyboard_button.py @@ -53,8 +53,12 @@ class InlineKeyboardButton(PyrogramType): # TODO: Add callback_game and pay fields - def __init__(self, text: str, callback_data: bytes = None, url: str = None, - switch_inline_query: str = None, switch_inline_query_current_chat: str = None): + def __init__(self, + text: str, + callback_data: bytes = None, + url: str = None, + switch_inline_query: str = None, + switch_inline_query_current_chat: str = None): super().__init__(None) self.text = text diff --git a/pyrogram/client/types/bots/inline_keyboard_markup.py b/pyrogram/client/types/bots/inline_keyboard_markup.py index b4eb936f..c13cc096 100644 --- a/pyrogram/client/types/bots/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots/inline_keyboard_markup.py @@ -29,7 +29,8 @@ class InlineKeyboardMarkup(PyrogramType): List of button rows, each represented by a List of InlineKeyboardButton objects. """ - def __init__(self, inline_keyboard: list): + def __init__(self, + inline_keyboard: list): super().__init__(None) self.inline_keyboard = inline_keyboard diff --git a/pyrogram/client/types/bots/keyboard_button.py b/pyrogram/client/types/bots/keyboard_button.py index ceb7a2b7..4b025375 100644 --- a/pyrogram/client/types/bots/keyboard_button.py +++ b/pyrogram/client/types/bots/keyboard_button.py @@ -40,7 +40,10 @@ class KeyboardButton(PyrogramType): Available in private chats only. """ - def __init__(self, text: str, request_contact: bool = None, request_location: bool = None): + def __init__(self, + text: str, + request_contact: bool = None, + request_location: bool = None): super().__init__(None) self.text = text diff --git a/pyrogram/client/types/bots/reply_keyboard_markup.py b/pyrogram/client/types/bots/reply_keyboard_markup.py index fb0817ac..a8378614 100644 --- a/pyrogram/client/types/bots/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots/reply_keyboard_markup.py @@ -47,7 +47,10 @@ class ReplyKeyboardMarkup(PyrogramType): select the new language. Other users in the group don't see the keyboard. """ - def __init__(self, keyboard: list, resize_keyboard: bool = None, one_time_keyboard: bool = None, + def __init__(self, + keyboard: list, + resize_keyboard: bool = None, + one_time_keyboard: bool = None, selective: bool = None): super().__init__(None) diff --git a/pyrogram/client/types/bots/reply_keyboard_remove.py b/pyrogram/client/types/bots/reply_keyboard_remove.py index 7f82fd6e..def9917c 100644 --- a/pyrogram/client/types/bots/reply_keyboard_remove.py +++ b/pyrogram/client/types/bots/reply_keyboard_remove.py @@ -35,7 +35,8 @@ class ReplyKeyboardRemove(PyrogramType): keyboard for that user, while still showing the keyboard with poll options to users who haven't voted yet. """ - def __init__(self, selective: bool = None): + def __init__(self, + selective: bool = None): super().__init__(None) self.selective = selective diff --git a/pyrogram/client/types/input_media/input_media.py b/pyrogram/client/types/input_media/input_media.py index 611d5865..7a380f89 100644 --- a/pyrogram/client/types/input_media/input_media.py +++ b/pyrogram/client/types/input_media/input_media.py @@ -18,7 +18,10 @@ class InputMedia: - def __init__(self, media: str, caption: str, parse_mode: str): + def __init__(self, + media: str, + caption: str, + parse_mode: str): self.media = media self.caption = caption self.parse_mode = parse_mode diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index cc3d9ad0..0e7b2433 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -52,8 +52,14 @@ class InputMediaAnimation(InputMedia): Animation duration. """ - def __init__(self, media: str, thumb: str = None, caption: str = "", parse_mode: str = "", width: int = 0, - height: int = 0, duration: int = 0): + def __init__(self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + width: int = 0, + height: int = 0, + duration: int = 0): super().__init__(media, caption, parse_mode) self.thumb = thumb diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index 7c1b6072..455c2292 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -53,8 +53,14 @@ class InputMediaAudio(InputMedia): Title of the audio """ - def __init__(self, media: str, thumb: str = None, caption: str = "", parse_mode: str = "", duration: int = 0, - performer: int = "", title: str = ""): + def __init__(self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + performer: int = "", + title: str = ""): super().__init__(media, caption, parse_mode) self.thumb = thumb diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index 267abbde..08fcae5b 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -43,7 +43,11 @@ class InputMediaDocument(InputMedia): Defaults to Markdown. """ - def __init__(self, media: str, thumb: str = None, caption: str = "", parse_mode: str = ""): + def __init__(self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = ""): super().__init__(media, caption, parse_mode) self.thumb = thumb diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py index f5af9c89..c8cdccb8 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -39,5 +39,8 @@ class InputMediaPhoto(InputMedia): Defaults to Markdown. """ - def __init__(self, media: str, caption: str = "", parse_mode: str = ""): + def __init__(self, + media: str, + caption: str = "", + parse_mode: str = ""): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index 1884ea8d..955cf633 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -57,8 +57,15 @@ class InputMediaVideo(InputMedia): Pass True, if the uploaded video is suitable for streaming. """ - def __init__(self, media: str, thumb: str = None, caption: str = "", parse_mode: str = "", width: int = 0, - height: int = 0, duration: int = 0, supports_streaming: bool = True): + def __init__(self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + width: int = 0, + height: int = 0, + duration: int = 0, + supports_streaming: bool = True): super().__init__(media, caption, parse_mode) self.thumb = thumb diff --git a/pyrogram/client/types/input_media/input_phone_contact.py b/pyrogram/client/types/input_media/input_phone_contact.py index 0e61c006..eacecaf8 100644 --- a/pyrogram/client/types/input_media/input_phone_contact.py +++ b/pyrogram/client/types/input_media/input_phone_contact.py @@ -35,10 +35,16 @@ class InputPhoneContact: Contact's last name """ - def __init__(self, phone: str, first_name: str, last_name: str = ""): + def __init__(self, + phone: str, + first_name: str, + last_name: str = ""): pass - def __new__(cls, phone: str, first_name: str, last_name: str = ""): + def __new__(cls, + phone: str, + first_name: str, + last_name: str = ""): return RawInputPhoneContact( client_id=MsgId(), phone="+" + phone.strip("+"), diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index 45beefc3..9f1e5734 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -56,8 +56,18 @@ class Animation(PyrogramType): Date the animation was sent in Unix time. """ - def __init__(self, *, client, file_id: str, width: int, height: int, duration: int, thumb=None, - file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None): + def __init__(self, + *, + client, + file_id: str, + width: int, + height: int, + duration: int, + thumb=None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 9d001615..dfcfd9fa 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -56,8 +56,17 @@ class Audio(PyrogramType): Title of the audio as defined by sender or by audio tags. """ - def __init__(self, *, client, file_id: str, duration: int, thumb=None, file_name: str = None, - mime_type: str = None, file_size: int = None, date: int = None, performer: str = None, + def __init__(self, + *, + client, + file_id: str, + duration: int, + thumb=None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None, + performer: str = None, title: str = None): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index 2195df45..bcff7961 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -40,7 +40,13 @@ class Contact(PyrogramType): Additional data about the contact in the form of a vCard. """ - def __init__(self, *, client, phone_number: str, first_name: str, last_name: str = None, user_id: int = None, + def __init__(self, + *, + client, + phone_number: str, + first_name: str, + last_name: str = None, + user_id: int = None, vcard: str = None): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index 6d124e2a..dd95846a 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -47,8 +47,15 @@ class Document(PyrogramType): Date the document was sent in Unix time. """ - def __init__(self, *, client, file_id: str, thumb=None, file_name: str = None, mime_type: str = None, - file_size: int = None, date: int = None): + def __init__(self, + *, + client, + file_id: str, + thumb=None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index 3f5bb059..82eb3d1d 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -31,7 +31,7 @@ class Location(PyrogramType): Latitude as defined by sender. """ - def __init__(self, *, client, longitude: float, latitude: float, ): + def __init__(self, *, client, longitude: float, latitude: float): super().__init__(client) self.longitude = longitude diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 00567491..e4b15357 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -224,18 +224,59 @@ class Message(PyrogramType): # TODO: Add game missing field. Also invoice, successful_payment, connected_website - def __init__(self, *, client, message_id: int, date: int = None, chat=None, from_user=None, forward_from=None, - forward_from_chat=None, forward_from_message_id: int = None, forward_signature: str = None, - forward_date: int = None, reply_to_message=None, mentioned=None, empty=None, service=None, media=None, - edit_date: int = None, media_group_id: str = None, author_signature: str = None, text: str = None, - entities: list = None, caption_entities: list = None, audio=None, document=None, photo=None, - sticker=None, animation=None, video=None, voice=None, video_note=None, caption: str = None, - contact=None, location=None, venue=None, web_page=None, new_chat_members: list = None, - left_chat_member=None, new_chat_title: str = None, new_chat_photo=None, delete_chat_photo: bool = None, - group_chat_created: bool = None, supergroup_chat_created: bool = None, - channel_chat_created: bool = None, migrate_to_chat_id: int = None, migrate_from_chat_id: int = None, - pinned_message=None, views: int = None, via_bot=None, outgoing: bool = None, matches: list = None, - command: list = None, reply_markup=None): + def __init__(self, + *, + client, + message_id: int, + date: int = None, + chat=None, + from_user=None, + forward_from=None, + forward_from_chat=None, + forward_from_message_id: int = None, + forward_signature: str = None, + forward_date: int = None, + reply_to_message=None, + mentioned=None, + empty=None, + service=None, + media=None, + edit_date: int = None, + media_group_id: str = None, + author_signature: str = None, + text: str = None, + entities: list = None, + caption_entities: list = None, + audio=None, + document=None, + photo=None, + sticker=None, + animation=None, + video=None, + voice=None, + video_note=None, + caption: str = None, + contact=None, + location=None, + venue=None, + web_page=None, + new_chat_members: list = None, + left_chat_member=None, + new_chat_title: str = None, + new_chat_photo=None, + delete_chat_photo: bool = None, + group_chat_created: bool = None, + supergroup_chat_created: bool = None, + channel_chat_created: bool = None, + migrate_to_chat_id: int = None, + migrate_from_chat_id: int = None, + pinned_message=None, + views: int = None, + via_bot=None, + outgoing: bool = None, + matches: list = None, + command: list = None, + reply_markup=None): super().__init__(client) self.message_id = message_id diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 9f894f75..325006af 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -61,7 +61,14 @@ class MessageEntity(PyrogramType): types.MessageEntityPhone.ID: "phone_number" } - def __init__(self, *, client, type: str, offset: int, length: int, url: str = None, user=None): + def __init__(self, + *, + client, + type: str, + offset: int, + length: int, + url: str = None, + user=None): super().__init__(client) self.type = type diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 6d2d666a..da1f595c 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -33,7 +33,11 @@ class Messages(PyrogramType): Requested messages. """ - def __init__(self, *, client, total_count: int, messages: list): + def __init__(self, + *, + client, + total_count: int, + messages: list): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 01edd0fc..61ae7b53 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -39,7 +39,12 @@ class Photo(PyrogramType): Available sizes of this photo. """ - def __init__(self, *, client, id: str, date: int, sizes: list): + def __init__(self, + *, + client, + id: str, + date: int, + sizes: list): super().__init__(client) self.id = id diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index efc9def9..f6a8cb76 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -40,7 +40,13 @@ class PhotoSize(PyrogramType): File size. """ - def __init__(self, *, client, file_id: str, width: int, height: int, file_size: int): + def __init__(self, + *, + client, + file_id: str, + width: int, + height: int, + file_size: int): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index 9892694a..16228d0e 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -63,9 +63,20 @@ class Sticker(PyrogramType): # TODO: Add mask position - def __init__(self, *, client, file_id: str, width: int, height: int, thumb=None, file_name: str = None, - mime_type: str = None, file_size: int = None, date: int = None, emoji: str = None, - set_name: str = None, mask_position=None): + def __init__(self, + *, + client, + file_id: str, + width: int, + height: int, + thumb=None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None, + emoji: str = None, + set_name: str = None, + mask_position=None): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index 23b3200c..9f0998b1 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -31,7 +31,11 @@ class UserProfilePhotos(PyrogramType): Requested profile pictures. """ - def __init__(self, *, client, total_count: int, photos: list): + def __init__(self, + *, + client, + total_count: int, + photos: list): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index c206a2a0..a1fb372e 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -43,7 +43,13 @@ class Venue(PyrogramType): """ - def __init__(self, *, client, location, title: str, address: str, foursquare_id: str = None, + def __init__(self, + *, + client, + location, + title: str, + address: str, + foursquare_id: str = None, foursquare_type: str = None): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 7d995ea8..39b29e64 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -56,8 +56,18 @@ class Video(PyrogramType): Date the video was sent in Unix time. """ - def __init__(self, *, client, file_id: str, width: int, height: int, duration: int, thumb=None, - file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None): + def __init__(self, + *, + client, + file_id: str, + width: int, + height: int, + duration: int, + thumb=None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index 688bca19..103bab76 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -50,8 +50,16 @@ class VideoNote(PyrogramType): Date the video note was sent in Unix time. """ - def __init__(self, *, client, file_id: str, length: int, duration: int, thumb=None, mime_type: str = None, - file_size: int = None, date: int = None): + def __init__(self, + *, + client, + file_id: str, + length: int, + duration: int, + thumb=None, + mime_type: str = None, + file_size: int = None, + date: int = None): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index cce70a31..e3ebd2e2 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -46,8 +46,15 @@ class Voice(PyrogramType): Date the voice was sent in Unix time. """ - def __init__(self, *, client, file_id: str, duration: int, waveform: bytes = None, mime_type: str = None, - file_size: int = None, date: int = None): + def __init__(self, + *, + client, + file_id: str, + duration: int, + waveform: bytes = None, + mime_type: str = None, + file_size: int = None, + date: int = None): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index b9d0d796..ddaa59dd 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -76,10 +76,24 @@ class Chat(PyrogramType): The reason why this chat might be unavailable to some users. """ - def __init__(self, *, client, id: int, type: str, title: str = None, username: str = None, - first_name: str = None, last_name: str = None, all_members_are_administrators: bool = None, photo=None, - description: str = None, invite_link: str = None, pinned_message=None, sticker_set_name: str = None, - can_set_sticker_set: bool = None, members_count: int = None, restriction_reason: str = None): + def __init__(self, + *, + client, + id: int, + type: str, + title: str = None, + username: str = None, + first_name: str = None, + last_name: str = None, + all_members_are_administrators: bool = None, + photo=None, + description: str = None, + invite_link: str = None, + pinned_message=None, + sticker_set_name: str = None, + can_set_sticker_set: bool = None, + members_count: int = None, + restriction_reason: str = None): super().__init__(client) self.id = id diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 27eeb897..50f44bad 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -79,11 +79,24 @@ class ChatMember(PyrogramType): Restricted only. True, if user may add web page previews to his messages, implies can_send_media_messages. """ - def __init__(self, *, client, user, status: str, until_date: int = None, can_be_edited: bool = None, - can_change_info: bool = None, can_post_messages: bool = None, can_edit_messages: bool = None, - can_delete_messages: bool = None, can_invite_users: bool = None, can_restrict_members: bool = None, - can_pin_messages: bool = None, can_promote_members: bool = None, can_send_messages: bool = None, - can_send_media_messages: bool = None, can_send_other_messages: bool = None, + def __init__(self, + *, + client, + user, + status: str, + until_date: int = None, + can_be_edited: bool = None, + can_change_info: bool = None, + can_post_messages: bool = None, + can_edit_messages: bool = None, + can_delete_messages: bool = None, + can_invite_users: bool = None, + can_restrict_members: bool = None, + can_pin_messages: bool = None, + can_promote_members: bool = None, + can_send_messages: bool = None, + can_send_media_messages: bool = None, + can_send_other_messages: bool = None, can_add_web_page_previews: bool = None): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index ba28b23d..5fcddecf 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -33,7 +33,11 @@ class ChatMembers(PyrogramType): Requested chat members. """ - def __init__(self, *, client, total_count: int, chat_members: list): + def __init__(self, + *, + client, + total_count: int, + chat_members: list): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index 91c77b06..b0e630a4 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -34,7 +34,11 @@ class ChatPhoto(PyrogramType): Unique file identifier of big (640x640) chat photo. This file_id can be used only for photo download. """ - def __init__(self, *, client, small_file_id: str, big_file_id: str): + def __init__(self, + *, + client, + small_file_id: str, + big_file_id: str): super().__init__(client) self.small_file_id = small_file_id diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index d617c11e..7b1d66b4 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -44,8 +44,15 @@ class Dialog(PyrogramType): True, if the dialog is pinned. """ - def __init__(self, *, client, chat, top_message, unread_messages_count: int, unread_mentions_count: int, - unread_mark: bool, is_pinned: bool): + def __init__(self, + *, + client, + chat, + top_message, + unread_messages_count: int, + unread_mentions_count: int, + unread_mark: bool, + is_pinned: bool): super().__init__(client) self.chat = chat diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py index 21eda2f0..e1e8c5e2 100644 --- a/pyrogram/client/types/user_and_chats/dialogs.py +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -33,7 +33,11 @@ class Dialogs(PyrogramType): Requested dialogs. """ - def __init__(self, *, client, total_count: int, dialogs: list): + def __init__(self, + *, + client, + total_count: int, + dialogs: list): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 66d68121..4755e297 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -69,9 +69,22 @@ class User(PyrogramType): The reason why this bot might be unavailable to some users. """ - def __init__(self, *, client, id: int, is_self: bool, is_contact: bool, is_mutual_contact: bool, - is_deleted: bool, is_bot: bool, first_name: str, last_name: str = None, status=None, - username: str = None, language_code: str = None, phone_number: str = None, photo=None, + def __init__(self, + *, + client, + id: int, + is_self: bool, + is_contact: bool, + is_mutual_contact: bool, + is_deleted: bool, + is_bot: bool, + first_name: str, + last_name: str = None, + status=None, + username: str = None, + language_code: str = None, + phone_number: str = None, + photo=None, restriction_reason: str = None): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index 8102ffe0..bcb4d195 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -62,8 +62,16 @@ class UserStatus(PyrogramType): always shown to blocked users), None otherwise. """ - def __init__(self, *, client, user_id: int, online: bool = None, offline: bool = None, date: int = None, - recently: bool = None, within_week: bool = None, within_month: bool = None, + def __init__(self, + *, + client, + user_id: int, + online: bool = None, + offline: bool = None, + date: int = None, + recently: bool = None, + within_week: bool = None, + within_month: bool = None, long_time_ago: bool = None): super().__init__(client) From a8599efa9481e710f1a65729a9e844cdd0bbe7b1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Dec 2018 12:29:55 +0100 Subject: [PATCH 092/326] Add backported typing package as requirement To be removed when Pyrogram drops Python 3.4 support --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 17447fdf..8f1eea95 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ pyaes==1.6.1 pysocks==1.6.8 +typing==3.6.6 \ No newline at end of file From b79f395d372620357216bcd3a33c2cf7e6deb2ef Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Dec 2018 12:30:25 +0100 Subject: [PATCH 093/326] Type hint Message --- .../types/messages_and_media/message.py | 75 ++++++++++--------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index e4b15357..61121b79 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import List, Match, Union + import pyrogram from pyrogram.api import types from pyrogram.api.errors import MessageIdsEmpty @@ -201,18 +203,18 @@ class Message(PyrogramType): via_bot (:obj:`User `): The information of the bot that generated the message from an inline query of a user. - + outgoing (``bool``, *optional*): Whether the message is incoming or outgoing. Messages received from other chats are incoming (*outgoing* is False). Messages sent from yourself to other chats are outgoing (*outgoing* is True). An exception is made for your own personal chat; messages sent there will be incoming. - matches (``list``, *optional*): + matches (``List of regex Matches``, *optional*): A list containing all `Match Objects `_ that match the text of this message. Only applicable when using :obj:`Filters.regex `. - command (``list``, *optional*): + command (``List of strings``, *optional*): A list containing the command and its arguments, if any. E.g.: "/start 1 2 3" would produce ["start", "1", "2", "3"]. Only applicable when using :obj:`Filters.command `. @@ -226,57 +228,60 @@ class Message(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", message_id: int, date: int = None, - chat=None, - from_user=None, - forward_from=None, - forward_from_chat=None, + chat: Chat = None, + from_user: User = None, + forward_from: User = None, + forward_from_chat: Chat = None, forward_from_message_id: int = None, forward_signature: str = None, forward_date: int = None, - reply_to_message=None, - mentioned=None, - empty=None, - service=None, - media=None, + reply_to_message: "Message" = None, + mentioned: bool = None, + empty: bool = None, + service: bool = None, + media: bool = None, edit_date: int = None, media_group_id: str = None, author_signature: str = None, text: str = None, - entities: list = None, - caption_entities: list = None, - audio=None, - document=None, - photo=None, - sticker=None, - animation=None, - video=None, - voice=None, - video_note=None, + entities: List["pyrogram.MessageEntity"] = None, + caption_entities: List["pyrogram.MessageEntity"] = None, + audio: "pyrogram.Audio" = None, + document: "pyrogram.Document" = None, + photo: "pyrogram.Photo" = None, + sticker: "pyrogram.Sticker" = None, + animation: "pyrogram.Animation" = None, + video: "pyrogram.Video" = None, + voice: "pyrogram.Voice" = None, + video_note: "pyrogram.VideoNote" = None, caption: str = None, - contact=None, - location=None, - venue=None, - web_page=None, - new_chat_members: list = None, - left_chat_member=None, + contact: "pyrogram.Contact" = None, + location: "pyrogram.Location" = None, + venue: "pyrogram.Venue" = None, + web_page: bool = None, + new_chat_members: List[User] = None, + left_chat_member: User = None, new_chat_title: str = None, - new_chat_photo=None, + new_chat_photo: "pyrogram.Photo" = None, delete_chat_photo: bool = None, group_chat_created: bool = None, supergroup_chat_created: bool = None, channel_chat_created: bool = None, migrate_to_chat_id: int = None, migrate_from_chat_id: int = None, - pinned_message=None, + pinned_message: "Message" = None, views: int = None, - via_bot=None, + via_bot: User = None, outgoing: bool = None, - matches: list = None, - command: list = None, - reply_markup=None): + matches: List[Match] = None, + command: List[str] = None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None): super().__init__(client) self.message_id = message_id From e8fbae31668f343fd64441e78cf8f00dc735498e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Dec 2018 13:00:33 +0100 Subject: [PATCH 094/326] Make all parse methods protected to hide them from the lib user --- pyrogram/client/dispatcher/dispatcher.py | 8 ++-- pyrogram/client/types/bots/callback_query.py | 4 +- .../types/messages_and_media/animation.py | 4 +- .../client/types/messages_and_media/audio.py | 4 +- .../types/messages_and_media/contact.py | 2 +- .../types/messages_and_media/document.py | 4 +- .../types/messages_and_media/location.py | 2 +- .../types/messages_and_media/message.py | 46 +++++++++---------- .../messages_and_media/message_entity.py | 4 +- .../types/messages_and_media/messages.py | 6 +-- .../client/types/messages_and_media/photo.py | 2 +- .../types/messages_and_media/photo_size.py | 2 +- .../types/messages_and_media/sticker.py | 4 +- .../messages_and_media/user_profile_photos.py | 4 +- .../client/types/messages_and_media/venue.py | 4 +- .../client/types/messages_and_media/video.py | 4 +- .../types/messages_and_media/video_note.py | 4 +- .../client/types/messages_and_media/voice.py | 2 +- pyrogram/client/types/user_and_chats/chat.py | 36 +++++++-------- .../types/user_and_chats/chat_member.py | 2 +- .../types/user_and_chats/chat_members.py | 6 +-- .../client/types/user_and_chats/chat_photo.py | 2 +- .../client/types/user_and_chats/dialog.py | 4 +- .../client/types/user_and_chats/dialogs.py | 6 +-- pyrogram/client/types/user_and_chats/user.py | 6 +-- .../types/user_and_chats/user_status.py | 2 +- 26 files changed, 87 insertions(+), 87 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 4f3ae620..5b30daf9 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -62,17 +62,17 @@ class Dispatcher: self.update_parsers = { Dispatcher.MESSAGE_UPDATES: - lambda upd, usr, cht: (pyrogram.Message.parse(self.client, upd.message, usr, cht), MessageHandler), + lambda upd, usr, cht: (pyrogram.Message._parse(self.client, upd.message, usr, cht), MessageHandler), Dispatcher.DELETE_MESSAGES_UPDATES: - lambda upd, usr, cht: (pyrogram.Messages.parse_deleted(self.client, upd), DeletedMessagesHandler), + lambda upd, usr, cht: (pyrogram.Messages._parse_deleted(self.client, upd), DeletedMessagesHandler), Dispatcher.CALLBACK_QUERY_UPDATES: - lambda upd, usr, cht: (pyrogram.CallbackQuery.parse(self.client, upd, usr), CallbackQueryHandler), + lambda upd, usr, cht: (pyrogram.CallbackQuery._parse(self.client, upd, usr), CallbackQueryHandler), (types.UpdateUserStatus,): lambda upd, usr, cht: ( - pyrogram.UserStatus.parse(self.client, upd.status, upd.user_id), UserStatusHandler + pyrogram.UserStatus._parse(self.client, upd.status, upd.user_id), UserStatusHandler ) } diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index c6c3ee66..ed96edea 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -77,7 +77,7 @@ class CallbackQuery(PyrogramType): self.game_short_name = game_short_name @staticmethod - def parse(client, callback_query, users) -> "CallbackQuery": + def _parse(client, callback_query, users) -> "CallbackQuery": message = None inline_message_id = None @@ -105,7 +105,7 @@ class CallbackQuery(PyrogramType): return CallbackQuery( id=str(callback_query.query_id), - from_user=User.parse(client, users[callback_query.user_id]), + from_user=User._parse(client, users[callback_query.user_id]), message=message, inline_message_id=inline_message_id, chat_instance=str(callback_query.chat_instance), diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index 9f1e5734..ba9b4dad 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -81,7 +81,7 @@ class Animation(PyrogramType): self.duration = duration @staticmethod - def parse(client, animation: types.Document, video_attributes: types.DocumentAttributeVideo, + def _parse(client, animation: types.Document, video_attributes: types.DocumentAttributeVideo, file_name: str) -> "Animation": return Animation( file_id=encode( @@ -96,7 +96,7 @@ class Animation(PyrogramType): width=getattr(video_attributes, "w", 0), height=getattr(video_attributes, "h", 0), duration=getattr(video_attributes, "duration", 0), - thumb=PhotoSize.parse(client, animation.thumb), + thumb=PhotoSize._parse(client, animation.thumb), mime_type=animation.mime_type, file_size=animation.size, file_name=file_name, diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index dfcfd9fa..0590e760 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -81,7 +81,7 @@ class Audio(PyrogramType): self.title = title @staticmethod - def parse(client, audio: types.Document, audio_attributes: types.DocumentAttributeAudio, file_name: str) -> "Audio": + def _parse(client, audio: types.Document, audio_attributes: types.DocumentAttributeAudio, file_name: str) -> "Audio": return Audio( file_id=encode( pack( @@ -97,7 +97,7 @@ class Audio(PyrogramType): title=audio_attributes.title, mime_type=audio.mime_type, file_size=audio.size, - thumb=PhotoSize.parse(client, audio.thumb), + thumb=PhotoSize._parse(client, audio.thumb), file_name=file_name, date=audio.date, client=client diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index bcff7961..05cbfb1d 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -57,7 +57,7 @@ class Contact(PyrogramType): self.vcard = vcard @staticmethod - def parse(client, contact: types.MessageMediaContact) -> "Contact": + def _parse(client, contact: types.MessageMediaContact) -> "Contact": return Contact( phone_number=contact.phone_number, first_name=contact.first_name, diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index dd95846a..172d816d 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -66,7 +66,7 @@ class Document(PyrogramType): self.date = date @staticmethod - def parse(client, document: types.Document, file_name: str) -> "Document": + def _parse(client, document: types.Document, file_name: str) -> "Document": return Document( file_id=encode( pack( @@ -77,7 +77,7 @@ class Document(PyrogramType): document.access_hash ) ), - thumb=PhotoSize.parse(client, document.thumb), + thumb=PhotoSize._parse(client, document.thumb), file_name=file_name, mime_type=document.mime_type, file_size=document.size, diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index 82eb3d1d..455e7f58 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -38,7 +38,7 @@ class Location(PyrogramType): self.latitude = latitude @staticmethod - def parse(client, geo_point: types.GeoPoint) -> "Location": + def _parse(client, geo_point: types.GeoPoint) -> "Location": if isinstance(geo_point, types.GeoPoint): return Location( longitude=geo_point.long, diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 61121b79..14662e00 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -336,7 +336,7 @@ class Message(PyrogramType): self.reply_markup = reply_markup @staticmethod - def parse(client, message: types.Message or types.MessageService or types.MessageEmpty, users: dict, chats: dict, + def _parse(client, message: types.Message or types.MessageService or types.MessageEmpty, users: dict, chats: dict, replies: int = 1): if isinstance(message, types.MessageEmpty): return Message(message_id=message.id, empty=True, client=client) @@ -355,11 +355,11 @@ class Message(PyrogramType): new_chat_photo = None if isinstance(action, types.MessageActionChatAddUser): - new_chat_members = [User.parse(client, users[i]) for i in action.users] + new_chat_members = [User._parse(client, users[i]) for i in action.users] elif isinstance(action, types.MessageActionChatJoinedByLink): - new_chat_members = [User.parse(client, users[message.from_id])] + new_chat_members = [User._parse(client, users[message.from_id])] elif isinstance(action, types.MessageActionChatDeleteUser): - left_chat_member = User.parse(client, users[action.user_id]) + left_chat_member = User._parse(client, users[action.user_id]) elif isinstance(action, types.MessageActionChatEditTitle): new_chat_title = action.title elif isinstance(action, types.MessageActionChatDeletePhoto): @@ -373,13 +373,13 @@ class Message(PyrogramType): elif isinstance(action, types.MessageActionChannelCreate): channel_chat_created = True elif isinstance(action, types.MessageActionChatEditPhoto): - new_chat_photo = Photo.parse(client, action.photo) + new_chat_photo = Photo._parse(client, action.photo) parsed_message = Message( message_id=message.id, date=message.date, - chat=Chat.parse(client, message, users, chats), - from_user=User.parse(client, users.get(message.from_id, None)), + chat=Chat._parse(client, message, users, chats), + from_user=User._parse(client, users.get(message.from_id, None)), service=True, new_chat_members=new_chat_members, left_chat_member=left_chat_member, @@ -407,7 +407,7 @@ class Message(PyrogramType): return parsed_message if isinstance(message, types.Message): - entities = [MessageEntity.parse(client, entity, users) for entity in message.entities] + entities = [MessageEntity._parse(client, entity, users) for entity in message.entities] entities = list(filter(lambda x: x is not None, entities)) forward_from = None @@ -422,9 +422,9 @@ class Message(PyrogramType): forward_date = forward_header.date if forward_header.from_id: - forward_from = User.parse(client, users[forward_header.from_id]) + forward_from = User._parse(client, users[forward_header.from_id]) else: - forward_from_chat = Chat.parse_channel_chat(client, chats[forward_header.channel_id]) + forward_from_chat = Chat._parse_channel_chat(client, chats[forward_header.channel_id]) forward_from_message_id = forward_header.channel_post forward_signature = forward_header.post_author @@ -445,13 +445,13 @@ class Message(PyrogramType): if media: if isinstance(media, types.MessageMediaPhoto): - photo = Photo.parse(client, media.photo) + photo = Photo._parse(client, media.photo) elif isinstance(media, types.MessageMediaGeo): - location = Location.parse(client, media.geo) + location = Location._parse(client, media.geo) elif isinstance(media, types.MessageMediaContact): - contact = Contact.parse(client, media) + contact = Contact._parse(client, media) elif isinstance(media, types.MessageMediaVenue): - venue = pyrogram.Venue.parse(client, media) + venue = pyrogram.Venue._parse(client, media) elif isinstance(media, types.MessageMediaDocument): doc = media.document @@ -468,20 +468,20 @@ class Message(PyrogramType): audio_attributes = attributes[types.DocumentAttributeAudio] if audio_attributes.voice: - voice = pyrogram.Voice.parse(client, doc, audio_attributes) + voice = pyrogram.Voice._parse(client, doc, audio_attributes) else: - audio = pyrogram.Audio.parse(client, doc, audio_attributes, file_name) + audio = pyrogram.Audio._parse(client, doc, audio_attributes, file_name) elif types.DocumentAttributeAnimated in attributes: video_attributes = attributes.get(types.DocumentAttributeVideo, None) - animation = pyrogram.Animation.parse(client, doc, video_attributes, file_name) + animation = pyrogram.Animation._parse(client, doc, video_attributes, file_name) elif types.DocumentAttributeVideo in attributes: video_attributes = attributes[types.DocumentAttributeVideo] if video_attributes.round_message: - video_note = pyrogram.VideoNote.parse(client, doc, video_attributes) + video_note = pyrogram.VideoNote._parse(client, doc, video_attributes) else: - video = pyrogram.Video.parse(client, doc, video_attributes, file_name) + video = pyrogram.Video._parse(client, doc, video_attributes, file_name) elif types.DocumentAttributeSticker in attributes: sticker = pyrogram.Sticker.parse( client, doc, @@ -490,7 +490,7 @@ class Message(PyrogramType): file_name ) else: - document = pyrogram.Document.parse(client, doc, file_name) + document = pyrogram.Document._parse(client, doc, file_name) elif isinstance(media, types.MessageMediaWebPage): web_page = True media = None @@ -514,8 +514,8 @@ class Message(PyrogramType): parsed_message = Message( message_id=message.id, date=message.date, - chat=Chat.parse(client, message, users, chats), - from_user=User.parse(client, users.get(message.from_id, None)), + chat=Chat._parse(client, message, users, chats), + from_user=User._parse(client, users.get(message.from_id, None)), text=Str(message.message).init(client, entities) or None if media is None else None, caption=Str(message.message).init(client, entities) or None if media is not None else None, entities=entities or None if media is None else None, @@ -543,7 +543,7 @@ class Message(PyrogramType): document=document, web_page=web_page, views=message.views, - via_bot=User.parse(client, users.get(message.via_bot_id, None)), + via_bot=User._parse(client, users.get(message.via_bot_id, None)), outgoing=message.out, reply_markup=reply_markup, client=client diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 325006af..4f625bd3 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -78,7 +78,7 @@ class MessageEntity(PyrogramType): self.user = user @staticmethod - def parse(client, entity, users: dict) -> "MessageEntity" or None: + def _parse(client, entity, users: dict) -> "MessageEntity" or None: type = MessageEntity.ENTITIES.get(entity.ID, None) if type is None: @@ -89,6 +89,6 @@ class MessageEntity(PyrogramType): offset=entity.offset, length=entity.length, url=getattr(entity, "url", None), - user=User.parse(client, users.get(getattr(entity, "user_id", None), None)), + user=User._parse(client, users.get(getattr(entity, "user_id", None), None)), client=client ) diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index da1f595c..48d6c85b 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -44,18 +44,18 @@ class Messages(PyrogramType): self.messages = messages @staticmethod - def parse(client, messages: types.messages.Messages) -> "Messages": + def _parse(client, messages: types.messages.Messages) -> "Messages": users = {i.id: i for i in messages.users} chats = {i.id: i for i in messages.chats} return Messages( total_count=getattr(messages, "count", len(messages.messages)), - messages=[Message.parse(client, message, users, chats) for message in messages.messages], + messages=[Message._parse(client, message, users, chats) for message in messages.messages], client=client ) @staticmethod - def parse_deleted(client, update) -> "Messages": + def _parse_deleted(client, update) -> "Messages": messages = update.messages channel_id = getattr(update, "channel_id", None) diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 61ae7b53..cdf3b191 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -52,7 +52,7 @@ class Photo(PyrogramType): self.sizes = sizes @staticmethod - def parse(client, photo: types.Photo): + def _parse(client, photo: types.Photo): if isinstance(photo, types.Photo): raw_sizes = photo.sizes sizes = [] diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index f6a8cb76..4fb9b30d 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -55,7 +55,7 @@ class PhotoSize(PyrogramType): self.file_size = file_size @staticmethod - def parse(client, photo_size: types.PhotoSize or types.PhotoCachedSize): + def _parse(client, photo_size: types.PhotoSize or types.PhotoCachedSize): if isinstance(photo_size, (types.PhotoSize, types.PhotoCachedSize)): if isinstance(photo_size, types.PhotoSize): diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index 16228d0e..22121dd8 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -104,7 +104,7 @@ class Sticker(PyrogramType): return None @staticmethod - def parse(client, sticker: types.Document, image_size_attributes: types.DocumentAttributeImageSize, + def _parse(client, sticker: types.Document, image_size_attributes: types.DocumentAttributeImageSize, sticker_attributes: types.DocumentAttributeSticker, file_name: str) -> "Sticker": sticker_set = sticker_attributes.stickerset @@ -126,7 +126,7 @@ class Sticker(PyrogramType): ), width=image_size_attributes.w if image_size_attributes else 0, height=image_size_attributes.h if image_size_attributes else 0, - thumb=PhotoSize.parse(client, sticker.thumb), + thumb=PhotoSize._parse(client, sticker.thumb), # TODO: mask_position set_name=set_name, emoji=sticker_attributes.alt or None, diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index 9f0998b1..ca27fe2c 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -42,9 +42,9 @@ class UserProfilePhotos(PyrogramType): self.photos = photos @staticmethod - def parse(client, photos) -> "UserProfilePhotos": + def _parse(client, photos) -> "UserProfilePhotos": return UserProfilePhotos( total_count=getattr(photos, "count", len(photos.photos)), - photos=[Photo.parse(client, photo) for photo in photos.photos], + photos=[Photo._parse(client, photo) for photo in photos.photos], client=client ) diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index a1fb372e..12701d8c 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -60,9 +60,9 @@ class Venue(PyrogramType): self.foursquare_type = foursquare_type @staticmethod - def parse(client, venue: types.MessageMediaVenue): + def _parse(client, venue: types.MessageMediaVenue): return Venue( - location=Location.parse(client, venue.geo), + location=Location._parse(client, venue.geo), title=venue.title, address=venue.address, foursquare_id=venue.venue_id or None, diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 39b29e64..a69f0138 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -81,7 +81,7 @@ class Video(PyrogramType): self.duration = duration @staticmethod - def parse(client, video: types.Document, video_attributes: types.DocumentAttributeVideo, file_name: str) -> "Video": + def _parse(client, video: types.Document, video_attributes: types.DocumentAttributeVideo, file_name: str) -> "Video": return Video( file_id=encode( pack( @@ -95,7 +95,7 @@ class Video(PyrogramType): width=video_attributes.w, height=video_attributes.h, duration=video_attributes.duration, - thumb=PhotoSize.parse(client, video.thumb), + thumb=PhotoSize._parse(client, video.thumb), mime_type=video.mime_type, file_size=video.size, file_name=file_name, diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index 103bab76..53419dda 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -71,7 +71,7 @@ class VideoNote(PyrogramType): self.duration = duration @staticmethod - def parse(client, video_note: types.Document, video_attributes: types.DocumentAttributeVideo) -> "VideoNote": + def _parse(client, video_note: types.Document, video_attributes: types.DocumentAttributeVideo) -> "VideoNote": return VideoNote( file_id=encode( pack( @@ -84,7 +84,7 @@ class VideoNote(PyrogramType): ), length=video_attributes.w, duration=video_attributes.duration, - thumb=PhotoSize.parse(client, video_note.thumb), + thumb=PhotoSize._parse(client, video_note.thumb), file_size=video_note.size, mime_type=video_note.mime_type, date=video_note.date, diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index e3ebd2e2..bf5ffaa8 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -65,7 +65,7 @@ class Voice(PyrogramType): self.date = date @staticmethod - def parse(client, voice: types.Document, attributes: types.DocumentAttributeAudio) -> "Voice": + def _parse(client, voice: types.Document, attributes: types.DocumentAttributeAudio) -> "Voice": return Voice( file_id=encode( pack( diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index ddaa59dd..4a3dfb20 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -113,20 +113,20 @@ class Chat(PyrogramType): self.restriction_reason = restriction_reason @staticmethod - def parse_user_chat(client, user: types.User) -> "Chat": + def _parse_user_chat(client, user: types.User) -> "Chat": return Chat( id=user.id, type="private", username=user.username, first_name=user.first_name, last_name=user.last_name, - photo=ChatPhoto.parse(client, user.photo), + photo=ChatPhoto._parse(client, user.photo), restriction_reason=user.restriction_reason, client=client ) @staticmethod - def parse_chat_chat(client, chat: types.Chat) -> "Chat": + def _parse_chat_chat(client, chat: types.Chat) -> "Chat": admins_enabled = getattr(chat, "admins_enabled", None) if admins_enabled is not None: @@ -137,45 +137,45 @@ class Chat(PyrogramType): type="group", title=chat.title, all_members_are_administrators=admins_enabled, - photo=ChatPhoto.parse(client, getattr(chat, "photo", None)), + photo=ChatPhoto._parse(client, getattr(chat, "photo", None)), client=client ) @staticmethod - def parse_channel_chat(client, channel: types.Channel) -> "Chat": + def _parse_channel_chat(client, channel: types.Channel) -> "Chat": return Chat( id=int("-100" + str(channel.id)), type="supergroup" if channel.megagroup else "channel", title=channel.title, username=getattr(channel, "username", None), - photo=ChatPhoto.parse(client, getattr(channel, "photo", None)), + photo=ChatPhoto._parse(client, getattr(channel, "photo", None)), restriction_reason=getattr(channel, "restriction_reason", None), client=client ) @staticmethod - def parse(client, message: types.Message or types.MessageService, users: dict, chats: dict) -> "Chat": + def _parse(client, message: types.Message or types.MessageService, users: dict, chats: dict) -> "Chat": if isinstance(message.to_id, types.PeerUser): - return Chat.parse_user_chat(client, users[message.to_id.user_id if message.out else message.from_id]) + return Chat._parse_user_chat(client, users[message.to_id.user_id if message.out else message.from_id]) if isinstance(message.to_id, types.PeerChat): - return Chat.parse_chat_chat(client, chats[message.to_id.chat_id]) + return Chat._parse_chat_chat(client, chats[message.to_id.chat_id]) - return Chat.parse_channel_chat(client, chats[message.to_id.channel_id]) + return Chat._parse_channel_chat(client, chats[message.to_id.channel_id]) @staticmethod - def parse_dialog(client, peer, users: dict, chats: dict): + def _parse_dialog(client, peer, users: dict, chats: dict): if isinstance(peer, types.PeerUser): - return Chat.parse_user_chat(client, users[peer.user_id]) + return Chat._parse_user_chat(client, users[peer.user_id]) elif isinstance(peer, types.PeerChat): - return Chat.parse_chat_chat(client, chats[peer.chat_id]) + return Chat._parse_chat_chat(client, chats[peer.chat_id]) else: - return Chat.parse_channel_chat(client, chats[peer.channel_id]) + return Chat._parse_channel_chat(client, chats[peer.channel_id]) @staticmethod - def parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> "Chat": + def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> "Chat": if isinstance(chat_full, types.UserFull): - parsed_chat = Chat.parse_user_chat(client, chat_full.user) + _parsed_chat = Chat.parse_user_chat(client, chat_full.user) parsed_chat.description = chat_full.about else: full_chat = chat_full.full_chat @@ -186,12 +186,12 @@ class Chat(PyrogramType): chat = i if isinstance(full_chat, types.ChatFull): - parsed_chat = Chat.parse_chat_chat(client, chat) + _parsed_chat = Chat.parse_chat_chat(client, chat) if isinstance(full_chat.participants, types.ChatParticipants): parsed_chat.members_count = len(full_chat.participants.participants) else: - parsed_chat = Chat.parse_channel_chat(client, chat) + _parsed_chat = Chat.parse_channel_chat(client, chat) parsed_chat.members_count = full_chat.participants_count parsed_chat.description = full_chat.about or None # TODO: Add StickerSet type diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 50f44bad..5fc9c988 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -118,7 +118,7 @@ class ChatMember(PyrogramType): self.can_add_web_page_previews = can_add_web_page_previews @staticmethod - def parse(client, member, user) -> "ChatMember": + def _parse(client, member, user) -> "ChatMember": if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)): return ChatMember(user=user, status="member", client=client) diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index 5fcddecf..a798f312 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -44,7 +44,7 @@ class ChatMembers(PyrogramType): self.chat_members = chat_members @staticmethod - def parse(client, members): + def _parse(client, members): users = {i.id: i for i in members.users} chat_members = [] @@ -56,8 +56,8 @@ class ChatMembers(PyrogramType): total_count = len(members) for member in members: - user = User.parse(client, users[member.user_id]) - chat_members.append(ChatMember.parse(client, member, user)) + user = User._parse(client, users[member.user_id]) + chat_members.append(ChatMember._parse(client, member, user)) return ChatMembers( total_count=total_count, diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index b0e630a4..33eca8c8 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -45,7 +45,7 @@ class ChatPhoto(PyrogramType): self.big_file_id = big_file_id @staticmethod - def parse(client, chat_photo: types.UserProfilePhoto or types.ChatPhoto): + def _parse(client, chat_photo: types.UserProfilePhoto or types.ChatPhoto): if not isinstance(chat_photo, (types.UserProfilePhoto, types.ChatPhoto)): return None diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index 7b1d66b4..b5679ed2 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -63,7 +63,7 @@ class Dialog(PyrogramType): self.is_pinned = is_pinned @staticmethod - def parse(client, dialog, messages, users, chats) -> "Dialog": + def _parse(client, dialog, messages, users, chats) -> "Dialog": chat_id = dialog.peer if isinstance(chat_id, types.PeerUser): @@ -74,7 +74,7 @@ class Dialog(PyrogramType): chat_id = int("-100" + str(chat_id.channel_id)) return Dialog( - chat=Chat.parse_dialog(client, dialog.peer, users, chats), + chat=Chat._parse_dialog(client, dialog.peer, users, chats), top_message=messages.get(chat_id), unread_messages_count=dialog.unread_count, unread_mentions_count=dialog.unread_mentions_count, diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py index e1e8c5e2..0668052b 100644 --- a/pyrogram/client/types/user_and_chats/dialogs.py +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -44,7 +44,7 @@ class Dialogs(PyrogramType): self.dialogs = dialogs @staticmethod - def parse(client, dialogs) -> "Dialogs": + def _parse(client, dialogs) -> "Dialogs": users = {i.id: i for i in dialogs.users} chats = {i.id: i for i in dialogs.chats} @@ -63,10 +63,10 @@ class Dialogs(PyrogramType): else: chat_id = int("-100" + str(to_id.channel_id)) - messages[chat_id] = Message.parse(client, message, users, chats) + messages[chat_id] = Message._parse(client, message, users, chats) return Dialogs( total_count=getattr(dialogs, "count", len(dialogs.dialogs)), - dialogs=[Dialog.parse(client, dialog, messages, users, chats) for dialog in dialogs.dialogs], + dialogs=[Dialog._parse(client, dialog, messages, users, chats) for dialog in dialogs.dialogs], client=client ) diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 4755e297..74fc0516 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -104,7 +104,7 @@ class User(PyrogramType): self.restriction_reason = restriction_reason @staticmethod - def parse(client, user: types.User) -> "User" or None: + def _parse(client, user: types.User) -> "User" or None: if user is None: return None @@ -117,11 +117,11 @@ class User(PyrogramType): is_bot=user.bot, first_name=user.first_name, last_name=user.last_name, - status=UserStatus.parse(client, user.status, user.id, user.bot), + status=UserStatus._parse(client, user.status, user.id, user.bot), username=user.username, language_code=user.lang_code, phone_number=user.phone, - photo=ChatPhoto.parse(client, user.photo), + photo=ChatPhoto._parse(client, user.photo), restriction_reason=user.restriction_reason, client=client ) diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index bcb4d195..feda1eaa 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -85,7 +85,7 @@ class UserStatus(PyrogramType): self.long_time_ago = long_time_ago @staticmethod - def parse(client, user_status, user_id: int, is_bot: bool = False): + def _parse(client, user_status, user_id: int, is_bot: bool = False): if is_bot: return None From 5035daa9d77e19b650d9b188eb8eaa42f4577e42 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Dec 2018 13:29:58 +0100 Subject: [PATCH 095/326] Type hint all Pyrogram types --- pyrogram/client/types/bots/callback_query.py | 7 ++++--- pyrogram/client/types/bots/inline_keyboard_markup.py | 4 +++- pyrogram/client/types/bots/reply_keyboard_markup.py | 4 +++- .../client/types/messages_and_media/animation.py | 7 ++++--- pyrogram/client/types/messages_and_media/audio.py | 8 +++++--- pyrogram/client/types/messages_and_media/contact.py | 4 +++- pyrogram/client/types/messages_and_media/document.py | 5 +++-- pyrogram/client/types/messages_and_media/location.py | 8 +++++++- pyrogram/client/types/messages_and_media/message.py | 2 +- .../types/messages_and_media/message_entity.py | 6 ++++-- pyrogram/client/types/messages_and_media/messages.py | 7 +++++-- pyrogram/client/types/messages_and_media/photo.py | 6 ++++-- .../client/types/messages_and_media/photo_size.py | 3 ++- pyrogram/client/types/messages_and_media/sticker.py | 12 ++++++------ .../types/messages_and_media/user_profile_photos.py | 7 +++++-- pyrogram/client/types/messages_and_media/venue.py | 5 +++-- pyrogram/client/types/messages_and_media/video.py | 8 +++++--- .../client/types/messages_and_media/video_note.py | 5 +++-- pyrogram/client/types/messages_and_media/voice.py | 3 ++- pyrogram/client/types/user_and_chats/chat.py | 11 ++++++----- pyrogram/client/types/user_and_chats/chat_member.py | 6 ++++-- pyrogram/client/types/user_and_chats/chat_members.py | 7 +++++-- pyrogram/client/types/user_and_chats/chat_photo.py | 3 ++- pyrogram/client/types/user_and_chats/dialog.py | 8 +++++--- pyrogram/client/types/user_and_chats/dialogs.py | 7 +++++-- pyrogram/client/types/user_and_chats/user.py | 7 ++++--- pyrogram/client/types/user_and_chats/user_status.py | 4 +++- 27 files changed, 106 insertions(+), 58 deletions(-) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index ed96edea..5d242162 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -19,6 +19,7 @@ from base64 import b64encode from struct import pack +import pyrogram from pyrogram.api import types from ..pyrogram_type import PyrogramType from ..user_and_chats import User @@ -58,11 +59,11 @@ class CallbackQuery(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", id: str, - from_user, + from_user: User, chat_instance: str, - message=None, + message: "pyrogram.Message" = None, inline_message_id: str = None, data: bytes = None, game_short_name: str = None): diff --git a/pyrogram/client/types/bots/inline_keyboard_markup.py b/pyrogram/client/types/bots/inline_keyboard_markup.py index c13cc096..d958f306 100644 --- a/pyrogram/client/types/bots/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots/inline_keyboard_markup.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import List + from pyrogram.api.types import ReplyInlineMarkup, KeyboardButtonRow from . import InlineKeyboardButton from ..pyrogram_type import PyrogramType @@ -30,7 +32,7 @@ class InlineKeyboardMarkup(PyrogramType): """ def __init__(self, - inline_keyboard: list): + inline_keyboard: List[List[InlineKeyboardButton]]): super().__init__(None) self.inline_keyboard = inline_keyboard diff --git a/pyrogram/client/types/bots/reply_keyboard_markup.py b/pyrogram/client/types/bots/reply_keyboard_markup.py index a8378614..85f38b10 100644 --- a/pyrogram/client/types/bots/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots/reply_keyboard_markup.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import List + from pyrogram.api.types import KeyboardButtonRow from pyrogram.api.types import ReplyKeyboardMarkup as RawReplyKeyboardMarkup from . import KeyboardButton @@ -48,7 +50,7 @@ class ReplyKeyboardMarkup(PyrogramType): """ def __init__(self, - keyboard: list, + keyboard: List[List[KeyboardButton]], resize_keyboard: bool = None, one_time_keyboard: bool = None, selective: bool = None): diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index ba9b4dad..ff5ab577 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -18,6 +18,7 @@ from struct import pack +import pyrogram from pyrogram.api import types from .photo_size import PhotoSize from ..pyrogram_type import PyrogramType @@ -58,12 +59,12 @@ class Animation(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", file_id: str, width: int, height: int, duration: int, - thumb=None, + thumb: PhotoSize = None, file_name: str = None, mime_type: str = None, file_size: int = None, @@ -82,7 +83,7 @@ class Animation(PyrogramType): @staticmethod def _parse(client, animation: types.Document, video_attributes: types.DocumentAttributeVideo, - file_name: str) -> "Animation": + file_name: str) -> "Animation": return Animation( file_id=encode( pack( diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 0590e760..614c18fb 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -18,6 +18,7 @@ from struct import pack +import pyrogram from pyrogram.api import types from .photo_size import PhotoSize from ..pyrogram_type import PyrogramType @@ -58,10 +59,10 @@ class Audio(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", file_id: str, duration: int, - thumb=None, + thumb: PhotoSize = None, file_name: str = None, mime_type: str = None, file_size: int = None, @@ -81,7 +82,8 @@ class Audio(PyrogramType): self.title = title @staticmethod - def _parse(client, audio: types.Document, audio_attributes: types.DocumentAttributeAudio, file_name: str) -> "Audio": + def _parse(client, audio: types.Document, audio_attributes: types.DocumentAttributeAudio, + file_name: str) -> "Audio": return Audio( file_id=encode( pack( diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index 05cbfb1d..29f76bfc 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram + from pyrogram.api import types from ..pyrogram_type import PyrogramType @@ -42,7 +44,7 @@ class Contact(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", phone_number: str, first_name: str, last_name: str = None, diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index 172d816d..330474c4 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -18,6 +18,7 @@ from struct import pack +import pyrogram from pyrogram.api import types from .photo_size import PhotoSize from ..pyrogram_type import PyrogramType @@ -49,9 +50,9 @@ class Document(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", file_id: str, - thumb=None, + thumb: PhotoSize = None, file_name: str = None, mime_type: str = None, file_size: int = None, diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index 455e7f58..b57efb46 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram + from pyrogram.api import types from ..pyrogram_type import PyrogramType @@ -31,7 +33,11 @@ class Location(PyrogramType): Latitude as defined by sender. """ - def __init__(self, *, client, longitude: float, latitude: float): + def __init__(self, + *, + client: "pyrogram.Client", + longitude: float, + latitude: float): super().__init__(client) self.longitude = longitude diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 14662e00..13fb1b16 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -337,7 +337,7 @@ class Message(PyrogramType): @staticmethod def _parse(client, message: types.Message or types.MessageService or types.MessageEmpty, users: dict, chats: dict, - replies: int = 1): + replies: int = 1): if isinstance(message, types.MessageEmpty): return Message(message_id=message.id, empty=True, client=client) diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 4f625bd3..637d9fdd 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram + from pyrogram.api import types from ..pyrogram_type import PyrogramType from ..user_and_chats.user import User @@ -63,12 +65,12 @@ class MessageEntity(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", type: str, offset: int, length: int, url: str = None, - user=None): + user: User = None): super().__init__(client) self.type = type diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 48d6c85b..55c4fde1 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import List + +import pyrogram from pyrogram.api import types from .message import Message from ..pyrogram_type import PyrogramType @@ -35,9 +38,9 @@ class Messages(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", total_count: int, - messages: list): + messages: List[Message]): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index cdf3b191..6531d1b7 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -18,7 +18,9 @@ from base64 import b64encode from struct import pack +from typing import List +import pyrogram from pyrogram.api import types from .photo_size import PhotoSize from ..pyrogram_type import PyrogramType @@ -41,10 +43,10 @@ class Photo(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", id: str, date: int, - sizes: list): + sizes: List[PhotoSize]): super().__init__(client) self.id = id diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index 4fb9b30d..0250d76b 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -18,6 +18,7 @@ from struct import pack +import pyrogram from pyrogram.api import types from pyrogram.client.ext.utils import encode from ..pyrogram_type import PyrogramType @@ -42,7 +43,7 @@ class PhotoSize(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", file_id: str, width: int, height: int, diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index 22121dd8..fcdf63e6 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -19,6 +19,7 @@ from functools import lru_cache from struct import pack +import pyrogram from pyrogram.api import types, functions from pyrogram.api.errors import StickersetInvalid from .photo_size import PhotoSize @@ -65,18 +66,17 @@ class Sticker(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", file_id: str, width: int, height: int, - thumb=None, + thumb: PhotoSize = None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, emoji: str = None, - set_name: str = None, - mask_position=None): + set_name: str = None): super().__init__(client) self.file_id = file_id @@ -89,7 +89,7 @@ class Sticker(PyrogramType): self.height = height self.emoji = emoji self.set_name = set_name - self.mask_position = mask_position + # self.mask_position = mask_position @staticmethod @lru_cache(maxsize=256) @@ -105,7 +105,7 @@ class Sticker(PyrogramType): @staticmethod def _parse(client, sticker: types.Document, image_size_attributes: types.DocumentAttributeImageSize, - sticker_attributes: types.DocumentAttributeSticker, file_name: str) -> "Sticker": + sticker_attributes: types.DocumentAttributeSticker, file_name: str) -> "Sticker": sticker_set = sticker_attributes.stickerset if isinstance(sticker_set, types.InputStickerSetID): diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index ca27fe2c..dd850475 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import List + +import pyrogram from .photo import Photo from ..pyrogram_type import PyrogramType @@ -33,9 +36,9 @@ class UserProfilePhotos(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", total_count: int, - photos: list): + photos: List[Photo]): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index 12701d8c..20c2d6db 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import types from .location import Location from ..pyrogram_type import PyrogramType @@ -45,8 +46,8 @@ class Venue(PyrogramType): def __init__(self, *, - client, - location, + client: "pyrogram.Client", + location: Location, title: str, address: str, foursquare_id: str = None, diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index a69f0138..fe183bbd 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -18,6 +18,7 @@ from struct import pack +import pyrogram from pyrogram.api import types from .photo_size import PhotoSize from ..pyrogram_type import PyrogramType @@ -58,12 +59,12 @@ class Video(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", file_id: str, width: int, height: int, duration: int, - thumb=None, + thumb: PhotoSize = None, file_name: str = None, mime_type: str = None, file_size: int = None, @@ -81,7 +82,8 @@ class Video(PyrogramType): self.duration = duration @staticmethod - def _parse(client, video: types.Document, video_attributes: types.DocumentAttributeVideo, file_name: str) -> "Video": + def _parse(client, video: types.Document, video_attributes: types.DocumentAttributeVideo, + file_name: str) -> "Video": return Video( file_id=encode( pack( diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index 53419dda..c9f23efb 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -18,6 +18,7 @@ from struct import pack +import pyrogram from pyrogram.api import types from .photo_size import PhotoSize from ..pyrogram_type import PyrogramType @@ -52,11 +53,11 @@ class VideoNote(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", file_id: str, length: int, duration: int, - thumb=None, + thumb: PhotoSize = None, mime_type: str = None, file_size: int = None, date: int = None): diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index bf5ffaa8..c06b1741 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -18,6 +18,7 @@ from struct import pack +import pyrogram from pyrogram.api import types from ..pyrogram_type import PyrogramType from ...ext.utils import encode @@ -48,7 +49,7 @@ class Voice(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", file_id: str, duration: int, waveform: bytes = None, diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 4a3dfb20..914383ac 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import types from .chat_photo import ChatPhoto from ..pyrogram_type import PyrogramType @@ -78,7 +79,7 @@ class Chat(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", id: int, type: str, title: str = None, @@ -86,7 +87,7 @@ class Chat(PyrogramType): first_name: str = None, last_name: str = None, all_members_are_administrators: bool = None, - photo=None, + photo: ChatPhoto = None, description: str = None, invite_link: str = None, pinned_message=None, @@ -175,7 +176,7 @@ class Chat(PyrogramType): @staticmethod def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> "Chat": if isinstance(chat_full, types.UserFull): - _parsed_chat = Chat.parse_user_chat(client, chat_full.user) + parsed_chat = Chat._parse_user_chat(client, chat_full.user) parsed_chat.description = chat_full.about else: full_chat = chat_full.full_chat @@ -186,12 +187,12 @@ class Chat(PyrogramType): chat = i if isinstance(full_chat, types.ChatFull): - _parsed_chat = Chat.parse_chat_chat(client, chat) + parsed_chat = Chat._parse_chat_chat(client, chat) if isinstance(full_chat.participants, types.ChatParticipants): parsed_chat.members_count = len(full_chat.participants.participants) else: - _parsed_chat = Chat.parse_channel_chat(client, chat) + parsed_chat = Chat._parse_channel_chat(client, chat) parsed_chat.members_count = full_chat.participants_count parsed_chat.description = full_chat.about or None # TODO: Add StickerSet type diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 5fc9c988..aaf39859 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram + from pyrogram.api import types from ..pyrogram_type import PyrogramType @@ -81,8 +83,8 @@ class ChatMember(PyrogramType): def __init__(self, *, - client, - user, + client: "pyrogram.Client", + user: "pyrogram.User", status: str, until_date: int = None, can_be_edited: bool = None, diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index a798f312..7e79cd5f 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import List + +import pyrogram from pyrogram.api import types from .chat_member import ChatMember from .user import User @@ -35,9 +38,9 @@ class ChatMembers(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", total_count: int, - chat_members: list): + chat_members: List[ChatMember]): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index 33eca8c8..f4ed0fe3 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -18,6 +18,7 @@ from struct import pack +import pyrogram from pyrogram.api import types from ..pyrogram_type import PyrogramType from ...ext.utils import encode @@ -36,7 +37,7 @@ class ChatPhoto(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", small_file_id: str, big_file_id: str): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index b5679ed2..82dff29b 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram + from pyrogram.api import types from ..pyrogram_type import PyrogramType from ..user_and_chats import Chat @@ -46,9 +48,9 @@ class Dialog(PyrogramType): def __init__(self, *, - client, - chat, - top_message, + client: "pyrogram.Client", + chat: Chat, + top_message: "pyrogram.Message", unread_messages_count: int, unread_mentions_count: int, unread_mark: bool, diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py index 0668052b..3f44ea4c 100644 --- a/pyrogram/client/types/user_and_chats/dialogs.py +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import List + +import pyrogram from pyrogram.api import types from .dialog import Dialog from ..messages_and_media import Message @@ -35,9 +38,9 @@ class Dialogs(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", total_count: int, - dialogs: list): + dialogs: List[Dialog]): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 74fc0516..c3b733b0 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import types from .chat_photo import ChatPhoto from .user_status import UserStatus @@ -71,7 +72,7 @@ class User(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", id: int, is_self: bool, is_contact: bool, @@ -80,11 +81,11 @@ class User(PyrogramType): is_bot: bool, first_name: str, last_name: str = None, - status=None, + status: UserStatus = None, username: str = None, language_code: str = None, phone_number: str = None, - photo=None, + photo: ChatPhoto = None, restriction_reason: str = None): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index feda1eaa..4a62adc8 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram + from pyrogram.api import types from ..pyrogram_type import PyrogramType @@ -64,7 +66,7 @@ class UserStatus(PyrogramType): def __init__(self, *, - client, + client: "pyrogram.Client", user_id: int, online: bool = None, offline: bool = None, From 4fb99694708d37747916cb519abec51b0dad2bbb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Dec 2018 14:45:16 +0100 Subject: [PATCH 096/326] Replace Client with BaseClient type hint --- pyrogram/client/types/bots/callback_query.py | 2 +- pyrogram/client/types/messages_and_media/animation.py | 2 +- pyrogram/client/types/messages_and_media/audio.py | 2 +- pyrogram/client/types/messages_and_media/contact.py | 2 +- pyrogram/client/types/messages_and_media/document.py | 2 +- pyrogram/client/types/messages_and_media/location.py | 2 +- pyrogram/client/types/messages_and_media/message.py | 6 +++--- pyrogram/client/types/messages_and_media/message_entity.py | 2 +- pyrogram/client/types/messages_and_media/messages.py | 2 +- pyrogram/client/types/messages_and_media/photo.py | 2 +- pyrogram/client/types/messages_and_media/photo_size.py | 2 +- pyrogram/client/types/messages_and_media/sticker.py | 2 +- .../client/types/messages_and_media/user_profile_photos.py | 2 +- pyrogram/client/types/messages_and_media/venue.py | 2 +- pyrogram/client/types/messages_and_media/video.py | 2 +- pyrogram/client/types/messages_and_media/video_note.py | 2 +- pyrogram/client/types/messages_and_media/voice.py | 2 +- pyrogram/client/types/pyrogram_type.py | 2 +- pyrogram/client/types/user_and_chats/chat.py | 2 +- pyrogram/client/types/user_and_chats/chat_member.py | 2 +- pyrogram/client/types/user_and_chats/chat_members.py | 2 +- pyrogram/client/types/user_and_chats/chat_photo.py | 2 +- pyrogram/client/types/user_and_chats/dialog.py | 2 +- pyrogram/client/types/user_and_chats/dialogs.py | 2 +- pyrogram/client/types/user_and_chats/user.py | 2 +- pyrogram/client/types/user_and_chats/user_status.py | 2 +- 26 files changed, 28 insertions(+), 28 deletions(-) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index 5d242162..c3c23333 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -59,7 +59,7 @@ class CallbackQuery(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", id: str, from_user: User, chat_instance: str, diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index ff5ab577..6b7f7cf7 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -59,7 +59,7 @@ class Animation(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", file_id: str, width: int, height: int, diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 614c18fb..148ae805 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -59,7 +59,7 @@ class Audio(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", file_id: str, duration: int, thumb: PhotoSize = None, diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index 29f76bfc..16dd52bb 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -44,7 +44,7 @@ class Contact(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", phone_number: str, first_name: str, last_name: str = None, diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index 330474c4..db41df6c 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -50,7 +50,7 @@ class Document(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", file_id: str, thumb: PhotoSize = None, file_name: str = None, diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index b57efb46..fcdd5e31 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -35,7 +35,7 @@ class Location(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", longitude: float, latitude: float): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 13fb1b16..0dede950 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -210,11 +210,11 @@ class Message(PyrogramType): Messages sent from yourself to other chats are outgoing (*outgoing* is True). An exception is made for your own personal chat; messages sent there will be incoming. - matches (``List of regex Matches``, *optional*): + matches (List of regex Matches, *optional*): A list containing all `Match Objects `_ that match the text of this message. Only applicable when using :obj:`Filters.regex `. - command (``List of strings``, *optional*): + command (List of ``str``, *optional*): A list containing the command and its arguments, if any. E.g.: "/start 1 2 3" would produce ["start", "1", "2", "3"]. Only applicable when using :obj:`Filters.command `. @@ -228,7 +228,7 @@ class Message(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", message_id: int, date: int = None, chat: Chat = None, diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 637d9fdd..7544424c 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -65,7 +65,7 @@ class MessageEntity(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", type: str, offset: int, length: int, diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 55c4fde1..cd401c1b 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -38,7 +38,7 @@ class Messages(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", total_count: int, messages: List[Message]): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 6531d1b7..a52a7aa2 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -43,7 +43,7 @@ class Photo(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", id: str, date: int, sizes: List[PhotoSize]): diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index 0250d76b..7a4db1c2 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -43,7 +43,7 @@ class PhotoSize(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", file_id: str, width: int, height: int, diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index fcdf63e6..8b88e7bf 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -66,7 +66,7 @@ class Sticker(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", file_id: str, width: int, height: int, diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index dd850475..2bfd29c8 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -36,7 +36,7 @@ class UserProfilePhotos(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", total_count: int, photos: List[Photo]): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index 20c2d6db..443f479a 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -46,7 +46,7 @@ class Venue(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", location: Location, title: str, address: str, diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index fe183bbd..2c476b39 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -59,7 +59,7 @@ class Video(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", file_id: str, width: int, height: int, diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index c9f23efb..718432fa 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -53,7 +53,7 @@ class VideoNote(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", file_id: str, length: int, duration: int, diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index c06b1741..b9f799b3 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -49,7 +49,7 @@ class Voice(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", file_id: str, duration: int, waveform: bytes = None, diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py index 3c2babf8..f5502a72 100644 --- a/pyrogram/client/types/pyrogram_type.py +++ b/pyrogram/client/types/pyrogram_type.py @@ -52,7 +52,7 @@ class Encoder(JSONEncoder): return remove_none( OrderedDict( - [("_", "pyrogram:" + o.__class__.__name__)] + [("_", "pyrogram." + o.__class__.__name__)] + [i for i in content.items()] ) ) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 914383ac..5b5a54a8 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -79,7 +79,7 @@ class Chat(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", id: int, type: str, title: str = None, diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index aaf39859..b778d4d8 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -83,7 +83,7 @@ class ChatMember(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", user: "pyrogram.User", status: str, until_date: int = None, diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index 7e79cd5f..838517ab 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -38,7 +38,7 @@ class ChatMembers(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", total_count: int, chat_members: List[ChatMember]): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index f4ed0fe3..ad4b3151 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -37,7 +37,7 @@ class ChatPhoto(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", small_file_id: str, big_file_id: str): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index 82dff29b..c001708f 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -48,7 +48,7 @@ class Dialog(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", chat: Chat, top_message: "pyrogram.Message", unread_messages_count: int, diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py index 3f44ea4c..2492d5e2 100644 --- a/pyrogram/client/types/user_and_chats/dialogs.py +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -38,7 +38,7 @@ class Dialogs(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", total_count: int, dialogs: List[Dialog]): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index c3b733b0..354e8a09 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -72,7 +72,7 @@ class User(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", id: int, is_self: bool, is_contact: bool, diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index 4a62adc8..69c1921b 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -66,7 +66,7 @@ class UserStatus(PyrogramType): def __init__(self, *, - client: "pyrogram.Client", + client: "pyrogram.client.ext.BaseClient", user_id: int, online: bool = None, offline: bool = None, From b593463bd7819b18a36175e590243786dccc637d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Dec 2018 14:50:23 +0100 Subject: [PATCH 097/326] Type hint all the remaining method parameters --- .../methods/bots/get_inline_bot_results.py | 4 ++- .../methods/bots/request_callback_answer.py | 4 ++- .../methods/bots/send_inline_bot_result.py | 4 ++- .../client/methods/chats/delete_chat_photo.py | 5 ++- .../methods/chats/export_chat_invite_link.py | 5 ++- pyrogram/client/methods/chats/get_chat.py | 7 ++-- .../client/methods/chats/get_chat_member.py | 10 +++--- .../client/methods/chats/get_chat_members.py | 8 +++-- .../methods/chats/get_chat_members_count.py | 5 ++- pyrogram/client/methods/chats/get_dialogs.py | 4 +-- pyrogram/client/methods/chats/join_chat.py | 3 +- .../client/methods/chats/kick_chat_member.py | 8 +++-- pyrogram/client/methods/chats/leave_chat.py | 6 +++- .../client/methods/chats/pin_chat_message.py | 7 +++- .../methods/chats/promote_chat_member.py | 6 ++-- .../methods/chats/restrict_chat_member.py | 6 ++-- .../methods/chats/set_chat_description.py | 6 +++- .../client/methods/chats/set_chat_photo.py | 5 ++- .../client/methods/chats/set_chat_title.py | 6 +++- .../client/methods/chats/unban_chat_member.py | 6 ++-- .../methods/chats/unpin_chat_message.py | 5 ++- .../client/methods/contacts/add_contacts.py | 10 ++++-- .../methods/contacts/delete_contacts.py | 7 ++-- .../methods/decorators/on_callback_query.py | 4 ++- .../methods/decorators/on_deleted_messages.py | 4 ++- .../client/methods/decorators/on_message.py | 4 ++- .../methods/decorators/on_raw_update.py | 3 +- .../methods/decorators/on_user_status.py | 4 ++- .../methods/messages/delete_messages.py | 6 ++-- .../methods/messages/edit_message_caption.py | 9 ++--- .../methods/messages/edit_message_media.py | 10 +++--- .../messages/edit_message_reply_markup.py | 8 +++-- .../methods/messages/edit_message_text.py | 8 +++-- .../methods/messages/forward_messages.py | 10 +++--- .../client/methods/messages/get_history.py | 6 ++-- .../client/methods/messages/get_messages.py | 10 +++--- .../client/methods/messages/send_animation.py | 10 ++++-- .../client/methods/messages/send_audio.py | 10 ++++-- .../methods/messages/send_chat_action.py | 6 ++-- .../client/methods/messages/send_contact.py | 11 +++++-- .../client/methods/messages/send_document.py | 10 ++++-- .../client/methods/messages/send_location.py | 11 +++++-- .../methods/messages/send_media_group.py | 11 ++++--- .../client/methods/messages/send_message.py | 11 +++++-- .../client/methods/messages/send_photo.py | 10 ++++-- .../client/methods/messages/send_sticker.py | 10 ++++-- .../client/methods/messages/send_venue.py | 11 +++++-- .../client/methods/messages/send_video.py | 10 ++++-- .../methods/messages/send_video_note.py | 10 ++++-- .../client/methods/messages/send_voice.py | 10 ++++-- .../methods/password/change_cloud_password.py | 5 ++- .../methods/password/enable_cloud_password.py | 5 ++- .../methods/password/remove_cloud_password.py | 3 +- .../users/delete_user_profile_photos.py | 4 ++- pyrogram/client/methods/users/get_me.py | 2 +- .../methods/users/get_user_profile_photos.py | 6 ++-- pyrogram/client/methods/users/get_users.py | 7 ++-- .../methods/users/set_user_profile_photo.py | 3 +- .../methods/utilities/download_media.py | 33 ++++++++++--------- .../types/messages_and_media/message.py | 2 +- 60 files changed, 295 insertions(+), 139 deletions(-) diff --git a/pyrogram/client/methods/bots/get_inline_bot_results.py b/pyrogram/client/methods/bots/get_inline_bot_results.py index cb931d49..a5b9ae9f 100644 --- a/pyrogram/client/methods/bots/get_inline_bot_results.py +++ b/pyrogram/client/methods/bots/get_inline_bot_results.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from pyrogram.api.errors import UnknownError from pyrogram.client.ext import BaseClient @@ -23,7 +25,7 @@ from pyrogram.client.ext import BaseClient class GetInlineBotResults(BaseClient): def get_inline_bot_results(self, - bot: int or str, + bot: Union[int, str], query: str, offset: str = "", latitude: float = None, diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py index 470ad15a..0cb6a1dd 100644 --- a/pyrogram/client/methods/bots/request_callback_answer.py +++ b/pyrogram/client/methods/bots/request_callback_answer.py @@ -16,13 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions from pyrogram.client.ext import BaseClient class RequestCallbackAnswer(BaseClient): def request_callback_answer(self, - chat_id: int or str, + chat_id: Union[int, str], message_id: int, callback_data: bytes): """Use this method to request a callback answer from bots. This is the equivalent of clicking an diff --git a/pyrogram/client/methods/bots/send_inline_bot_result.py b/pyrogram/client/methods/bots/send_inline_bot_result.py index 97ea2a4a..23d36cba 100644 --- a/pyrogram/client/methods/bots/send_inline_bot_result.py +++ b/pyrogram/client/methods/bots/send_inline_bot_result.py @@ -16,13 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions from pyrogram.client.ext import BaseClient class SendInlineBotResult(BaseClient): def send_inline_bot_result(self, - chat_id: int or str, + chat_id: Union[int, str], query_id: int, result_id: str, disable_notification: bool = None, diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/client/methods/chats/delete_chat_photo.py index 5a1fe74e..d4658e4d 100644 --- a/pyrogram/client/methods/chats/delete_chat_photo.py +++ b/pyrogram/client/methods/chats/delete_chat_photo.py @@ -16,12 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from ...ext import BaseClient class DeleteChatPhoto(BaseClient): - def delete_chat_photo(self, chat_id: int or str): + def delete_chat_photo(self, + chat_id: Union[int, str]): """Use this method to delete a chat photo. Photos can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index 4972493b..8f068586 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -16,12 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from ...ext import BaseClient class ExportChatInviteLink(BaseClient): - def export_chat_invite_link(self, chat_id: int or str): + def export_chat_invite_link(self, + chat_id: Union[int, str]): """Use this method to generate a new invite link for a chat; any previously generated link is revoked. You must be an administrator in the chat for this to work and have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 7c191bf3..24651d37 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -16,13 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions, types from ...ext import BaseClient class GetChat(BaseClient): - def get_chat(self, chat_id: int or str): + def get_chat(self, + chat_id: Union[int, str]): """Use this method to get up to date information about the chat (current name of the user for one-on-one conversations, current username of a user, group or channel, etc.) @@ -45,4 +48,4 @@ class GetChat(BaseClient): else: r = self.send(functions.messages.GetFullChat(peer.chat_id)) - return pyrogram.Chat.parse_full(self, r) + return pyrogram.Chat._parse_full(self, r) diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index b304be17..7708f6fc 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions, types, errors from ...ext import BaseClient @@ -23,8 +25,8 @@ from ...ext import BaseClient class GetChatMember(BaseClient): def get_chat_member(self, - chat_id: int or str, - user_id: int or str): + chat_id: Union[int, str], + user_id: Union[int, str]): """Use this method to get information about one member of a chat. Args: @@ -52,7 +54,7 @@ class GetChatMember(BaseClient): ) ) - for member in pyrogram.ChatMembers.parse(self, full_chat).chat_members: + for member in pyrogram.ChatMembers._parse(self, full_chat).chat_members: if member.user.id == user_id.user_id: return member else: @@ -65,7 +67,7 @@ class GetChatMember(BaseClient): ) ) - return pyrogram.ChatMembers.parse( + return pyrogram.ChatMembers._parse( self, types.channels.ChannelParticipants( count=1, diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index 71728676..031ea46b 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions, types from ...ext import BaseClient @@ -32,7 +34,7 @@ class Filters: class GetChatMembers(BaseClient): def get_chat_members(self, - chat_id: int or str, + chat_id: Union[int, str], offset: int = 0, limit: int = 200, query: str = "", @@ -84,7 +86,7 @@ class GetChatMembers(BaseClient): peer = self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChat): - return pyrogram.ChatMembers.parse( + return pyrogram.ChatMembers._parse( self, self.send( functions.messages.GetFullChat( @@ -110,7 +112,7 @@ class GetChatMembers(BaseClient): else: raise ValueError("Invalid filter \"{}\"".format(filter)) - return pyrogram.ChatMembers.parse( + return pyrogram.ChatMembers._parse( self, self.send( functions.channels.GetParticipants( diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py index ec5aaae0..01bbd835 100644 --- a/pyrogram/client/methods/chats/get_chat_members_count.py +++ b/pyrogram/client/methods/chats/get_chat_members_count.py @@ -16,12 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from ...ext import BaseClient class GetChatMembersCount(BaseClient): - def get_chat_members_count(self, chat_id: int or str): + def get_chat_members_count(self, + chat_id: Union[int, str]): """Use this method to get the number of members in a chat. Args: diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index f8bed7c8..87cd5be2 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -23,7 +23,7 @@ from ...ext import BaseClient class GetDialogs(BaseClient): def get_dialogs(self, - offset_dialog=None, + offset_dialog: "pyrogram.Dialog" = None, limit: int = 100, pinned_only: bool = False): """Use this method to get the user's dialogs @@ -64,4 +64,4 @@ class GetDialogs(BaseClient): ) ) - return pyrogram.Dialogs.parse(self, r) + return pyrogram.Dialogs._parse(self, r) diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py index f5e3953c..2f14c617 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/client/methods/chats/join_chat.py @@ -21,7 +21,8 @@ from ...ext import BaseClient class JoinChat(BaseClient): - def join_chat(self, chat_id: str): + def join_chat(self, + chat_id: str): """Use this method to join a group chat or channel. Args: diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index 292fdfd6..a7459110 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions, types from ...ext import BaseClient @@ -23,8 +25,8 @@ from ...ext import BaseClient class KickChatMember(BaseClient): def kick_chat_member(self, - chat_id: int or str, - user_id: int or str, + chat_id: Union[int, str], + user_id: Union[int, str], until_date: int = 0): """Use this method to kick a user from a group, a supergroup or a channel. In the case of supergroups and channels, the user will not be able to return to the group on their own using @@ -86,7 +88,7 @@ class KickChatMember(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/chats/leave_chat.py b/pyrogram/client/methods/chats/leave_chat.py index 8f070b82..e0ac3bb6 100644 --- a/pyrogram/client/methods/chats/leave_chat.py +++ b/pyrogram/client/methods/chats/leave_chat.py @@ -16,12 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from ...ext import BaseClient class LeaveChat(BaseClient): - def leave_chat(self, chat_id: int or str, delete: bool = False): + def leave_chat(self, + chat_id: Union[int, str], + delete: bool = False): """Use this method to leave a group chat or channel. Args: diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py index de7e69d4..1ef07d34 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -16,12 +16,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from ...ext import BaseClient class PinChatMessage(BaseClient): - def pin_chat_message(self, chat_id: int or str, message_id: int, disable_notification: bool = None): + def pin_chat_message(self, + chat_id: Union[int, str], + message_id: int, + disable_notification: bool = None): """Use this method to pin a message in a supergroup or a channel. You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py index 9db7709b..d22a8ea7 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/client/methods/chats/promote_chat_member.py @@ -16,14 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from ...ext import BaseClient class PromoteChatMember(BaseClient): def promote_chat_member(self, - chat_id: int or str, - user_id: int or str, + chat_id: Union[int, str], + user_id: Union[int, str], can_change_info: bool = True, can_post_messages: bool = False, can_edit_messages: bool = False, diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index 35db5e59..c772d100 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -16,14 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from ...ext import BaseClient class RestrictChatMember(BaseClient): def restrict_chat_member(self, - chat_id: int or str, - user_id: int or str, + chat_id: Union[int, str], + user_id: Union[int, str], until_date: int = 0, can_send_messages: bool = False, can_send_media_messages: bool = False, diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/client/methods/chats/set_chat_description.py index 5f5ead7f..1606fe7e 100644 --- a/pyrogram/client/methods/chats/set_chat_description.py +++ b/pyrogram/client/methods/chats/set_chat_description.py @@ -16,12 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from ...ext import BaseClient class SetChatDescription(BaseClient): - def set_chat_description(self, chat_id: int or str, description: str): + def set_chat_description(self, + chat_id: Union[int, str], + description: str): """Use this method to change the description of a supergroup or a channel. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 51045a8a..ed058000 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -19,13 +19,16 @@ import os from base64 import b64decode from struct import unpack +from typing import Union from pyrogram.api import functions, types from ...ext import BaseClient class SetChatPhoto(BaseClient): - def set_chat_photo(self, chat_id: int or str, photo: str): + def set_chat_photo(self, + chat_id: Union[int, str], + photo: str): """Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/set_chat_title.py b/pyrogram/client/methods/chats/set_chat_title.py index c7176c82..58d4179c 100644 --- a/pyrogram/client/methods/chats/set_chat_title.py +++ b/pyrogram/client/methods/chats/set_chat_title.py @@ -16,12 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from ...ext import BaseClient class SetChatTitle(BaseClient): - def set_chat_title(self, chat_id: int or str, title: str): + def set_chat_title(self, + chat_id: Union[int, str], + title: str): """Use this method to change the title of a chat. Titles can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/unban_chat_member.py b/pyrogram/client/methods/chats/unban_chat_member.py index c8b20131..428e6fe8 100644 --- a/pyrogram/client/methods/chats/unban_chat_member.py +++ b/pyrogram/client/methods/chats/unban_chat_member.py @@ -16,14 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from ...ext import BaseClient class UnbanChatMember(BaseClient): def unban_chat_member(self, - chat_id: int or str, - user_id: int or str): + chat_id: Union[int, str], + user_id: Union[int, str]): """Use this method to unban a previously kicked user in a supergroup or channel. The user will **not** return to the group or channel automatically, but will be able to join via link, etc. You must be an administrator for this to work. diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/client/methods/chats/unpin_chat_message.py index 9bbb1021..bd6a22f8 100644 --- a/pyrogram/client/methods/chats/unpin_chat_message.py +++ b/pyrogram/client/methods/chats/unpin_chat_message.py @@ -16,12 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions, types from ...ext import BaseClient class UnpinChatMessage(BaseClient): - def unpin_chat_message(self, chat_id: int or str): + def unpin_chat_message(self, + chat_id: Union[int, str]): """Use this method to unpin a message in a supergroup or a channel. You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. diff --git a/pyrogram/client/methods/contacts/add_contacts.py b/pyrogram/client/methods/contacts/add_contacts.py index 3a1dcc92..a5f06050 100644 --- a/pyrogram/client/methods/contacts/add_contacts.py +++ b/pyrogram/client/methods/contacts/add_contacts.py @@ -16,17 +16,21 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import List + +import pyrogram from pyrogram.api import functions from ...ext import BaseClient class AddContacts(BaseClient): - def add_contacts(self, contacts: list): + def add_contacts(self, + contacts: List["pyrogram.InputPhoneContact"]): """Use this method to add contacts to your Telegram address book. Args: - contacts (``list``): - A list of :obj:`InputPhoneContact ` + contacts (List of :obj:`InputPhoneContact `): + The contact list to be added Returns: On success, the added contacts are returned. diff --git a/pyrogram/client/methods/contacts/delete_contacts.py b/pyrogram/client/methods/contacts/delete_contacts.py index 74f08dd1..2c18c6d8 100644 --- a/pyrogram/client/methods/contacts/delete_contacts.py +++ b/pyrogram/client/methods/contacts/delete_contacts.py @@ -16,17 +16,20 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import List + from pyrogram.api import functions, types from pyrogram.api.errors import PeerIdInvalid from ...ext import BaseClient class DeleteContacts(BaseClient): - def delete_contacts(self, ids: list): + def delete_contacts(self, + ids: List[int]): """Use this method to delete contacts from your Telegram address book Args: - ids (``list``): + ids (List of ``int``): A list of unique identifiers for the target users. Can be an ID (int), a username (string) or phone number (string). diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py index 51a6df2e..f34119e2 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/client/methods/decorators/on_callback_query.py @@ -22,7 +22,9 @@ from ...ext import BaseClient class OnCallbackQuery(BaseClient): - def on_callback_query(self=None, filters=None, group: int = 0): + def on_callback_query(self=None, + filters=None, + group: int = 0): """Use this decorator to automatically register a function for handling callback queries. This does the same thing as :meth:`add_handler` using the :class:`CallbackQueryHandler`. diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index c23b7594..406563f2 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.py @@ -22,7 +22,9 @@ from ...ext import BaseClient class OnDeletedMessages(BaseClient): - def on_deleted_messages(self=None, filters=None, group: int = 0): + def on_deleted_messages(self=None, + filters=None, + group: int = 0): """Use this decorator to automatically register a function for handling deleted messages. This does the same thing as :meth:`add_handler` using the :class:`DeletedMessagesHandler`. diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index a098cfa2..ec0e11e8 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.py @@ -22,7 +22,9 @@ from ...ext import BaseClient class OnMessage(BaseClient): - def on_message(self=None, filters=None, group: int = 0): + def on_message(self=None, + filters=None, + group: int = 0): """Use this decorator to automatically register a function for handling messages. This does the same thing as :meth:`add_handler` using the :class:`MessageHandler`. diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py index 52728e1c..8b2723df 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -21,7 +21,8 @@ from ...ext import BaseClient class OnRawUpdate(BaseClient): - def on_raw_update(self=None, group: int = 0): + def on_raw_update(self=None, + group: int = 0): """Use this decorator to automatically register a function for handling raw updates. This does the same thing as :meth:`add_handler` using the :class:`RawUpdateHandler`. diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py index 2ca41e56..6ae47a95 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/client/methods/decorators/on_user_status.py @@ -22,7 +22,9 @@ from ...ext import BaseClient class OnUserStatus(BaseClient): - def on_user_status(self=None, filters=None, group: int = 0): + def on_user_status(self=None, + filters=None, + group: int = 0): """Use this decorator to automatically register a function for handling user status updates. This does the same thing as :meth:`add_handler` using the :class:`UserStatusHandler`. diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/client/methods/messages/delete_messages.py index f824a4b6..6aaf710a 100644 --- a/pyrogram/client/methods/messages/delete_messages.py +++ b/pyrogram/client/methods/messages/delete_messages.py @@ -16,14 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union, Iterable + from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient class DeleteMessages(BaseClient): def delete_messages(self, - chat_id: int or str, - message_ids, + chat_id: Union[int, str], + message_ids: Iterable[int], revoke: bool = True): """Use this method to delete messages, including service messages, with the following limitations: diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index a79c21f4..938e7af5 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -16,19 +16,20 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import pyrogram +from typing import Union +import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient class EditMessageCaption(BaseClient): def edit_message_caption(self, - chat_id: int or str, + chat_id: Union[int, str], message_id: int, caption: str, parse_mode: str = "", - reply_markup=None): + reply_markup: "pyrogram.InlineKeyboardMarkup" = None): """Use this method to edit captions of messages. Args: @@ -70,7 +71,7 @@ class EditMessageCaption(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 5d06c31b..bdfbf353 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -20,6 +20,7 @@ import binascii import mimetypes import os import struct +from typing import Union import pyrogram from pyrogram.api import functions, types @@ -29,14 +30,15 @@ from pyrogram.client.types import ( InputMediaPhoto, InputMediaVideo, InputMediaAudio, InputMediaAnimation, InputMediaDocument ) +from pyrogram.client.types.input_media import InputMedia class EditMessageMedia(BaseClient): def edit_message_media(self, - chat_id: int or str, + chat_id: Union[int, str], message_id: int, - media, - reply_markup=None): + media: InputMedia, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None): """Use this method to edit audio, document, photo, or video messages. If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, @@ -334,7 +336,7 @@ class EditMessageMedia(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py index 3b301dcb..31d8f0b7 100644 --- a/pyrogram/client/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient @@ -23,9 +25,9 @@ from pyrogram.client.ext import BaseClient class EditMessageReplyMarkup(BaseClient): def edit_message_reply_markup(self, - chat_id: int or str, + chat_id: Union[int, str], message_id: int, - reply_markup=None): + reply_markup: "pyrogram.InlineKeyboardMarkup" = None): """Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots). Args: @@ -58,7 +60,7 @@ class EditMessageReplyMarkup(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index 991ff049..688c7ce2 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient @@ -23,12 +25,12 @@ from pyrogram.client.ext import BaseClient class EditMessageText(BaseClient): def edit_message_text(self, - chat_id: int or str, + chat_id: Union[int, str], message_id: int, text: str, parse_mode: str = "", disable_web_page_preview: bool = None, - reply_markup=None): + reply_markup: "pyrogram.InlineKeyboardMarkup" = None): """Use this method to edit text messages. Args: @@ -74,7 +76,7 @@ class EditMessageText(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index e2e10c93..cafa468b 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union, Iterable + import pyrogram from pyrogram.api import functions, types from ...ext import BaseClient @@ -23,9 +25,9 @@ from ...ext import BaseClient class ForwardMessages(BaseClient): def forward_messages(self, - chat_id: int or str, - from_chat_id: int or str, - message_ids, + chat_id: Union[int, str], + from_chat_id: Union[int, str], + message_ids: Iterable[int], disable_notification: bool = None): """Use this method to forward messages of any kind. @@ -78,7 +80,7 @@ class ForwardMessages(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): messages.append( - pyrogram.Message.parse( + pyrogram.Message._parse( self, i.message, users, chats ) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index bc272ce3..2a36d46c 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions from ...ext import BaseClient @@ -23,7 +25,7 @@ from ...ext import BaseClient class GetHistory(BaseClient): def get_history(self, - chat_id: int or str, + chat_id: Union[int, str], offset: int = 0, limit: int = 100, offset_id: int = 0, @@ -59,7 +61,7 @@ class GetHistory(BaseClient): :class:`Error ` in case of a Telegram RPC error. """ - return pyrogram.Messages.parse( + return pyrogram.Messages._parse( self, self.send( functions.messages.GetHistory( diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index 2e3930ae..9f67d9a0 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union, Iterable + import pyrogram from pyrogram.api import functions, types from ...ext import BaseClient @@ -23,9 +25,9 @@ from ...ext import BaseClient class GetMessages(BaseClient): def get_messages(self, - chat_id: int or str, - message_ids: int or list = None, - reply_to_message_ids: int or list = None, + chat_id: Union[int, str], + message_ids: Union[int, Iterable[int]] = None, + reply_to_message_ids: Union[int, Iterable[int]] = None, replies: int = 1): """Use this method to get one or more messages that belong to a specific chat. You can retrieve up to 200 messages at once. @@ -76,6 +78,6 @@ class GetMessages(BaseClient): else: rpc = functions.messages.GetMessages(id=ids) - messages = pyrogram.Messages.parse(self, self.send(rpc)) + messages = pyrogram.Messages._parse(self, self.send(rpc)) return messages if is_iterable else messages.messages[0] diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index c86fb867..8e240f5b 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -20,6 +20,7 @@ import binascii import mimetypes import os import struct +from typing import Union import pyrogram from pyrogram.api import functions, types @@ -29,7 +30,7 @@ from pyrogram.client.ext import BaseClient, utils class SendAnimation(BaseClient): def send_animation(self, - chat_id: int or str, + chat_id: Union[int, str], animation: str, caption: str = "", parse_mode: str = "", @@ -39,7 +40,10 @@ class SendAnimation(BaseClient): thumb: str = None, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None, progress: callable = None, progress_args: tuple = ()): """Use this method to send animation files (animation or H.264/MPEG-4 AVC video without sound). @@ -185,7 +189,7 @@ class SendAnimation(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 12c65dec..405d0561 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -20,6 +20,7 @@ import binascii import mimetypes import os import struct +from typing import Union import pyrogram from pyrogram.api import functions, types @@ -29,7 +30,7 @@ from pyrogram.client.ext import BaseClient, utils class SendAudio(BaseClient): def send_audio(self, - chat_id: int or str, + chat_id: Union[int, str], audio: str, caption: str = "", parse_mode: str = "", @@ -39,7 +40,10 @@ class SendAudio(BaseClient): thumb: str = None, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None, progress: callable = None, progress_args: tuple = ()): """Use this method to send audio files. @@ -185,7 +189,7 @@ class SendAudio(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_chat_action.py b/pyrogram/client/methods/messages/send_chat_action.py index c519f67b..61e9994f 100644 --- a/pyrogram/client/methods/messages/send_chat_action.py +++ b/pyrogram/client/methods/messages/send_chat_action.py @@ -16,14 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram.api import functions from pyrogram.client.ext import BaseClient, ChatAction class SendChatAction(BaseClient): def send_chat_action(self, - chat_id: int or str, - action: ChatAction or str, + chat_id: Union[int, str], + action: Union[ChatAction, str], progress: int = 0): """Use this method when you need to tell the other party that something is happening on your side. diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index a5d88f97..d7f93743 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient @@ -23,14 +25,17 @@ from pyrogram.client.ext import BaseClient class SendContact(BaseClient): def send_contact(self, - chat_id: int or str, + chat_id: Union[int, str], phone_number: str, first_name: str, last_name: str = "", vcard: str = "", disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None): + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None): """Use this method to send phone contacts. Args: @@ -87,7 +92,7 @@ class SendContact(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 66a65e9d..d7bb62d1 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -20,6 +20,7 @@ import binascii import mimetypes import os import struct +from typing import Union import pyrogram from pyrogram.api import functions, types @@ -29,14 +30,17 @@ from pyrogram.client.ext import BaseClient, utils class SendDocument(BaseClient): def send_document(self, - chat_id: int or str, + chat_id: Union[int, str], document: str, thumb: str = None, caption: str = "", parse_mode: str = "", disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None, progress: callable = None, progress_args: tuple = ()): """Use this method to send general files. @@ -166,7 +170,7 @@ class SendDocument(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py index baa84966..0efdb1df 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/client/methods/messages/send_location.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient @@ -23,12 +25,15 @@ from pyrogram.client.ext import BaseClient class SendLocation(BaseClient): def send_location(self, - chat_id: int or str, + chat_id: Union[int, str], latitude: float, longitude: float, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None): + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None): """Use this method to send points on the map. Args: @@ -79,7 +84,7 @@ class SendLocation(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index bc9f6971..d5549089 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -20,10 +20,11 @@ import binascii import mimetypes import os import struct +from typing import Union, List +import pyrogram from pyrogram.api import functions, types from pyrogram.api.errors import FileIdInvalid -from pyrogram.client import types as pyrogram_types from pyrogram.client.ext import BaseClient, utils @@ -32,8 +33,8 @@ class SendMediaGroup(BaseClient): # TODO: Return new Message object # TODO: Figure out how to send albums using URLs def send_media_group(self, - chat_id: int or str, - media: list, + chat_id: Union[int, str], + media: List[Union["pyrogram.InputMediaPhoto", "pyrogram.InputMediaVideo"]], disable_notification: bool = None, reply_to_message_id: int = None): """Use this method to send a group of photos or videos as an album. @@ -62,7 +63,7 @@ class SendMediaGroup(BaseClient): for i in media: style = self.html if i.parse_mode.lower() == "html" else self.markdown - if isinstance(i, pyrogram_types.InputMediaPhoto): + if isinstance(i, pyrogram.InputMediaPhoto): if os.path.exists(i.media): media = self.send( functions.messages.UploadMedia( @@ -101,7 +102,7 @@ class SendMediaGroup(BaseClient): access_hash=unpacked[3] ) ) - elif isinstance(i, pyrogram_types.InputMediaVideo): + elif isinstance(i, pyrogram.InputMediaVideo): if os.path.exists(i.media): media = self.send( functions.messages.UploadMedia( diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 77af4db8..a04a67f2 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions, types from ...ext import BaseClient @@ -23,13 +25,16 @@ from ...ext import BaseClient class SendMessage(BaseClient): def send_message(self, - chat_id: int or str, + chat_id: Union[int, str], text: str, parse_mode: str = "", disable_web_page_preview: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None): + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None): """Use this method to send text messages. Args: @@ -99,7 +104,7 @@ class SendMessage(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 323c2fa5..5b7b3268 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -19,6 +19,7 @@ import binascii import os import struct +from typing import Union import pyrogram from pyrogram.api import functions, types @@ -28,14 +29,17 @@ from pyrogram.client.ext import BaseClient, utils class SendPhoto(BaseClient): def send_photo(self, - chat_id: int or str, + chat_id: Union[int, str], photo: str, caption: str = "", parse_mode: str = "", ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None, progress: callable = None, progress_args: tuple = ()): """Use this method to send photos. @@ -161,7 +165,7 @@ class SendPhoto(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index fccfb0b5..550bcddb 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -19,6 +19,7 @@ import binascii import os import struct +from typing import Union import pyrogram from pyrogram.api import functions, types @@ -28,11 +29,14 @@ from pyrogram.client.ext import BaseClient, utils class SendSticker(BaseClient): def send_sticker(self, - chat_id: int or str, + chat_id: Union[int, str], sticker: str, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None, progress: callable = None, progress_args: tuple = ()): """Use this method to send .webp stickers. @@ -145,7 +149,7 @@ class SendSticker(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/client/methods/messages/send_venue.py index 1155ca79..35d725d1 100644 --- a/pyrogram/client/methods/messages/send_venue.py +++ b/pyrogram/client/methods/messages/send_venue.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient @@ -23,7 +25,7 @@ from pyrogram.client.ext import BaseClient class SendVenue(BaseClient): def send_venue(self, - chat_id: int or str, + chat_id: Union[int, str], latitude: float, longitude: float, title: str, @@ -32,7 +34,10 @@ class SendVenue(BaseClient): foursquare_type: str = "", disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None): + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None): """Use this method to send information about a venue. Args: @@ -101,7 +106,7 @@ class SendVenue(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 47b15d59..c6936cff 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -20,6 +20,7 @@ import binascii import mimetypes import os import struct +from typing import Union import pyrogram from pyrogram.api import functions, types @@ -29,7 +30,7 @@ from pyrogram.client.ext import BaseClient, utils class SendVideo(BaseClient): def send_video(self, - chat_id: int or str, + chat_id: Union[int, str], video: str, caption: str = "", parse_mode: str = "", @@ -40,7 +41,10 @@ class SendVideo(BaseClient): supports_streaming: bool = True, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None, progress: callable = None, progress_args: tuple = ()): """Use this method to send video files. @@ -188,7 +192,7 @@ class SendVideo(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 61ae0017..75345607 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -20,6 +20,7 @@ import binascii import mimetypes import os import struct +from typing import Union import pyrogram from pyrogram.api import functions, types @@ -29,14 +30,17 @@ from pyrogram.client.ext import BaseClient, utils class SendVideoNote(BaseClient): def send_video_note(self, - chat_id: int or str, + chat_id: Union[int, str], video_note: str, duration: int = 0, length: int = 1, thumb: str = None, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None, progress: callable = None, progress_args: tuple = ()): """Use this method to send video messages. @@ -164,7 +168,7 @@ class SendVideoNote(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 4882a87f..52cbc833 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -20,6 +20,7 @@ import binascii import mimetypes import os import struct +from typing import Union import pyrogram from pyrogram.api import functions, types @@ -29,14 +30,17 @@ from pyrogram.client.ext import BaseClient, utils class SendVoice(BaseClient): def send_voice(self, - chat_id: int or str, + chat_id: Union[int, str], voice: str, caption: str = "", parse_mode: str = "", duration: int = 0, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup=None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None, progress: callable = None, progress_args: tuple = ()): """Use this method to send audio files. @@ -164,7 +168,7 @@ class SendVoice(BaseClient): else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message.parse( + return pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index 4b5e86b3..600f58fa 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -24,7 +24,10 @@ from ...ext import BaseClient class ChangeCloudPassword(BaseClient): - def change_cloud_password(self, current_password: str, new_password: str, new_hint: str = ""): + def change_cloud_password(self, + current_password: str, + new_password: str, + new_hint: str = ""): """Use this method to change your Two-Step Verification password (Cloud Password) with a new one. Args: diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index 80d527c4..77c731dd 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -24,7 +24,10 @@ from ...ext import BaseClient class EnableCloudPassword(BaseClient): - def enable_cloud_password(self, password: str, hint: str = "", email: str = ""): + def enable_cloud_password(self, + password: str, + hint: str = "", + email: str = ""): """Use this method to enable the Two-Step Verification security feature (Cloud Password) on your account. This password will be asked when you log in on a new device in addition to the SMS code. diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py index 5a9875ff..c82436b7 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -23,7 +23,8 @@ from ...ext import BaseClient class RemoveCloudPassword(BaseClient): - def remove_cloud_password(self, password: str): + def remove_cloud_password(self, + password: str): """Use this method to turn off the Two-Step Verification security feature (Cloud Password) on your account. Args: diff --git a/pyrogram/client/methods/users/delete_user_profile_photos.py b/pyrogram/client/methods/users/delete_user_profile_photos.py index 6f13e17f..4f3c5fde 100644 --- a/pyrogram/client/methods/users/delete_user_profile_photos.py +++ b/pyrogram/client/methods/users/delete_user_profile_photos.py @@ -18,13 +18,15 @@ from base64 import b64decode from struct import unpack +from typing import List, Union from pyrogram.api import functions, types from ...ext import BaseClient class DeleteUserProfilePhotos(BaseClient): - def delete_user_profile_photos(self, id: str or list): + def delete_user_profile_photos(self, + id: Union[str, List[str]]): """Use this method to delete your own profile photos Args: diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index c7d32968..9072a909 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -31,7 +31,7 @@ class GetMe(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ - return pyrogram.User.parse( + return pyrogram.User._parse( self, self.send( functions.users.GetFullUser( diff --git a/pyrogram/client/methods/users/get_user_profile_photos.py b/pyrogram/client/methods/users/get_user_profile_photos.py index 1403eb79..86ecb74d 100644 --- a/pyrogram/client/methods/users/get_user_profile_photos.py +++ b/pyrogram/client/methods/users/get_user_profile_photos.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions from ...ext import BaseClient @@ -23,7 +25,7 @@ from ...ext import BaseClient class GetUserProfilePhotos(BaseClient): def get_user_profile_photos(self, - user_id: int or str, + user_id: Union[int, str], offset: int = 0, limit: int = 100): """Use this method to get a list of profile pictures for a user. @@ -48,7 +50,7 @@ class GetUserProfilePhotos(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ - return pyrogram.UserProfilePhotos.parse( + return pyrogram.UserProfilePhotos._parse( self, self.send( functions.photos.GetUserPhotos( diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py index 7251476a..075814ca 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/client/methods/users/get_users.py @@ -16,13 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Iterable, Union + import pyrogram from pyrogram.api import functions from ...ext import BaseClient class GetUsers(BaseClient): - def get_users(self, user_ids): + def get_users(self, + user_ids: Iterable[Union[int, str]]): """Use this method to get information about a user. You can retrieve up to 200 users at once. @@ -53,6 +56,6 @@ class GetUsers(BaseClient): users = [] for i in r: - users.append(pyrogram.User.parse(self, i)) + users.append(pyrogram.User._parse(self, i)) return users if is_iterable else users[0] diff --git a/pyrogram/client/methods/users/set_user_profile_photo.py b/pyrogram/client/methods/users/set_user_profile_photo.py index b3ab66b1..68e174d6 100644 --- a/pyrogram/client/methods/users/set_user_profile_photo.py +++ b/pyrogram/client/methods/users/set_user_profile_photo.py @@ -21,7 +21,8 @@ from ...ext import BaseClient class SetUserProfilePhoto(BaseClient): - def set_user_profile_photo(self, photo: str): + def set_user_profile_photo(self, + photo: str): """Use this method to set a new profile photo. This method only works for Users. diff --git a/pyrogram/client/methods/utilities/download_media.py b/pyrogram/client/methods/utilities/download_media.py index f0ad0396..8c609d98 100644 --- a/pyrogram/client/methods/utilities/download_media.py +++ b/pyrogram/client/methods/utilities/download_media.py @@ -17,14 +17,15 @@ # along with Pyrogram. If not, see . from threading import Event +from typing import Union -from pyrogram.client import types as pyrogram_types +import pyrogram from ...ext import BaseClient class DownloadMedia(BaseClient): def download_media(self, - message: pyrogram_types.Message or str, + message: Union["pyrogram.Message", str], file_name: str = "", block: bool = True, progress: callable = None, @@ -78,9 +79,9 @@ class DownloadMedia(BaseClient): """ error_message = "This message doesn't contain any downloadable media" - if isinstance(message, pyrogram_types.Message): + if isinstance(message, pyrogram.Message): if message.photo: - media = pyrogram_types.Document( + media = pyrogram.Document( file_id=message.photo.sizes[-1].file_id, file_size=message.photo.sizes[-1].file_size, mime_type="", @@ -104,18 +105,18 @@ class DownloadMedia(BaseClient): else: raise ValueError(error_message) elif isinstance(message, ( - pyrogram_types.Photo, - pyrogram_types.PhotoSize, - pyrogram_types.Audio, - pyrogram_types.Document, - pyrogram_types.Video, - pyrogram_types.Voice, - pyrogram_types.VideoNote, - pyrogram_types.Sticker, - pyrogram_types.Animation + pyrogram.Photo, + pyrogram.PhotoSize, + pyrogram.Audio, + pyrogram.Document, + pyrogram.Video, + pyrogram.Voice, + pyrogram.VideoNote, + pyrogram.Sticker, + pyrogram.Animation )): - if isinstance(message, pyrogram_types.Photo): - media = pyrogram_types.Document( + if isinstance(message, pyrogram.Photo): + media = pyrogram.Document( file_id=message.sizes[-1].file_id, file_size=message.sizes[-1].file_size, mime_type="", @@ -125,7 +126,7 @@ class DownloadMedia(BaseClient): else: media = message elif isinstance(message, str): - media = pyrogram_types.Document( + media = pyrogram.Document( file_id=message, file_size=0, mime_type="", diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 0dede950..25c35456 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -483,7 +483,7 @@ class Message(PyrogramType): else: video = pyrogram.Video._parse(client, doc, video_attributes, file_name) elif types.DocumentAttributeSticker in attributes: - sticker = pyrogram.Sticker.parse( + sticker = pyrogram.Sticker._parse( client, doc, attributes.get(types.DocumentAttributeImageSize, None), attributes[types.DocumentAttributeSticker], From bf0b947253f7814c97e5c23efcb3a7c56281beec Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Dec 2018 14:55:48 +0100 Subject: [PATCH 098/326] Type hint all Client methods --- pyrogram/client/client.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 1f06d064..f5aa33ea 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -37,6 +37,7 @@ from importlib import import_module from pathlib import Path from signal import signal, SIGINT, SIGTERM, SIGABRT from threading import Thread +from typing import Union, List from pyrogram.api import functions, types from pyrogram.api.core import Object @@ -152,7 +153,7 @@ class Client(Methods, BaseClient): def __init__(self, session_name: str, - api_id: int or str = None, + api_id: Union[int, str] = None, api_hash: str = None, app_version: str = None, device_model: str = None, @@ -162,7 +163,7 @@ class Client(Methods, BaseClient): proxy: dict = None, test_mode: bool = False, phone_number: str = None, - phone_code: str or callable = None, + phone_code: Union[str, callable] = None, password: str = None, force_sms: bool = False, first_name: str = None, @@ -367,7 +368,7 @@ class Client(Methods, BaseClient): self.start() self.idle() - def add_handler(self, handler, group: int = 0): + def add_handler(self, handler: Handler, group: int = 0): """Use this method to register an update handler. You can register multiple handlers, but at most one handler within a group @@ -391,7 +392,7 @@ class Client(Methods, BaseClient): return handler, group - def remove_handler(self, handler, group: int = 0): + def remove_handler(self, handler: Handler, group: int = 0): """Removes a previously-added update handler. Make sure to provide the right group that the handler was added in. You can use @@ -624,7 +625,9 @@ class Client(Methods, BaseClient): print("Logged in successfully as {}".format(r.user.first_name)) - def fetch_peers(self, entities: list): + def fetch_peers(self, entities: List[Union[types.User, + types.Chat, types.ChatForbidden, + types.Channel, types.ChannelForbidden]]): for entity in entities: if isinstance(entity, types.User): user_id = entity.id @@ -886,7 +889,10 @@ class Client(Methods, BaseClient): log.debug("{} stopped".format(name)) - def send(self, data: Object, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT): + def send(self, + data: Object, + retries: int = Session.MAX_RETRIES, + timeout: float = Session.WAIT_TIMEOUT): """Use this method to send Raw Function queries. This method makes possible to manually call every single Telegram API method in a low-level manner. @@ -1045,7 +1051,8 @@ class Client(Methods, BaseClient): indent=4 ) - def get_initial_dialogs_chunk(self, offset_date: int = 0): + def get_initial_dialogs_chunk(self, + offset_date: int = 0): while True: try: r = self.send( @@ -1077,7 +1084,8 @@ class Client(Methods, BaseClient): self.get_initial_dialogs_chunk() - def resolve_peer(self, peer_id: int or str): + def resolve_peer(self, + peer_id: Union[int, str]): """Use this method to get the InputPeer of a known peer_id. This is a utility method intended to be used only when working with Raw Functions (i.e: a Telegram API method From a0355a5cc44cd6a8f86298f635a1f64345317a9f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 19 Dec 2018 15:59:15 +0100 Subject: [PATCH 099/326] Type hint all methods return values --- pyrogram/client/methods/chats/delete_chat_photo.py | 2 +- .../client/methods/chats/export_chat_invite_link.py | 2 +- pyrogram/client/methods/chats/get_chat.py | 2 +- pyrogram/client/methods/chats/get_chat_member.py | 11 ++--------- pyrogram/client/methods/chats/get_chat_members.py | 2 +- .../client/methods/chats/get_chat_members_count.py | 2 +- pyrogram/client/methods/chats/get_dialogs.py | 2 +- pyrogram/client/methods/chats/kick_chat_member.py | 2 +- pyrogram/client/methods/chats/pin_chat_message.py | 2 +- pyrogram/client/methods/chats/promote_chat_member.py | 2 +- pyrogram/client/methods/chats/restrict_chat_member.py | 2 +- pyrogram/client/methods/chats/set_chat_description.py | 2 +- pyrogram/client/methods/chats/set_chat_photo.py | 2 +- pyrogram/client/methods/chats/set_chat_title.py | 2 +- pyrogram/client/methods/chats/unban_chat_member.py | 2 +- pyrogram/client/methods/chats/unpin_chat_message.py | 2 +- .../client/methods/decorators/on_callback_query.py | 7 +++++-- .../client/methods/decorators/on_deleted_messages.py | 7 +++++-- pyrogram/client/methods/decorators/on_disconnect.py | 5 +++-- pyrogram/client/methods/decorators/on_message.py | 2 +- pyrogram/client/methods/decorators/on_raw_update.py | 7 +++++-- pyrogram/client/methods/decorators/on_user_status.py | 7 +++++-- pyrogram/client/methods/messages/delete_messages.py | 2 +- .../client/methods/messages/edit_message_caption.py | 2 +- .../client/methods/messages/edit_message_media.py | 2 +- .../methods/messages/edit_message_reply_markup.py | 2 +- pyrogram/client/methods/messages/edit_message_text.py | 2 +- pyrogram/client/methods/messages/forward_messages.py | 10 +++++++--- pyrogram/client/methods/messages/get_messages.py | 2 +- pyrogram/client/methods/messages/send_animation.py | 2 +- pyrogram/client/methods/messages/send_audio.py | 2 +- pyrogram/client/methods/messages/send_contact.py | 2 +- pyrogram/client/methods/messages/send_document.py | 2 +- pyrogram/client/methods/messages/send_location.py | 2 +- pyrogram/client/methods/messages/send_message.py | 2 +- pyrogram/client/methods/messages/send_photo.py | 2 +- pyrogram/client/methods/messages/send_sticker.py | 2 +- pyrogram/client/methods/messages/send_venue.py | 2 +- pyrogram/client/methods/messages/send_video.py | 2 +- pyrogram/client/methods/messages/send_video_note.py | 2 +- pyrogram/client/methods/messages/send_voice.py | 2 +- .../client/methods/password/change_cloud_password.py | 2 +- .../client/methods/password/enable_cloud_password.py | 2 +- .../client/methods/password/remove_cloud_password.py | 2 +- .../methods/users/delete_user_profile_photos.py | 2 +- pyrogram/client/methods/users/get_me.py | 2 +- .../client/methods/users/get_user_profile_photos.py | 2 +- pyrogram/client/methods/users/get_users.py | 8 ++++---- .../client/methods/users/set_user_profile_photo.py | 2 +- pyrogram/client/methods/utilities/download_media.py | 2 +- pyrogram/client/types/user_and_chats/chat_member.py | 2 ++ 51 files changed, 80 insertions(+), 68 deletions(-) diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/client/methods/chats/delete_chat_photo.py index d4658e4d..a33cae05 100644 --- a/pyrogram/client/methods/chats/delete_chat_photo.py +++ b/pyrogram/client/methods/chats/delete_chat_photo.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class DeleteChatPhoto(BaseClient): def delete_chat_photo(self, - chat_id: Union[int, str]): + chat_id: Union[int, str]) -> bool: """Use this method to delete a chat photo. Photos can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index 8f068586..f458e91e 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class ExportChatInviteLink(BaseClient): def export_chat_invite_link(self, - chat_id: Union[int, str]): + chat_id: Union[int, str]) -> str: """Use this method to generate a new invite link for a chat; any previously generated link is revoked. You must be an administrator in the chat for this to work and have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 24651d37..f5bf19a9 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -25,7 +25,7 @@ from ...ext import BaseClient class GetChat(BaseClient): def get_chat(self, - chat_id: Union[int, str]): + chat_id: Union[int, str]) -> "pyrogram.Chat": """Use this method to get up to date information about the chat (current name of the user for one-on-one conversations, current username of a user, group or channel, etc.) diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index 7708f6fc..9f13eac2 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -26,7 +26,7 @@ from ...ext import BaseClient class GetChatMember(BaseClient): def get_chat_member(self, chat_id: Union[int, str], - user_id: Union[int, str]): + user_id: Union[int, str]) -> "pyrogram.ChatMember": """Use this method to get information about one member of a chat. Args: @@ -67,13 +67,6 @@ class GetChatMember(BaseClient): ) ) - return pyrogram.ChatMembers._parse( - self, - types.channels.ChannelParticipants( - count=1, - participants=[r.participant], - users=r.users - ) - ).chat_members[0] + return pyrogram.ChatMember._parse(self, r.participant, r.users[0]) else: raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index 031ea46b..8173423b 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -38,7 +38,7 @@ class GetChatMembers(BaseClient): offset: int = 0, limit: int = 200, query: str = "", - filter: str = Filters.ALL): + filter: str = Filters.ALL) -> "pyrogram.ChatMembers": """Use this method to get the members list of a chat. A chat can be either a basic group, a supergroup or a channel. diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py index 01bbd835..37eb69cb 100644 --- a/pyrogram/client/methods/chats/get_chat_members_count.py +++ b/pyrogram/client/methods/chats/get_chat_members_count.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class GetChatMembersCount(BaseClient): def get_chat_members_count(self, - chat_id: Union[int, str]): + chat_id: Union[int, str]) -> int: """Use this method to get the number of members in a chat. Args: diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 87cd5be2..0e04423c 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -25,7 +25,7 @@ class GetDialogs(BaseClient): def get_dialogs(self, offset_dialog: "pyrogram.Dialog" = None, limit: int = 100, - pinned_only: bool = False): + pinned_only: bool = False) -> "pyrogram.Dialogs": """Use this method to get the user's dialogs You can get up to 100 dialogs at once. diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index a7459110..4ea4d6bb 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -27,7 +27,7 @@ class KickChatMember(BaseClient): def kick_chat_member(self, chat_id: Union[int, str], user_id: Union[int, str], - until_date: int = 0): + until_date: int = 0) -> "pyrogram.Message": """Use this method to kick a user from a group, a supergroup or a channel. In the case of supergroups and channels, the user will not be able to return to the group on their own using invite links, etc., unless unbanned first. You must be an administrator in the chat for this to work and must diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py index 1ef07d34..5a4ab50d 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -26,7 +26,7 @@ class PinChatMessage(BaseClient): def pin_chat_message(self, chat_id: Union[int, str], message_id: int, - disable_notification: bool = None): + disable_notification: bool = None) -> bool: """Use this method to pin a message in a supergroup or a channel. You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py index d22a8ea7..18453b58 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/client/methods/chats/promote_chat_member.py @@ -33,7 +33,7 @@ class PromoteChatMember(BaseClient): can_invite_users: bool = True, can_restrict_members: bool = True, can_pin_messages: bool = False, - can_promote_members: bool = False): + can_promote_members: bool = False) -> bool: """Use this method to promote or demote a user in a supergroup or a channel. You must be an administrator in the chat for this to work and must have the appropriate admin rights. Pass False for all boolean parameters to demote a user. diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index c772d100..f9670250 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -30,7 +30,7 @@ class RestrictChatMember(BaseClient): can_send_messages: bool = False, can_send_media_messages: bool = False, can_send_other_messages: bool = False, - can_add_web_page_previews: bool = False): + can_add_web_page_previews: bool = False) -> bool: """Use this method to restrict a user in a supergroup. The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. Pass True for all boolean parameters to lift restrictions from a user. diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/client/methods/chats/set_chat_description.py index 1606fe7e..7cf91da5 100644 --- a/pyrogram/client/methods/chats/set_chat_description.py +++ b/pyrogram/client/methods/chats/set_chat_description.py @@ -25,7 +25,7 @@ from ...ext import BaseClient class SetChatDescription(BaseClient): def set_chat_description(self, chat_id: Union[int, str], - description: str): + description: str) -> bool: """Use this method to change the description of a supergroup or a channel. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index ed058000..1d4ab5e5 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -28,7 +28,7 @@ from ...ext import BaseClient class SetChatPhoto(BaseClient): def set_chat_photo(self, chat_id: Union[int, str], - photo: str): + photo: str) -> bool: """Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/set_chat_title.py b/pyrogram/client/methods/chats/set_chat_title.py index 58d4179c..af2b6e77 100644 --- a/pyrogram/client/methods/chats/set_chat_title.py +++ b/pyrogram/client/methods/chats/set_chat_title.py @@ -25,7 +25,7 @@ from ...ext import BaseClient class SetChatTitle(BaseClient): def set_chat_title(self, chat_id: Union[int, str], - title: str): + title: str) -> bool: """Use this method to change the title of a chat. Titles can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/unban_chat_member.py b/pyrogram/client/methods/chats/unban_chat_member.py index 428e6fe8..3513f38d 100644 --- a/pyrogram/client/methods/chats/unban_chat_member.py +++ b/pyrogram/client/methods/chats/unban_chat_member.py @@ -25,7 +25,7 @@ from ...ext import BaseClient class UnbanChatMember(BaseClient): def unban_chat_member(self, chat_id: Union[int, str], - user_id: Union[int, str]): + user_id: Union[int, str]) -> bool: """Use this method to unban a previously kicked user in a supergroup or channel. The user will **not** return to the group or channel automatically, but will be able to join via link, etc. You must be an administrator for this to work. diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/client/methods/chats/unpin_chat_message.py index bd6a22f8..435f38d7 100644 --- a/pyrogram/client/methods/chats/unpin_chat_message.py +++ b/pyrogram/client/methods/chats/unpin_chat_message.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class UnpinChatMessage(BaseClient): def unpin_chat_message(self, - chat_id: Union[int, str]): + chat_id: Union[int, str]) -> bool: """Use this method to unpin a message in a supergroup or a channel. You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py index f34119e2..8c152706 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/client/methods/decorators/on_callback_query.py @@ -16,15 +16,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Tuple + import pyrogram from pyrogram.client.filters.filter import Filter +from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient class OnCallbackQuery(BaseClient): def on_callback_query(self=None, filters=None, - group: int = 0): + group: int = 0) -> callable: """Use this decorator to automatically register a function for handling callback queries. This does the same thing as :meth:`add_handler` using the :class:`CallbackQueryHandler`. @@ -45,7 +48,7 @@ class OnCallbackQuery(BaseClient): The group identifier, defaults to 0. """ - def decorator(func): + def decorator(func: callable) -> Tuple[Handler, int]: if isinstance(func, tuple): func = func[0].callback diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index 406563f2..84abc92e 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.py @@ -16,15 +16,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Tuple + import pyrogram from pyrogram.client.filters.filter import Filter +from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient class OnDeletedMessages(BaseClient): def on_deleted_messages(self=None, filters=None, - group: int = 0): + group: int = 0) -> callable: """Use this decorator to automatically register a function for handling deleted messages. This does the same thing as :meth:`add_handler` using the :class:`DeletedMessagesHandler`. @@ -45,7 +48,7 @@ class OnDeletedMessages(BaseClient): The group identifier, defaults to 0. """ - def decorator(func): + def decorator(func: callable) -> Tuple[Handler, int]: if isinstance(func, tuple): func = func[0].callback diff --git a/pyrogram/client/methods/decorators/on_disconnect.py b/pyrogram/client/methods/decorators/on_disconnect.py index e2288619..56796bf5 100644 --- a/pyrogram/client/methods/decorators/on_disconnect.py +++ b/pyrogram/client/methods/decorators/on_disconnect.py @@ -17,17 +17,18 @@ # along with Pyrogram. If not, see . import pyrogram +from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient class OnDisconnect(BaseClient): - def on_disconnect(self=None): + def on_disconnect(self=None) -> callable: """Use this decorator to automatically register a function for handling disconnections. This does the same thing as :meth:`add_handler` using the :class:`DisconnectHandler`. """ - def decorator(func): + def decorator(func: callable) -> Handler: handler = pyrogram.DisconnectHandler(func) if self is not None: diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index ec0e11e8..68ed1fab 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class OnMessage(BaseClient): def on_message(self=None, filters=None, - group: int = 0): + group: int = 0) -> callable: """Use this decorator to automatically register a function for handling messages. This does the same thing as :meth:`add_handler` using the :class:`MessageHandler`. diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py index 8b2723df..ce2584d5 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -16,13 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Tuple + import pyrogram +from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient class OnRawUpdate(BaseClient): def on_raw_update(self=None, - group: int = 0): + group: int = 0) -> callable: """Use this decorator to automatically register a function for handling raw updates. This does the same thing as :meth:`add_handler` using the :class:`RawUpdateHandler`. @@ -39,7 +42,7 @@ class OnRawUpdate(BaseClient): The group identifier, defaults to 0. """ - def decorator(func): + def decorator(func: callable) -> Tuple[Handler, int]: if isinstance(func, tuple): func = func[0].callback diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py index 6ae47a95..c552e706 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/client/methods/decorators/on_user_status.py @@ -16,15 +16,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Tuple + import pyrogram from pyrogram.client.filters.filter import Filter +from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient class OnUserStatus(BaseClient): def on_user_status(self=None, filters=None, - group: int = 0): + group: int = 0) -> callable: """Use this decorator to automatically register a function for handling user status updates. This does the same thing as :meth:`add_handler` using the :class:`UserStatusHandler`. @@ -44,7 +47,7 @@ class OnUserStatus(BaseClient): The group identifier, defaults to 0. """ - def decorator(func): + def decorator(func: callable) -> Tuple[Handler, int]: if isinstance(func, tuple): func = func[0].callback diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/client/methods/messages/delete_messages.py index 6aaf710a..030a1663 100644 --- a/pyrogram/client/methods/messages/delete_messages.py +++ b/pyrogram/client/methods/messages/delete_messages.py @@ -26,7 +26,7 @@ class DeleteMessages(BaseClient): def delete_messages(self, chat_id: Union[int, str], message_ids: Iterable[int], - revoke: bool = True): + revoke: bool = True) -> bool: """Use this method to delete messages, including service messages, with the following limitations: - A message can only be deleted if it was sent less than 48 hours ago. diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index 938e7af5..7709a3d8 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -29,7 +29,7 @@ class EditMessageCaption(BaseClient): message_id: int, caption: str, parse_mode: str = "", - reply_markup: "pyrogram.InlineKeyboardMarkup" = None): + reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "pyrogram.Message": """Use this method to edit captions of messages. Args: diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index bdfbf353..50fd1f48 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -38,7 +38,7 @@ class EditMessageMedia(BaseClient): chat_id: Union[int, str], message_id: int, media: InputMedia, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None): + reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "pyrogram.Message": """Use this method to edit audio, document, photo, or video messages. If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, diff --git a/pyrogram/client/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py index 31d8f0b7..f46dd2f4 100644 --- a/pyrogram/client/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py @@ -27,7 +27,7 @@ class EditMessageReplyMarkup(BaseClient): def edit_message_reply_markup(self, chat_id: Union[int, str], message_id: int, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None): + reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "pyrogram.Message": """Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots). Args: diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index 688c7ce2..173caa93 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -30,7 +30,7 @@ class EditMessageText(BaseClient): text: str, parse_mode: str = "", disable_web_page_preview: bool = None, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None): + reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "pyrogram.Message": """Use this method to edit text messages. Args: diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index cafa468b..f8379c0c 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -28,7 +28,7 @@ class ForwardMessages(BaseClient): chat_id: Union[int, str], from_chat_id: Union[int, str], message_ids: Iterable[int], - disable_notification: bool = None): + disable_notification: bool = None) -> "pyrogram.Messages": """Use this method to forward messages of any kind. Args: @@ -51,7 +51,7 @@ class ForwardMessages(BaseClient): Users will receive a notification with no sound. Returns: - On success and in case *message_ids* was a list, the returned value will be a list of the forwarded + On success and in case *message_ids* was an iterable, the returned value will be a list of the forwarded :obj:`Messages ` even if a list contains just one element, otherwise if *message_ids* was an integer, the single forwarded :obj:`Message ` is returned. @@ -86,4 +86,8 @@ class ForwardMessages(BaseClient): ) ) - return messages if is_iterable else messages[0] + return pyrogram.Messages( + client=self, + total_count=len(messages), + messages=messages + ) if is_iterable else messages[0] diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index 9f67d9a0..da2f6578 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -28,7 +28,7 @@ class GetMessages(BaseClient): chat_id: Union[int, str], message_ids: Union[int, Iterable[int]] = None, reply_to_message_ids: Union[int, Iterable[int]] = None, - replies: int = 1): + replies: int = 1) -> "pyrogram.Messages": """Use this method to get one or more messages that belong to a specific chat. You can retrieve up to 200 messages at once. diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 8e240f5b..7b1467f2 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -45,7 +45,7 @@ class SendAnimation(BaseClient): "pyrogram.ReplyKeyboardRemove", "pyrogram.ForceReply"] = None, progress: callable = None, - progress_args: tuple = ()): + progress_args: tuple = ()) -> "pyrogram.Message": """Use this method to send animation files (animation or H.264/MPEG-4 AVC video without sound). Args: diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 405d0561..685e2a2b 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -45,7 +45,7 @@ class SendAudio(BaseClient): "pyrogram.ReplyKeyboardRemove", "pyrogram.ForceReply"] = None, progress: callable = None, - progress_args: tuple = ()): + progress_args: tuple = ()) -> "pyrogram.Message": """Use this method to send audio files. For sending voice messages, use the :obj:`send_voice()` method instead. diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index d7f93743..5312f0ac 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -35,7 +35,7 @@ class SendContact(BaseClient): reply_markup: Union["pyrogram.InlineKeyboardMarkup", "pyrogram.ReplyKeyboardMarkup", "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None): + "pyrogram.ForceReply"] = None) -> "pyrogram.Message": """Use this method to send phone contacts. Args: diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index d7bb62d1..da61aaea 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -42,7 +42,7 @@ class SendDocument(BaseClient): "pyrogram.ReplyKeyboardRemove", "pyrogram.ForceReply"] = None, progress: callable = None, - progress_args: tuple = ()): + progress_args: tuple = ()) -> "pyrogram.Message": """Use this method to send general files. Args: diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py index 0efdb1df..dbd04e7a 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/client/methods/messages/send_location.py @@ -33,7 +33,7 @@ class SendLocation(BaseClient): reply_markup: Union["pyrogram.InlineKeyboardMarkup", "pyrogram.ReplyKeyboardMarkup", "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None): + "pyrogram.ForceReply"] = None) -> "pyrogram.Message": """Use this method to send points on the map. Args: diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index a04a67f2..982ce1c7 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -34,7 +34,7 @@ class SendMessage(BaseClient): reply_markup: Union["pyrogram.InlineKeyboardMarkup", "pyrogram.ReplyKeyboardMarkup", "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None): + "pyrogram.ForceReply"] = None) -> "pyrogram.Message": """Use this method to send text messages. Args: diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 5b7b3268..00d06ea1 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -41,7 +41,7 @@ class SendPhoto(BaseClient): "pyrogram.ReplyKeyboardRemove", "pyrogram.ForceReply"] = None, progress: callable = None, - progress_args: tuple = ()): + progress_args: tuple = ()) -> "pyrogram.Message": """Use this method to send photos. Args: diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 550bcddb..045e1b21 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -38,7 +38,7 @@ class SendSticker(BaseClient): "pyrogram.ReplyKeyboardRemove", "pyrogram.ForceReply"] = None, progress: callable = None, - progress_args: tuple = ()): + progress_args: tuple = ()) -> "pyrogram.Message": """Use this method to send .webp stickers. Args: diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/client/methods/messages/send_venue.py index 35d725d1..fca07898 100644 --- a/pyrogram/client/methods/messages/send_venue.py +++ b/pyrogram/client/methods/messages/send_venue.py @@ -37,7 +37,7 @@ class SendVenue(BaseClient): reply_markup: Union["pyrogram.InlineKeyboardMarkup", "pyrogram.ReplyKeyboardMarkup", "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None): + "pyrogram.ForceReply"] = None) -> "pyrogram.Message": """Use this method to send information about a venue. Args: diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index c6936cff..fa0a51a6 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -46,7 +46,7 @@ class SendVideo(BaseClient): "pyrogram.ReplyKeyboardRemove", "pyrogram.ForceReply"] = None, progress: callable = None, - progress_args: tuple = ()): + progress_args: tuple = ()) -> "pyrogram.Message": """Use this method to send video files. Args: diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 75345607..dd64c14a 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -42,7 +42,7 @@ class SendVideoNote(BaseClient): "pyrogram.ReplyKeyboardRemove", "pyrogram.ForceReply"] = None, progress: callable = None, - progress_args: tuple = ()): + progress_args: tuple = ()) -> "pyrogram.Message": """Use this method to send video messages. Args: diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 52cbc833..27621fa0 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -42,7 +42,7 @@ class SendVoice(BaseClient): "pyrogram.ReplyKeyboardRemove", "pyrogram.ForceReply"] = None, progress: callable = None, - progress_args: tuple = ()): + progress_args: tuple = ()) -> "pyrogram.Message": """Use this method to send audio files. Args: diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index 600f58fa..d7d0e1fa 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -27,7 +27,7 @@ class ChangeCloudPassword(BaseClient): def change_cloud_password(self, current_password: str, new_password: str, - new_hint: str = ""): + new_hint: str = "") -> bool: """Use this method to change your Two-Step Verification password (Cloud Password) with a new one. Args: diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index 77c731dd..4eb8df6a 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -27,7 +27,7 @@ class EnableCloudPassword(BaseClient): def enable_cloud_password(self, password: str, hint: str = "", - email: str = ""): + email: str = "") -> bool: """Use this method to enable the Two-Step Verification security feature (Cloud Password) on your account. This password will be asked when you log in on a new device in addition to the SMS code. diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py index c82436b7..3dab720d 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class RemoveCloudPassword(BaseClient): def remove_cloud_password(self, - password: str): + password: str) -> bool: """Use this method to turn off the Two-Step Verification security feature (Cloud Password) on your account. Args: diff --git a/pyrogram/client/methods/users/delete_user_profile_photos.py b/pyrogram/client/methods/users/delete_user_profile_photos.py index 4f3c5fde..764c3f3e 100644 --- a/pyrogram/client/methods/users/delete_user_profile_photos.py +++ b/pyrogram/client/methods/users/delete_user_profile_photos.py @@ -26,7 +26,7 @@ from ...ext import BaseClient class DeleteUserProfilePhotos(BaseClient): def delete_user_profile_photos(self, - id: Union[str, List[str]]): + id: Union[str, List[str]]) -> bool: """Use this method to delete your own profile photos Args: diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index 9072a909..3f3e85c3 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -22,7 +22,7 @@ from ...ext import BaseClient class GetMe(BaseClient): - def get_me(self): + def get_me(self) -> pyrogram.User: """A simple method for testing your authorization. Requires no parameters. Returns: diff --git a/pyrogram/client/methods/users/get_user_profile_photos.py b/pyrogram/client/methods/users/get_user_profile_photos.py index 86ecb74d..c097aced 100644 --- a/pyrogram/client/methods/users/get_user_profile_photos.py +++ b/pyrogram/client/methods/users/get_user_profile_photos.py @@ -27,7 +27,7 @@ class GetUserProfilePhotos(BaseClient): def get_user_profile_photos(self, user_id: Union[int, str], offset: int = 0, - limit: int = 100): + limit: int = 100) -> pyrogram.UserProfilePhotos: """Use this method to get a list of profile pictures for a user. Args: diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py index 075814ca..3ee1bc61 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/client/methods/users/get_users.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Iterable, Union +from typing import Iterable, Union, List import pyrogram from pyrogram.api import functions @@ -25,7 +25,7 @@ from ...ext import BaseClient class GetUsers(BaseClient): def get_users(self, - user_ids: Iterable[Union[int, str]]): + user_ids: Iterable[Union[int, str]]) -> Union[pyrogram.User or List[pyrogram.User]]: """Use this method to get information about a user. You can retrieve up to 200 users at once. @@ -36,9 +36,9 @@ class GetUsers(BaseClient): Iterators and Generators are also accepted. Returns: - On success and in case *user_ids* was a list, the returned value will be a list of the requested + On success and in case *user_ids* was an iterable, the returned value will be a list of the requested :obj:`Users ` even if a list contains just one element, otherwise if - *user_ids* was an integer, the single requested :obj:`User` is returned. + *user_ids* was an integer or string, the single requested :obj:`User` is returned. Raises: :class:`Error ` in case of a Telegram RPC error. diff --git a/pyrogram/client/methods/users/set_user_profile_photo.py b/pyrogram/client/methods/users/set_user_profile_photo.py index 68e174d6..f2a2c302 100644 --- a/pyrogram/client/methods/users/set_user_profile_photo.py +++ b/pyrogram/client/methods/users/set_user_profile_photo.py @@ -22,7 +22,7 @@ from ...ext import BaseClient class SetUserProfilePhoto(BaseClient): def set_user_profile_photo(self, - photo: str): + photo: str) -> bool: """Use this method to set a new profile photo. This method only works for Users. diff --git a/pyrogram/client/methods/utilities/download_media.py b/pyrogram/client/methods/utilities/download_media.py index 8c609d98..33d72f82 100644 --- a/pyrogram/client/methods/utilities/download_media.py +++ b/pyrogram/client/methods/utilities/download_media.py @@ -29,7 +29,7 @@ class DownloadMedia(BaseClient): file_name: str = "", block: bool = True, progress: callable = None, - progress_args: tuple = ()): + progress_args: tuple = ()) -> Union[str, None]: """Use this method to download the media from a Message. Args: diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index b778d4d8..fa43f526 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -121,6 +121,8 @@ class ChatMember(PyrogramType): @staticmethod def _parse(client, member, user) -> "ChatMember": + user = pyrogram.User._parse(client, user) + if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)): return ChatMember(user=user, status="member", client=client) From 4bf6831be85ab07d56924eaf320b9090019004c3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Dec 2018 11:22:58 +0100 Subject: [PATCH 100/326] Fix some non-importable types for type hint --- pyrogram/client/methods/users/get_me.py | 2 +- pyrogram/client/methods/users/get_user_profile_photos.py | 2 +- pyrogram/client/methods/users/get_users.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index 3f3e85c3..211e2ee9 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -22,7 +22,7 @@ from ...ext import BaseClient class GetMe(BaseClient): - def get_me(self) -> pyrogram.User: + def get_me(self) -> "pyrogram.User": """A simple method for testing your authorization. Requires no parameters. Returns: diff --git a/pyrogram/client/methods/users/get_user_profile_photos.py b/pyrogram/client/methods/users/get_user_profile_photos.py index c097aced..2108f2d6 100644 --- a/pyrogram/client/methods/users/get_user_profile_photos.py +++ b/pyrogram/client/methods/users/get_user_profile_photos.py @@ -27,7 +27,7 @@ class GetUserProfilePhotos(BaseClient): def get_user_profile_photos(self, user_id: Union[int, str], offset: int = 0, - limit: int = 100) -> pyrogram.UserProfilePhotos: + limit: int = 100) -> "pyrogram.UserProfilePhotos": """Use this method to get a list of profile pictures for a user. Args: diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py index 3ee1bc61..d7b80579 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/client/methods/users/get_users.py @@ -25,7 +25,7 @@ from ...ext import BaseClient class GetUsers(BaseClient): def get_users(self, - user_ids: Iterable[Union[int, str]]) -> Union[pyrogram.User or List[pyrogram.User]]: + user_ids: Iterable[Union[int, str]]) -> Union["pyrogram.User", List["pyrogram.User"]]: """Use this method to get information about a user. You can retrieve up to 200 users at once. From 6e686fdd894af026a6de8a92e1ac13ff5f73d460 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Dec 2018 14:18:23 +0100 Subject: [PATCH 101/326] Fix docs not using bytes when showing examples dealing with callback queries --- docs/source/resources/UsingFilters.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/resources/UsingFilters.rst b/docs/source/resources/UsingFilters.rst index 79ecd24f..d70005a5 100644 --- a/docs/source/resources/UsingFilters.rst +++ b/docs/source/resources/UsingFilters.rst @@ -162,7 +162,7 @@ yourself. This allows you to test your filter by pressing the inline button: "username", # Change this to your username or id "Pyrogram's custom filter test", reply_markup=InlineKeyboardMarkup( - [[InlineKeyboardButton("Press me", "pyrogram")]] + [[InlineKeyboardButton("Press me", b"pyrogram")]] ) ) @@ -178,7 +178,7 @@ containing "pyrogram" as data: hardcoded_data = Filters.create( name="HardcodedData", - func=lambda filter, callback_query: callback_query.data == "pyrogram" + func=lambda filter, callback_query: callback_query.data == b"pyrogram" ) The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example, the same @@ -187,7 +187,7 @@ could be achieved with a normal function, but we don't really need it as it make .. code-block:: python def func(filter, callback_query): - return callback_query.data == "pyrogram" + return callback_query.data == b"pyrogram" hardcoded_data = Filters.create( name="HardcodedData", @@ -223,6 +223,6 @@ And its usage: .. code-block:: python - @app.on_callback_query(dynamic_data("pyrogram")) + @app.on_callback_query(dynamic_data(b"pyrogram")) def pyrogram_data(client, callback_query): client.answer_callback_query(callback_query.id, "it works!") \ No newline at end of file From 77f15af8808f31b734b98897e488dc12296764a9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Dec 2018 23:11:16 +0100 Subject: [PATCH 102/326] Update API schema to Layer 91 --- compiler/api/source/main_api.tl | 213 +++++++++++++++++++++++--------- 1 file changed, 156 insertions(+), 57 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 9ee01cb3..61bd1a41 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -45,7 +45,8 @@ inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = In inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; 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#7b1a118f geo_point:InputGeoPoint period:int = InputMedia; +inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia; +inputMediaPoll#6b3765b poll:Poll = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; @@ -55,16 +56,14 @@ inputGeoPointEmpty#e4c123d6 = InputGeoPoint; inputGeoPoint#f3b7acc9 lat:double long:double = InputGeoPoint; inputPhotoEmpty#1cd7bf0d = InputPhoto; -inputPhoto#fb95c6c4 id:long access_hash:long = InputPhoto; +inputPhoto#3bb3b94a id:long access_hash:long file_reference:bytes = InputPhoto; -inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLocation; +inputFileLocation#dfdaabe1 volume_id:long local_id:int secret:long file_reference:bytes = InputFileLocation; inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation; -inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation; +inputDocumentFileLocation#196683d9 id:long access_hash:long file_reference:bytes = InputFileLocation; inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation; inputTakeoutFileLocation#29be5899 = InputFileLocation; -inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent; - peerUser#9db1bc6d user_id:int = Peer; peerChat#bad0e5bb chat_id:int = Peer; peerChannel#bddde532 channel_id:int = Peer; @@ -81,7 +80,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileLocation; -fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation; +fileLocation#91d11eb dc_id:int volume_id:long local_id:int secret:long file_reference:bytes = FileLocation; userEmpty#200250ba id:int = User; user#2e13f4c3 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 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?string bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; @@ -102,8 +101,8 @@ chatForbidden#7328bdb id:int title:string = Chat; channel#c88974ac flags:# creator:flags.0?true left:flags.2?true editor:flags.3?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true democracy:flags.10?true signatures:flags.11?true min:flags.12?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChannelAdminRights banned_rights:flags.15?ChannelBannedRights participants_count:flags.17?int = Chat; 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#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector = ChatFull; -channelFull#76af5481 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?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 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 = ChatFull; +chatFull#edd2a791 flags:# id:int participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int = ChatFull; +channelFull#1c87a71a 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 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 = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -116,7 +115,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#6153276a photo_small:FileLocation photo_big:FileLocation = ChatPhoto; messageEmpty#83e5de54 id:int = Message; -message#44f9b43d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?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 = Message; +message#44f9b43d 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 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 = Message; messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -130,6 +129,7 @@ messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:str 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; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#a6638b9a title:string users:Vector = MessageAction; @@ -153,11 +153,12 @@ messageActionCustomAction#fae69f56 message:string = MessageAction; messageActionBotAllowed#abe9affe domain:string = MessageAction; messageActionSecureValuesSentMe#1b287353 values:Vector credentials:SecureCredentialsEncrypted = MessageAction; messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; +messageActionContactSignUp#f3f25f76 = MessageAction; dialog#e4def5db flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog; photoEmpty#2331b22d id:long = Photo; -photo#9288dd29 flags:# has_stickers:flags.0?true id:long access_hash:long date:int sizes:Vector = Photo; +photo#9c477dd8 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector = Photo; photoSizeEmpty#e17e23c type:string = PhotoSize; photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; @@ -177,6 +178,7 @@ auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorizat inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer; inputNotifyUsers#193b4417 = InputNotifyPeer; inputNotifyChats#4a95e84e = InputNotifyPeer; +inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer; inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = InputPeerNotifySettings; @@ -190,9 +192,11 @@ wallPaperSolid#63117f24 id:int title:string bg_color:int color:int = WallPaper; inputReportReasonSpam#58dbcab8 = ReportReason; inputReportReasonViolence#1e22c78d = ReportReason; inputReportReasonPornography#2e59d922 = ReportReason; +inputReportReasonChildAbuse#adf44ee3 = ReportReason; inputReportReasonOther#e1746d0a text:string = ReportReason; +inputReportReasonCopyright#9b89f93a = ReportReason; -userFull#f220f3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true user:User about:flags.1?string link:contacts.Link profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo common_chats_count:int = UserFull; +userFull#8ea4a881 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true user:User about:flags.1?string link:contacts.Link profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int = UserFull; contact#f911c994 user_id:int mutual:Bool = Contact; @@ -218,7 +222,7 @@ messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs; messages.messages#8c718e87 messages:Vector chats:Vector users:Vector = messages.Messages; messages.messagesSlice#b446ae3 count:int messages:Vector chats:Vector users:Vector = messages.Messages; -messages.channelMessages#99262e37 flags:# pts:int count:int messages:Vector chats:Vector users:Vector = messages.Messages; +messages.channelMessages#99262e37 flags:# inexact:flags.1?true pts:int count:int messages:Vector chats:Vector users:Vector = messages.Messages; messages.messagesNotModified#74535f21 count:int = messages.Messages; messages.chats#64ff9fd5 chats:Vector = messages.Chats; @@ -254,7 +258,6 @@ updateChatParticipants#7761198 participants:ChatParticipants = Update; updateUserStatus#1bfbd823 user_id:int status:UserStatus = Update; updateUserName#a7332b73 user_id:int first_name:string last_name:string username:string = Update; updateUserPhoto#95313b0c user_id:int date:int photo:UserProfilePhoto previous:Bool = Update; -updateContactRegistered#2575bbb9 user_id:int date:int = Update; updateContactLink#9d2e67c5 user_id:int my_link:ContactLink foreign_link:ContactLink = Update; updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update; updateEncryptedChatTyping#1710f156 chat_id:int = Update; @@ -305,13 +308,16 @@ updateBotWebhookJSONQuery#9b9240a6 query_id:long data:DataJSON timeout:int = Upd updateBotShippingQuery#e0cdc940 query_id:long user_id:int payload:bytes shipping_address:PostAddress = Update; updateBotPrecheckoutQuery#5d2f3aa9 flags:# query_id:long user_id:int payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update; updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update; -updateLangPackTooLong#10c2404b = Update; +updateLangPackTooLong#46560264 lang_code:string = Update; updateLangPack#56022f4d difference:LangPackDifference = Update; updateFavedStickers#e511996d = Update; updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector = Update; updateContactsReset#7084a7be = Update; updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update; updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update; +updateUserPinnedMessage#4c43da18 user_id:int id:int = Update; +updateChatPinnedMessage#22893b26 chat_id:int id:int = Update; +updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -338,11 +344,11 @@ upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption; -config#3213dbba flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int = Config; +config#e6ca25f6 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; -help.appUpdate#8987f311 id:int critical:Bool url:string text:string = help.AppUpdate; +help.appUpdate#1da7158f flags:# popup:flags.0?true id:int version:string text:string entities:Vector document:flags.1?Document url:flags.2?string = help.AppUpdate; help.noAppUpdate#c45a6536 = help.AppUpdate; help.inviteText#18cb9f78 message:string = help.InviteText; @@ -373,16 +379,17 @@ messages.sentEncryptedMessage#560f8935 date:int = messages.SentEncryptedMessage; messages.sentEncryptedFile#9493ff32 date:int file:EncryptedFile = messages.SentEncryptedMessage; inputDocumentEmpty#72f0eaae = InputDocument; -inputDocument#18798952 id:long access_hash:long = InputDocument; +inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument; documentEmpty#36f8c871 id:long = Document; -document#87232bc7 id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int version:int attributes:Vector = Document; +document#59534e4c id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumb:PhotoSize dc_id:int attributes:Vector = Document; help.support#17c6b5f6 phone_number:string user:User = help.Support; notifyPeer#9fd40bd8 peer:Peer = NotifyPeer; notifyUsers#b4c83b4c = NotifyPeer; notifyChats#c007cec3 = NotifyPeer; +notifyBroadcasts#d612e8ef = NotifyPeer; sendMessageTypingAction#16bf744e = SendMessageAction; sendMessageCancelAction#fd5ec8f5 = SendMessageAction; @@ -403,10 +410,12 @@ contacts.found#b3134d9d my_results:Vector results:Vector chats:Vecto inputPrivacyKeyStatusTimestamp#4f96cb18 = InputPrivacyKey; inputPrivacyKeyChatInvite#bdfb0426 = InputPrivacyKey; inputPrivacyKeyPhoneCall#fabadc5f = InputPrivacyKey; +inputPrivacyKeyPhoneP2P#db9e70d2 = InputPrivacyKey; privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey; privacyKeyChatInvite#500e6dfa = PrivacyKey; privacyKeyPhoneCall#3d662b7b = PrivacyKey; +privacyKeyPhoneP2P#39491cc8 = PrivacyKey; inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule; inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule; @@ -454,16 +463,15 @@ webPagePending#c586da1c id:long date:int = WebPage; webPage#5f07b4bc 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 = WebPage; webPageNotModified#85849473 = WebPage; -authorization#7bf2e6f6 hash:long flags:int 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; +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; account.authorizations#1250abde authorizations:Vector = account.Authorizations; -account.noPassword#5ea182f6 new_salt:bytes new_secure_salt:bytes secure_random:bytes email_unconfirmed_pattern:string = account.Password; -account.password#ca39b447 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true current_salt:bytes new_salt:bytes new_secure_salt:bytes secure_random:bytes hint:string email_unconfirmed_pattern:string = account.Password; +account.password#ad2641f8 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes = account.Password; -account.passwordSettings#7bd9c3f1 email:string secure_salt:bytes secure_secret:bytes secure_secret_id:long = account.PasswordSettings; +account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings; -account.passwordInputSettings#21ffa60d flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_salt:flags.2?bytes new_secure_secret:flags.2?bytes new_secure_secret_id:flags.2?long = account.PasswordInputSettings; +account.passwordInputSettings#c23727c9 flags:# new_algo:flags.0?PasswordKdfAlgo new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_settings:flags.2?SecureSecretSettings = account.PasswordInputSettings; auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; @@ -663,6 +671,12 @@ textFixed#6c3f19b9 text:RichText = RichText; textUrl#3c2884c1 text:RichText url:string webpage_id:long = RichText; textEmail#de5a0dd6 text:RichText email:string = RichText; textConcat#7e6260d7 texts:Vector = RichText; +textSubscript#ed6a8504 text:RichText = RichText; +textSuperscript#c7fb5e01 text:RichText = RichText; +textMarked#34b8621 text:RichText = RichText; +textPhone#1ccb966a text:RichText phone:string = RichText; +textImage#81ccf4f document_id:long w:int h:int = RichText; +textAnchor#35553762 text:RichText name:string = RichText; pageBlockUnsupported#13567e8a = PageBlock; pageBlockTitle#70abc3fd text:RichText = PageBlock; @@ -675,21 +689,24 @@ pageBlockPreformatted#c070d93e text:RichText language:string = PageBlock; pageBlockFooter#48870999 text:RichText = PageBlock; pageBlockDivider#db20b188 = PageBlock; pageBlockAnchor#ce0d37b0 name:string = PageBlock; -pageBlockList#3a58c7f4 ordered:Bool items:Vector = PageBlock; +pageBlockList#e4e88011 items:Vector = PageBlock; pageBlockBlockquote#263d7c26 text:RichText caption:RichText = PageBlock; pageBlockPullquote#4f4456d3 text:RichText caption:RichText = PageBlock; -pageBlockPhoto#e9c69982 photo_id:long caption:RichText = PageBlock; -pageBlockVideo#d9d71866 flags:# autoplay:flags.0?true loop:flags.1?true video_id:long caption:RichText = PageBlock; +pageBlockPhoto#1759c560 flags:# photo_id:long caption:PageCaption url:flags.0?string webpage_id:flags.0?long = PageBlock; +pageBlockVideo#7c8fe7b6 flags:# autoplay:flags.0?true loop:flags.1?true video_id:long caption:PageCaption = PageBlock; pageBlockCover#39f23300 cover:PageBlock = PageBlock; -pageBlockEmbed#cde200d1 flags:# full_width:flags.0?true allow_scrolling:flags.3?true url:flags.1?string html:flags.2?string poster_photo_id:flags.4?long w:int h:int caption:RichText = PageBlock; -pageBlockEmbedPost#292c7be9 url:string webpage_id:long author_photo_id:long author:string date:int blocks:Vector caption:RichText = PageBlock; -pageBlockCollage#8b31c4f items:Vector caption:RichText = PageBlock; -pageBlockSlideshow#130c8963 items:Vector caption:RichText = PageBlock; +pageBlockEmbed#a8718dc5 flags:# full_width:flags.0?true allow_scrolling:flags.3?true url:flags.1?string html:flags.2?string poster_photo_id:flags.4?long w:flags.5?int h:flags.5?int caption:PageCaption = PageBlock; +pageBlockEmbedPost#f259a80b url:string webpage_id:long author_photo_id:long author:string date:int blocks:Vector caption:PageCaption = PageBlock; +pageBlockCollage#65a0fa4d items:Vector caption:PageCaption = PageBlock; +pageBlockSlideshow#31f9590 items:Vector caption:PageCaption = PageBlock; pageBlockChannel#ef1751b5 channel:Chat = PageBlock; -pageBlockAudio#31b81a7f audio_id:long caption:RichText = PageBlock; - -pagePart#8e3f9ebe blocks:Vector photos:Vector documents:Vector = Page; -pageFull#556ec7aa blocks:Vector photos:Vector documents:Vector = Page; +pageBlockAudio#804361ea audio_id:long caption:PageCaption = PageBlock; +pageBlockKicker#1e148390 text:RichText = PageBlock; +pageBlockTable#bf4dea82 flags:# bordered:flags.0?true striped:flags.1?true title:RichText rows:Vector = PageBlock; +pageBlockOrderedList#9a8ae1e1 items:Vector = PageBlock; +pageBlockDetails#76768bed flags:# open:flags.0?true blocks:Vector title:RichText = PageBlock; +pageBlockRelatedArticles#16115a96 title:RichText articles:Vector = PageBlock; +pageBlockMap#a44f3ef6 geo:GeoPoint zoom:int w:int h:int caption:PageCaption = PageBlock; phoneCallDiscardReasonMissed#85e42301 = PhoneCallDiscardReason; phoneCallDiscardReasonDisconnect#e095c1a0 = PhoneCallDiscardReason; @@ -748,7 +765,7 @@ phoneCallEmpty#5366c915 id:long = PhoneCall; phoneCallWaiting#1b8f4ad1 flags:# id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; phoneCallRequested#83761ce4 id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; phoneCallAccepted#6d003d3f id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall; -phoneCall#ffe6ab67 id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connection:PhoneConnection alternative_connections:Vector start_date:int = PhoneCall; +phoneCall#e6f9ddf3 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 connection:PhoneConnection alternative_connections:Vector start_date:int = PhoneCall; phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?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; @@ -770,7 +787,7 @@ langPackStringDeleted#2979eeb2 key:string = LangPackString; langPackDifference#f385c1f6 lang_code:string from_version:int version:int strings:Vector = LangPackDifference; -langPackLanguage#117698f1 name:string native_name:string lang_code:string = LangPackLanguage; +langPackLanguage#eeca5ce3 flags:# official:flags.0?true rtl:flags.2?true beta:flags.3?true name:string native_name:string lang_code:string base_lang_code:flags.1?string plural_code:string strings_count:int translated_count:int translations_url:string = LangPackLanguage; channelAdminRights#5d7ceba5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true invite_link:flags.6?true pin_messages:flags.7?true add_admins:flags.9?true manage_call:flags.10?true = ChannelAdminRights; @@ -864,9 +881,9 @@ secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType; secureValueTypePhone#b320aadb = SecureValueType; secureValueTypeEmail#8e3ca7ee = SecureValueType; -secureValue#b4b4b699 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile files:flags.4?Vector plain_data:flags.5?SecurePlainData hash:bytes = SecureValue; +secureValue#187fa0ca flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile translation:flags.6?Vector files:flags.4?Vector plain_data:flags.5?SecurePlainData hash:bytes = SecureValue; -inputSecureValue#67872e8 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile files:flags.4?Vector plain_data:flags.5?SecurePlainData = InputSecureValue; +inputSecureValue#db21d0a7 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile translation:flags.6?Vector files:flags.4?Vector plain_data:flags.5?SecurePlainData = InputSecureValue; secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash; @@ -876,10 +893,13 @@ secureValueErrorReverseSide#868a2aa5 type:SecureValueType file_hash:bytes text:s secureValueErrorSelfie#e537ced6 type:SecureValueType file_hash:bytes text:string = SecureValueError; secureValueErrorFile#7a700873 type:SecureValueType file_hash:bytes text:string = SecureValueError; secureValueErrorFiles#666220e9 type:SecureValueType file_hash:Vector text:string = SecureValueError; +secureValueError#869d758f type:SecureValueType hash:bytes text:string = SecureValueError; +secureValueErrorTranslationFile#a1144770 type:SecureValueType file_hash:bytes text:string = SecureValueError; +secureValueErrorTranslationFiles#34636dd8 type:SecureValueType file_hash:Vector text:string = SecureValueError; secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted; -account.authorizationForm#cb976d53 flags:# selfie_required:flags.1?true required_types:Vector values:Vector errors:Vector users:Vector privacy_policy_url:flags.0?string = account.AuthorizationForm; +account.authorizationForm#ad2e1cd8 flags:# required_types:Vector values:Vector errors:Vector users:Vector privacy_policy_url:flags.0?string = account.AuthorizationForm; account.sentEmailCode#811f854f email_pattern:string length:int = account.SentEmailCode; @@ -890,6 +910,68 @@ savedPhoneContact#1142bd56 phone:string first_name:string last_name:string date: account.takeout#4dba4501 id:long = account.Takeout; +passwordKdfAlgoUnknown#d45ab096 = PasswordKdfAlgo; +passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow#3a912d4a salt1:bytes salt2:bytes g:int p:bytes = PasswordKdfAlgo; + +securePasswordKdfAlgoUnknown#4a8537 = SecurePasswordKdfAlgo; +securePasswordKdfAlgoPBKDF2HMACSHA512iter100000#bbf2dda0 salt:bytes = SecurePasswordKdfAlgo; +securePasswordKdfAlgoSHA512#86471d92 salt:bytes = SecurePasswordKdfAlgo; + +secureSecretSettings#1527bcac secure_algo:SecurePasswordKdfAlgo secure_secret:bytes secure_secret_id:long = SecureSecretSettings; + +inputCheckPasswordEmpty#9880f658 = InputCheckPasswordSRP; +inputCheckPasswordSRP#d27ff082 srp_id:long A:bytes M1:bytes = InputCheckPasswordSRP; + +secureRequiredType#829d99da flags:# native_names:flags.0?true selfie_required:flags.1?true translation_required:flags.2?true type:SecureValueType = SecureRequiredType; +secureRequiredTypeOneOf#27477b4 types:Vector = SecureRequiredType; + +help.passportConfigNotModified#bfb9f457 = help.PassportConfig; +help.passportConfig#a098d6af hash:int countries_langs:DataJSON = help.PassportConfig; + +inputAppEvent#1d1b1245 time:double type:string peer:long data:JSONValue = InputAppEvent; + +jsonObjectValue#c0de1bd9 key:string value:JSONValue = JSONObjectValue; + +jsonNull#3f6d7b68 = JSONValue; +jsonBool#c7345e6a value:Bool = JSONValue; +jsonNumber#2be0dfa4 value:double = JSONValue; +jsonString#b71e767a value:string = JSONValue; +jsonArray#f7444763 value:Vector = JSONValue; +jsonObject#99c1d49d value:Vector = JSONValue; + +pageTableCell#34566b6a flags:# header:flags.0?true align_center:flags.3?true align_right:flags.4?true valign_middle:flags.5?true valign_bottom:flags.6?true text:flags.7?RichText colspan:flags.1?int rowspan:flags.2?int = PageTableCell; + +pageTableRow#e0c0c5e5 cells:Vector = PageTableRow; + +pageCaption#6f747657 text:RichText credit:RichText = PageCaption; + +pageListItemText#b92fb6cd text:RichText = PageListItem; +pageListItemBlocks#25e073fc blocks:Vector = PageListItem; + +pageListOrderedItemText#5e068047 num:string text:RichText = PageListOrderedItem; +pageListOrderedItemBlocks#98dd8936 num:string blocks:Vector = PageListOrderedItem; + +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; + +help.supportName#8c05f1c9 name:string = help.SupportName; + +help.userInfoEmpty#f3ae2eed = help.UserInfo; +help.userInfo#1eb3758 message:string entities:Vector author:string date:int = help.UserInfo; + +pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer; + +poll#d5529d06 id:long flags:# closed:flags.0?true question:string answers:Vector = Poll; + +pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true option:bytes voters:int = PollAnswerVoters; + +pollResults#5755785a flags:# min:flags.0?true results:flags.1?Vector total_voters:flags.2?int = PollResults; + +chatOnlines#f041e250 onlines:int = ChatOnlines; + +statsURL#47a971e0 url:string = StatsURL; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -909,7 +991,7 @@ auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization; auth.importAuthorization#e3ef9613 id:int bytes:bytes = auth.Authorization; auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool; auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization; -auth.checkPassword#a63011e password_hash:bytes = auth.Authorization; +auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization; auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery; auth.recoverPassword#4ea56e92 code:string = auth.Authorization; auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode; @@ -938,11 +1020,11 @@ account.updateDeviceLocked#38df3532 period:int = Bool; account.getAuthorizations#e320c158 = account.Authorizations; account.resetAuthorization#df77f3bc hash:long = Bool; account.getPassword#548a30f5 = account.Password; -account.getPasswordSettings#bc8d11bb current_password_hash:bytes = account.PasswordSettings; -account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings:account.PasswordInputSettings = Bool; +account.getPasswordSettings#9cd4eaf9 password:InputCheckPasswordSRP = account.PasswordSettings; +account.updatePasswordSettings#a59b102f password:InputCheckPasswordSRP new_settings:account.PasswordInputSettings = Bool; account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode; account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool; -account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPassword; +account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword; account.getWebAuthorizations#182e6d6f = account.WebAuthorizations; account.resetWebAuthorization#2d01b9ef hash:long = Bool; account.resetWebAuthorizations#682d2594 = Bool; @@ -958,21 +1040,27 @@ account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode; account.verifyEmail#ecba39db email:string code:string = Bool; account.initTakeoutSession#f05b4804 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?int = account.Takeout; account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool; +account.confirmPasswordEmail#8fdf1920 code:string = Bool; +account.resendPasswordEmail#7a7f2a15 = Bool; +account.cancelPasswordEmail#c1cbd5b6 = Bool; +account.getContactSignUpNotification#9f07c728 = Bool; +account.setContactSignUpNotification#cff43f61 silent:Bool = Bool; +account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector = Bool; +contacts.getContactIDs#2caa4a42 hash:int = Vector; contacts.getStatuses#c4a353ee = Vector; contacts.getContacts#c023849f hash:int = contacts.Contacts; contacts.importContacts#2c800be5 contacts:Vector = contacts.ImportedContacts; contacts.deleteContact#8e953744 id:InputUser = contacts.Link; contacts.deleteContacts#59ab389e id:Vector = Bool; +contacts.deleteByPhones#1013fd9e phones:Vector = Bool; contacts.block#332b49fc id:InputUser = Bool; contacts.unblock#e54100bd id:InputUser = Bool; contacts.getBlocked#f57c350f offset:int limit:int = contacts.Blocked; -contacts.exportCard#84e53737 = Vector; -contacts.importCard#4fe196fe export_card:Vector = User; contacts.search#11f812d8 q:string limit:int = contacts.Found; contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer; contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:int = contacts.TopPeers; @@ -1038,10 +1126,10 @@ 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; messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool; -messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string = Updates; +messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; -messages.editMessage#c000e4c8 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector geo_point:flags.13?InputGeoPoint = Updates; -messages.editInlineBotMessage#adc3e828 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector geo_point:flags.13?InputGeoPoint = Bool; +messages.editMessage#d116f31e flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; +messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; @@ -1080,6 +1168,12 @@ messages.searchStickerSets#c2b7d08b flags:# exclude_featured:flags.0?true q:stri messages.getSplitRanges#1cff7e08 = Vector; messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool; messages.getDialogUnreadMarks#22e24e22 = Vector; +messages.clearAllDrafts#7e58ee9c = Bool; +messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true peer:InputPeer id:int = Updates; +messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector = Updates; +messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates; +messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines; +messages.getStatsURL#83f6c0cd peer:InputPeer = StatsURL; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1101,8 +1195,7 @@ upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector = Bool; +help.getAppUpdate#522d5a7d source:string = help.AppUpdate; help.getInviteText#4d392343 = help.InviteText; help.getSupport#9cdf08cd = help.Support; help.getAppChangelog#9010ef6f prev_app_version:string = Updates; @@ -1113,6 +1206,12 @@ help.getProxyData#3d7758e1 = help.ProxyData; help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate; help.acceptTermsOfService#ee72f79a id:DataJSON = Bool; help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo; +help.getAppConfig#98914110 = JSONValue; +help.saveAppLog#6f02f748 events:Vector = Bool; +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; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; @@ -1138,7 +1237,6 @@ channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; channels.toggleInvites#49609307 channel:InputChannel enabled:Bool = Updates; channels.exportMessageLink#ceb77163 channel:InputChannel id:int grouped:Bool = ExportedMessageLink; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; -channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates; channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats; channels.editBanned#bfd915cd channel:InputChannel user_id:InputUser banned_rights:ChannelBannedRights = Updates; channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector max_id:long min_id:long limit:int = channels.AdminLogResults; @@ -1172,9 +1270,10 @@ phone.discardCall#78d413a6 peer:InputPhoneCall duration:int reason:PhoneCallDisc phone.setCallRating#1c536a34 peer:InputPhoneCall rating:int comment:string = Updates; phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; -langpack.getLangPack#9ab5c58e lang_code:string = LangPackDifference; -langpack.getStrings#2e1ee318 lang_code:string keys:Vector = Vector; -langpack.getDifference#b2e4d7d from_version:int = LangPackDifference; -langpack.getLanguages#800fd57d = Vector; +langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; +langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; +langpack.getDifference#9d51e814 lang_code:string from_version:int = LangPackDifference; +langpack.getLanguages#42c6978f lang_pack:string = Vector; +langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLanguage; -// LAYER 82 +// LAYER 91 From aef02f049cea0f947f71947d45e4f29516694121 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Dec 2018 23:54:18 +0100 Subject: [PATCH 103/326] Add Poll and PollAnswer types --- .../client/types/messages_and_media/poll.py | 79 +++++++++++++++++++ .../types/messages_and_media/poll_answer.py | 32 ++++++++ 2 files changed, 111 insertions(+) create mode 100644 pyrogram/client/types/messages_and_media/poll.py create mode 100644 pyrogram/client/types/messages_and_media/poll_answer.py diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py new file mode 100644 index 00000000..2e3c3d1a --- /dev/null +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -0,0 +1,79 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import List + +import pyrogram +from pyrogram.api import types +from .poll_answer import PollAnswer +from ..pyrogram_type import PyrogramType + + +class Poll(PyrogramType): + def __init__(self, + *, + client: "pyrogram.client.ext.BaseClient", + id: int, + closed: bool, + question: str, + answers: List[PollAnswer], + answer_chosen: int = None, + total_voters: int): + super().__init__(client) + + self.id = id + self.closed = closed + self.question = question + self.answers = answers + self.answer_chosen = answer_chosen + self.total_voters = total_voters + + @staticmethod + def _parse(client, media_poll: types.MessageMediaPoll) -> "Poll": + poll = media_poll.poll + results = media_poll.results.results + total_voters = media_poll.results.total_voters + answer_chosen = None + + answers = [] + + for i, answer in enumerate(poll.answers): + voters = None + + if results: + result = results[i] + voters = result.voters + + if result.chosen: + answer_chosen = i + + answers.append(PollAnswer( + text=answer.text, + voters=voters, + client=client + )) + + return Poll( + id=poll.id, + closed=poll.closed, + question=poll.question, + answers=answers, + answer_chosen=answer_chosen, + total_voters=total_voters, + client=client + ) diff --git a/pyrogram/client/types/messages_and_media/poll_answer.py b/pyrogram/client/types/messages_and_media/poll_answer.py new file mode 100644 index 00000000..a9dbc513 --- /dev/null +++ b/pyrogram/client/types/messages_and_media/poll_answer.py @@ -0,0 +1,32 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from ..pyrogram_type import PyrogramType + + +class PollAnswer(PyrogramType): + def __init__(self, + *, + client: "pyrogram.client.ext.BaseClient", + text: str, + voters: int = None): + super().__init__(client) + + self.text = text + self.voters = voters From 1ef3bc758f1e0f93fcf9248a16c549d5c940361c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Dec 2018 23:54:42 +0100 Subject: [PATCH 104/326] Export Poll and PollAnswer types --- pyrogram/__init__.py | 3 ++- pyrogram/client/types/__init__.py | 2 +- pyrogram/client/types/messages_and_media/__init__.py | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 6ea6aa5c..44d27f79 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -31,7 +31,8 @@ from .client.types import ( InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMediaAnimation, InputPhoneContact, Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, - InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove + InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, + Poll, PollAnswer ) from .client import ( Client, ChatAction, ParseMode, Emoji, diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index 8289a947..0fdfc685 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -31,7 +31,7 @@ from .input_media import ( from .messages_and_media import ( Audio, Contact, Document, Animation, Location, Photo, PhotoSize, Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos, - Message, Messages, MessageEntity + Message, Messages, MessageEntity, Poll, PollAnswer ) from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/client/types/messages_and_media/__init__.py index 3ab359ae..527fee27 100644 --- a/pyrogram/client/types/messages_and_media/__init__.py +++ b/pyrogram/client/types/messages_and_media/__init__.py @@ -26,6 +26,8 @@ from .message_entity import MessageEntity from .messages import Messages from .photo import Photo from .photo_size import PhotoSize +from .poll import Poll +from .poll_answer import PollAnswer from .sticker import Sticker from .user_profile_photos import UserProfilePhotos from .venue import Venue From 2b25b9469ba59fc61cef2f6c6d92efdec9717cc6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Dec 2018 23:55:04 +0100 Subject: [PATCH 105/326] Parse Poll objects inside Message --- pyrogram/client/types/messages_and_media/message.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 25c35456..baeac31f 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -262,6 +262,7 @@ class Message(PyrogramType): location: "pyrogram.Location" = None, venue: "pyrogram.Venue" = None, web_page: bool = None, + poll: "pyrogram.Poll" = None, new_chat_members: List[User] = None, left_chat_member: User = None, new_chat_title: str = None, @@ -317,6 +318,7 @@ class Message(PyrogramType): self.location = location self.venue = venue self.web_page = web_page + self.poll = poll self.new_chat_members = new_chat_members self.left_chat_member = left_chat_member self.new_chat_title = new_chat_title @@ -440,6 +442,7 @@ class Message(PyrogramType): sticker = None document = None web_page = None + poll = None media = message.media @@ -494,6 +497,8 @@ class Message(PyrogramType): elif isinstance(media, types.MessageMediaWebPage): web_page = True media = None + elif isinstance(media, types.MessageMediaPoll): + poll = pyrogram.Poll._parse(client, media) else: media = None @@ -542,6 +547,7 @@ class Message(PyrogramType): sticker=sticker, document=document, web_page=web_page, + poll=poll, views=message.views, via_bot=User._parse(client, users.get(message.via_bot_id, None)), outgoing=message.out, From 8eab47123ae6d4338159d8d74ad40061253259a1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Dec 2018 23:55:16 +0100 Subject: [PATCH 106/326] Add Filters.poll to filter Poll messages --- pyrogram/client/filters/filters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index fa9eace3..3e5c8fd5 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -121,6 +121,9 @@ class Filters: web_page = create("WebPage", lambda _, m: m.web_page) """Filter messages sent with a webpage preview.""" + poll = create("Poll", lambda _, m: m.poll) + """Filter messages that contain :obj:`Poll ` objects.""" + private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private")) """Filter messages sent in private chats.""" From e669a6a9ae3e1cd23e348267bcf35929bc9b858a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 00:33:15 +0100 Subject: [PATCH 107/326] Add send_poll method --- pyrogram/client/methods/messages/__init__.py | 4 +- pyrogram/client/methods/messages/send_poll.py | 65 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/messages/send_poll.py diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index 35dae756..77919d17 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -33,6 +33,7 @@ from .send_location import SendLocation from .send_media_group import SendMediaGroup from .send_message import SendMessage from .send_photo import SendPhoto +from .send_poll import SendPoll from .send_sticker import SendSticker from .send_venue import SendVenue from .send_video import SendVideo @@ -62,6 +63,7 @@ class Messages( SendVenue, SendVideo, SendVideoNote, - SendVoice + SendVoice, + SendPoll ): pass diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py new file mode 100644 index 00000000..e5229b85 --- /dev/null +++ b/pyrogram/client/methods/messages/send_poll.py @@ -0,0 +1,65 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union, List + +import pyrogram +from pyrogram.api import functions, types +from pyrogram.client.ext import BaseClient + + +class SendPoll(BaseClient): + # TODO: Docs + def send_poll(self, + chat_id: Union[int, str], + question: str, + options: List[str], + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None) -> "pyrogram.Message": + r = self.send( + functions.messages.SendMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaPoll( + poll=types.Poll( + id=0, + question=question, + answers=[ + types.PollAnswer(text=o, option=bytes([i])) + for i, o in enumerate(options) + ] + ) + ), + message="", + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None + ) + ) + + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) From 84fef9ecf18da186cfb98be09050e24b3e12a332 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 00:55:00 +0100 Subject: [PATCH 108/326] Add missing file_reference argument where applicable --- pyrogram/client/client.py | 6 ++-- .../client/methods/chats/set_chat_photo.py | 3 +- .../methods/messages/edit_message_media.py | 30 ++++++++++++------- .../client/methods/messages/send_animation.py | 3 +- .../client/methods/messages/send_audio.py | 3 +- .../client/methods/messages/send_document.py | 3 +- .../methods/messages/send_media_group.py | 12 +++++--- .../client/methods/messages/send_photo.py | 3 +- .../client/methods/messages/send_sticker.py | 3 +- .../client/methods/messages/send_video.py | 3 +- .../methods/messages/send_video_note.py | 3 +- .../client/methods/messages/send_voice.py | 3 +- .../users/delete_user_profile_photos.py | 3 +- 13 files changed, 51 insertions(+), 27 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index f5aa33ea..2782a940 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1223,7 +1223,6 @@ class Client(Methods, BaseClient): volume_id: int = None, local_id: int = None, secret: int = None, - version: int = 0, size: int = None, progress: callable = None, progress_args: tuple = ()) -> str: @@ -1271,13 +1270,14 @@ class Client(Methods, BaseClient): location = types.InputFileLocation( volume_id=volume_id, local_id=local_id, - secret=secret + secret=secret, + file_reference=b"" ) else: # Any other file can be more easily accessed by id and access_hash location = types.InputDocumentFileLocation( id=id, access_hash=access_hash, - version=version + file_reference=b"" ) limit = 1024 * 1024 diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 1d4ab5e5..7d276648 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -61,7 +61,8 @@ class SetChatPhoto(BaseClient): photo = types.InputChatPhoto( id=types.InputPhoto( id=s[0], - access_hash=s[1] + access_hash=s[1], + file_reference=b"" ) ) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 50fd1f48..02b26ea6 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -84,7 +84,8 @@ class EditMessageMedia(BaseClient): media = types.InputMediaPhoto( id=types.InputPhoto( id=media.photo.id, - access_hash=media.photo.access_hash + access_hash=media.photo.access_hash, + file_reference=b"" ) ) elif media.media.startswith("http"): @@ -110,7 +111,8 @@ class EditMessageMedia(BaseClient): media = types.InputMediaPhoto( id=types.InputPhoto( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) @@ -138,7 +140,8 @@ class EditMessageMedia(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=media.document.id, - access_hash=media.document.access_hash + access_hash=media.document.access_hash, + file_reference=b"" ) ) elif media.media.startswith("http"): @@ -164,7 +167,8 @@ class EditMessageMedia(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) @@ -191,7 +195,8 @@ class EditMessageMedia(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=media.document.id, - access_hash=media.document.access_hash + access_hash=media.document.access_hash, + file_reference=b"" ) ) elif media.media.startswith("http"): @@ -217,7 +222,8 @@ class EditMessageMedia(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) @@ -246,7 +252,8 @@ class EditMessageMedia(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=media.document.id, - access_hash=media.document.access_hash + access_hash=media.document.access_hash, + file_reference=b"" ) ) elif media.media.startswith("http"): @@ -272,7 +279,8 @@ class EditMessageMedia(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) @@ -294,7 +302,8 @@ class EditMessageMedia(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=media.document.id, - access_hash=media.document.access_hash + access_hash=media.document.access_hash, + file_reference=b"" ) ) elif media.media.startswith("http"): @@ -320,7 +329,8 @@ class EditMessageMedia(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 7b1467f2..2ebfd87f 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -167,7 +167,8 @@ class SendAnimation(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 685e2a2b..47db3cbe 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -167,7 +167,8 @@ class SendAudio(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index da61aaea..f42c83d7 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -148,7 +148,8 @@ class SendDocument(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index d5549089..0b18cac3 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -77,7 +77,8 @@ class SendMediaGroup(BaseClient): media = types.InputMediaPhoto( id=types.InputPhoto( id=media.photo.id, - access_hash=media.photo.access_hash + access_hash=media.photo.access_hash, + file_reference=b"" ) ) else: @@ -99,7 +100,8 @@ class SendMediaGroup(BaseClient): media = types.InputMediaPhoto( id=types.InputPhoto( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) elif isinstance(i, pyrogram.InputMediaVideo): @@ -127,7 +129,8 @@ class SendMediaGroup(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=media.document.id, - access_hash=media.document.access_hash + access_hash=media.document.access_hash, + file_reference=b"" ) ) else: @@ -149,7 +152,8 @@ class SendMediaGroup(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 00d06ea1..26f6948a 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -142,7 +142,8 @@ class SendPhoto(BaseClient): media = types.InputMediaPhoto( id=types.InputPhoto( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ), ttl_seconds=ttl_seconds ) diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 045e1b21..936d2e54 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -127,7 +127,8 @@ class SendSticker(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index fa0a51a6..15b98732 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -170,7 +170,8 @@ class SendVideo(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index dd64c14a..32202a0b 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -146,7 +146,8 @@ class SendVideoNote(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 27621fa0..014e8e9f 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -146,7 +146,8 @@ class SendVoice(BaseClient): media = types.InputMediaDocument( id=types.InputDocument( id=unpacked[2], - access_hash=unpacked[3] + access_hash=unpacked[3], + file_reference=b"" ) ) diff --git a/pyrogram/client/methods/users/delete_user_profile_photos.py b/pyrogram/client/methods/users/delete_user_profile_photos.py index 764c3f3e..91c59247 100644 --- a/pyrogram/client/methods/users/delete_user_profile_photos.py +++ b/pyrogram/client/methods/users/delete_user_profile_photos.py @@ -49,7 +49,8 @@ class DeleteUserProfilePhotos(BaseClient): input_photos.append( types.InputPhoto( id=s[0], - access_hash=s[1] + access_hash=s[1], + file_reference=b"" ) ) From 7ee89c94cb837311cf7f7e90e2700dce2615e46f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 01:00:31 +0100 Subject: [PATCH 109/326] Update pin/unpin_chat_message It is now possible to pin messages in basic groups as well as in the own user's chat. --- .../client/methods/chats/pin_chat_message.py | 25 ++++++------------- .../methods/chats/unpin_chat_message.py | 21 ++++++---------- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py index 5a4ab50d..7b99e8ff 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -18,7 +18,7 @@ from typing import Union -from pyrogram.api import functions, types +from pyrogram.api import functions from ...ext import BaseClient @@ -27,7 +27,7 @@ class PinChatMessage(BaseClient): chat_id: Union[int, str], message_id: int, disable_notification: bool = None) -> bool: - """Use this method to pin a message in a supergroup or a channel. + """Use this method to pin a message in a group, channel or your own chat. You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. @@ -49,19 +49,10 @@ class PinChatMessage(BaseClient): :class:`Error ` in case of a Telegram RPC error. ``ValueError`` if a chat_id doesn't belong to a supergroup or a channel. """ - peer = self.resolve_peer(chat_id) - - if isinstance(peer, types.InputPeerChannel): - self.send( - functions.channels.UpdatePinnedMessage( - channel=peer, - id=message_id, - silent=disable_notification or None - ) + self.send( + functions.messages.UpdatePinnedMessage( + peer=self.resolve_peer(chat_id), + id=message_id, + silent=disable_notification or None ) - elif isinstance(peer, types.InputPeerChat): - raise ValueError("The chat_id \"{}\" belongs to a basic group".format(chat_id)) - else: - raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) - - return True + ) diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/client/methods/chats/unpin_chat_message.py index 435f38d7..9eb35eea 100644 --- a/pyrogram/client/methods/chats/unpin_chat_message.py +++ b/pyrogram/client/methods/chats/unpin_chat_message.py @@ -18,14 +18,14 @@ from typing import Union -from pyrogram.api import functions, types +from pyrogram.api import functions from ...ext import BaseClient class UnpinChatMessage(BaseClient): def unpin_chat_message(self, chat_id: Union[int, str]) -> bool: - """Use this method to unpin a message in a supergroup or a channel. + """Use this method to unpin a message in a group, channel or your own chat. You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. @@ -40,18 +40,11 @@ class UnpinChatMessage(BaseClient): :class:`Error ` in case of a Telegram RPC error. ``ValueError`` if a chat_id doesn't belong to a supergroup or a channel. """ - peer = self.resolve_peer(chat_id) - - if isinstance(peer, types.InputPeerChannel): - self.send( - functions.channels.UpdatePinnedMessage( - channel=peer, - id=0 - ) + self.send( + functions.messages.UpdatePinnedMessage( + peer=self.resolve_peer(chat_id), + id=0 ) - elif isinstance(peer, types.InputPeerChat): - raise ValueError("The chat_id \"{}\" belongs to a basic group".format(chat_id)) - else: - raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + ) return True From 0371f4ce8b8a07dd7295e68e98d7499085fdb256 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 01:05:44 +0100 Subject: [PATCH 110/326] Make cloud password methods raise NotImplementedError. See #178 The protocol changed (SRP) and they are currently not re-implemented. --- .../methods/password/change_cloud_password.py | 44 +++++++++---------- .../methods/password/enable_cloud_password.py | 42 +++++++++--------- .../methods/password/remove_cloud_password.py | 37 ++++++++-------- 3 files changed, 59 insertions(+), 64 deletions(-) diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index d7d0e1fa..6ae660f9 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -16,10 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import os -from hashlib import sha256 - -from pyrogram.api import functions, types from ...ext import BaseClient @@ -46,23 +42,25 @@ class ChangeCloudPassword(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ - r = self.send(functions.account.GetPassword()) + raise NotImplementedError - if isinstance(r, types.account.Password): - current_password_hash = sha256(r.current_salt + current_password.encode() + r.current_salt).digest() - - new_salt = r.new_salt + os.urandom(8) - new_password_hash = sha256(new_salt + new_password.encode() + new_salt).digest() - - return self.send( - functions.account.UpdatePasswordSettings( - current_password_hash=current_password_hash, - new_settings=types.account.PasswordInputSettings( - new_salt=new_salt, - new_password_hash=new_password_hash, - hint=new_hint - ) - ) - ) - else: - return False + # r = self.send(functions.account.GetPassword()) + # + # if isinstance(r, types.account.Password): + # current_password_hash = sha256(r.current_salt + current_password.encode() + r.current_salt).digest() + # + # new_salt = r.new_salt + os.urandom(8) + # new_password_hash = sha256(new_salt + new_password.encode() + new_salt).digest() + # + # return self.send( + # functions.account.UpdatePasswordSettings( + # current_password_hash=current_password_hash, + # new_settings=types.account.PasswordInputSettings( + # new_salt=new_salt, + # new_password_hash=new_password_hash, + # hint=new_hint + # ) + # ) + # ) + # else: + # return False diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index 4eb8df6a..f5a71f4b 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -16,10 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import os -from hashlib import sha256 - -from pyrogram.api import functions, types from ...ext import BaseClient @@ -48,22 +44,24 @@ class EnableCloudPassword(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ - r = self.send(functions.account.GetPassword()) + raise NotImplementedError - if isinstance(r, types.account.NoPassword): - salt = r.new_salt + os.urandom(8) - password_hash = sha256(salt + password.encode() + salt).digest() - - return self.send( - functions.account.UpdatePasswordSettings( - current_password_hash=salt, - new_settings=types.account.PasswordInputSettings( - new_salt=salt, - new_password_hash=password_hash, - hint=hint, - email=email - ) - ) - ) - else: - return False + # r = self.send(functions.account.GetPassword()) + # + # if isinstance(r, types.account.NoPassword): + # salt = r.new_salt + os.urandom(8) + # password_hash = sha256(salt + password.encode() + salt).digest() + # + # return self.send( + # functions.account.UpdatePasswordSettings( + # current_password_hash=salt, + # new_settings=types.account.PasswordInputSettings( + # new_salt=salt, + # new_password_hash=password_hash, + # hint=hint, + # email=email + # ) + # ) + # ) + # else: + # return False diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py index 3dab720d..92ae666d 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -16,9 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from hashlib import sha256 - -from pyrogram.api import functions, types from ...ext import BaseClient @@ -37,20 +34,22 @@ class RemoveCloudPassword(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ - r = self.send(functions.account.GetPassword()) + raise NotImplementedError - if isinstance(r, types.account.Password): - password_hash = sha256(r.current_salt + password.encode() + r.current_salt).digest() - - return self.send( - functions.account.UpdatePasswordSettings( - current_password_hash=password_hash, - new_settings=types.account.PasswordInputSettings( - new_salt=b"", - new_password_hash=b"", - hint="" - ) - ) - ) - else: - return False + # r = self.send(functions.account.GetPassword()) + # + # if isinstance(r, types.account.Password): + # password_hash = sha256(r.current_salt + password.encode() + r.current_salt).digest() + # + # return self.send( + # functions.account.UpdatePasswordSettings( + # current_password_hash=password_hash, + # new_settings=types.account.PasswordInputSettings( + # new_salt=b"", + # new_password_hash=b"", + # hint="" + # ) + # ) + # ) + # else: + # return False From 2101dfb8db04714855de0389becb78f10bd09176 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 01:09:20 +0100 Subject: [PATCH 111/326] Show a meaningful error and hint to read more when using cloud password --- pyrogram/client/methods/password/change_cloud_password.py | 5 ++++- pyrogram/client/methods/password/enable_cloud_password.py | 5 ++++- pyrogram/client/methods/password/remove_cloud_password.py | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index 6ae660f9..6a65d51b 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -42,7 +42,10 @@ class ChangeCloudPassword(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ - raise NotImplementedError + raise NotImplementedError( + "Cloud password methods are currently not available. " + "See https://github.com/pyrogram/pyrogram/issues/178" + ) # r = self.send(functions.account.GetPassword()) # diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index f5a71f4b..28542815 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -44,7 +44,10 @@ class EnableCloudPassword(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ - raise NotImplementedError + raise NotImplementedError( + "Cloud password methods are currently not available. " + "See https://github.com/pyrogram/pyrogram/issues/178" + ) # r = self.send(functions.account.GetPassword()) # diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py index 92ae666d..9b45fc8e 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -34,7 +34,10 @@ class RemoveCloudPassword(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ - raise NotImplementedError + raise NotImplementedError( + "Cloud password methods are currently not available. " + "See https://github.com/pyrogram/pyrogram/issues/178" + ) # r = self.send(functions.account.GetPassword()) # From f8de518f6bb68acdf657191e3fa43eca331b5491 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 13:21:32 +0100 Subject: [PATCH 112/326] Update future version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 44d27f79..b2f930b3 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès Date: Sun, 23 Dec 2018 13:28:53 +0100 Subject: [PATCH 113/326] Refactor Poll. Move PollAnswer into poll.py and rename it to PollOption --- pyrogram/__init__.py | 2 +- pyrogram/client/types/__init__.py | 2 +- .../types/messages_and_media/__init__.py | 1 - .../client/types/messages_and_media/poll.py | 35 ++++++++++++------- .../types/messages_and_media/poll_answer.py | 32 ----------------- 5 files changed, 25 insertions(+), 47 deletions(-) delete mode 100644 pyrogram/client/types/messages_and_media/poll_answer.py diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index b2f930b3..94b6c951 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -32,7 +32,7 @@ from .client.types import ( Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, - Poll, PollAnswer + Poll ) from .client import ( Client, ChatAction, ParseMode, Emoji, diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index 0fdfc685..983c3804 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -31,7 +31,7 @@ from .input_media import ( from .messages_and_media import ( Audio, Contact, Document, Animation, Location, Photo, PhotoSize, Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos, - Message, Messages, MessageEntity, Poll, PollAnswer + Message, Messages, MessageEntity, Poll ) from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/client/types/messages_and_media/__init__.py index 527fee27..d77b3494 100644 --- a/pyrogram/client/types/messages_and_media/__init__.py +++ b/pyrogram/client/types/messages_and_media/__init__.py @@ -27,7 +27,6 @@ from .messages import Messages from .photo import Photo from .photo_size import PhotoSize from .poll import Poll -from .poll_answer import PollAnswer from .sticker import Sticker from .user_profile_photos import UserProfilePhotos from .venue import Venue diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py index 2e3c3d1a..a7277f49 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -20,10 +20,21 @@ from typing import List import pyrogram from pyrogram.api import types -from .poll_answer import PollAnswer from ..pyrogram_type import PyrogramType +class PollOption(PyrogramType): + def __init__(self, + *, + client: "pyrogram.client.ext.BaseClient", + text: str, + voters: int): + super().__init__(client) + + self.text = text + self.voters = voters + + class Poll(PyrogramType): def __init__(self, *, @@ -31,16 +42,16 @@ class Poll(PyrogramType): id: int, closed: bool, question: str, - answers: List[PollAnswer], - answer_chosen: int = None, + options: List[PollOption], + option_chosen: int = None, total_voters: int): super().__init__(client) self.id = id self.closed = closed self.question = question - self.answers = answers - self.answer_chosen = answer_chosen + self.options = options + self.option_chosen = option_chosen self.total_voters = total_voters @staticmethod @@ -48,21 +59,21 @@ class Poll(PyrogramType): poll = media_poll.poll results = media_poll.results.results total_voters = media_poll.results.total_voters - answer_chosen = None + option_chosen = None - answers = [] + options = [] for i, answer in enumerate(poll.answers): - voters = None + voters = 0 if results: result = results[i] voters = result.voters if result.chosen: - answer_chosen = i + option_chosen = i - answers.append(PollAnswer( + options.append(PollOption( text=answer.text, voters=voters, client=client @@ -72,8 +83,8 @@ class Poll(PyrogramType): id=poll.id, closed=poll.closed, question=poll.question, - answers=answers, - answer_chosen=answer_chosen, + options=options, + option_chosen=option_chosen, total_voters=total_voters, client=client ) diff --git a/pyrogram/client/types/messages_and_media/poll_answer.py b/pyrogram/client/types/messages_and_media/poll_answer.py deleted file mode 100644 index a9dbc513..00000000 --- a/pyrogram/client/types/messages_and_media/poll_answer.py +++ /dev/null @@ -1,32 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import pyrogram -from ..pyrogram_type import PyrogramType - - -class PollAnswer(PyrogramType): - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - text: str, - voters: int = None): - super().__init__(client) - - self.text = text - self.voters = voters From 0ce7498f810af9ddabfa96ed491f2f4751815106 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 15:37:44 +0100 Subject: [PATCH 114/326] Parse the pinned message on basic chats too --- pyrogram/client/types/user_and_chats/chat.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 5b5a54a8..7b4240dd 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -199,11 +199,11 @@ class Chat(PyrogramType): parsed_chat.can_set_sticker_set = full_chat.can_set_stickers parsed_chat.sticker_set_name = full_chat.stickerset - if full_chat.pinned_msg_id: - parsed_chat.pinned_message = client.get_messages( - parsed_chat.id, - message_ids=full_chat.pinned_msg_id - ) + if full_chat.pinned_msg_id: + parsed_chat.pinned_message = client.get_messages( + parsed_chat.id, + message_ids=full_chat.pinned_msg_id + ) if isinstance(full_chat.exported_invite, types.ChatInviteExported): parsed_chat.invite_link = full_chat.exported_invite.link From 1f82eaa26f6a48c927814ea069306a1b24045611 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 16:19:24 +0100 Subject: [PATCH 115/326] Add vote_poll method --- pyrogram/client/methods/messages/__init__.py | 4 +- pyrogram/client/methods/messages/vote_poll.py | 39 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/messages/vote_poll.py diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index 77919d17..0401aa35 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -39,6 +39,7 @@ from .send_venue import SendVenue from .send_video import SendVideo from .send_video_note import SendVideoNote from .send_voice import SendVoice +from .vote_poll import VotePoll class Messages( @@ -64,6 +65,7 @@ class Messages( SendVideo, SendVideoNote, SendVoice, - SendPoll + SendPoll, + VotePoll ): pass diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py new file mode 100644 index 00000000..06b45d13 --- /dev/null +++ b/pyrogram/client/methods/messages/vote_poll.py @@ -0,0 +1,39 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +from pyrogram.api import functions +from pyrogram.client.ext import BaseClient + + +class VotePoll(BaseClient): + # TODO: Docs + def vote_poll(self, + chat_id: Union[int, str], + message_id: id, + option: int) -> bool: + self.send( + functions.messages.SendVote( + peer=self.resolve_peer(chat_id), + msg_id=message_id, + options=[bytes([option])] + ) + ) + + return True From 10f1e063269438b720531bc4d31552b8803d951c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 16:30:42 +0100 Subject: [PATCH 116/326] Rework vote_poll to work with all polls generated by different clients --- pyrogram/client/methods/messages/vote_poll.py | 4 +++- pyrogram/client/types/messages_and_media/poll.py | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py index 06b45d13..fcb3932f 100644 --- a/pyrogram/client/methods/messages/vote_poll.py +++ b/pyrogram/client/methods/messages/vote_poll.py @@ -28,11 +28,13 @@ class VotePoll(BaseClient): chat_id: Union[int, str], message_id: id, option: int) -> bool: + poll = self.get_messages(chat_id, message_id).poll + self.send( functions.messages.SendVote( peer=self.resolve_peer(chat_id), msg_id=message_id, - options=[bytes([option])] + options=[poll.options[option]._data] ) ) diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py index a7277f49..42ba8640 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -28,11 +28,13 @@ class PollOption(PyrogramType): *, client: "pyrogram.client.ext.BaseClient", text: str, - voters: int): + voters: int, + data: bytes): super().__init__(client) self.text = text self.voters = voters + self._data = data class Poll(PyrogramType): @@ -76,6 +78,7 @@ class Poll(PyrogramType): options.append(PollOption( text=answer.text, voters=voters, + data=answer.option, client=client )) From 03aa5094f7f278286ddb9db2036dd83155fa9770 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 16:40:09 +0100 Subject: [PATCH 117/326] Add retract_vote method --- pyrogram/client/methods/messages/__init__.py | 4 +- .../client/methods/messages/retract_vote.py | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/messages/retract_vote.py diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index 0401aa35..66340283 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -24,6 +24,7 @@ from .edit_message_text import EditMessageText from .forward_messages import ForwardMessages from .get_history import GetHistory from .get_messages import GetMessages +from .retract_vote import RetractVote from .send_animation import SendAnimation from .send_audio import SendAudio from .send_chat_action import SendChatAction @@ -66,6 +67,7 @@ class Messages( SendVideoNote, SendVoice, SendPoll, - VotePoll + VotePoll, + RetractVote ): pass diff --git a/pyrogram/client/methods/messages/retract_vote.py b/pyrogram/client/methods/messages/retract_vote.py new file mode 100644 index 00000000..ff1be582 --- /dev/null +++ b/pyrogram/client/methods/messages/retract_vote.py @@ -0,0 +1,38 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +from pyrogram.api import functions +from pyrogram.client.ext import BaseClient + + +class RetractVote(BaseClient): + # TODO: Docs + def retract_vote(self, + chat_id: Union[int, str], + message_id: id) -> bool: + self.send( + functions.messages.SendVote( + peer=self.resolve_peer(chat_id), + msg_id=message_id, + options=[] + ) + ) + + return True From f477171344f5d7fde075b077c82fc14ff721d8a7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 16:45:54 +0100 Subject: [PATCH 118/326] Document vote_poll --- pyrogram/client/methods/messages/vote_poll.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py index fcb3932f..7d2d10cc 100644 --- a/pyrogram/client/methods/messages/vote_poll.py +++ b/pyrogram/client/methods/messages/vote_poll.py @@ -23,11 +23,30 @@ from pyrogram.client.ext import BaseClient class VotePoll(BaseClient): - # TODO: Docs def vote_poll(self, chat_id: Union[int, str], message_id: id, option: int) -> bool: + """Use this method to vote a poll. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + message_id (``int``): + Unique poll message identifier inside this chat. + + option (``int``): + Index of the poll option you want to vote for (0 to 9). + + Returns: + On success, True is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ poll = self.get_messages(chat_id, message_id).poll self.send( From 03d6c49d924c7d2ba0b3f16496a086ca25ea57ca Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 16:52:25 +0100 Subject: [PATCH 119/326] Document send_poll method --- pyrogram/client/methods/messages/send_poll.py | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index e5229b85..33f44767 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -24,7 +24,6 @@ from pyrogram.client.ext import BaseClient class SendPoll(BaseClient): - # TODO: Docs def send_poll(self, chat_id: Union[int, str], question: str, @@ -35,6 +34,37 @@ class SendPoll(BaseClient): "pyrogram.ReplyKeyboardMarkup", "pyrogram.ReplyKeyboardRemove", "pyrogram.ForceReply"] = None) -> "pyrogram.Message": + """Use this method to send a new poll. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + question (``str``): + The poll question, as string. + + options (List of ``str``): + The poll options, as list of strings (2 to 10 options are allowed). + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ r = self.send( functions.messages.SendMedia( peer=self.resolve_peer(chat_id), From 2994929903aeee20ddc56a74bd05d5cfbdf02640 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 16:59:45 +0100 Subject: [PATCH 120/326] Document Poll --- .../client/types/messages_and_media/poll.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py index 42ba8640..9e1f481d 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -38,6 +38,28 @@ class PollOption(PyrogramType): class Poll(PyrogramType): + """This object represents a Poll + + Args: + id (``int``): + The poll id in this chat. + + closed (``bool``): + Whether the poll is closed or not. + + question (``str``): + Poll question + + options (List of :obj:`PollOption`): + The available poll options. + + total_voters (``int``): + Total amount of voters for this poll. + + option_chosen (``int``, *optional*): + The index of your chosen option (in case you voted already), None otherwise. + """ + def __init__(self, *, client: "pyrogram.client.ext.BaseClient", @@ -45,16 +67,16 @@ class Poll(PyrogramType): closed: bool, question: str, options: List[PollOption], - option_chosen: int = None, - total_voters: int): + total_voters: int, + option_chosen: int = None): super().__init__(client) self.id = id self.closed = closed self.question = question self.options = options - self.option_chosen = option_chosen self.total_voters = total_voters + self.option_chosen = option_chosen @staticmethod def _parse(client, media_poll: types.MessageMediaPoll) -> "Poll": @@ -87,7 +109,7 @@ class Poll(PyrogramType): closed=poll.closed, question=poll.question, options=options, - option_chosen=option_chosen, total_voters=total_voters, + option_chosen=option_chosen, client=client ) From 3fe7fb20bee8285ad06b0508673aeed80898e5ee Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 17:06:34 +0100 Subject: [PATCH 121/326] Document retract_vote --- .../client/methods/messages/retract_vote.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/retract_vote.py b/pyrogram/client/methods/messages/retract_vote.py index ff1be582..e0852355 100644 --- a/pyrogram/client/methods/messages/retract_vote.py +++ b/pyrogram/client/methods/messages/retract_vote.py @@ -23,10 +23,26 @@ from pyrogram.client.ext import BaseClient class RetractVote(BaseClient): - # TODO: Docs def retract_vote(self, chat_id: Union[int, str], message_id: id) -> bool: + """Use this method to retract your vote in a poll. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + message_id (``int``): + Unique poll message identifier inside this chat. + + Returns: + On success, True is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ self.send( functions.messages.SendVote( peer=self.resolve_peer(chat_id), From 9fe04ff7cdb0fe9362e66a066f5310a3b080740c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 19:51:45 +0100 Subject: [PATCH 122/326] Fix filter links to Objects --- pyrogram/client/filters/filters.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index fa9eace3..69d3fad3 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -86,37 +86,37 @@ class Filters: """Filter edited messages.""" audio = create("Audio", lambda _, m: bool(m.audio)) - """Filter messages that contain :obj:`Audio ` objects.""" + """Filter messages that contain :obj:`Audio ` objects.""" document = create("Document", lambda _, m: bool(m.document)) - """Filter messages that contain :obj:`Document ` objects.""" + """Filter messages that contain :obj:`Document ` objects.""" photo = create("Photo", lambda _, m: bool(m.photo)) - """Filter messages that contain :obj:`Photo ` objects.""" + """Filter messages that contain :obj:`Photo ` objects.""" sticker = create("Sticker", lambda _, m: bool(m.sticker)) - """Filter messages that contain :obj:`Sticker ` objects.""" + """Filter messages that contain :obj:`Sticker ` objects.""" animation = create("GIF", lambda _, m: bool(m.animation)) - """Filter messages that contain :obj:`Animation ` objects.""" + """Filter messages that contain :obj:`Animation ` objects.""" video = create("Video", lambda _, m: bool(m.video)) - """Filter messages that contain :obj:`Video ` objects.""" + """Filter messages that contain :obj:`Video ` objects.""" voice = create("Voice", lambda _, m: bool(m.voice)) - """Filter messages that contain :obj:`Voice ` note objects.""" + """Filter messages that contain :obj:`Voice ` note objects.""" video_note = create("Voice", lambda _, m: bool(m.video_note)) - """Filter messages that contain :obj:`VideoNote ` objects.""" + """Filter messages that contain :obj:`VideoNote ` objects.""" contact = create("Contact", lambda _, m: bool(m.contact)) - """Filter messages that contain :obj:`Contact ` objects.""" + """Filter messages that contain :obj:`Contact ` objects.""" location = create("Location", lambda _, m: bool(m.location)) - """Filter messages that contain :obj:`Location ` objects.""" + """Filter messages that contain :obj:`Location ` objects.""" venue = create("Venue", lambda _, m: bool(m.venue)) - """Filter messages that contain :obj:`Venue ` objects.""" + """Filter messages that contain :obj:`Venue ` objects.""" web_page = create("WebPage", lambda _, m: m.web_page) """Filter messages sent with a webpage preview.""" From a551f1fe1eff70a523e8b5d553ee2f33b80f687c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 19:52:49 +0100 Subject: [PATCH 123/326] Fix Filters.poll docstrings link to Poll type --- pyrogram/client/filters/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index cbd44a10..1347f993 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -122,7 +122,7 @@ class Filters: """Filter messages sent with a webpage preview.""" poll = create("Poll", lambda _, m: m.poll) - """Filter messages that contain :obj:`Poll ` objects.""" + """Filter messages that contain :obj:`Poll ` objects.""" private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private")) """Filter messages sent in private chats.""" From 7881320a88c899b2534481732220c78016a95abc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Dec 2018 19:53:22 +0100 Subject: [PATCH 124/326] Update develop branch version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 6ea6aa5c..d5a74409 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès Date: Mon, 24 Dec 2018 14:13:47 +0100 Subject: [PATCH 125/326] Add hide_via parameter to send_inline_bot_result --- pyrogram/client/methods/bots/send_inline_bot_result.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/bots/send_inline_bot_result.py b/pyrogram/client/methods/bots/send_inline_bot_result.py index 23d36cba..8c6a38b5 100644 --- a/pyrogram/client/methods/bots/send_inline_bot_result.py +++ b/pyrogram/client/methods/bots/send_inline_bot_result.py @@ -28,7 +28,8 @@ class SendInlineBotResult(BaseClient): query_id: int, result_id: str, disable_notification: bool = None, - reply_to_message_id: int = None): + reply_to_message_id: int = None, + hide_via: bool = None): """Use this method to send an inline bot result. Bot results can be retrieved using :obj:`get_inline_bot_results ` @@ -51,6 +52,9 @@ class SendInlineBotResult(BaseClient): reply_to_message_id (``bool``, *optional*): If the message is a reply, ID of the original message. + hide_via (``bool``): + Sends the message with *via @bot* hidden. + Returns: On success, the sent Message is returned. @@ -64,6 +68,7 @@ class SendInlineBotResult(BaseClient): id=result_id, random_id=self.rnd_id(), silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id + reply_to_msg_id=reply_to_message_id, + hide_via=hide_via or None ) ) From c833b3842add3833b38af03b664fbfa3d7f50b7e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Dec 2018 14:19:21 +0100 Subject: [PATCH 126/326] Put PollOption back in a separate file, its docstrings must be visible --- pyrogram/__init__.py | 2 +- pyrogram/client/types/__init__.py | 2 +- .../types/messages_and_media/__init__.py | 1 + .../client/types/messages_and_media/poll.py | 19 ++------ .../types/messages_and_media/poll_option.py | 46 +++++++++++++++++++ 5 files changed, 52 insertions(+), 18 deletions(-) create mode 100644 pyrogram/client/types/messages_and_media/poll_option.py diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 94b6c951..6400abc1 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -32,7 +32,7 @@ from .client.types import ( Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, - Poll + Poll, PollOption ) from .client import ( Client, ChatAction, ParseMode, Emoji, diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index 983c3804..24de120f 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -31,7 +31,7 @@ from .input_media import ( from .messages_and_media import ( Audio, Contact, Document, Animation, Location, Photo, PhotoSize, Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos, - Message, Messages, MessageEntity, Poll + Message, Messages, MessageEntity, Poll, PollOption ) from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/client/types/messages_and_media/__init__.py index d77b3494..d402ae48 100644 --- a/pyrogram/client/types/messages_and_media/__init__.py +++ b/pyrogram/client/types/messages_and_media/__init__.py @@ -27,6 +27,7 @@ from .messages import Messages from .photo import Photo from .photo_size import PhotoSize from .poll import Poll +from .poll_option import PollOption from .sticker import Sticker from .user_profile_photos import UserProfilePhotos from .venue import Venue diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py index 9e1f481d..ab59b1ad 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -20,25 +20,12 @@ from typing import List import pyrogram from pyrogram.api import types +from .poll_option import PollOption from ..pyrogram_type import PyrogramType -class PollOption(PyrogramType): - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - text: str, - voters: int, - data: bytes): - super().__init__(client) - - self.text = text - self.voters = voters - self._data = data - - class Poll(PyrogramType): - """This object represents a Poll + """This object represents a Poll. Args: id (``int``): @@ -48,7 +35,7 @@ class Poll(PyrogramType): Whether the poll is closed or not. question (``str``): - Poll question + Poll question. options (List of :obj:`PollOption`): The available poll options. diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/client/types/messages_and_media/poll_option.py new file mode 100644 index 00000000..175b3701 --- /dev/null +++ b/pyrogram/client/types/messages_and_media/poll_option.py @@ -0,0 +1,46 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from ..pyrogram_type import PyrogramType + + +class PollOption(PyrogramType): + def __init__(self, + *, + client: "pyrogram.client.ext.BaseClient", + text: str, + voters: int, + data: bytes): + """This object represents a Poll Option. + + Args: + text (``str``): + Text of the poll option. + + voters (``int``): + The number of users who voted this option. + + data (``bytes``): + Unique data that identifies this option (among all the other options in a poll). + """ + super().__init__(client) + + self.text = text + self.voters = voters + self.data = data From 46d206610ed1e3e50cf23c31deefd0e20e9cf9af Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Dec 2018 14:20:54 +0100 Subject: [PATCH 127/326] Add Poll related stuff in docs --- docs/source/pyrogram/Client.rst | 3 +++ docs/source/pyrogram/Types.rst | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index 50e213ea..a0177a12 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -62,6 +62,9 @@ Messages delete_messages get_messages get_history + send_poll + vote_poll + retract_vote Chats ----- diff --git a/docs/source/pyrogram/Types.rst b/docs/source/pyrogram/Types.rst index 8763c0e1..47c755f9 100644 --- a/docs/source/pyrogram/Types.rst +++ b/docs/source/pyrogram/Types.rst @@ -40,6 +40,8 @@ Messages & Media Location Venue Sticker + Poll + PollOption Bots ---- @@ -146,6 +148,12 @@ Input Media .. autoclass:: Sticker :members: +.. autoclass:: Poll + :members: + +.. autoclass:: PollOption + :members: + .. Bots ---- From 098b06d1b742bf8aa874115789e3bd644e91a896 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Dec 2018 14:32:53 +0100 Subject: [PATCH 128/326] Fix poll docstrings and vote_poll wrong attribute access --- pyrogram/client/methods/messages/vote_poll.py | 2 +- .../types/messages_and_media/poll_option.py | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py index 7d2d10cc..3404a7bd 100644 --- a/pyrogram/client/methods/messages/vote_poll.py +++ b/pyrogram/client/methods/messages/vote_poll.py @@ -53,7 +53,7 @@ class VotePoll(BaseClient): functions.messages.SendVote( peer=self.resolve_peer(chat_id), msg_id=message_id, - options=[poll.options[option]._data] + options=[poll.options[option].data] ) ) diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/client/types/messages_and_media/poll_option.py index 175b3701..240368fc 100644 --- a/pyrogram/client/types/messages_and_media/poll_option.py +++ b/pyrogram/client/types/messages_and_media/poll_option.py @@ -21,24 +21,25 @@ from ..pyrogram_type import PyrogramType class PollOption(PyrogramType): + """This object represents a Poll Option. + + Args: + text (``str``): + Text of the poll option. + + voters (``int``): + The number of users who voted this option. + + data (``bytes``): + Unique data that identifies this option among all the other options in a poll. + """ + def __init__(self, *, client: "pyrogram.client.ext.BaseClient", text: str, voters: int, data: bytes): - """This object represents a Poll Option. - - Args: - text (``str``): - Text of the poll option. - - voters (``int``): - The number of users who voted this option. - - data (``bytes``): - Unique data that identifies this option (among all the other options in a poll). - """ super().__init__(client) self.text = text From 6e964b51b157733b7ff84a4180c3469af57eb462 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Dec 2018 14:50:04 +0100 Subject: [PATCH 129/326] Type hint on_message decorator --- pyrogram/client/methods/decorators/on_message.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index 68ed1fab..a252c6ba 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.py @@ -16,8 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Tuple + import pyrogram from pyrogram.client.filters.filter import Filter +from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -45,7 +48,7 @@ class OnMessage(BaseClient): The group identifier, defaults to 0. """ - def decorator(func): + def decorator(func: callable) -> Tuple[Handler, int]: if isinstance(func, tuple): func = func[0].callback From 00e4e385aa676afe2b67f84ff97112f3a67d9c81 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Dec 2018 22:31:45 +0100 Subject: [PATCH 130/326] Allow bots to message old chats even if they don't exist in the session --- pyrogram/client/client.py | 75 +++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index f5aa33ea..3dfd733c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1104,35 +1104,58 @@ class Client(Methods, BaseClient): :class:`Error ` in case of a Telegram RPC error. ``KeyError`` in case the peer doesn't exist in the internal database. """ - if type(peer_id) is str: - if peer_id in ("self", "me"): - return types.InputPeerSelf() - - peer_id = re.sub(r"[@+\s]", "", peer_id.lower()) - - try: - int(peer_id) - except ValueError: - if peer_id not in self.peers_by_username: - self.send(functions.contacts.ResolveUsername(peer_id)) - - return self.peers_by_username[peer_id] - else: - try: - return self.peers_by_phone[peer_id] - except KeyError: - raise PeerIdInvalid - - try: # User + try: return self.peers_by_id[peer_id] except KeyError: - try: # Chat - return self.peers_by_id[-peer_id] + if type(peer_id) is str: + if peer_id in ("self", "me"): + return types.InputPeerSelf() + + peer_id = re.sub(r"[@+\s]", "", peer_id.lower()) + + try: + int(peer_id) + except ValueError: + if peer_id not in self.peers_by_username: + self.send( + functions.contacts.ResolveUsername( + username=peer_id + ) + ) + + return self.peers_by_username[peer_id] + else: + try: + return self.peers_by_phone[peer_id] + except KeyError: + raise PeerIdInvalid + + if peer_id > 0: + self.fetch_peers( + self.send( + functions.users.GetUsers( + id=[types.InputUser(peer_id, 0)] + ) + ) + ) + else: + if str(peer_id).startswith("-100"): + self.send( + functions.channels.GetChannels( + id=[types.InputChannel(int(str(peer_id)[4:]), 0)] + ) + ) + else: + self.send( + functions.messages.GetChats( + id=[-peer_id] + ) + ) + + try: + return self.peers_by_id[peer_id] except KeyError: - try: # Channel - return self.peers_by_id[int("-100" + str(peer_id))] - except (KeyError, ValueError): - raise PeerIdInvalid + raise PeerIdInvalid def save_file(self, path: str, From 684b90100598119960264a14540ea761aae43d8b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Dec 2018 14:42:56 +0100 Subject: [PATCH 131/326] Reword AUTH_KEY_DUPLICATED error message --- compiler/error/source/406_NOT_ACCEPTABLE.tsv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/error/source/406_NOT_ACCEPTABLE.tsv b/compiler/error/source/406_NOT_ACCEPTABLE.tsv index 3a88a7b6..27e4c5bd 100644 --- a/compiler/error/source/406_NOT_ACCEPTABLE.tsv +++ b/compiler/error/source/406_NOT_ACCEPTABLE.tsv @@ -1,2 +1,2 @@ id message -AUTH_KEY_DUPLICATED Authorization error. You must log out and log in again with your phone number. We apologize for the inconvenience. \ No newline at end of file +AUTH_KEY_DUPLICATED Authorization error - you must delete your session file and log in again with your phone number \ No newline at end of file From 68426eaf7240e098b90c856461403bda518b1659 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Dec 2018 14:48:59 +0100 Subject: [PATCH 132/326] Add FILEREF_UPGRADE_NEEDED error --- compiler/error/source/406_NOT_ACCEPTABLE.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/406_NOT_ACCEPTABLE.tsv b/compiler/error/source/406_NOT_ACCEPTABLE.tsv index 27e4c5bd..e94706ed 100644 --- a/compiler/error/source/406_NOT_ACCEPTABLE.tsv +++ b/compiler/error/source/406_NOT_ACCEPTABLE.tsv @@ -1,2 +1,3 @@ id message -AUTH_KEY_DUPLICATED Authorization error - you must delete your session file and log in again with your phone number \ No newline at end of file +AUTH_KEY_DUPLICATED Authorization error - you must delete your session file and log in again with your phone number +FILEREF_UPGRADE_NEEDED The file reference has expired - you must obtain the original media message \ No newline at end of file From b18890fddd21d034c6737da0e028b93a8027be8e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Dec 2018 14:51:03 +0100 Subject: [PATCH 133/326] Add INPUT_USER_DEACTIVATED error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 08db4c4f..fa41142a 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -76,4 +76,5 @@ USER_IS_BLOCKED The user blocked you YOU_BLOCKED_USER You blocked this user ADMINS_TOO_MUCH The chat has too many administrators BOTS_TOO_MUCH The chat has too many bots -USER_ADMIN_INVALID The action requires admin privileges \ No newline at end of file +USER_ADMIN_INVALID The action requires admin privileges +INPUT_USER_DEACTIVATED The target user has been deactivated \ No newline at end of file From e3371c90f254ac4ceffdc729584af590bf5aa49c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Dec 2018 15:38:56 +0100 Subject: [PATCH 134/326] Add PASSWORD_RECOVERY_NA and PASSWORD_EMPTY errors --- compiler/error/source/400_BAD_REQUEST.tsv | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index fa41142a..c1a02761 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -77,4 +77,6 @@ YOU_BLOCKED_USER You blocked this user ADMINS_TOO_MUCH The chat has too many administrators BOTS_TOO_MUCH The chat has too many bots USER_ADMIN_INVALID The action requires admin privileges -INPUT_USER_DEACTIVATED The target user has been deactivated \ No newline at end of file +INPUT_USER_DEACTIVATED The target user has been deactivated +PASSWORD_RECOVERY_NA The password recovery e-mail is not available +PASSWORD_EMPTY The password entered is empty \ No newline at end of file From d91acfe2ca4baff2c05eee2b27d81d1ac89a02eb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Dec 2018 15:41:55 +0100 Subject: [PATCH 135/326] Re-implement password-protected log-ins and support password recovery --- pyrogram/client/client.py | 44 ++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 64ed4e8e..226f783c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -18,7 +18,6 @@ import base64 import binascii -import getpass import json import logging import math @@ -46,9 +45,12 @@ from pyrogram.api.errors import ( PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty, PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, - VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied) + VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied, + PasswordRecoveryNa, PasswordEmpty +) from pyrogram.client.handlers import DisconnectHandler from pyrogram.client.handlers.handler import Handler +from pyrogram.client.methods.password.utils import compute_check from pyrogram.crypto import AES from pyrogram.session import Auth, Session from .dispatcher import Dispatcher @@ -574,21 +576,46 @@ class Client(Methods, BaseClient): self.first_name = None except SessionPasswordNeeded as e: print(e.MESSAGE) - r = self.send(functions.account.GetPassword()) while True: try: + r = self.send(functions.account.GetPassword()) if self.password is None: print("Hint: {}".format(r.hint)) - self.password = getpass.getpass("Enter password: ") - if type(self.password) is str: - self.password = r.current_salt + self.password.encode() + r.current_salt + self.password = input("Enter password (empty to recover): ") - password_hash = sha256(self.password).digest() + if self.password == "": + r = self.send(functions.auth.RequestPasswordRecovery()) - r = self.send(functions.auth.CheckPassword(password_hash)) + print("An e-mail containing the recovery code has been sent to {}".format( + r.email_pattern + )) + + r = self.send( + functions.auth.RecoverPassword( + code=input("Enter password recovery code: ") + ) + ) + else: + r = self.send( + functions.auth.CheckPassword( + password=compute_check(r, self.password) + ) + ) + except PasswordEmpty as e: + if password_hash_invalid_raises: + raise + else: + print(e.MESSAGE) + self.password = None + except PasswordRecoveryNa as e: + if password_hash_invalid_raises: + raise + else: + print(e.MESSAGE) + self.password = None except PasswordHashInvalid as e: if password_hash_invalid_raises: raise @@ -601,6 +628,7 @@ class Client(Methods, BaseClient): else: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) + self.password = None except Exception as e: log.error(e, exc_info=True) else: From 7e4b96cfdf1d1503c58f64a7800ebdfab82b0f3f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Dec 2018 16:20:09 +0100 Subject: [PATCH 136/326] Suppress PyPep8Naming soft-warnings --- pyrogram/client/methods/password/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/client/methods/password/utils.py b/pyrogram/client/methods/password/utils.py index 4bf0ddec..01c3fe49 100644 --- a/pyrogram/client/methods/password/utils.py +++ b/pyrogram/client/methods/password/utils.py @@ -46,6 +46,7 @@ def compute_hash(algo: types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter1000 return sha256(algo.salt2 + hash3 + algo.salt2) +# noinspection PyPep8Naming def compute_check(r: types.account.Password, password: str) -> types.InputCheckPasswordSRP: algo = r.current_algo From 9066c9687e656c81a2917f3edd1051bda0d0375e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Dec 2018 18:11:16 +0100 Subject: [PATCH 137/326] Update README.md (Layer 91) Closes #178, closes #179 --- README.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 17df05c5..478683c3 100644 --- a/README.rst +++ b/README.rst @@ -17,8 +17,8 @@ Pyrogram app.run() -**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building -custom Telegram applications that interact with the MTProto API as both User and Bot. +**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for +building custom Telegram applications that interact with the MTProto API as both User and Bot. Features -------- @@ -26,7 +26,7 @@ Features - **Easy to use**: You can easily install Pyrogram using pip and start building your app right away. - **High-level**: The low-level details of MTProto are abstracted and automatically handled. - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. -- **Updated** to the latest Telegram API version, currently Layer 82 on top of MTProto 2.0. +- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0. - **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API. - **Full API**, allowing to execute any advanced action an official client is able to do, and more. @@ -99,7 +99,7 @@ Copyright & License
- Schema Layer @@ -114,10 +114,10 @@ Copyright & License .. |description| replace:: **Telegram MTProto API Client Library for Python** -.. |scheme| image:: "https://img.shields.io/badge/SCHEME-LAYER%2082-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" +.. |scheme| image:: "https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30" :target: compiler/api/source/main_api.tl :alt: Scheme Layer -.. |tgcrypto| image:: "https://img.shields.io/badge/TGCRYPTO-V1.1.1-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" +.. |tgcrypto| image:: "https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30" :target: https://github.com/pyrogram/tgcrypto :alt: TgCrypto From c3f4fab58b4728920d8e8b3083fb683544e3dbd6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Dec 2018 18:12:54 +0100 Subject: [PATCH 138/326] Update docs version --- docs/source/start/Installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/start/Installation.rst b/docs/source/start/Installation.rst index efebac5a..f139ddb2 100644 --- a/docs/source/start/Installation.rst +++ b/docs/source/start/Installation.rst @@ -82,7 +82,7 @@ If no error shows up you are good to go. >>> import pyrogram >>> pyrogram.__version__ - '0.9.3' + '0.9.4' .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto .. _develop: http://github.com/pyrogram/pyrogram From 1eee4b77e15d52bd0ab6b688c570d7f8c13a58dc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Dec 2018 23:08:45 +0100 Subject: [PATCH 139/326] Pin/unpin_chat_message don't raise a ValueError anymore --- pyrogram/client/methods/chats/pin_chat_message.py | 1 - pyrogram/client/methods/chats/unpin_chat_message.py | 1 - 2 files changed, 2 deletions(-) diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py index 7b99e8ff..66d5497d 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -47,7 +47,6 @@ class PinChatMessage(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. - ``ValueError`` if a chat_id doesn't belong to a supergroup or a channel. """ self.send( functions.messages.UpdatePinnedMessage( diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/client/methods/chats/unpin_chat_message.py index 9eb35eea..9aa56a7c 100644 --- a/pyrogram/client/methods/chats/unpin_chat_message.py +++ b/pyrogram/client/methods/chats/unpin_chat_message.py @@ -38,7 +38,6 @@ class UnpinChatMessage(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. - ``ValueError`` if a chat_id doesn't belong to a supergroup or a channel. """ self.send( functions.messages.UpdatePinnedMessage( From 74477f2cac9aae5566915659887a5dae7f863e7b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Dec 2018 09:18:48 +0100 Subject: [PATCH 140/326] Add PHONE_NUMBER_FLOOD error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index c1a02761..c0a5da73 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -79,4 +79,5 @@ BOTS_TOO_MUCH The chat has too many bots USER_ADMIN_INVALID The action requires admin privileges INPUT_USER_DEACTIVATED The target user has been deactivated PASSWORD_RECOVERY_NA The password recovery e-mail is not available -PASSWORD_EMPTY The password entered is empty \ No newline at end of file +PASSWORD_EMPTY The password entered is empty +PHONE_NUMBER_FLOOD This number has tried to login too many times \ No newline at end of file From 127123006f97eda979241c40427393f49fc391a1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Dec 2018 19:44:22 +0100 Subject: [PATCH 141/326] Add TestServers documentation page --- docs/source/index.rst | 5 ++-- docs/source/resources/TestServers.rst | 39 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 docs/source/resources/TestServers.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index c6ff71b8..34456b21 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -25,7 +25,7 @@ Welcome to Pyrogram
- Scheme Layer @@ -64,7 +64,7 @@ Features - **Easy to use**: You can easily install Pyrogram using pip and start building your app right away. - **High-level**: The low-level details of MTProto are abstracted and automatically handled. - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. -- **Updated** to the latest Telegram API version, currently Layer 82 on top of MTProto 2.0. +- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0. - **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API. - **Full API**, allowing to execute any advanced action an official client is able to do, and more. @@ -89,6 +89,7 @@ To get started, press the Next button. resources/CustomizeSessions resources/TgCrypto resources/TextFormatting + resources/TestServers resources/SOCKS5Proxy resources/BotsInteraction resources/ErrorHandling diff --git a/docs/source/resources/TestServers.rst b/docs/source/resources/TestServers.rst new file mode 100644 index 00000000..2f82f24c --- /dev/null +++ b/docs/source/resources/TestServers.rst @@ -0,0 +1,39 @@ +Test Servers +============ + +If you wish to test your application in a separate environment, Pyrogram is able to authorize your account into +Telegram's test servers without hassle. All you need to do is start a new session (e.g.: "my_account_test") using +``test_mode=True``: + +.. code-block:: python + + from pyrogram import Client + + with Client("my_account_test", test_mode=True) as app: + print(app.get_me()) + +.. note:: + + If this is the first time you login into test servers, you will be asked to register your account first. + Don't worry about your contacts and chats, they will be kept untouched inside the production environment; + accounts authorized on test servers reside in a different, parallel instance of a Telegram database. + +Test Mode in Official Apps +-------------------------- + +You can also login yourself into test servers using official desktop apps, such as Webogram and TDesktop: + +- **Webogram**: Login here: https://web.telegram.org/?test=1 +- **TDesktop**: Open settings and type ``testmode``. + +Test Numbers +------------ + +Beside normal numbers, the test environment allows you to login with reserved test numbers. +Valid phone numbers follow the pattern ``99966XYYYY``, where ``X`` is the DC number (1 to 3) and ``YYYY`` are random +numbers. Users with such numbers always get ``XXXXX`` as the confirmation code (the DC number, repeated five times). + +.. important:: + + Do not store any important or private information in such test users' accounts; anyone can make use of the + simplified authorization mechanism and login at any time. From 39a458a5f85f21afe90439547b3743a94c195cdb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Dec 2018 20:08:11 +0100 Subject: [PATCH 142/326] Add Changelog documentation page --- docs/source/index.rst | 3 ++- docs/source/resources/Changelog.rst | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 docs/source/resources/Changelog.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 34456b21..8234374c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -89,10 +89,11 @@ To get started, press the Next button. resources/CustomizeSessions resources/TgCrypto resources/TextFormatting - resources/TestServers resources/SOCKS5Proxy resources/BotsInteraction resources/ErrorHandling + resources/TestServers + resources/Changelog .. toctree:: :hidden: diff --git a/docs/source/resources/Changelog.rst b/docs/source/resources/Changelog.rst new file mode 100644 index 00000000..77daa21e --- /dev/null +++ b/docs/source/resources/Changelog.rst @@ -0,0 +1,5 @@ +Changelog +========= + +Currently, all Pyrogram release notes live inside the GitHub repository web page: +https://github.com/pyrogram/pyrogram/releases \ No newline at end of file From e4bead530cfb330b8c78c0ed86d620e019e8b00b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Dec 2018 14:20:59 +0100 Subject: [PATCH 143/326] Automatically redirect to GitHub release notes in 10 seconds --- docs/source/resources/Changelog.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/source/resources/Changelog.rst b/docs/source/resources/Changelog.rst index 77daa21e..732a1311 100644 --- a/docs/source/resources/Changelog.rst +++ b/docs/source/resources/Changelog.rst @@ -2,4 +2,10 @@ Changelog ========= Currently, all Pyrogram release notes live inside the GitHub repository web page: -https://github.com/pyrogram/pyrogram/releases \ No newline at end of file +https://github.com/pyrogram/pyrogram/releases + +(You will be automatically redirected in 10 seconds.) + +.. raw:: html + + \ No newline at end of file From 4a01745c68c825bddfdb9bed9a20d4d2cfb904f5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Dec 2018 22:58:02 +0100 Subject: [PATCH 144/326] Add more error classes to docs --- docs/source/errors/Forbidden.rst | 8 ++++++++ docs/source/errors/NotAcceptable.rst | 8 ++++++++ docs/source/pyrogram/Error.rst | 2 ++ docs/source/resources/ErrorHandling.rst | 9 ++++----- 4 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 docs/source/errors/Forbidden.rst create mode 100644 docs/source/errors/NotAcceptable.rst diff --git a/docs/source/errors/Forbidden.rst b/docs/source/errors/Forbidden.rst new file mode 100644 index 00000000..fd2e08ce --- /dev/null +++ b/docs/source/errors/Forbidden.rst @@ -0,0 +1,8 @@ +Forbidden +========= + +.. module:: pyrogram.api.errors.Forbidden + +.. automodule:: pyrogram.api.errors.exceptions.forbidden_403 + :members: + :show-inheritance: diff --git a/docs/source/errors/NotAcceptable.rst b/docs/source/errors/NotAcceptable.rst new file mode 100644 index 00000000..66ebaf72 --- /dev/null +++ b/docs/source/errors/NotAcceptable.rst @@ -0,0 +1,8 @@ +Not Acceptable +============== + +.. module:: pyrogram.api.errors.NotAcceptable + +.. automodule:: pyrogram.api.errors.exceptions.not_acceptable_406 + :members: + :show-inheritance: diff --git a/docs/source/pyrogram/Error.rst b/docs/source/pyrogram/Error.rst index b5474e73..2ec1159d 100644 --- a/docs/source/pyrogram/Error.rst +++ b/docs/source/pyrogram/Error.rst @@ -9,6 +9,8 @@ Error ../errors/SeeOther ../errors/BadRequest ../errors/Unauthorized + ../errors/Forbidden + ../errors/NotAcceptable ../errors/Flood ../errors/InternalServerError ../errors/UnknownError diff --git a/docs/source/resources/ErrorHandling.rst b/docs/source/resources/ErrorHandling.rst index 0d5cf6f9..f2a77b7a 100644 --- a/docs/source/resources/ErrorHandling.rst +++ b/docs/source/resources/ErrorHandling.rst @@ -1,15 +1,16 @@ Error Handling ============== -Errors are inevitable when working with the API, and they must be correctly handled by -the use of ``try..except`` blocks. +Errors are inevitable when working with the API, and they must be correctly handled with ``try..except`` blocks. -There are many errors that Telegram could return, but they all fall in one of these five exception categories +There are many errors that Telegram could return, but they all fall in one of these categories (which are in turn children of the :obj:`pyrogram.Error` superclass) - :obj:`303 See Other ` - :obj:`400 Bad Request ` - :obj:`401 Unauthorized ` +- :obj:`403 Forbidden ` +- :obj:`406 Not Acceptable ` - :obj:`420 Flood ` - :obj:`500 Internal Server Error ` @@ -56,5 +57,3 @@ before you can try again. The value is always stored in the ``x`` field of the r ... except FloodWait as e: time.sleep(e.x) - -**TODO: Better explanation on how to deal with exceptions** \ No newline at end of file From decaa9e7f03aadc24d725dd800e781c555219d53 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Dec 2018 23:55:56 +0100 Subject: [PATCH 145/326] Rename downwards to reversed --- .../client/methods/messages/get_history.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 845b9066..ce12e9b0 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -26,13 +26,11 @@ from ...ext import BaseClient class GetHistory(BaseClient): def get_history(self, chat_id: Union[int, str], - offset: int = 0, limit: int = 100, offset: int = 0, offset_id: int = 0, offset_date: int = 0, - add_offset: int = 0, - downwards: bool = False): + reversed: bool = False): """Use this method to retrieve the history of a chat. You can get up to 100 messages at once. @@ -43,34 +41,38 @@ class GetHistory(BaseClient): For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - offset (``int``, *optional*) - Sequential number of the first message to be returned. - Defaults to 0 (most recent message). - limit (``int``, *optional*): Limits the number of messages to be retrieved. By default, the first 100 messages are returned. + offset (``int``, *optional*) + Sequential number of the first message to be returned. Defaults to 0 (most recent message). + Negative values are also accepted and become useful in case you set offset_id or offset_date. + offset_id (``int``, *optional*): Pass a message identifier as offset to retrieve only older messages starting from that message. offset_date (``int``, *optional*): Pass a date in Unix time as offset to retrieve only older messages starting from that date. + reversed (``bool``, *optional*): + Pass True to retrieve the messages in reversed order (from older to most recent). + Returns: On success, a :obj:`Messages ` object is returned. Raises: :class:`Error ` in case of a Telegram RPC error. """ - return pyrogram.Messages._parse( + + messages = pyrogram.Messages._parse( self, self.send( functions.messages.GetHistory( peer=self.resolve_peer(chat_id), offset_id=offset_id, offset_date=offset_date, - add_offset=offset, + add_offset=offset - (limit if reversed else 0), limit=limit, max_id=0, min_id=0, @@ -78,3 +80,8 @@ class GetHistory(BaseClient): ) ) ) + + if reversed: + messages.messages.reverse() + + return messages From 3814471af409836cc4855ec402f5aa9dd2aa014d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Dec 2018 23:56:45 +0100 Subject: [PATCH 146/326] Change get_dialogs behaviour It now accepts an offset_date instead of an offset_dialog --- pyrogram/client/methods/chats/get_dialogs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 0e04423c..757cf5bc 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -23,7 +23,7 @@ from ...ext import BaseClient class GetDialogs(BaseClient): def get_dialogs(self, - offset_dialog: "pyrogram.Dialog" = None, + offset_date: int = 0, limit: int = 100, pinned_only: bool = False) -> "pyrogram.Dialogs": """Use this method to get the user's dialogs @@ -31,13 +31,13 @@ class GetDialogs(BaseClient): You can get up to 100 dialogs at once. Args: - offset_dialog (:obj:`Dialog`): - Sequential Dialog of the first dialog to be returned. - Defaults to None (start from the beginning). + offset_date (``int``): + The offset date in Unix time taken from the top message of a :obj:`Dialog`. + Defaults to 0. Valid for non-pinned dialogs only. limit (``str``, *optional*): Limits the number of dialogs to be retrieved. - Defaults to 100. + Defaults to 100. Valid for non-pinned dialogs only. pinned_only (``bool``, *optional*): Pass True if you want to get only pinned dialogs. @@ -55,7 +55,7 @@ class GetDialogs(BaseClient): else: r = self.send( functions.messages.GetDialogs( - offset_date=offset_dialog.top_message.date if offset_dialog else 0, + offset_date=offset_date, offset_id=0, offset_peer=types.InputPeerEmpty(), limit=limit, From 9fadbbd7286fec739e9a120f576cf708aeb0188e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Dec 2018 00:37:20 +0100 Subject: [PATCH 147/326] Update develop version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 6400abc1..e3628fe4 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès Date: Fri, 28 Dec 2018 14:08:09 +0100 Subject: [PATCH 148/326] Add get_chat_preview method and ChatPreview object --- docs/source/pyrogram/Client.rst | 1 + docs/source/pyrogram/Types.rst | 4 + pyrogram/__init__.py | 2 +- pyrogram/client/methods/chats/__init__.py | 4 +- .../client/methods/chats/get_chat_preview.py | 63 +++++++++++++++ pyrogram/client/types/__init__.py | 2 +- .../client/types/user_and_chats/__init__.py | 1 + .../types/user_and_chats/chat_preview.py | 78 +++++++++++++++++++ 8 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 pyrogram/client/methods/chats/get_chat_preview.py create mode 100644 pyrogram/client/types/user_and_chats/chat_preview.py diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index a0177a12..179910a3 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -74,6 +74,7 @@ Chats join_chat leave_chat + get_chat_preview kick_chat_member unban_chat_member restrict_chat_member diff --git a/docs/source/pyrogram/Types.rst b/docs/source/pyrogram/Types.rst index 47c755f9..bf1bf937 100644 --- a/docs/source/pyrogram/Types.rst +++ b/docs/source/pyrogram/Types.rst @@ -12,6 +12,7 @@ Users & Chats User UserStatus Chat + ChatPreview ChatPhoto ChatMember ChatMembers @@ -82,6 +83,9 @@ Input Media .. autoclass:: Chat :members: +.. autoclass:: ChatPreview + :members: + .. autoclass:: ChatPhoto :members: diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index e3628fe4..47af42c7 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -32,7 +32,7 @@ from .client.types import ( Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, - Poll, PollOption + Poll, PollOption, ChatPreview ) from .client import ( Client, ChatAction, ParseMode, Emoji, diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index f9eb25f3..3d928e87 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -22,6 +22,7 @@ from .get_chat import GetChat from .get_chat_member import GetChatMember from .get_chat_members import GetChatMembers from .get_chat_members_count import GetChatMembersCount +from .get_chat_preview import GetChatPreview from .get_dialogs import GetDialogs from .join_chat import JoinChat from .kick_chat_member import KickChatMember @@ -54,6 +55,7 @@ class Chats( PinChatMessage, UnpinChatMessage, GetDialogs, - GetChatMembersCount + GetChatMembersCount, + GetChatPreview ): pass diff --git a/pyrogram/client/methods/chats/get_chat_preview.py b/pyrogram/client/methods/chats/get_chat_preview.py new file mode 100644 index 00000000..434c385b --- /dev/null +++ b/pyrogram/client/methods/chats/get_chat_preview.py @@ -0,0 +1,63 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class GetChatPreview(BaseClient): + def get_chat_preview(self, + invite_link: str): + """Use this method to get the preview of a chat using the invite link. + + This method only returns a chat preview, if you want to join a chat use :meth:`join_chat` + + Args: + invite_link (``str``): + Unique identifier for the target chat in form of *t.me/joinchat/* links. + + Returns: + Either :obj:`Chat` or :obj:`ChatPreview`, depending on whether you already joined the chat or not. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + ``ValueError`` in case of an invalid invite_link. + """ + match = self.INVITE_LINK_RE.match(invite_link) + + if match: + r = self.send( + functions.messages.CheckChatInvite( + hash=match.group(1) + ) + ) + + if isinstance(r, types.ChatInvite): + return pyrogram.ChatPreview._parse(self, r) + + if isinstance(r, types.ChatInviteAlready): + chat = r.chat + + if isinstance(chat, types.Chat): + return pyrogram.Chat._parse_chat_chat(self, chat) + + if isinstance(chat, types.Channel): + return pyrogram.Chat._parse_channel_chat(self, chat) + else: + raise ValueError("The invite_link is invalid") diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index 24de120f..7b30670f 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -35,5 +35,5 @@ from .messages_and_media import ( ) from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, - Dialog, Dialogs, User, UserStatus + Dialog, Dialogs, User, UserStatus, ChatPreview ) diff --git a/pyrogram/client/types/user_and_chats/__init__.py b/pyrogram/client/types/user_and_chats/__init__.py index 0f814b92..ec082dfe 100644 --- a/pyrogram/client/types/user_and_chats/__init__.py +++ b/pyrogram/client/types/user_and_chats/__init__.py @@ -20,6 +20,7 @@ from .chat import Chat from .chat_member import ChatMember from .chat_members import ChatMembers from .chat_photo import ChatPhoto +from .chat_preview import ChatPreview from .dialog import Dialog from .dialogs import Dialogs from .user import User diff --git a/pyrogram/client/types/user_and_chats/chat_preview.py b/pyrogram/client/types/user_and_chats/chat_preview.py new file mode 100644 index 00000000..45048637 --- /dev/null +++ b/pyrogram/client/types/user_and_chats/chat_preview.py @@ -0,0 +1,78 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import List + +import pyrogram +from pyrogram.api import types +from .chat_photo import ChatPhoto +from ..pyrogram_type import PyrogramType +from ..user_and_chats.user import User + + +class ChatPreview(PyrogramType): + """This object represents a chat preview. + + Args: + title (``str``): + Title of the chat. + + photo (:obj:`ChatPhoto`): + Chat photo. Suitable for downloads only. + + type (``str``): + Type of chat, can be either, "group", "supergroup" or "channel". + + members_count (``int``): + Chat members count. + + members (List of :obj:`User`, *optional*): + Preview of some of the chat members. + """ + + def __init__(self, + *, + client: "pyrogram.client.ext.BaseClient", + title: str, + photo: ChatPhoto, + type: str, + members_count: int, + members: List[User] = None): + super().__init__(client) + + self.title = title + self.photo = photo + self.type = type + self.members_count = members_count + self.members = members + + @staticmethod + def _parse(client, chat_invite: types.ChatInvite) -> "ChatPreview": + return ChatPreview( + title=chat_invite.title, + photo=ChatPhoto._parse(client, chat_invite.photo), + type=("group" if not chat_invite.channel else + "channel" if chat_invite.broadcast else + "supergroup"), + members_count=chat_invite.participants_count, + members=[User._parse(client, user) for user in chat_invite.participants] or None, + client=client + ) + + # TODO: Maybe just merge this object into Chat itself by adding the "members" field. + # get_chat can be used as well instead of get_chat_preview From c907e8146a77a7e04fa74d230def9c162fb83008 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Dec 2018 14:34:47 +0100 Subject: [PATCH 149/326] Allow get_chat to work with chat invite links --- pyrogram/client/methods/chats/get_chat.py | 23 ++++++++++++++++++++++ pyrogram/client/methods/chats/join_chat.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index f5bf19a9..03bf2a08 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -32,13 +32,36 @@ class GetChat(BaseClient): Args: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. + Unique identifier for the target chat in form of a *t.me/joinchat/* link, identifier (int) or username + of the target channel/supergroup (in the format @username). Returns: On success, a :obj:`Chat ` object is returned. Raises: :class:`Error ` in case of a Telegram RPC error. + ``ValueError`` in case the chat invite link refers to a chat you haven't joined yet. """ + match = self.INVITE_LINK_RE.match(str(chat_id)) + + if match: + h = match.group(1) + + r = self.send( + functions.messages.CheckChatInvite( + hash=h + ) + ) + + if isinstance(r, types.ChatInvite): + raise ValueError("You haven't joined \"t.me/joinchat/{}\" yet".format(h)) + + if isinstance(r.chat, types.Chat): + chat_id = -r.chat.id + + if isinstance(r.chat, types.Channel): + chat_id = int("-100" + str(r.chat.id)) + peer = self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py index 2f14c617..2c70a52f 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/client/methods/chats/join_chat.py @@ -27,7 +27,7 @@ class JoinChat(BaseClient): Args: chat_id (``str``): - Unique identifier for the target chat in form of *t.me/joinchat/* links or username of the target + Unique identifier for the target chat in form of a *t.me/joinchat/* link or username of the target channel/supergroup (in the format @username). Raises: From 64ec26850e4b6b86064fbe8eb186f992d00202e4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Dec 2018 15:16:46 +0100 Subject: [PATCH 150/326] Document save_file --- docs/source/pyrogram/Client.rst | 2 +- pyrogram/client/client.py | 51 +++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index 179910a3..5f402c65 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -19,7 +19,7 @@ Utilities remove_handler send resolve_peer - download_media + save_file Decorators ---------- diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 226f783c..673fc33c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1116,9 +1116,9 @@ class Client(Methods, BaseClient): peer_id: Union[int, str]): """Use this method to get the InputPeer of a known peer_id. - This is a utility method intended to be used only when working with Raw Functions (i.e: a Telegram API method - you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an InputPeer - type is required. + This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API + method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an + InputPeer type is required. Args: peer_id (``int`` | ``str``): @@ -1191,6 +1191,51 @@ class Client(Methods, BaseClient): file_part: int = 0, progress: callable = None, progress_args: tuple = ()): + """Use this method to upload a file onto Telegram servers, without actually sending the message to anyone. + + This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API + method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an + InputFile type is required. + + Args: + path (``str``): + The path of the file you want to upload that exists on your local machine. + + file_id (``int``, *optional*): + In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. + + file_part (``int``, *optional*): + In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the uploaded file is returned in form of an InputFile object. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ part_size = 512 * 1024 file_size = os.path.getsize(path) From 74c4e35a4fd47c4ed8f79a3b3c64a9a09e00961c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Dec 2018 15:17:05 +0100 Subject: [PATCH 151/326] Move download_media into messages and media namespace --- docs/source/pyrogram/Client.rst | 1 + pyrogram/client/methods/messages/__init__.py | 4 ++- .../{utilities => messages}/download_media.py | 2 +- pyrogram/client/methods/utilities/__init__.py | 25 ------------------- 4 files changed, 5 insertions(+), 27 deletions(-) rename pyrogram/client/methods/{utilities => messages}/download_media.py (99%) delete mode 100644 pyrogram/client/methods/utilities/__init__.py diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index 5f402c65..d8b04c40 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -65,6 +65,7 @@ Messages send_poll vote_poll retract_vote + download_media Chats ----- diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index 66340283..cfb36fd0 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .delete_messages import DeleteMessages +from .download_media import DownloadMedia from .edit_message_caption import EditMessageCaption from .edit_message_media import EditMessageMedia from .edit_message_reply_markup import EditMessageReplyMarkup @@ -68,6 +69,7 @@ class Messages( SendVoice, SendPoll, VotePoll, - RetractVote + RetractVote, + DownloadMedia ): pass diff --git a/pyrogram/client/methods/utilities/download_media.py b/pyrogram/client/methods/messages/download_media.py similarity index 99% rename from pyrogram/client/methods/utilities/download_media.py rename to pyrogram/client/methods/messages/download_media.py index 33d72f82..1cddfe98 100644 --- a/pyrogram/client/methods/utilities/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -20,7 +20,7 @@ from threading import Event from typing import Union import pyrogram -from ...ext import BaseClient +from pyrogram.client.ext import BaseClient class DownloadMedia(BaseClient): diff --git a/pyrogram/client/methods/utilities/__init__.py b/pyrogram/client/methods/utilities/__init__.py deleted file mode 100644 index f8db23e5..00000000 --- a/pyrogram/client/methods/utilities/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from .download_media import DownloadMedia - - -class Utilities( - DownloadMedia -): - pass From 5dcc19cfb3e0509c5a02ae83df82fc327c86213c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Dec 2018 15:17:05 +0100 Subject: [PATCH 152/326] Move download_media into messages and media namespace --- docs/source/pyrogram/Client.rst | 1 + pyrogram/client/methods/__init__.py | 2 -- pyrogram/client/methods/messages/__init__.py | 4 ++- .../{utilities => messages}/download_media.py | 2 +- pyrogram/client/methods/utilities/__init__.py | 25 ------------------- 5 files changed, 5 insertions(+), 29 deletions(-) rename pyrogram/client/methods/{utilities => messages}/download_media.py (99%) delete mode 100644 pyrogram/client/methods/utilities/__init__.py diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index 5f402c65..d8b04c40 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -65,6 +65,7 @@ Messages send_poll vote_poll retract_vote + download_media Chats ----- diff --git a/pyrogram/client/methods/__init__.py b/pyrogram/client/methods/__init__.py index eba768bb..ea249e3f 100644 --- a/pyrogram/client/methods/__init__.py +++ b/pyrogram/client/methods/__init__.py @@ -23,7 +23,6 @@ from .decorators import Decorators from .messages import Messages from .password import Password from .users import Users -from .utilities import Utilities class Methods( @@ -32,7 +31,6 @@ class Methods( Password, Chats, Users, - Utilities, Messages, Decorators ): diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index 66340283..cfb36fd0 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .delete_messages import DeleteMessages +from .download_media import DownloadMedia from .edit_message_caption import EditMessageCaption from .edit_message_media import EditMessageMedia from .edit_message_reply_markup import EditMessageReplyMarkup @@ -68,6 +69,7 @@ class Messages( SendVoice, SendPoll, VotePoll, - RetractVote + RetractVote, + DownloadMedia ): pass diff --git a/pyrogram/client/methods/utilities/download_media.py b/pyrogram/client/methods/messages/download_media.py similarity index 99% rename from pyrogram/client/methods/utilities/download_media.py rename to pyrogram/client/methods/messages/download_media.py index 33d72f82..1cddfe98 100644 --- a/pyrogram/client/methods/utilities/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -20,7 +20,7 @@ from threading import Event from typing import Union import pyrogram -from ...ext import BaseClient +from pyrogram.client.ext import BaseClient class DownloadMedia(BaseClient): diff --git a/pyrogram/client/methods/utilities/__init__.py b/pyrogram/client/methods/utilities/__init__.py deleted file mode 100644 index f8db23e5..00000000 --- a/pyrogram/client/methods/utilities/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from .download_media import DownloadMedia - - -class Utilities( - DownloadMedia -): - pass From 8f38ce3b9ea61b5d26be9965df209ee8a2f8ab5e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Dec 2018 16:19:30 +0100 Subject: [PATCH 153/326] Remove Raw Functions from Usage doc page --- docs/source/start/Usage.rst | 80 ++----------------------------------- 1 file changed, 4 insertions(+), 76 deletions(-) diff --git a/docs/source/start/Usage.rst b/docs/source/start/Usage.rst index 6c1697b9..8bb197ab 100644 --- a/docs/source/start/Usage.rst +++ b/docs/source/start/Usage.rst @@ -1,8 +1,7 @@ Usage ===== -Having your `project set up`_ and your account authorized_, it's time to play with the API. -In this section, you'll be shown two ways of communicating with Telegram using Pyrogram. Let's start! +Having your `project set up`_ and your account authorized_, it's time to play with the API. Let's start! High-level API -------------- @@ -43,79 +42,8 @@ exceptions in your code: More examples on `GitHub `_. -Raw Functions -------------- - -If you can't find a high-level method for your needs or if you want complete, low-level access to the whole Telegram API, -you have to use the raw :mod:`functions ` and :mod:`types ` exposed by the -``pyrogram.api`` package and call any Telegram API method you wish using the :meth:`send() ` -method provided by the Client class. - -.. hint:: - - Every high-level method mentioned in the section above is built on top of these raw functions. - - Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already - re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API. - - If you think a raw function should be wrapped and added as a high-level method, feel free to ask in our Community_! - -Examples (more on `GitHub `_): - -- Update first name, last name and bio: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.api import functions - - with Client("my_account") as app: - app.send( - functions.account.UpdateProfile( - first_name="Dan", last_name="Tès", - about="Bio written from Pyrogram" - ) - ) - -- Share your Last Seen time only with your contacts: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.api import functions, types - - with Client("my_account") as app: - app.send( - functions.account.SetPrivacy( - key=types.InputPrivacyKeyStatusTimestamp(), - rules=[types.InputPrivacyValueAllowContacts()] - ) - ) - -- Invite users to your channel/supergroup: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.api import functions, types - - with Client("my_account") as app: - app.send( - functions.channels.InviteToChannel( - channel=app.resolve_peer(123456789), # ID or Username - users=[ # The users you want to invite - app.resolve_peer(23456789), # By ID - app.resolve_peer("username"), # By username - app.resolve_peer("393281234567"), # By phone number - ] - ) - ) - -.. _methods: ../pyrogram/Client.html#messages -.. _plenty of them: ../pyrogram/Client.html#messages -.. _types: ../pyrogram/Types.html -.. _Raw Functions: Usage.html#using-raw-functions -.. _Community: https://t.me/PyrogramChat .. _project set up: Setup.html .. _authorized: Setup.html#user-authorization -.. _Telegram Bot API: https://core.telegram.org/bots/api \ No newline at end of file +.. _Telegram Bot API: https://core.telegram.org/bots/api +.. _methods: ../pyrogram/Client.html#messages +.. _types: ../pyrogram/Types.html \ No newline at end of file From 46aeb569d5e82eb51c567bbbcea1f8cb409db5f4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 28 Dec 2018 16:19:42 +0100 Subject: [PATCH 154/326] Add AdvancedUsage.rst --- docs/source/index.rst | 1 + docs/source/resources/AdvancedUsage.rst | 121 ++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 docs/source/resources/AdvancedUsage.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 8234374c..085b38fb 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -93,6 +93,7 @@ To get started, press the Next button. resources/BotsInteraction resources/ErrorHandling resources/TestServers + resources/AdvancedUsage resources/Changelog .. toctree:: diff --git a/docs/source/resources/AdvancedUsage.rst b/docs/source/resources/AdvancedUsage.rst new file mode 100644 index 00000000..02395f26 --- /dev/null +++ b/docs/source/resources/AdvancedUsage.rst @@ -0,0 +1,121 @@ +Advanced Usage +============== + +In this section, you'll be shown the alternative way of communicating with Telegram using Pyrogram: the main Telegram +API with its raw functions and types. + +Telegram Raw API +---------------- + +If you can't find a high-level method for your needs or if you want complete, low-level access to the whole +Telegram API, you have to use the raw :mod:`functions ` and :mod:`types ` +exposed by the ``pyrogram.api`` package and call any Telegram API method you wish using the +:meth:`send() ` method provided by the Client class. + +.. hint:: + + Every available high-level method mentioned in the previous page is built on top of these raw functions. + + Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already + re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API. + + If you think a raw function should be wrapped and added as a high-level method, feel free to ask in our Community_! + +Caveats +------- + +As hinted before, raw functions and types can be confusing, mainly because people don't realize they must accept +*exactly* the right values, but also because most of them don't have enough Python experience to fully grasp how things +work. + +This section will therefore explain some pitfalls to take into consideration when working with the raw API. + +Chat IDs +^^^^^^^^ + +The way Telegram works makes it impossible to directly send a message to a user or a chat by using their IDs only. +Instead, a pair of ``id`` and ``access_hash`` wrapped in a so called ``InputPeer`` is always needed. + +There are three different InputPeer types, one for each kind of Telegram entity. +Whenever an InputPeer is needed you must pass one of these: + + - `InputPeerUser `_ - Users + - `InputPeerChat `_ - Basic Chats + - `InputPeerChannel `_ - Either Channels or Supergroups + +But you don't necessarily have to manually instantiate each object because, luckily for you, Pyrogram already provides +:meth:`resolve_peer() ` as a convenience utility method that returns the correct InputPeer +by accepting a peer ID only. + +Another thing to take into consideration about chat IDs is the way they are represented: they are all integers and +all positive within their respective raw types. + +Things are different when working with Pyrogram's API because having them in the same space can theoretically lead to +collisions, and that's why Pyrogram (as well as the official Bot API) uses a slightly different representation for each +kind of ID. + +For example, given the ID *123456789*, here's how Pyrogram can tell entities apart: + + - ``+ID`` - User: *123456789* + - ``-ID`` - Chat: *-123456789* + - ``-100ID`` - Channel (and Supergroup): *-100123456789* + +So, every time you take a raw ID, make sure to translate it into the correct ID when you want to use it with an +high-level method. + +Examples +-------- + +- Update first name, last name and bio: + + .. code-block:: python + + from pyrogram import Client + from pyrogram.api import functions + + with Client("my_account") as app: + app.send( + functions.account.UpdateProfile( + first_name="Dan", last_name="Tès", + about="Bio written from Pyrogram" + ) + ) + +- Share your Last Seen time only with your contacts: + + .. code-block:: python + + from pyrogram import Client + from pyrogram.api import functions, types + + with Client("my_account") as app: + app.send( + functions.account.SetPrivacy( + key=types.InputPrivacyKeyStatusTimestamp(), + rules=[types.InputPrivacyValueAllowContacts()] + ) + ) + +- Invite users to your channel/supergroup: + + .. code-block:: python + + from pyrogram import Client + from pyrogram.api import functions, types + + with Client("my_account") as app: + app.send( + functions.channels.InviteToChannel( + channel=app.resolve_peer(123456789), # ID or Username + users=[ # The users you want to invite + app.resolve_peer(23456789), # By ID + app.resolve_peer("username"), # By username + app.resolve_peer("393281234567"), # By phone number + ] + ) + ) + + +.. _plenty of them: ../pyrogram/Client.html#messages +.. _Raw Functions: Usage.html#using-raw-functions +.. _Community: https://t.me/PyrogramChat \ No newline at end of file From d00fde74de9908a295c1457bf4606b2bb8f864d5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Dec 2018 13:37:13 +0100 Subject: [PATCH 155/326] Use clearer error pages' titles --- docs/source/errors/BadRequest.rst | 4 ++-- docs/source/errors/Flood.rst | 4 ++-- docs/source/errors/Forbidden.rst | 4 ++-- docs/source/errors/InternalServerError.rst | 4 ++-- docs/source/errors/NotAcceptable.rst | 4 ++-- docs/source/errors/SeeOther.rst | 4 ++-- docs/source/errors/Unauthorized.rst | 4 ++-- docs/source/errors/UnknownError.rst | 4 ++-- docs/source/resources/ErrorHandling.rst | 14 +++++++------- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/source/errors/BadRequest.rst b/docs/source/errors/BadRequest.rst index 7ea6cb4b..c51a7d54 100644 --- a/docs/source/errors/BadRequest.rst +++ b/docs/source/errors/BadRequest.rst @@ -1,5 +1,5 @@ -Bad Request -=========== +400 - Bad Request +================= .. module:: pyrogram.api.errors.BadRequest diff --git a/docs/source/errors/Flood.rst b/docs/source/errors/Flood.rst index a83a216c..72f819ea 100644 --- a/docs/source/errors/Flood.rst +++ b/docs/source/errors/Flood.rst @@ -1,5 +1,5 @@ -Flood -===== +420 - Flood +=========== .. module:: pyrogram.api.errors.Flood diff --git a/docs/source/errors/Forbidden.rst b/docs/source/errors/Forbidden.rst index fd2e08ce..aaaceaff 100644 --- a/docs/source/errors/Forbidden.rst +++ b/docs/source/errors/Forbidden.rst @@ -1,5 +1,5 @@ -Forbidden -========= +403 - Forbidden +=============== .. module:: pyrogram.api.errors.Forbidden diff --git a/docs/source/errors/InternalServerError.rst b/docs/source/errors/InternalServerError.rst index 310c5cfc..5e506fc9 100644 --- a/docs/source/errors/InternalServerError.rst +++ b/docs/source/errors/InternalServerError.rst @@ -1,5 +1,5 @@ -Internal Server Error -===================== +500 - Internal Server Error +=========================== .. module:: pyrogram.api.errors.InternalServerError diff --git a/docs/source/errors/NotAcceptable.rst b/docs/source/errors/NotAcceptable.rst index 66ebaf72..e9301396 100644 --- a/docs/source/errors/NotAcceptable.rst +++ b/docs/source/errors/NotAcceptable.rst @@ -1,5 +1,5 @@ -Not Acceptable -============== +406 - Not Acceptable +==================== .. module:: pyrogram.api.errors.NotAcceptable diff --git a/docs/source/errors/SeeOther.rst b/docs/source/errors/SeeOther.rst index 49411379..a916e779 100644 --- a/docs/source/errors/SeeOther.rst +++ b/docs/source/errors/SeeOther.rst @@ -1,5 +1,5 @@ -See Other -========= +303 - See Other +=============== .. module:: pyrogram.api.errors.SeeOther diff --git a/docs/source/errors/Unauthorized.rst b/docs/source/errors/Unauthorized.rst index b3926132..6de3ff67 100644 --- a/docs/source/errors/Unauthorized.rst +++ b/docs/source/errors/Unauthorized.rst @@ -1,5 +1,5 @@ -Unauthorized -============ +401 - Unauthorized +================== .. module:: pyrogram.api.errors.Unauthorized diff --git a/docs/source/errors/UnknownError.rst b/docs/source/errors/UnknownError.rst index 030f3e02..767f19c3 100644 --- a/docs/source/errors/UnknownError.rst +++ b/docs/source/errors/UnknownError.rst @@ -1,5 +1,5 @@ -Unknown Error -============= +520 - Unknown Error +=================== .. module:: pyrogram.api.errors.UnknownError diff --git a/docs/source/resources/ErrorHandling.rst b/docs/source/resources/ErrorHandling.rst index f2a77b7a..1f08c165 100644 --- a/docs/source/resources/ErrorHandling.rst +++ b/docs/source/resources/ErrorHandling.rst @@ -6,13 +6,13 @@ Errors are inevitable when working with the API, and they must be correctly hand There are many errors that Telegram could return, but they all fall in one of these categories (which are in turn children of the :obj:`pyrogram.Error` superclass) -- :obj:`303 See Other ` -- :obj:`400 Bad Request ` -- :obj:`401 Unauthorized ` -- :obj:`403 Forbidden ` -- :obj:`406 Not Acceptable ` -- :obj:`420 Flood ` -- :obj:`500 Internal Server Error ` +- :obj:`303 - See Other ` +- :obj:`400 - Bad Request ` +- :obj:`401 - Unauthorized ` +- :obj:`403 - Forbidden ` +- :obj:`406 - Not Acceptable ` +- :obj:`420 - Flood ` +- :obj:`500 - Internal Server Error ` As stated above, there are really many (too many) errors, and in case Pyrogram does not know anything yet about a specific one, it raises a special :obj:`520 Unknown Error ` exception and logs it From a02846e90066053bd7dec6e563c26dbf0a60a475 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Dec 2018 07:53:14 +0100 Subject: [PATCH 156/326] Move get_chat_preview in a better place --- docs/source/pyrogram/Client.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index d8b04c40..14796ef2 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -75,7 +75,6 @@ Chats join_chat leave_chat - get_chat_preview kick_chat_member unban_chat_member restrict_chat_member @@ -88,6 +87,7 @@ Chats pin_chat_message unpin_chat_message get_chat + get_chat_preview get_chat_member get_chat_members get_chat_members_count From 490b8bf5791ef889a8b5886e0de4a56e98e89312 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 31 Dec 2018 12:02:15 +0100 Subject: [PATCH 157/326] Fix get_message getting infinite replies --- pyrogram/client/methods/messages/get_messages.py | 2 +- pyrogram/client/types/messages_and_media/messages.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index da2f6578..d0c064f9 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -78,6 +78,6 @@ class GetMessages(BaseClient): else: rpc = functions.messages.GetMessages(id=ids) - messages = pyrogram.Messages._parse(self, self.send(rpc)) + messages = pyrogram.Messages._parse(self, self.send(rpc), replies) return messages if is_iterable else messages.messages[0] diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index cd401c1b..51d3fbdf 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -47,13 +47,13 @@ class Messages(PyrogramType): self.messages = messages @staticmethod - def _parse(client, messages: types.messages.Messages) -> "Messages": + def _parse(client, messages: types.messages.Messages, replies: int = 1) -> "Messages": users = {i.id: i for i in messages.users} chats = {i.id: i for i in messages.chats} return Messages( total_count=getattr(messages, "count", len(messages.messages)), - messages=[Message._parse(client, message, users, chats) for message in messages.messages], + messages=[Message._parse(client, message, users, chats, replies) for message in messages.messages], client=client ) From 024e3852f7737a39c8139fb60dd6ce2a8dae3249 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 31 Dec 2018 13:22:14 +0100 Subject: [PATCH 158/326] Don't rely on git for GitHub installations Instead, let people install from zips GitHub already provides. --- docs/source/start/Installation.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/source/start/Installation.rst b/docs/source/start/Installation.rst index f139ddb2..14205d9d 100644 --- a/docs/source/start/Installation.rst +++ b/docs/source/start/Installation.rst @@ -18,23 +18,23 @@ Install Pyrogram .. code-block:: text - $ pip3 install --upgrade pyrogram + $ pip3 install -U pyrogram - or, with TgCrypto_ as extra requirement (recommended): .. code-block:: text - $ pip3 install --upgrade pyrogram[fast] + $ pip3 install -U pyrogram[fast] Bleeding Edge ------------- If you want the latest development version of Pyrogram, you can install it straight from the develop_ -branch using this command (you might need to install **git** first): +branch using this command (note "develop.zip" in the link): .. code-block:: text - $ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git + $ pip3 install -U https://github.com/pyrogram/pyrogram/archive/develop.zip Asynchronous ------------ @@ -43,11 +43,11 @@ Pyrogram heavily depends on IO-bound network code (it's a cloud-based messaging where asyncio shines the most by providing extra performance while running on a single OS-level thread only. **A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5+ required). -Use this command to install: +Use this command to install (note "asyncio.zip" in the link): .. code-block:: text - $ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git@asyncio + $ pip3 install -U https://github.com/pyrogram/pyrogram/archive/asyncio.zip Pyrogram API remains the same and features are kept up to date from the non-async, default develop branch, but you From e5be7fcc90c0bdd09308e04effc318489b79e21c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 31 Dec 2018 13:22:27 +0100 Subject: [PATCH 159/326] Update to v0.10.1 --- docs/source/start/Installation.rst | 2 +- pyrogram/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/start/Installation.rst b/docs/source/start/Installation.rst index 14205d9d..7619701d 100644 --- a/docs/source/start/Installation.rst +++ b/docs/source/start/Installation.rst @@ -82,7 +82,7 @@ If no error shows up you are good to go. >>> import pyrogram >>> pyrogram.__version__ - '0.9.4' + '0.10.1' .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto .. _develop: http://github.com/pyrogram/pyrogram diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 47af42c7..f30c2c2f 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès Date: Mon, 31 Dec 2018 17:13:50 +0100 Subject: [PATCH 160/326] Fix style parsers randomly returning "unsorted" dicts. This is due to Python <3.6 having "unsorted" dicts. Dicts are inherently unsorted, but starting from Python 3.6 they keep the order in which the keys are inserted (useful for unpacking) --- pyrogram/client/style/html.py | 10 ++++++---- pyrogram/client/style/markdown.py | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 63a80733..ec839fcd 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import re +from collections import OrderedDict from pyrogram.api.types import ( MessageEntityBold as Bold, @@ -75,10 +76,11 @@ class HTML: text = text.replace(match.group(), body) offset += len(style) * 2 + 5 + (len(url) + 8 if url else 0) - return dict( - message=utils.remove_surrogates(text), - entities=entities - ) + # TODO: OrderedDict to be removed in Python3.6 + return OrderedDict([ + ("message", utils.remove_surrogates(text)), + ("entities", entities) + ]) def unparse(self, message: str, entities: list): message = utils.add_surrogates(message).strip() diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index 7a67d0ae..f1ea23fc 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import re +from collections import OrderedDict from pyrogram.api.types import ( MessageEntityBold as Bold, @@ -97,10 +98,11 @@ class Markdown: entities.append(entity) message = message.replace(match.group(), body) - return dict( - message=utils.remove_surrogates(message), - entities=entities - ) + # TODO: OrderedDict to be removed in Python3.6 + return OrderedDict([ + ("message", utils.remove_surrogates(message)), + ("entities", entities) + ]) def unparse(self, message: str, entities: list): message = utils.add_surrogates(message).strip() From 4f04d4aee8e6b9c7f1202aa0a86f1c85965be767 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 31 Dec 2018 17:17:58 +0100 Subject: [PATCH 161/326] Update to v0.10.2 --- docs/source/start/Installation.rst | 2 +- pyrogram/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/start/Installation.rst b/docs/source/start/Installation.rst index 7619701d..3769b9f9 100644 --- a/docs/source/start/Installation.rst +++ b/docs/source/start/Installation.rst @@ -82,7 +82,7 @@ If no error shows up you are good to go. >>> import pyrogram >>> pyrogram.__version__ - '0.10.1' + '0.10.2' .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto .. _develop: http://github.com/pyrogram/pyrogram diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index f30c2c2f..bb155d90 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès Date: Tue, 1 Jan 2019 12:36:16 +0100 Subject: [PATCH 162/326] Update copyright year --- NOTICE | 2 +- compiler/__init__.py | 2 +- compiler/api/__init__.py | 2 +- compiler/api/compiler.py | 2 +- compiler/docs/__init__.py | 2 +- compiler/docs/compiler.py | 2 +- compiler/error/__init__.py | 2 +- compiler/error/compiler.py | 2 +- pyrogram/__init__.py | 2 +- pyrogram/api/__init__.py | 2 +- pyrogram/api/core/__init__.py | 2 +- pyrogram/api/core/future_salt.py | 2 +- pyrogram/api/core/future_salts.py | 2 +- pyrogram/api/core/gzip_packed.py | 2 +- pyrogram/api/core/message.py | 2 +- pyrogram/api/core/msg_container.py | 2 +- pyrogram/api/core/object.py | 2 +- pyrogram/api/core/primitives/__init__.py | 2 +- pyrogram/api/core/primitives/bool.py | 2 +- pyrogram/api/core/primitives/bytes.py | 2 +- pyrogram/api/core/primitives/double.py | 2 +- pyrogram/api/core/primitives/int.py | 2 +- pyrogram/api/core/primitives/null.py | 2 +- pyrogram/api/core/primitives/string.py | 2 +- pyrogram/api/core/primitives/vector.py | 2 +- pyrogram/api/errors/__init__.py | 2 +- pyrogram/api/errors/error.py | 2 +- pyrogram/client/__init__.py | 2 +- pyrogram/client/client.py | 2 +- pyrogram/client/dispatcher/__init__.py | 2 +- pyrogram/client/dispatcher/dispatcher.py | 2 +- pyrogram/client/ext/__init__.py | 2 +- pyrogram/client/ext/base_client.py | 2 +- pyrogram/client/ext/chat_action.py | 2 +- pyrogram/client/ext/emoji.py | 2 +- pyrogram/client/ext/parse_mode.py | 2 +- pyrogram/client/ext/syncer.py | 2 +- pyrogram/client/ext/utils.py | 2 +- pyrogram/client/filters/__init__.py | 2 +- pyrogram/client/filters/filter.py | 2 +- pyrogram/client/filters/filters.py | 2 +- pyrogram/client/handlers/__init__.py | 2 +- pyrogram/client/handlers/callback_query_handler.py | 2 +- pyrogram/client/handlers/deleted_messages_handler.py | 2 +- pyrogram/client/handlers/disconnect_handler.py | 2 +- pyrogram/client/handlers/handler.py | 2 +- pyrogram/client/handlers/message_handler.py | 2 +- pyrogram/client/handlers/raw_update_handler.py | 2 +- pyrogram/client/handlers/user_status_handler.py | 2 +- pyrogram/client/methods/__init__.py | 2 +- pyrogram/client/methods/bots/__init__.py | 2 +- pyrogram/client/methods/bots/answer_callback_query.py | 2 +- pyrogram/client/methods/bots/get_inline_bot_results.py | 2 +- pyrogram/client/methods/bots/request_callback_answer.py | 2 +- pyrogram/client/methods/bots/send_inline_bot_result.py | 2 +- pyrogram/client/methods/chats/__init__.py | 2 +- pyrogram/client/methods/chats/delete_chat_photo.py | 2 +- pyrogram/client/methods/chats/export_chat_invite_link.py | 2 +- pyrogram/client/methods/chats/get_chat.py | 2 +- pyrogram/client/methods/chats/get_chat_member.py | 2 +- pyrogram/client/methods/chats/get_chat_members.py | 2 +- pyrogram/client/methods/chats/get_chat_members_count.py | 2 +- pyrogram/client/methods/chats/get_dialogs.py | 2 +- pyrogram/client/methods/chats/join_chat.py | 2 +- pyrogram/client/methods/chats/kick_chat_member.py | 2 +- pyrogram/client/methods/chats/leave_chat.py | 2 +- pyrogram/client/methods/chats/pin_chat_message.py | 2 +- pyrogram/client/methods/chats/promote_chat_member.py | 2 +- pyrogram/client/methods/chats/restrict_chat_member.py | 2 +- pyrogram/client/methods/chats/set_chat_description.py | 2 +- pyrogram/client/methods/chats/set_chat_photo.py | 2 +- pyrogram/client/methods/chats/set_chat_title.py | 2 +- pyrogram/client/methods/chats/unban_chat_member.py | 2 +- pyrogram/client/methods/chats/unpin_chat_message.py | 2 +- pyrogram/client/methods/contacts/__init__.py | 2 +- pyrogram/client/methods/contacts/add_contacts.py | 2 +- pyrogram/client/methods/contacts/delete_contacts.py | 2 +- pyrogram/client/methods/contacts/get_contacts.py | 2 +- pyrogram/client/methods/decorators/__init__.py | 2 +- pyrogram/client/methods/decorators/on_callback_query.py | 2 +- pyrogram/client/methods/decorators/on_deleted_messages.py | 2 +- pyrogram/client/methods/decorators/on_disconnect.py | 2 +- pyrogram/client/methods/decorators/on_message.py | 2 +- pyrogram/client/methods/decorators/on_raw_update.py | 2 +- pyrogram/client/methods/decorators/on_user_status.py | 2 +- pyrogram/client/methods/messages/__init__.py | 2 +- pyrogram/client/methods/messages/delete_messages.py | 2 +- pyrogram/client/methods/messages/download_media.py | 2 +- pyrogram/client/methods/messages/edit_message_caption.py | 2 +- pyrogram/client/methods/messages/edit_message_media.py | 2 +- pyrogram/client/methods/messages/edit_message_reply_markup.py | 2 +- pyrogram/client/methods/messages/edit_message_text.py | 2 +- pyrogram/client/methods/messages/forward_messages.py | 2 +- pyrogram/client/methods/messages/get_history.py | 2 +- pyrogram/client/methods/messages/get_messages.py | 2 +- pyrogram/client/methods/messages/retract_vote.py | 2 +- pyrogram/client/methods/messages/send_animation.py | 2 +- pyrogram/client/methods/messages/send_audio.py | 2 +- pyrogram/client/methods/messages/send_chat_action.py | 2 +- pyrogram/client/methods/messages/send_contact.py | 2 +- pyrogram/client/methods/messages/send_document.py | 2 +- pyrogram/client/methods/messages/send_location.py | 2 +- pyrogram/client/methods/messages/send_media_group.py | 2 +- pyrogram/client/methods/messages/send_message.py | 2 +- pyrogram/client/methods/messages/send_photo.py | 2 +- pyrogram/client/methods/messages/send_poll.py | 2 +- pyrogram/client/methods/messages/send_sticker.py | 2 +- pyrogram/client/methods/messages/send_venue.py | 2 +- pyrogram/client/methods/messages/send_video.py | 2 +- pyrogram/client/methods/messages/send_video_note.py | 2 +- pyrogram/client/methods/messages/send_voice.py | 2 +- pyrogram/client/methods/messages/vote_poll.py | 2 +- pyrogram/client/methods/password/__init__.py | 2 +- pyrogram/client/methods/password/change_cloud_password.py | 2 +- pyrogram/client/methods/password/enable_cloud_password.py | 2 +- pyrogram/client/methods/password/remove_cloud_password.py | 2 +- pyrogram/client/methods/password/utils.py | 2 +- pyrogram/client/methods/users/__init__.py | 2 +- pyrogram/client/methods/users/delete_user_profile_photos.py | 2 +- pyrogram/client/methods/users/get_me.py | 2 +- pyrogram/client/methods/users/get_user_profile_photos.py | 2 +- pyrogram/client/methods/users/get_users.py | 2 +- pyrogram/client/methods/users/set_user_profile_photo.py | 2 +- pyrogram/client/style/__init__.py | 2 +- pyrogram/client/style/html.py | 2 +- pyrogram/client/style/markdown.py | 2 +- pyrogram/client/style/utils.py | 2 +- pyrogram/client/types/__init__.py | 2 +- pyrogram/client/types/bots/__init__.py | 2 +- pyrogram/client/types/bots/callback_query.py | 2 +- pyrogram/client/types/bots/force_reply.py | 2 +- pyrogram/client/types/bots/inline_keyboard_button.py | 2 +- pyrogram/client/types/bots/inline_keyboard_markup.py | 2 +- pyrogram/client/types/bots/keyboard_button.py | 2 +- pyrogram/client/types/bots/reply_keyboard_markup.py | 2 +- pyrogram/client/types/bots/reply_keyboard_remove.py | 2 +- pyrogram/client/types/input_media/__init__.py | 2 +- pyrogram/client/types/input_media/input_media.py | 2 +- pyrogram/client/types/input_media/input_media_animation.py | 2 +- pyrogram/client/types/input_media/input_media_audio.py | 2 +- pyrogram/client/types/input_media/input_media_document.py | 2 +- pyrogram/client/types/input_media/input_media_photo.py | 2 +- pyrogram/client/types/input_media/input_media_video.py | 2 +- pyrogram/client/types/input_media/input_phone_contact.py | 2 +- pyrogram/client/types/messages_and_media/__init__.py | 2 +- pyrogram/client/types/messages_and_media/animation.py | 2 +- pyrogram/client/types/messages_and_media/audio.py | 2 +- pyrogram/client/types/messages_and_media/contact.py | 2 +- pyrogram/client/types/messages_and_media/document.py | 2 +- pyrogram/client/types/messages_and_media/location.py | 2 +- pyrogram/client/types/messages_and_media/message.py | 2 +- pyrogram/client/types/messages_and_media/message_entity.py | 2 +- pyrogram/client/types/messages_and_media/messages.py | 2 +- pyrogram/client/types/messages_and_media/photo.py | 2 +- pyrogram/client/types/messages_and_media/photo_size.py | 2 +- pyrogram/client/types/messages_and_media/poll.py | 2 +- pyrogram/client/types/messages_and_media/poll_option.py | 2 +- pyrogram/client/types/messages_and_media/sticker.py | 2 +- pyrogram/client/types/messages_and_media/user_profile_photos.py | 2 +- pyrogram/client/types/messages_and_media/venue.py | 2 +- pyrogram/client/types/messages_and_media/video.py | 2 +- pyrogram/client/types/messages_and_media/video_note.py | 2 +- pyrogram/client/types/messages_and_media/voice.py | 2 +- pyrogram/client/types/pyrogram_type.py | 2 +- pyrogram/client/types/user_and_chats/__init__.py | 2 +- pyrogram/client/types/user_and_chats/chat.py | 2 +- pyrogram/client/types/user_and_chats/chat_member.py | 2 +- pyrogram/client/types/user_and_chats/chat_members.py | 2 +- pyrogram/client/types/user_and_chats/chat_photo.py | 2 +- pyrogram/client/types/user_and_chats/dialog.py | 2 +- pyrogram/client/types/user_and_chats/dialogs.py | 2 +- pyrogram/client/types/user_and_chats/user.py | 2 +- pyrogram/client/types/user_and_chats/user_status.py | 2 +- pyrogram/connection/__init__.py | 2 +- pyrogram/connection/connection.py | 2 +- pyrogram/connection/transport/__init__.py | 2 +- pyrogram/connection/transport/tcp/__init__.py | 2 +- pyrogram/connection/transport/tcp/tcp.py | 2 +- pyrogram/connection/transport/tcp/tcp_abridged.py | 2 +- pyrogram/connection/transport/tcp/tcp_abridged_o.py | 2 +- pyrogram/connection/transport/tcp/tcp_full.py | 2 +- pyrogram/connection/transport/tcp/tcp_intermediate.py | 2 +- pyrogram/connection/transport/tcp/tcp_intermediate_o.py | 2 +- pyrogram/crypto/__init__.py | 2 +- pyrogram/crypto/aes.py | 2 +- pyrogram/crypto/kdf.py | 2 +- pyrogram/crypto/prime.py | 2 +- pyrogram/crypto/rsa.py | 2 +- pyrogram/session/__init__.py | 2 +- pyrogram/session/auth.py | 2 +- pyrogram/session/internals/__init__.py | 2 +- pyrogram/session/internals/data_center.py | 2 +- pyrogram/session/internals/msg_factory.py | 2 +- pyrogram/session/internals/msg_id.py | 2 +- pyrogram/session/internals/seq_no.py | 2 +- pyrogram/session/session.py | 2 +- setup.py | 2 +- 197 files changed, 197 insertions(+), 197 deletions(-) diff --git a/NOTICE b/NOTICE index a7e2b00a..dd0fdf54 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Pyrogram - Telegram MTProto API Client Library for Python -Copyright (C) 2017-2018 Dan Tès +Copyright (C) 2017-2019 Dan Tès This file is part of Pyrogram. diff --git a/compiler/__init__.py b/compiler/__init__.py index eddf3281..f3769dd4 100644 --- a/compiler/__init__.py +++ b/compiler/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/compiler/api/__init__.py b/compiler/api/__init__.py index eddf3281..f3769dd4 100644 --- a/compiler/api/__init__.py +++ b/compiler/api/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index d31353c6..57e4d22b 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/compiler/docs/__init__.py b/compiler/docs/__init__.py index eddf3281..f3769dd4 100644 --- a/compiler/docs/__init__.py +++ b/compiler/docs/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 57f4827f..6ea2240d 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/compiler/error/__init__.py b/compiler/error/__init__.py index eddf3281..f3769dd4 100644 --- a/compiler/error/__init__.py +++ b/compiler/error/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/compiler/error/compiler.py b/compiler/error/compiler.py index aaefde9f..b86222e7 100644 --- a/compiler/error/compiler.py +++ b/compiler/error/compiler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index bb155d90..97dd4688 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/__init__.py b/pyrogram/api/__init__.py index 71e28d6b..e57f0661 100644 --- a/pyrogram/api/__init__.py +++ b/pyrogram/api/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/__init__.py b/pyrogram/api/core/__init__.py index 2bf38b8d..daba6b7c 100644 --- a/pyrogram/api/core/__init__.py +++ b/pyrogram/api/core/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/future_salt.py b/pyrogram/api/core/future_salt.py index 99f11678..bce01dc8 100644 --- a/pyrogram/api/core/future_salt.py +++ b/pyrogram/api/core/future_salt.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/future_salts.py b/pyrogram/api/core/future_salts.py index dc579035..bddfdb47 100644 --- a/pyrogram/api/core/future_salts.py +++ b/pyrogram/api/core/future_salts.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/gzip_packed.py b/pyrogram/api/core/gzip_packed.py index 93c3b377..8b26be9d 100644 --- a/pyrogram/api/core/gzip_packed.py +++ b/pyrogram/api/core/gzip_packed.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/message.py b/pyrogram/api/core/message.py index 1a48489a..35459ef8 100644 --- a/pyrogram/api/core/message.py +++ b/pyrogram/api/core/message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/msg_container.py b/pyrogram/api/core/msg_container.py index 7728bd37..4373498d 100644 --- a/pyrogram/api/core/msg_container.py +++ b/pyrogram/api/core/msg_container.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/object.py b/pyrogram/api/core/object.py index a1e20726..1a3cb388 100644 --- a/pyrogram/api/core/object.py +++ b/pyrogram/api/core/object.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/primitives/__init__.py b/pyrogram/api/core/primitives/__init__.py index 63d7ef97..8885878b 100644 --- a/pyrogram/api/core/primitives/__init__.py +++ b/pyrogram/api/core/primitives/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/primitives/bool.py b/pyrogram/api/core/primitives/bool.py index 9641e865..117ee7a4 100644 --- a/pyrogram/api/core/primitives/bool.py +++ b/pyrogram/api/core/primitives/bool.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/primitives/bytes.py b/pyrogram/api/core/primitives/bytes.py index d161cc9c..8030b598 100644 --- a/pyrogram/api/core/primitives/bytes.py +++ b/pyrogram/api/core/primitives/bytes.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/primitives/double.py b/pyrogram/api/core/primitives/double.py index 94d7e3b9..3dcaa461 100644 --- a/pyrogram/api/core/primitives/double.py +++ b/pyrogram/api/core/primitives/double.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/primitives/int.py b/pyrogram/api/core/primitives/int.py index 4b9aded8..7833a610 100644 --- a/pyrogram/api/core/primitives/int.py +++ b/pyrogram/api/core/primitives/int.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/primitives/null.py b/pyrogram/api/core/primitives/null.py index 7a26b112..d2d3b1c0 100644 --- a/pyrogram/api/core/primitives/null.py +++ b/pyrogram/api/core/primitives/null.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/primitives/string.py b/pyrogram/api/core/primitives/string.py index 3584d1b9..a271695a 100644 --- a/pyrogram/api/core/primitives/string.py +++ b/pyrogram/api/core/primitives/string.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/core/primitives/vector.py b/pyrogram/api/core/primitives/vector.py index e2642e0f..cd24ec35 100644 --- a/pyrogram/api/core/primitives/vector.py +++ b/pyrogram/api/core/primitives/vector.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/errors/__init__.py b/pyrogram/api/errors/__init__.py index 0ed04e02..ca65619c 100644 --- a/pyrogram/api/errors/__init__.py +++ b/pyrogram/api/errors/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/api/errors/error.py b/pyrogram/api/errors/error.py index 397af546..5f92a369 100644 --- a/pyrogram/api/errors/error.py +++ b/pyrogram/api/errors/error.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/__init__.py b/pyrogram/client/__init__.py index 00b9905a..2719d82e 100644 --- a/pyrogram/client/__init__.py +++ b/pyrogram/client/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 673fc33c..3dcaaf12 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/dispatcher/__init__.py b/pyrogram/client/dispatcher/__init__.py index c0cb368a..e2e67b70 100644 --- a/pyrogram/client/dispatcher/__init__.py +++ b/pyrogram/client/dispatcher/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 5b30daf9..d167cf13 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/ext/__init__.py b/pyrogram/client/ext/__init__.py index 38eaf089..ce80958a 100644 --- a/pyrogram/client/ext/__init__.py +++ b/pyrogram/client/ext/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 32046115..24a621de 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/ext/chat_action.py b/pyrogram/client/ext/chat_action.py index 96c5164e..c0ee0585 100644 --- a/pyrogram/client/ext/chat_action.py +++ b/pyrogram/client/ext/chat_action.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/ext/emoji.py b/pyrogram/client/ext/emoji.py index b2dd99fd..20d154f4 100644 --- a/pyrogram/client/ext/emoji.py +++ b/pyrogram/client/ext/emoji.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/ext/parse_mode.py b/pyrogram/client/ext/parse_mode.py index 817bccb0..46ed97e3 100644 --- a/pyrogram/client/ext/parse_mode.py +++ b/pyrogram/client/ext/parse_mode.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index 125c5ce0..e169d2a3 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index 3e67e3cd..087773f4 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/filters/__init__.py b/pyrogram/client/filters/__init__.py index 88ae14e3..318d8645 100644 --- a/pyrogram/client/filters/__init__.py +++ b/pyrogram/client/filters/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/filters/filter.py b/pyrogram/client/filters/filter.py index feec51df..f8bf5e3e 100644 --- a/pyrogram/client/filters/filter.py +++ b/pyrogram/client/filters/filter.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 1347f993..c0ebfdcf 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/handlers/__init__.py b/pyrogram/client/handlers/__init__.py index ff1ead7a..499ed007 100644 --- a/pyrogram/client/handlers/__init__.py +++ b/pyrogram/client/handlers/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/handlers/callback_query_handler.py b/pyrogram/client/handlers/callback_query_handler.py index 5d09f7d9..e991c019 100644 --- a/pyrogram/client/handlers/callback_query_handler.py +++ b/pyrogram/client/handlers/callback_query_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/handlers/deleted_messages_handler.py b/pyrogram/client/handlers/deleted_messages_handler.py index 8f5ef448..c084f353 100644 --- a/pyrogram/client/handlers/deleted_messages_handler.py +++ b/pyrogram/client/handlers/deleted_messages_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/handlers/disconnect_handler.py b/pyrogram/client/handlers/disconnect_handler.py index a8b800a8..1e88a7ee 100644 --- a/pyrogram/client/handlers/disconnect_handler.py +++ b/pyrogram/client/handlers/disconnect_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/handlers/handler.py b/pyrogram/client/handlers/handler.py index 0e46a205..9fd0e206 100644 --- a/pyrogram/client/handlers/handler.py +++ b/pyrogram/client/handlers/handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/handlers/message_handler.py b/pyrogram/client/handlers/message_handler.py index e4c3d13f..8a52a0b5 100644 --- a/pyrogram/client/handlers/message_handler.py +++ b/pyrogram/client/handlers/message_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/handlers/raw_update_handler.py b/pyrogram/client/handlers/raw_update_handler.py index 5a8913b6..3a5dea50 100644 --- a/pyrogram/client/handlers/raw_update_handler.py +++ b/pyrogram/client/handlers/raw_update_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/handlers/user_status_handler.py b/pyrogram/client/handlers/user_status_handler.py index 2442d7eb..643a064d 100644 --- a/pyrogram/client/handlers/user_status_handler.py +++ b/pyrogram/client/handlers/user_status_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/__init__.py b/pyrogram/client/methods/__init__.py index ea249e3f..625ec09a 100644 --- a/pyrogram/client/methods/__init__.py +++ b/pyrogram/client/methods/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/bots/__init__.py b/pyrogram/client/methods/bots/__init__.py index 2d89c2fb..b0430efe 100644 --- a/pyrogram/client/methods/bots/__init__.py +++ b/pyrogram/client/methods/bots/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/bots/answer_callback_query.py b/pyrogram/client/methods/bots/answer_callback_query.py index 00e437b1..5e8468e5 100644 --- a/pyrogram/client/methods/bots/answer_callback_query.py +++ b/pyrogram/client/methods/bots/answer_callback_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/bots/get_inline_bot_results.py b/pyrogram/client/methods/bots/get_inline_bot_results.py index a5b9ae9f..e76a0d1d 100644 --- a/pyrogram/client/methods/bots/get_inline_bot_results.py +++ b/pyrogram/client/methods/bots/get_inline_bot_results.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py index 0cb6a1dd..74e2d26d 100644 --- a/pyrogram/client/methods/bots/request_callback_answer.py +++ b/pyrogram/client/methods/bots/request_callback_answer.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/bots/send_inline_bot_result.py b/pyrogram/client/methods/bots/send_inline_bot_result.py index 8c6a38b5..66caab16 100644 --- a/pyrogram/client/methods/bots/send_inline_bot_result.py +++ b/pyrogram/client/methods/bots/send_inline_bot_result.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 3d928e87..745678cc 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/client/methods/chats/delete_chat_photo.py index a33cae05..0164ff43 100644 --- a/pyrogram/client/methods/chats/delete_chat_photo.py +++ b/pyrogram/client/methods/chats/delete_chat_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index f458e91e..9ff8c9c0 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 03bf2a08..188c39e5 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index 9f13eac2..4b67dd5e 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index 8173423b..1d99ec4b 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py index 37eb69cb..9360b64f 100644 --- a/pyrogram/client/methods/chats/get_chat_members_count.py +++ b/pyrogram/client/methods/chats/get_chat_members_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 757cf5bc..f80518b0 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py index 2c70a52f..f5b632fc 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/client/methods/chats/join_chat.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index 4ea4d6bb..b02e02cc 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/leave_chat.py b/pyrogram/client/methods/chats/leave_chat.py index e0ac3bb6..5b765ac0 100644 --- a/pyrogram/client/methods/chats/leave_chat.py +++ b/pyrogram/client/methods/chats/leave_chat.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py index 66d5497d..bb70f9e8 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py index 18453b58..9f2260f0 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/client/methods/chats/promote_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index f9670250..dbe0054b 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/client/methods/chats/set_chat_description.py index 7cf91da5..e6c5bba1 100644 --- a/pyrogram/client/methods/chats/set_chat_description.py +++ b/pyrogram/client/methods/chats/set_chat_description.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 7d276648..cc5c776a 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/set_chat_title.py b/pyrogram/client/methods/chats/set_chat_title.py index af2b6e77..fff330ee 100644 --- a/pyrogram/client/methods/chats/set_chat_title.py +++ b/pyrogram/client/methods/chats/set_chat_title.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/unban_chat_member.py b/pyrogram/client/methods/chats/unban_chat_member.py index 3513f38d..da706a1f 100644 --- a/pyrogram/client/methods/chats/unban_chat_member.py +++ b/pyrogram/client/methods/chats/unban_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/client/methods/chats/unpin_chat_message.py index 9aa56a7c..4355010d 100644 --- a/pyrogram/client/methods/chats/unpin_chat_message.py +++ b/pyrogram/client/methods/chats/unpin_chat_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/contacts/__init__.py b/pyrogram/client/methods/contacts/__init__.py index e0fe52fb..ab9ae6ef 100644 --- a/pyrogram/client/methods/contacts/__init__.py +++ b/pyrogram/client/methods/contacts/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/contacts/add_contacts.py b/pyrogram/client/methods/contacts/add_contacts.py index a5f06050..48575a78 100644 --- a/pyrogram/client/methods/contacts/add_contacts.py +++ b/pyrogram/client/methods/contacts/add_contacts.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/contacts/delete_contacts.py b/pyrogram/client/methods/contacts/delete_contacts.py index 2c18c6d8..dba2581d 100644 --- a/pyrogram/client/methods/contacts/delete_contacts.py +++ b/pyrogram/client/methods/contacts/delete_contacts.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py index c8f903ce..7e90476c 100644 --- a/pyrogram/client/methods/contacts/get_contacts.py +++ b/pyrogram/client/methods/contacts/get_contacts.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/decorators/__init__.py b/pyrogram/client/methods/decorators/__init__.py index 6cf9940a..07f84b54 100644 --- a/pyrogram/client/methods/decorators/__init__.py +++ b/pyrogram/client/methods/decorators/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py index 8c152706..bf0a823a 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/client/methods/decorators/on_callback_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index 84abc92e..1b1a602b 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/decorators/on_disconnect.py b/pyrogram/client/methods/decorators/on_disconnect.py index 56796bf5..4657af3b 100644 --- a/pyrogram/client/methods/decorators/on_disconnect.py +++ b/pyrogram/client/methods/decorators/on_disconnect.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index a252c6ba..c2f35a19 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py index ce2584d5..2deedb00 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py index c552e706..6198c9cd 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/client/methods/decorators/on_user_status.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index cfb36fd0..237b6493 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/client/methods/messages/delete_messages.py index 030a1663..06acbb1f 100644 --- a/pyrogram/client/methods/messages/delete_messages.py +++ b/pyrogram/client/methods/messages/delete_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 1cddfe98..cfdcfce7 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index 7709a3d8..fe704e41 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 02b26ea6..bc423a01 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py index f46dd2f4..f8b5c0a5 100644 --- a/pyrogram/client/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index 173caa93..1d2a065b 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index f8379c0c..72e43e08 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index ce12e9b0..6d97578f 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index d0c064f9..f5497629 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/retract_vote.py b/pyrogram/client/methods/messages/retract_vote.py index e0852355..7893f768 100644 --- a/pyrogram/client/methods/messages/retract_vote.py +++ b/pyrogram/client/methods/messages/retract_vote.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 2ebfd87f..08b69c17 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 47db3cbe..da08f5f7 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_chat_action.py b/pyrogram/client/methods/messages/send_chat_action.py index 61e9994f..479e7abb 100644 --- a/pyrogram/client/methods/messages/send_chat_action.py +++ b/pyrogram/client/methods/messages/send_chat_action.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index 5312f0ac..96b23a9b 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index f42c83d7..393b094c 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py index dbd04e7a..b899a938 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/client/methods/messages/send_location.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 0b18cac3..e1731e0b 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 982ce1c7..c25ec570 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 26f6948a..a5f858b2 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index 33f44767..8e938a1a 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 936d2e54..aea8b057 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/client/methods/messages/send_venue.py index fca07898..163be38e 100644 --- a/pyrogram/client/methods/messages/send_venue.py +++ b/pyrogram/client/methods/messages/send_venue.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 15b98732..42f0d314 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 32202a0b..5333311c 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 014e8e9f..74a30ccf 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py index 3404a7bd..9e400e62 100644 --- a/pyrogram/client/methods/messages/vote_poll.py +++ b/pyrogram/client/methods/messages/vote_poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/password/__init__.py b/pyrogram/client/methods/password/__init__.py index 07d8dd7d..8a29b0a4 100644 --- a/pyrogram/client/methods/password/__init__.py +++ b/pyrogram/client/methods/password/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index 73775056..3504dab1 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index c8fd4dcb..980f50fd 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py index a86ed5f8..6817ab12 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/password/utils.py b/pyrogram/client/methods/password/utils.py index 01c3fe49..24c4dd28 100644 --- a/pyrogram/client/methods/password/utils.py +++ b/pyrogram/client/methods/password/utils.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index 11f51d19..db5e5869 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/users/delete_user_profile_photos.py b/pyrogram/client/methods/users/delete_user_profile_photos.py index 91c59247..025a5e95 100644 --- a/pyrogram/client/methods/users/delete_user_profile_photos.py +++ b/pyrogram/client/methods/users/delete_user_profile_photos.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index 211e2ee9..beea1243 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/users/get_user_profile_photos.py b/pyrogram/client/methods/users/get_user_profile_photos.py index 2108f2d6..2129dba3 100644 --- a/pyrogram/client/methods/users/get_user_profile_photos.py +++ b/pyrogram/client/methods/users/get_user_profile_photos.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py index d7b80579..6c340ffa 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/client/methods/users/get_users.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/methods/users/set_user_profile_photo.py b/pyrogram/client/methods/users/set_user_profile_photo.py index f2a2c302..0863c695 100644 --- a/pyrogram/client/methods/users/set_user_profile_photo.py +++ b/pyrogram/client/methods/users/set_user_profile_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/style/__init__.py b/pyrogram/client/style/__init__.py index e60b4da1..768cee7b 100644 --- a/pyrogram/client/style/__init__.py +++ b/pyrogram/client/style/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index ec839fcd..0014c4d9 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index f1ea23fc..7bd96ed6 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/style/utils.py b/pyrogram/client/style/utils.py index 6a0a9667..b001f1cf 100644 --- a/pyrogram/client/style/utils.py +++ b/pyrogram/client/style/utils.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index 7b30670f..e4ed11ba 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/bots/__init__.py b/pyrogram/client/types/bots/__init__.py index 9f7cc7e6..28cfe724 100644 --- a/pyrogram/client/types/bots/__init__.py +++ b/pyrogram/client/types/bots/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index c3c23333..456e705f 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/bots/force_reply.py b/pyrogram/client/types/bots/force_reply.py index 0c15b92a..2e5c2f42 100644 --- a/pyrogram/client/types/bots/force_reply.py +++ b/pyrogram/client/types/bots/force_reply.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/bots/inline_keyboard_button.py b/pyrogram/client/types/bots/inline_keyboard_button.py index 655e78f4..f9c1267a 100644 --- a/pyrogram/client/types/bots/inline_keyboard_button.py +++ b/pyrogram/client/types/bots/inline_keyboard_button.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/bots/inline_keyboard_markup.py b/pyrogram/client/types/bots/inline_keyboard_markup.py index d958f306..f18bd605 100644 --- a/pyrogram/client/types/bots/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots/inline_keyboard_markup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/bots/keyboard_button.py b/pyrogram/client/types/bots/keyboard_button.py index 4b025375..e93eccb3 100644 --- a/pyrogram/client/types/bots/keyboard_button.py +++ b/pyrogram/client/types/bots/keyboard_button.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/bots/reply_keyboard_markup.py b/pyrogram/client/types/bots/reply_keyboard_markup.py index 85f38b10..7840dbe5 100644 --- a/pyrogram/client/types/bots/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots/reply_keyboard_markup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/bots/reply_keyboard_remove.py b/pyrogram/client/types/bots/reply_keyboard_remove.py index def9917c..5b67fbb4 100644 --- a/pyrogram/client/types/bots/reply_keyboard_remove.py +++ b/pyrogram/client/types/bots/reply_keyboard_remove.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/input_media/__init__.py b/pyrogram/client/types/input_media/__init__.py index 5f5be30d..e2e0b0f6 100644 --- a/pyrogram/client/types/input_media/__init__.py +++ b/pyrogram/client/types/input_media/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/input_media/input_media.py b/pyrogram/client/types/input_media/input_media.py index 7a380f89..f55d8aa0 100644 --- a/pyrogram/client/types/input_media/input_media.py +++ b/pyrogram/client/types/input_media/input_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index 0e7b2433..af0c2b2a 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index 455c2292..5034ed06 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index 08fcae5b..25e17d0f 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py index c8cdccb8..5917fbf0 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index 955cf633..6fa7936e 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/input_media/input_phone_contact.py b/pyrogram/client/types/input_media/input_phone_contact.py index eacecaf8..1a89759a 100644 --- a/pyrogram/client/types/input_media/input_phone_contact.py +++ b/pyrogram/client/types/input_media/input_phone_contact.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/client/types/messages_and_media/__init__.py index d402ae48..b6847bdb 100644 --- a/pyrogram/client/types/messages_and_media/__init__.py +++ b/pyrogram/client/types/messages_and_media/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index 6b7f7cf7..d5661ea8 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 148ae805..cfecceae 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index 16dd52bb..51acb59f 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index db41df6c..e84b5149 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index fcdd5e31..64acdbf5 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index baeac31f..95eb4f48 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 7544424c..88beeb2f 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 51d3fbdf..12fdb2e3 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index a52a7aa2..72187761 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index 7a4db1c2..05f00455 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py index ab59b1ad..72735c28 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/client/types/messages_and_media/poll_option.py index 240368fc..227fe74f 100644 --- a/pyrogram/client/types/messages_and_media/poll_option.py +++ b/pyrogram/client/types/messages_and_media/poll_option.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index 8b88e7bf..39b140b6 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index 2bfd29c8..8f8525cc 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index 443f479a..91b3455a 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 2c476b39..553e8171 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index 718432fa..bfb88c8d 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index b9f799b3..e6bba45b 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py index f5502a72..3708cdc5 100644 --- a/pyrogram/client/types/pyrogram_type.py +++ b/pyrogram/client/types/pyrogram_type.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/user_and_chats/__init__.py b/pyrogram/client/types/user_and_chats/__init__.py index ec082dfe..24a9a51b 100644 --- a/pyrogram/client/types/user_and_chats/__init__.py +++ b/pyrogram/client/types/user_and_chats/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 7b4240dd..ec30b866 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index fa43f526..e901e0e1 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index 838517ab..f526725a 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index ad4b3151..848f7250 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index c001708f..b71caba7 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py index 2492d5e2..394ddd28 100644 --- a/pyrogram/client/types/user_and_chats/dialogs.py +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 354e8a09..c6ee03e8 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index 69c1921b..531b37f1 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/__init__.py b/pyrogram/connection/__init__.py index 4d1ff9f1..731e7456 100644 --- a/pyrogram/connection/__init__.py +++ b/pyrogram/connection/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 41f64a40..0c325fae 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/__init__.py b/pyrogram/connection/transport/__init__.py index 1790ee60..80e0d848 100644 --- a/pyrogram/connection/transport/__init__.py +++ b/pyrogram/connection/transport/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/__init__.py b/pyrogram/connection/transport/tcp/__init__.py index ce662e61..6ed12ad8 100644 --- a/pyrogram/connection/transport/tcp/__init__.py +++ b/pyrogram/connection/transport/tcp/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 4d8d4a58..debe52bd 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py index 5566b179..56f8d025 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index 91ee8375..d15d0389 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py index 8704247b..36f14adb 100644 --- a/pyrogram/connection/transport/tcp/tcp_full.py +++ b/pyrogram/connection/transport/tcp/tcp_full.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate.py b/pyrogram/connection/transport/tcp/tcp_intermediate.py index aa198db7..e27455d7 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py index f0598d12..c59deed7 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/crypto/__init__.py b/pyrogram/crypto/__init__.py index 08ed44f0..a0f66b52 100644 --- a/pyrogram/crypto/__init__.py +++ b/pyrogram/crypto/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py index f16688c4..de275bd0 100644 --- a/pyrogram/crypto/aes.py +++ b/pyrogram/crypto/aes.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/crypto/kdf.py b/pyrogram/crypto/kdf.py index a0da2e2c..56e52339 100644 --- a/pyrogram/crypto/kdf.py +++ b/pyrogram/crypto/kdf.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/crypto/prime.py b/pyrogram/crypto/prime.py index 8e9426ca..a1b76e67 100644 --- a/pyrogram/crypto/prime.py +++ b/pyrogram/crypto/prime.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/crypto/rsa.py b/pyrogram/crypto/rsa.py index 10302dab..26a9092b 100644 --- a/pyrogram/crypto/rsa.py +++ b/pyrogram/crypto/rsa.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/session/__init__.py b/pyrogram/session/__init__.py index afebf563..3a3107c0 100644 --- a/pyrogram/session/__init__.py +++ b/pyrogram/session/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 87817da1..05b20eec 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/session/internals/__init__.py b/pyrogram/session/internals/__init__.py index 0d28c8f3..272855a7 100644 --- a/pyrogram/session/internals/__init__.py +++ b/pyrogram/session/internals/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py index d36e0613..fd51932a 100644 --- a/pyrogram/session/internals/data_center.py +++ b/pyrogram/session/internals/data_center.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/session/internals/msg_factory.py b/pyrogram/session/internals/msg_factory.py index 76a35458..7d922ec3 100644 --- a/pyrogram/session/internals/msg_factory.py +++ b/pyrogram/session/internals/msg_factory.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py index 99aa9d14..3826aaa5 100644 --- a/pyrogram/session/internals/msg_id.py +++ b/pyrogram/session/internals/msg_id.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/session/internals/seq_no.py b/pyrogram/session/internals/seq_no.py index bef0d1a3..ebc3efea 100644 --- a/pyrogram/session/internals/seq_no.py +++ b/pyrogram/session/internals/seq_no.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 0d513430..21bb1133 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # diff --git a/setup.py b/setup.py index 8d1af092..cc2a3880 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # From b60853b2e247ea765a500a19351711497c8843f6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 1 Jan 2019 13:12:39 +0100 Subject: [PATCH 163/326] Make typing available for Python <3.5 only --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8f1eea95..227aacf6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ pyaes==1.6.1 pysocks==1.6.8 -typing==3.6.6 \ No newline at end of file +typing==3.6.6; python_version<"3.5" \ No newline at end of file From f440b1f9694b0e67d04565585cda4114a76360bd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 2 Jan 2019 17:27:40 +0100 Subject: [PATCH 164/326] Add missing colon --- pyrogram/client/methods/messages/get_history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 6d97578f..c2f4226b 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -45,7 +45,7 @@ class GetHistory(BaseClient): Limits the number of messages to be retrieved. By default, the first 100 messages are returned. - offset (``int``, *optional*) + offset (``int``, *optional*): Sequential number of the first message to be returned. Defaults to 0 (most recent message). Negative values are also accepted and become useful in case you set offset_id or offset_date. From 1960b00280b137d5341cb7f58dac5fb2e35ef32a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 2 Jan 2019 18:11:22 +0100 Subject: [PATCH 165/326] Add a way to stop iterating through handlers Closes #125 --- docs/source/index.rst | 1 + docs/source/resources/MoreOnUpdates.rst | 148 ++++++++++++++++++ docs/source/resources/UsingFilters.rst | 42 +---- pyrogram/__init__.py | 2 +- pyrogram/client/dispatcher/dispatcher.py | 33 ++-- pyrogram/client/types/__init__.py | 1 + pyrogram/client/types/bots/callback_query.py | 3 +- .../types/messages_and_media/message.py | 3 +- .../types/messages_and_media/messages.py | 3 +- pyrogram/client/types/update.py | 26 +++ .../types/user_and_chats/user_status.py | 3 +- 11 files changed, 206 insertions(+), 59 deletions(-) create mode 100644 docs/source/resources/MoreOnUpdates.rst create mode 100644 pyrogram/client/types/update.py diff --git a/docs/source/index.rst b/docs/source/index.rst index 085b38fb..ca9a38a3 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -84,6 +84,7 @@ To get started, press the Next button. resources/UpdateHandling resources/UsingFilters + resources/MoreOnUpdates resources/SmartPlugins resources/AutoAuthorization resources/CustomizeSessions diff --git a/docs/source/resources/MoreOnUpdates.rst b/docs/source/resources/MoreOnUpdates.rst new file mode 100644 index 00000000..44295f35 --- /dev/null +++ b/docs/source/resources/MoreOnUpdates.rst @@ -0,0 +1,148 @@ +More on Updates +=============== + +Here we'll show some advanced usages when working with updates. + +.. note:: + This page makes use of Handlers and Filters to show you how to handle updates. + Learn more at `Update Handling `_ and `Using Filters `_. + +Handler Groups +-------------- + +If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored. + +In order to process the same update more than once, you can register your handler in a different group. +Groups are identified by a number (number 0 being the default) and are sorted, that is, a lower group number has a +higher priority. + +For example, in: + +.. code-block:: python + + @app.on_message(Filters.text | Filters.sticker) + def text_or_sticker(client, message): + print("Text or Sticker") + + + @app.on_message(Filters.text) + def just_text(client, message): + print("Just Text") + +``just_text`` is never executed because ``text_or_sticker`` already handles texts. To enable it, simply register the +function using a different group: + +.. code-block:: python + + @app.on_message(Filters.text, group=1) + def just_text(client, message): + print("Just Text") + +Or, if you want ``just_text`` to be fired *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``): + +.. code-block:: python + + @app.on_message(Filters.text, group=-1) + def just_text(client, message): + print("Just Text") + +With :meth:`add_handler() ` (without decorators) the same can be achieved with: + +.. code-block:: python + + app.add_handler(MessageHandler(just_text, Filters.text), -1) + +Update propagation +------------------ + +Registering multiple handlers, each in a different group, becomes useful when you want to handle the same update more +than once. Any incoming update will be sequentially processed by all of your registered functions by respecting the +groups priority policy described above. Even in case any handler raises an unhandled exception, Pyrogram will still +continue to propagate the same update to the next groups until all the handlers are done. Example: + +.. code-block:: python + + @app.on_message(Filters.private) + def _(client, message): + print(0) + + + @app.on_message(Filters.private, group=1) + def _(client, message): + print(1 / 0) # Unhandled exception: ZeroDivisionError + + + @app.on_message(Filters.private, group=2) + def _(client, message): + print(2) + +All these handlers will handle the same kind of messages, that are, messages sent or received in private chats. +The output for each incoming update will therefore be: + +.. code-block:: text + + 0 + ZeroDivisionError: division by zero + 2 + +Stop Propagation +^^^^^^^^^^^^^^^^ + +In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following: + +- Call the update's bound-method ``.stop_propagation()`` (preferred way). +- Manually ``raise StopPropagation`` error (more suitable for raw updates only). + +.. note:: + + Note that ``.stop_propagation()`` is just an elegant and intuitive way to raise a ``StopPropagation`` error; + this means that any code coming *after* calling it won't be executed as your function just raised a custom exception + to signal the dispatcher not to propagate the update anymore. + +Example with ``stop_propagation()``: + +.. code-block:: python + + @app.on_message(Filters.private) + def _(client, message): + print(0) + + + @app.on_message(Filters.private, group=1) + def _(client, message): + print(1) + message.stop_propagation() + + + @app.on_message(Filters.private, group=2) + def _(client, message): + print(2) + +Example with ``raise StopPropagation``: + +.. code-block:: python + + from pyrogram import StopPropagation + + @app.on_message(Filters.private) + def _(client, message): + print(0) + + + @app.on_message(Filters.private, group=1) + def _(client, message): + print(1) + raise StopPropagation + + + @app.on_message(Filters.private, group=2) + def _(client, message): + print(2) + +The handler in group number 2 will never be executed because the propagation was stopped before. The output of both +examples will be: + +.. code-block:: text + + 0 + 1 diff --git a/docs/source/resources/UsingFilters.rst b/docs/source/resources/UsingFilters.rst index d70005a5..3fe87b8a 100644 --- a/docs/source/resources/UsingFilters.rst +++ b/docs/source/resources/UsingFilters.rst @@ -5,7 +5,8 @@ For a finer grained control over what kind of messages will be allowed or not in :class:`Filters `. .. note:: - This section makes use of Handlers to handle updates. Learn more at `Update Handling `_. + This page makes use of Handlers to show you how to handle updates. + Learn more at `Update Handling `_. - This example will show you how to **only** handle messages containing an :obj:`Audio ` object and ignore any other message: @@ -99,45 +100,6 @@ More handlers using different filters can also live together. def from_pyrogramchat(client, message): print("New message in @PyrogramChat") -Handler Groups --------------- - -If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored. - -In order to process the same message more than once, you can register your handler in a different group. -Groups are identified by a number (number 0 being the default) and are sorted. This means that a lower group number has -a higher priority. - -For example, in: - -.. code-block:: python - - @app.on_message(Filters.text | Filters.sticker) - def text_or_sticker(client, message): - print("Text or Sticker") - - - @app.on_message(Filters.text) - def just_text(client, message): - print("Just Text") - -``just_text`` is never executed because ``text_or_sticker`` already handles texts. To enable it, simply register the -function using a different group: - -.. code-block:: python - - @app.on_message(Filters.text, group=1) - def just_text(client, message): - print("Just Text") - -or, if you want ``just_text`` to be fired *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``): - -.. code-block:: python - - @app.on_message(Filters.text, group=-1) - def just_text(client, message): - print("Just Text") - Custom Filters -------------- diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 97dd4688..bb67afce 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -32,7 +32,7 @@ from .client.types import ( Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, - Poll, PollOption, ChatPreview + Poll, PollOption, ChatPreview, StopPropagation ) from .client import ( Client, ChatAction, ParseMode, Emoji, diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index d167cf13..47999bc6 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -134,24 +134,29 @@ class Dispatcher: parsed_update, handler_type = parser(update, users, chats) for group in self.groups.values(): - for handler in group: - args = None + try: + for handler in group: + args = None - if isinstance(handler, RawUpdateHandler): - args = (update, users, chats) - elif isinstance(handler, handler_type): - if handler.check(parsed_update): - args = (parsed_update,) + if isinstance(handler, RawUpdateHandler): + args = (update, users, chats) + elif isinstance(handler, handler_type): + if handler.check(parsed_update): + args = (parsed_update,) - if args is None: - continue + if args is None: + continue + + try: + handler.callback(self.client, *args) + except StopIteration: + raise + except Exception as e: + log.error(e, exc_info=True) - try: - handler.callback(self.client, *args) - except Exception as e: - log.error(e, exc_info=True) - finally: break + except StopIteration: + break except Exception as e: log.error(e, exc_info=True) diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index e4ed11ba..0f4628ce 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -37,3 +37,4 @@ from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, Dialog, Dialogs, User, UserStatus, ChatPreview ) +from .update import StopPropagation diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index 456e705f..62f651d0 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -22,10 +22,11 @@ from struct import pack import pyrogram from pyrogram.api import types from ..pyrogram_type import PyrogramType +from ..update import Update from ..user_and_chats import User -class CallbackQuery(PyrogramType): +class CallbackQuery(PyrogramType, Update): """This object represents an incoming callback query from a callback button in an inline keyboard. If the button that originated the query was attached to a message sent by the bot, the field message will be present. If the button was attached to a message sent via the bot (in inline mode), diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 95eb4f48..830a24de 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -26,11 +26,12 @@ from .location import Location from .message_entity import MessageEntity from ..messages_and_media.photo import Photo from ..pyrogram_type import PyrogramType +from ..update import Update from ..user_and_chats.chat import Chat from ..user_and_chats.user import User -class Message(PyrogramType): +class Message(PyrogramType, Update): """This object represents a message. Args: diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 12fdb2e3..67dc2367 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -22,10 +22,11 @@ import pyrogram from pyrogram.api import types from .message import Message from ..pyrogram_type import PyrogramType +from ..update import Update from ..user_and_chats import Chat -class Messages(PyrogramType): +class Messages(PyrogramType, Update): """This object represents a chat's messages. Args: diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py new file mode 100644 index 00000000..80c233c0 --- /dev/null +++ b/pyrogram/client/types/update.py @@ -0,0 +1,26 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + + +class StopPropagation(StopIteration): + pass + + +class Update: + def stop_propagation(self): + raise StopPropagation diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index 531b37f1..8c936f8e 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -20,9 +20,10 @@ import pyrogram from pyrogram.api import types from ..pyrogram_type import PyrogramType +from ..update import Update -class UserStatus(PyrogramType): +class UserStatus(PyrogramType, Update): """This object represents a User status (Last Seen privacy). .. note:: From 12e070f6b1cd3ce29835a944278af05d08acb92f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 2 Jan 2019 18:19:37 +0100 Subject: [PATCH 166/326] Tell readthedocs to use Python 3.6 because 3.5.2 is bugged https://github.com/rtfd/readthedocs.org/issues/5051 To be removed once readthedocs images use an updated Python interpreter. --- readthedocs.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 readthedocs.yml diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 00000000..9b172987 --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,6 @@ +build: + image: latest + +python: + version: 3.6 + setup_py_install: true \ No newline at end of file From 1f8f313398ed591911497484fde45cdc1e8e70ce Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 2 Jan 2019 19:13:01 +0100 Subject: [PATCH 167/326] Fix broken ChatMember parser working on already parsed Users --- pyrogram/client/types/user_and_chats/chat_members.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index f526725a..88219514 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -59,8 +59,7 @@ class ChatMembers(PyrogramType): total_count = len(members) for member in members: - user = User._parse(client, users[member.user_id]) - chat_members.append(ChatMember._parse(client, member, user)) + chat_members.append(ChatMember._parse(client, member, users[member.user_id])) return ChatMembers( total_count=total_count, From 372bbabe285c7a03209523c31fa971f285e7b66f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 2 Jan 2019 22:39:16 +0100 Subject: [PATCH 168/326] Make invite links of public channels work with get_chat --- pyrogram/client/ext/base_client.py | 3 +++ pyrogram/client/methods/chats/get_chat.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 24a621de..7aee5302 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -103,6 +103,9 @@ class BaseClient: def resolve_peer(self, peer_id: int or str): pass + def fetch_peers(self, entities): + pass + def add_handler(self, handler, group: int = 0) -> tuple: pass diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 188c39e5..d30fd9bb 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -56,6 +56,8 @@ class GetChat(BaseClient): if isinstance(r, types.ChatInvite): raise ValueError("You haven't joined \"t.me/joinchat/{}\" yet".format(h)) + self.fetch_peers([r.chat]) + if isinstance(r.chat, types.Chat): chat_id = -r.chat.id From 4493f781389df58dd046f8b73812cbacf33383f6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 3 Jan 2019 08:59:39 +0100 Subject: [PATCH 169/326] Update to v0.10.3 --- docs/source/start/Installation.rst | 2 +- pyrogram/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/start/Installation.rst b/docs/source/start/Installation.rst index 3769b9f9..78adf6ae 100644 --- a/docs/source/start/Installation.rst +++ b/docs/source/start/Installation.rst @@ -82,7 +82,7 @@ If no error shows up you are good to go. >>> import pyrogram >>> pyrogram.__version__ - '0.10.2' + '0.10.3' .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto .. _develop: http://github.com/pyrogram/pyrogram diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index bb67afce..abeb8b43 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès Date: Thu, 3 Jan 2019 11:13:24 +0100 Subject: [PATCH 170/326] Add no_updates parameter in Client Useful to completely disable incoming updates for batch programs --- pyrogram/client/client.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 3dcaaf12..daef9d34 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -151,6 +151,12 @@ class Client(Methods, BaseClient): Define a custom directory for your plugins. The plugins directory is the location in your filesystem where Pyrogram will automatically load your update handlers. Defaults to None (plugins disabled). + + no_updates (``bool``, *optional*): + Pass True to completely disable incoming updates for the current session. + When updates are disabled your client can't receive any new message. + Useful for batch programs that don't need to deal with updates. + Defaults to False (updates enabled and always received). """ def __init__(self, @@ -173,7 +179,8 @@ class Client(Methods, BaseClient): workers: int = BaseClient.WORKERS, workdir: str = BaseClient.WORKDIR, config_file: str = BaseClient.CONFIG_FILE, - plugins_dir: str = None): + plugins_dir: str = None, + no_updates: bool = None): super().__init__() self.session_name = session_name @@ -197,6 +204,7 @@ class Client(Methods, BaseClient): self.workdir = workdir self.config_file = config_file self.plugins_dir = plugins_dir + self.no_updates = no_updates self.dispatcher = Dispatcher(self, workers) @@ -943,6 +951,9 @@ class Client(Methods, BaseClient): if not self.is_started: raise ConnectionError("Client has not been started") + if self.no_updates: + data = functions.InvokeWithoutUpdates(data) + r = self.session.send(data, retries, timeout) self.fetch_peers(getattr(r, "users", [])) From 7c008ca4e353a648de1ebd4ac3bc241ff589c5b7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 3 Jan 2019 11:42:15 +0100 Subject: [PATCH 171/326] Add a bunch of takeout related errors --- compiler/error/source/400_BAD_REQUEST.tsv | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index c0a5da73..1837d8df 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -80,4 +80,6 @@ USER_ADMIN_INVALID The action requires admin privileges INPUT_USER_DEACTIVATED The target user has been deactivated PASSWORD_RECOVERY_NA The password recovery e-mail is not available PASSWORD_EMPTY The password entered is empty -PHONE_NUMBER_FLOOD This number has tried to login too many times \ No newline at end of file +PHONE_NUMBER_FLOOD This number has tried to login too many times +TAKEOUT_INVALID The takeout id is invalid +TAKEOUT_REQUIRED The method must be invoked inside a takeout session \ No newline at end of file From 4f6990d735d63404ff7c5297e9c7b12d2f76599b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 3 Jan 2019 12:20:42 +0100 Subject: [PATCH 172/326] Add takeout parameter in Client This lets the client use a takeout session instead of a normal one. Takeout sessions are useful for exporting Telegram data. Methods invoked inside a takeout session are less prone to throw FloodWait exceptions. --- pyrogram/client/client.py | 22 +++++++++++++++++++++- pyrogram/client/ext/base_client.py | 2 ++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index daef9d34..bd72c582 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -157,6 +157,13 @@ class Client(Methods, BaseClient): When updates are disabled your client can't receive any new message. Useful for batch programs that don't need to deal with updates. Defaults to False (updates enabled and always received). + + takeout (``bool``, *optional*): + Pass True to let the client use a takeout session instead of a normal one, implies no_updates. + Useful for exporting your Telegram data. Methods invoked inside a takeout session (such as get_history, + download_media, ...) are less prone to throw FloodWait exceptions. + Only available for users, bots will ignore this parameter. + Defaults to False (normal session). """ def __init__(self, @@ -180,7 +187,8 @@ class Client(Methods, BaseClient): workdir: str = BaseClient.WORKDIR, config_file: str = BaseClient.CONFIG_FILE, plugins_dir: str = None, - no_updates: bool = None): + no_updates: bool = None, + takeout: bool = None): super().__init__() self.session_name = session_name @@ -205,6 +213,7 @@ class Client(Methods, BaseClient): self.config_file = config_file self.plugins_dir = plugins_dir self.no_updates = no_updates + self.takeout = takeout self.dispatcher = Dispatcher(self, workers) @@ -261,6 +270,10 @@ class Client(Methods, BaseClient): self.save_session() if self.bot_token is None: + if self.takeout: + self.takeout_id = self.send(functions.account.InitTakeoutSession()).id + log.warning("Takeout session {} initiated".format(self.takeout_id)) + now = time.time() if abs(now - self.date) > Client.OFFLINE_SLEEP: @@ -316,6 +329,10 @@ class Client(Methods, BaseClient): if not self.is_started: raise ConnectionError("Client is already stopped") + if self.takeout_id: + self.send(functions.account.FinishTakeoutSession()) + log.warning("Takeout session {} finished".format(self.takeout_id)) + Syncer.remove(self) self.dispatcher.stop() @@ -954,6 +971,9 @@ class Client(Methods, BaseClient): if self.no_updates: data = functions.InvokeWithoutUpdates(data) + if self.takeout_id: + data = functions.InvokeWithTakeout(self.takeout_id, data) + r = self.session.send(data, retries, timeout) self.fetch_peers(getattr(r, "users", [])) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 7aee5302..cecbf5fb 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -90,6 +90,8 @@ class BaseClient: self.is_started = None self.is_idle = None + self.takeout_id = None + self.updates_queue = Queue() self.updates_workers_list = [] self.download_queue = Queue() From 9256185b5a59fa2f7959a4e5052e3a3b3e2d6f84 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 3 Jan 2019 18:27:47 +0100 Subject: [PATCH 173/326] Update copyright year --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 478683c3..f7d2b44e 100644 --- a/README.rst +++ b/README.rst @@ -61,7 +61,7 @@ and documentation. Any help is appreciated! Copyright & License ------------------- -- Copyright (C) 2017-2018 Dan Tès +- Copyright (C) 2017-2019 Dan Tès - Licensed under the terms of the `GNU Lesser General Public License v3 or later (LGPLv3+)`_ .. _`Telegram`: https://telegram.org/ From d69a93d253c203a43bd0c0da5bc21c08be3a4899 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 3 Jan 2019 20:53:48 +0100 Subject: [PATCH 174/326] Automatically cast message and caption arguments to str --- pyrogram/client/style/html.py | 10 +++++----- pyrogram/client/style/markdown.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 0014c4d9..9a72a565 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -38,12 +38,12 @@ class HTML: def __init__(self, peers_by_id): self.peers_by_id = peers_by_id - def parse(self, text): + def parse(self, message: str): entities = [] - text = utils.add_surrogates(text) + message = utils.add_surrogates(str(message)) offset = 0 - for match in self.HTML_RE.finditer(text): + for match in self.HTML_RE.finditer(message): start = match.start() - offset style, url, body = match.group(1, 3, 4) @@ -73,12 +73,12 @@ class HTML: continue entities.append(entity) - text = text.replace(match.group(), body) + message = message.replace(match.group(), body) offset += len(style) * 2 + 5 + (len(url) + 8 if url else 0) # TODO: OrderedDict to be removed in Python3.6 return OrderedDict([ - ("message", utils.remove_surrogates(text)), + ("message", utils.remove_surrogates(message)), ("entities", entities) ]) diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index 7bd96ed6..05a11a25 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -56,7 +56,7 @@ class Markdown: self.peers_by_id = peers_by_id def parse(self, message: str): - message = utils.add_surrogates(message).strip() + message = utils.add_surrogates(str(message)).strip() entities = [] offset = 0 From 36f987e9791af3843a41ea95982028961dcbc018 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 3 Jan 2019 20:58:38 +0100 Subject: [PATCH 175/326] Add Filters.me Useful to filter messages coming from the current running user. Does the same thing as Filters.user("me") --- pyrogram/client/filters/filters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index c0ebfdcf..ea6d7202 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -61,6 +61,9 @@ class Filters: create = create + me = create("Me", lambda _, m: bool(m.from_user and m.from_user.is_self)) + """Filter messages coming from you yourself""" + bot = create("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot)) """Filter messages coming from bots""" From fe4e8c5a4208d3446d5aa169242a39622918b4c8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 14:36:42 +0100 Subject: [PATCH 176/326] Rename get_history's "reversed" parameter to "reverse" It was colliding with the built-in "reversed" function --- pyrogram/client/methods/messages/get_history.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index c2f4226b..14af0110 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -30,7 +30,7 @@ class GetHistory(BaseClient): offset: int = 0, offset_id: int = 0, offset_date: int = 0, - reversed: bool = False): + reverse: bool = False): """Use this method to retrieve the history of a chat. You can get up to 100 messages at once. @@ -55,7 +55,7 @@ class GetHistory(BaseClient): offset_date (``int``, *optional*): Pass a date in Unix time as offset to retrieve only older messages starting from that date. - reversed (``bool``, *optional*): + reverse (``bool``, *optional*): Pass True to retrieve the messages in reversed order (from older to most recent). Returns: @@ -72,7 +72,7 @@ class GetHistory(BaseClient): peer=self.resolve_peer(chat_id), offset_id=offset_id, offset_date=offset_date, - add_offset=offset - (limit if reversed else 0), + add_offset=offset - (limit if reverse else 0), limit=limit, max_id=0, min_id=0, @@ -81,7 +81,7 @@ class GetHistory(BaseClient): ) ) - if reversed: + if reverse: messages.messages.reverse() return messages From fe89974523c8ae8d604f96b6c53671fd7bc2ef37 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 14:37:26 +0100 Subject: [PATCH 177/326] Add get_history signature to BaseClient Also make other method parameters generic --- pyrogram/client/ext/base_client.py | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index cecbf5fb..93d4e57e 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -23,8 +23,6 @@ from threading import Lock from pyrogram import __version__ from ..style import Markdown, HTML -from ...api.core import Object -from ...session import Session from ...session.internals import MsgId @@ -99,33 +97,23 @@ class BaseClient: self.disconnect_handler = None - def send(self, data: Object, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT): + def send(self, *args, **kwargs): pass - def resolve_peer(self, peer_id: int or str): + def resolve_peer(self, *args, **kwargs): pass - def fetch_peers(self, entities): + def fetch_peers(self, *args, **kwargs): pass - def add_handler(self, handler, group: int = 0) -> tuple: + def add_handler(self, *args, **kwargs): pass - def save_file( - self, - path: str, - file_id: int = None, - file_part: int = 0, - progress: callable = None, - progress_args: tuple = () - ): + def save_file(self, *args, **kwargs): pass - def get_messages( - self, - chat_id: int or str, - message_ids: int or list = None, - reply_to_message_ids: int or list = None, - replies: int = 1 - ): + def get_messages(self, *args, **kwargs): + pass + + def get_history(self, *args, **kwargs): pass From 7e3513f8ee0b94643c750dae4b127c6cebbaab7e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 14:38:08 +0100 Subject: [PATCH 178/326] Wait in case of flood errors in get_messages --- pyrogram/client/methods/messages/get_messages.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index f5497629..f24e996f 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -16,12 +16,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import logging +import time from typing import Union, Iterable import pyrogram from pyrogram.api import functions, types +from pyrogram.api.errors import FloodWait from ...ext import BaseClient +log = logging.getLogger(__name__) + class GetMessages(BaseClient): def get_messages(self, @@ -78,6 +83,15 @@ class GetMessages(BaseClient): else: rpc = functions.messages.GetMessages(id=ids) - messages = pyrogram.Messages._parse(self, self.send(rpc), replies) + while True: + try: + r = self.send(rpc) + except FloodWait as e: + log.warning("Sleeping for {}s".format(e.x)) + time.sleep(e.x) + else: + break + + messages = pyrogram.Messages._parse(self, r, replies=replies) return messages if is_iterable else messages.messages[0] From 04542dbddf5ac4179ea3e8a950f062afaec95b4e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 14:42:39 +0100 Subject: [PATCH 179/326] Make parsing multiple Messages more efficient This is achieved by not calling get_messages for each single reply. Instead, all the available replies are retrieved in one request only. --- .../types/messages_and_media/messages.py | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 67dc2367..d89f0bad 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -52,9 +52,38 @@ class Messages(PyrogramType, Update): users = {i.id: i for i in messages.users} chats = {i.id: i for i in messages.chats} + total_count = getattr(messages, "count", len(messages.messages)) + + if not messages.messages: + return Messages( + total_count=total_count, + messages=[], + client=client + ) + + parsed_messages = [Message._parse(client, message, users, chats, replies=0) for message in messages.messages] + + if replies: + messages_with_replies = {i.id: getattr(i, "reply_to_msg_id", None) for i in messages.messages} + reply_message_ids = [i[0] for i in filter(lambda x: x[1] is not None, messages_with_replies.items())] + + if reply_message_ids: + reply_messages = client.get_messages( + parsed_messages[0].chat.id, + reply_to_message_ids=reply_message_ids, + replies=0 + ).messages + + for message in parsed_messages: + reply_id = messages_with_replies[message.message_id] + + for reply in reply_messages: + if reply.message_id == reply_id: + message.reply_to_message = reply + return Messages( - total_count=getattr(messages, "count", len(messages.messages)), - messages=[Message._parse(client, message, users, chats, replies) for message in messages.messages], + total_count=total_count, + messages=parsed_messages, client=client ) From fbc18cace0948b48c1569792cf8596d0ea45ec53 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 15:36:43 +0100 Subject: [PATCH 180/326] Update get_history docstrings --- pyrogram/client/methods/messages/get_history.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 14af0110..d45623f4 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -31,9 +31,10 @@ class GetHistory(BaseClient): offset_id: int = 0, offset_date: int = 0, reverse: bool = False): - """Use this method to retrieve the history of a chat. + """Use this method to retrieve a chunk of the history of a chat. You can get up to 100 messages at once. + For a more convenient way of getting a chat history see :meth:`iter_history`. Args: chat_id (``int`` | ``str``): @@ -72,7 +73,7 @@ class GetHistory(BaseClient): peer=self.resolve_peer(chat_id), offset_id=offset_id, offset_date=offset_date, - add_offset=offset - (limit if reverse else 0), + add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0), limit=limit, max_id=0, min_id=0, From 8628d3a56d6fd36929468c13e2c6c73e2a2d89a2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 15:37:08 +0100 Subject: [PATCH 181/326] Add iter_history method For #170 --- docs/source/pyrogram/Client.rst | 1 + pyrogram/client/methods/messages/__init__.py | 4 +- .../client/methods/messages/iter_history.py | 93 +++++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/messages/iter_history.py diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index 14796ef2..78b5a32b 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -62,6 +62,7 @@ Messages delete_messages get_messages get_history + iter_history send_poll vote_poll retract_vote diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index 237b6493..d5d81044 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -25,6 +25,7 @@ from .edit_message_text import EditMessageText from .forward_messages import ForwardMessages from .get_history import GetHistory from .get_messages import GetMessages +from .iter_history import IterHistory from .retract_vote import RetractVote from .send_animation import SendAnimation from .send_audio import SendAudio @@ -70,6 +71,7 @@ class Messages( SendPoll, VotePoll, RetractVote, - DownloadMedia + DownloadMedia, + IterHistory ): pass diff --git a/pyrogram/client/methods/messages/iter_history.py b/pyrogram/client/methods/messages/iter_history.py new file mode 100644 index 00000000..ab587988 --- /dev/null +++ b/pyrogram/client/methods/messages/iter_history.py @@ -0,0 +1,93 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union, Generator + +import pyrogram +from ...ext import BaseClient + + +class IterHistory(BaseClient): + def iter_history(self, + chat_id: Union[int, str], + limit: int = 0, + offset: int = 0, + offset_id: int = 0, + offset_date: int = 0, + reverse: bool = False) -> Generator["pyrogram.Message", None, None]: + """Use this method to iterate through a chat history sequentially. + + This convenience method does the same as repeatedly calling :meth:`get_history` in a loop, thus saving you from + the hassle of setting up boilerplate code. It is useful for getting the whole chat history with a single call. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + limit (``int``, *optional*): + Limits the number of messages to be retrieved. + By default, no limit is applied and all messages are returned. + + offset (``int``, *optional*): + Sequential number of the first message to be returned.. + Negative values are also accepted and become useful in case you set offset_id or offset_date. + + offset_id (``int``, *optional*): + Identifier of the first message to be returned. + + offset_date (``int``, *optional*): + Pass a date in Unix time as offset to retrieve only older messages starting from that date. + + reverse (``bool``, *optional*): + Pass True to retrieve the messages in reversed order (from older to most recent). + + Returns: + A generator yielding :obj:`Message ` objects. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + offset_id = offset_id or (1 if reverse else 0) + current = 0 + total = limit or (1 << 31) - 1 + limit = min(100, total) + + while True: + messages = self.get_history( + chat_id=chat_id, + limit=limit, + offset=offset, + offset_id=offset_id, + offset_date=offset_date, + reverse=reverse + ).messages + + if not messages: + return + + offset_id = messages[-1].message_id + (1 if reverse else 0) + + for message in messages: + yield message + + current += 1 + + if current >= total: + return From 4d1d70082b31c03bae90056cd3df0c8db18a89cb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 16:08:05 +0100 Subject: [PATCH 182/326] Sleep in case of get_dialogs flood waits --- pyrogram/client/methods/chats/get_dialogs.py | 40 +++++++++++++------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index f80518b0..35fc7752 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -16,10 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import logging +import time + import pyrogram from pyrogram.api import functions, types +from pyrogram.api.errors import FloodWait from ...ext import BaseClient +log = logging.getLogger(__name__) + class GetDialogs(BaseClient): def get_dialogs(self, @@ -29,6 +35,7 @@ class GetDialogs(BaseClient): """Use this method to get the user's dialogs You can get up to 100 dialogs at once. + For a more convenient way of getting a user's dialogs see :meth:`iter_dialogs`. Args: offset_date (``int``): @@ -50,18 +57,25 @@ class GetDialogs(BaseClient): :class:`Error ` in case of a Telegram RPC error. """ - if pinned_only: - r = self.send(functions.messages.GetPinnedDialogs()) - else: - r = self.send( - functions.messages.GetDialogs( - offset_date=offset_date, - offset_id=0, - offset_peer=types.InputPeerEmpty(), - limit=limit, - hash=0, - exclude_pinned=True - ) - ) + while True: + try: + if pinned_only: + r = self.send(functions.messages.GetPinnedDialogs()) + else: + r = self.send( + functions.messages.GetDialogs( + offset_date=offset_date, + offset_id=0, + offset_peer=types.InputPeerEmpty(), + limit=limit, + hash=0, + exclude_pinned=True + ) + ) + except FloodWait as e: + log.warning("Sleeping {}s".format(e.x)) + time.sleep(e.x) + else: + break return pyrogram.Dialogs._parse(self, r) From 948f2b44ed69116e2c84b644a9951353d0e54dec Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 16:10:34 +0100 Subject: [PATCH 183/326] Add iter_dialogs method. Reference #170 --- docs/source/pyrogram/Client.rst | 1 + pyrogram/client/ext/base_client.py | 3 + pyrogram/client/methods/chats/__init__.py | 4 +- pyrogram/client/methods/chats/iter_dialogs.py | 82 +++++++++++++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/chats/iter_dialogs.py diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index 78b5a32b..ff45fad1 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -93,6 +93,7 @@ Chats get_chat_members get_chat_members_count get_dialogs + iter_dialogs Users ----- diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 93d4e57e..5e03e100 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -117,3 +117,6 @@ class BaseClient: def get_history(self, *args, **kwargs): pass + + def get_dialogs(self, *args, **kwargs): + pass diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 745678cc..7f7761c5 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -24,6 +24,7 @@ from .get_chat_members import GetChatMembers from .get_chat_members_count import GetChatMembersCount from .get_chat_preview import GetChatPreview from .get_dialogs import GetDialogs +from .iter_dialogs import IterDialogs from .join_chat import JoinChat from .kick_chat_member import KickChatMember from .leave_chat import LeaveChat @@ -56,6 +57,7 @@ class Chats( UnpinChatMessage, GetDialogs, GetChatMembersCount, - GetChatPreview + GetChatPreview, + IterDialogs ): pass diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/client/methods/chats/iter_dialogs.py new file mode 100644 index 00000000..6058cd17 --- /dev/null +++ b/pyrogram/client/methods/chats/iter_dialogs.py @@ -0,0 +1,82 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Generator + +import pyrogram +from ...ext import BaseClient + + +class IterDialogs(BaseClient): + def iter_dialogs(self, + offset_date: int = 0, + limit: int = 0) -> Generator["pyrogram.Dialog", None, None]: + """Use this method to iterate through a user's dialogs sequentially. + + This convenience method does the same as repeatedly calling :meth:`get_dialogs` in a loop, thus saving you from + the hassle of setting up boilerplate code. It is useful for getting the whole dialogs list with a single call. + + Args: + offset_date (``int``): + The offset date in Unix time taken from the top message of a :obj:`Dialog`. + Defaults to 0 (most recent dialog). + + limit (``str``, *optional*): + Limits the number of dialogs to be retrieved. + By default, no limit is applied and all dialogs are returned. + + Returns: + A generator yielding :obj:`Dialog ` objects. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + current = 0 + total = limit or (1 << 31) - 1 + limit = min(100, total) + + pinned_dialogs = self.get_dialogs( + pinned_only=True + ).dialogs + + for dialog in pinned_dialogs: + yield dialog + + current += 1 + + if current >= total: + return + + while True: + dialogs = self.get_dialogs( + offset_date=offset_date, + limit=limit + ).dialogs + + if not dialogs: + return + + offset_date = dialogs[-1].top_message.date + + for dialog in dialogs: + yield dialog + + current += 1 + + if current >= total: + return From e1cac13f0d35b5a24546000d08108c512733a5d7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 16:58:57 +0100 Subject: [PATCH 184/326] Fix get_dialogs docstrings --- pyrogram/client/methods/chats/get_dialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 35fc7752..c5fe6cfb 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -32,7 +32,7 @@ class GetDialogs(BaseClient): offset_date: int = 0, limit: int = 100, pinned_only: bool = False) -> "pyrogram.Dialogs": - """Use this method to get the user's dialogs + """Use this method to get a chunk of the user's dialogs You can get up to 100 dialogs at once. For a more convenient way of getting a user's dialogs see :meth:`iter_dialogs`. From d7e3397050c9f0d718b550b8b8cababc918688eb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 16:59:36 +0100 Subject: [PATCH 185/326] Update get_chat_members docstrings --- pyrogram/client/methods/chats/get_chat_members.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index 1d99ec4b..382d7f0f 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -39,10 +39,12 @@ class GetChatMembers(BaseClient): limit: int = 200, query: str = "", filter: str = Filters.ALL) -> "pyrogram.ChatMembers": - """Use this method to get the members list of a chat. + """Use this method to get a chunk of the members list of a chat. + You can get up to 200 chat members at once. A chat can be either a basic group, a supergroup or a channel. You must be admin to retrieve the members list of a channel (also known as "subscribers"). + For a more convenient way of getting chat members see :meth:`iter_chat_members`. Args: chat_id (``int`` | ``str``): From 153439ac88e5e130deee6186d8dd946b79152a17 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 17:13:44 +0100 Subject: [PATCH 186/326] Add iter_chat_members method. Reference #170 --- docs/source/pyrogram/Client.rst | 1 + pyrogram/client/ext/base_client.py | 3 + pyrogram/client/methods/chats/__init__.py | 4 +- .../client/methods/chats/iter_chat_members.py | 124 ++++++++++++++++++ 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/chats/iter_chat_members.py diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index ff45fad1..0d49068a 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -92,6 +92,7 @@ Chats get_chat_member get_chat_members get_chat_members_count + iter_chat_members get_dialogs iter_dialogs diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 5e03e100..aaed5786 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -120,3 +120,6 @@ class BaseClient: def get_dialogs(self, *args, **kwargs): pass + + def get_chat_members(self, *args, **kwargs): + pass diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 7f7761c5..6cc034e4 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -24,6 +24,7 @@ from .get_chat_members import GetChatMembers from .get_chat_members_count import GetChatMembersCount from .get_chat_preview import GetChatPreview from .get_dialogs import GetDialogs +from .iter_chat_members import IterChatMembers from .iter_dialogs import IterDialogs from .join_chat import JoinChat from .kick_chat_member import KickChatMember @@ -58,6 +59,7 @@ class Chats( GetDialogs, GetChatMembersCount, GetChatPreview, - IterDialogs + IterDialogs, + IterChatMembers ): pass diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py new file mode 100644 index 00000000..bdd8d117 --- /dev/null +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -0,0 +1,124 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from string import ascii_lowercase +from typing import Union, Generator + +import pyrogram +from ...ext import BaseClient + + +class Filters: + ALL = "all" + KICKED = "kicked" + RESTRICTED = "restricted" + BOTS = "bots" + RECENT = "recent" + ADMINISTRATORS = "administrators" + + +QUERIES = [""] + [str(i) for i in range(10)] + list(ascii_lowercase) +QUERYABLE_FILTERS = (Filters.ALL, Filters.KICKED, Filters.RESTRICTED) + + +class IterChatMembers(BaseClient): + def iter_chat_members(self, + chat_id: Union[int, str], + limit: int = 0, + query: str = "", + filter: str = Filters.ALL) -> Generator["pyrogram.ChatMember", None, None]: + """Use this method to iterate through the members of a chat sequentially. + + This convenience method does the same as repeatedly calling :meth:`get_chat_members` in a loop, thus saving you + from the hassle of setting up boilerplate code. It is useful for getting the whole members list of a chat with + a single call. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + limit (``int``, *optional*): + Limits the number of members to be retrieved. + By default, no limit is applied and all members are returned. + + query (``str``, *optional*): + Query string to filter members based on their display names and usernames. + Defaults to "" (empty string) [2]_. + + filter (``str``, *optional*): + Filter used to select the kind of members you want to retrieve. Only applicable for supergroups + and channels. It can be any of the followings: + *"all"* - all kind of members, + *"kicked"* - kicked (banned) members only, + *"restricted"* - restricted members only, + *"bots"* - bots only, + *"recent"* - recent members only, + *"administrators"* - chat administrators only. + Defaults to *"all"*. + + .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members + on channels. + + .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. + + Returns: + A generator yielding :obj:`ChatMember ` objects. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + current = 0 + yielded = set() + queries = [query] if query else QUERIES + total = limit or (1 << 31) - 1 + limit = min(200, total) + + if filter not in QUERYABLE_FILTERS: + queries = [""] + + for q in queries: + offset = 0 + + while True: + chat_members = self.get_chat_members( + chat_id=chat_id, + offset=offset, + limit=limit, + query=q, + filter=filter + ).chat_members + + if not chat_members: + break + + offset += len(chat_members) + + for chat_member in chat_members: + user_id = chat_member.user.id + + if user_id in yielded: + continue + + yield chat_member + + yielded.add(chat_member.user.id) + + current += 1 + + if current >= total: + return From b8a3d02eef230b7a52d8a9dc785914b1e2bdf6ab Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 23:12:06 +0100 Subject: [PATCH 187/326] Fix get_messages return type --- pyrogram/client/methods/messages/get_messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index f24e996f..b1c90d10 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -33,7 +33,7 @@ class GetMessages(BaseClient): chat_id: Union[int, str], message_ids: Union[int, Iterable[int]] = None, reply_to_message_ids: Union[int, Iterable[int]] = None, - replies: int = 1) -> "pyrogram.Messages": + replies: int = 1) -> Union["pyrogram.Message", "pyrogram.Messages"]: """Use this method to get one or more messages that belong to a specific chat. You can retrieve up to 200 messages at once. From 7e354b12bfac977a9c4e5d1abc3f637b9c1dc117 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 4 Jan 2019 23:15:57 +0100 Subject: [PATCH 188/326] Fix kick_chat_member docstrings and return type --- pyrogram/client/methods/chats/kick_chat_member.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index b02e02cc..4cd66ec4 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -27,7 +27,7 @@ class KickChatMember(BaseClient): def kick_chat_member(self, chat_id: Union[int, str], user_id: Union[int, str], - until_date: int = 0) -> "pyrogram.Message": + until_date: int = 0) -> Union["pyrogram.Message", bool]: """Use this method to kick a user from a group, a supergroup or a channel. In the case of supergroups and channels, the user will not be able to return to the group on their own using invite links, etc., unless unbanned first. You must be an administrator in the chat for this to work and must @@ -52,7 +52,7 @@ class KickChatMember(BaseClient): considered to be banned forever. Defaults to 0 (ban forever). Returns: - True on success. + On success, either True or a service :obj:`Message ` will be returned (when applicable). Raises: :class:`Error ` in case of a Telegram RPC error. @@ -93,3 +93,5 @@ class KickChatMember(BaseClient): {i.id: i for i in r.users}, {i.id: i for i in r.chats} ) + else: + return True From 999b9ce667e5723fba63b2685fd281e0485111d3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Jan 2019 12:06:54 +0100 Subject: [PATCH 189/326] Add MESSAGE_POLL_CLOSED error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 1837d8df..f0e68e52 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -82,4 +82,5 @@ PASSWORD_RECOVERY_NA The password recovery e-mail is not available PASSWORD_EMPTY The password entered is empty PHONE_NUMBER_FLOOD This number has tried to login too many times TAKEOUT_INVALID The takeout id is invalid -TAKEOUT_REQUIRED The method must be invoked inside a takeout session \ No newline at end of file +TAKEOUT_REQUIRED The method must be invoked inside a takeout session +MESSAGE_POLL_CLOSED You can't interact with a closed poll \ No newline at end of file From 0716380737e4818569cc86a1e19ce25e477e5223 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Jan 2019 12:26:05 +0100 Subject: [PATCH 190/326] Add MEDIA_INVALID error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index f0e68e52..f01439f5 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -83,4 +83,5 @@ PASSWORD_EMPTY The password entered is empty PHONE_NUMBER_FLOOD This number has tried to login too many times TAKEOUT_INVALID The takeout id is invalid TAKEOUT_REQUIRED The method must be invoked inside a takeout session -MESSAGE_POLL_CLOSED You can't interact with a closed poll \ No newline at end of file +MESSAGE_POLL_CLOSED You can't interact with a closed poll +MEDIA_INVALID The media is invalid \ No newline at end of file From a50dba2b4cb7bfb22f9a42be37ed4c9c3773bf84 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Jan 2019 14:44:10 +0100 Subject: [PATCH 191/326] Add close_poll method --- pyrogram/client/methods/messages/__init__.py | 2 + .../client/methods/messages/close_poll.py | 65 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 pyrogram/client/methods/messages/close_poll.py diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index d5d81044..f76d0a22 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .close_poll import ClosePoll from .delete_messages import DeleteMessages from .download_media import DownloadMedia from .edit_message_caption import EditMessageCaption @@ -70,6 +71,7 @@ class Messages( SendVoice, SendPoll, VotePoll, + ClosePoll, RetractVote, DownloadMedia, IterHistory diff --git a/pyrogram/client/methods/messages/close_poll.py b/pyrogram/client/methods/messages/close_poll.py new file mode 100644 index 00000000..c2d2706b --- /dev/null +++ b/pyrogram/client/methods/messages/close_poll.py @@ -0,0 +1,65 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +from pyrogram.api import functions, types +from pyrogram.client.ext import BaseClient + + +class ClosePoll(BaseClient): + def close_poll(self, + chat_id: Union[int, str], + message_id: id) -> bool: + """Use this method to close (stop) a poll. + + Closed polls can't be reopened and nobody will be able to vote in it anymore. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + message_id (``int``): + Unique poll message identifier inside this chat. + + Returns: + On success, True is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + poll = self.get_messages(chat_id, message_id).poll + + self.send( + functions.messages.EditMessage( + peer=self.resolve_peer(chat_id), + id=message_id, + media=types.InputMediaPoll( + poll=types.Poll( + id=poll.id, + closed=True, + question="", + answers=[] + ) + ) + ) + ) + + return True From c7b1d6f70a97674fc59afc21b1334cd10f024b87 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Jan 2019 15:26:40 +0100 Subject: [PATCH 192/326] Take into account that flags:# could be not always the first argument For instance, in Layer 91, Poll's flags:# is at the second position. This mess also happened in the past (thanks tg devs) and eventually will be fixed again with the next Layer update, but from now on Pyrogram will be able to correctly generate code even in such cases. --- compiler/api/compiler.py | 52 +++++++++++++++++-------------- compiler/api/template/mtproto.txt | 2 -- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 57e4d22b..9e671e80 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -26,7 +26,7 @@ NOTICE_PATH = "NOTICE" SECTION_RE = re.compile(r"---(\w+)---") LAYER_RE = re.compile(r"//\sLAYER\s(\d+)") COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);(?: // Docs: (.+))?$", re.MULTILINE) -ARGS_RE = re.compile("[^{](\w+):([\w?!.<>]+)") +ARGS_RE = re.compile("[^{](\w+):([\w?!.<>#]+)") FLAGS_RE = re.compile(r"flags\.(\d+)\?") FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)") FLAGS_RE_3 = re.compile(r"flags:#") @@ -288,17 +288,20 @@ def start(): sorted_args = sort_args(c.args) arguments = ", " + ", ".join( - [get_argument_type(i) for i in sorted_args] + [get_argument_type(i) for i in sorted_args if i != ("flags", "#")] ) if c.args else "" fields = "\n ".join( - ["self.{0} = {0} # {1}".format(i[0], i[1]) for i in c.args] + ["self.{0} = {0} # {1}".format(i[0], i[1]) for i in c.args if i != ("flags", "#")] ) if c.args else "pass" docstring_args = [] docs = c.docs.split("|")[1:] if c.docs else None for i, arg in enumerate(sorted_args): + if arg == ("flags", "#"): + continue + arg_name, arg_type = arg is_optional = FLAGS_RE.match(arg_type) flag_number = is_optional.group(1) if is_optional else -1 @@ -338,28 +341,31 @@ def start(): if references: docstring_args += "\n\n See Also:\n This object can be returned by " + references + "." - if c.has_flags: - write_flags = [] - for i in c.args: - flag = FLAGS_RE.match(i[1]) - if flag: - write_flags.append("flags |= (1 << {}) if self.{} is not None else 0".format(flag.group(1), i[0])) - - write_flags = "\n ".join([ - "flags = 0", - "\n ".join(write_flags), - "b.write(Int(flags))" - ]) - else: - write_flags = "# No flags" - - read_flags = "flags = Int.read(b)" if c.has_flags else "# No flags" - - write_types = read_types = "" + write_types = read_types = "" if c.has_flags else "# No flags\n " for arg_name, arg_type in c.args: flag = FLAGS_RE_2.findall(arg_type) + if arg_name == "flags" and arg_type == "#": + write_flags = [] + + for i in c.args: + flag = FLAGS_RE.match(i[1]) + if flag: + write_flags.append( + "flags |= (1 << {}) if self.{} is not None else 0".format(flag.group(1), i[0])) + + write_flags = "\n ".join([ + "flags = 0", + "\n ".join(write_flags), + "b.write(Int(flags))\n " + ]) + + write_types += write_flags + read_types += "flags = Int.read(b)\n " + + continue + if flag: index, flag_type = flag[0] @@ -448,11 +454,9 @@ def start(): object_id=c.id, arguments=arguments, fields=fields, - read_flags=read_flags, read_types=read_types, - write_flags=write_flags, write_types=write_types, - return_arguments=", ".join([i[0] for i in sorted_args]) + return_arguments=", ".join([i[0] for i in sorted_args if i != ("flags", "#")]) ) ) diff --git a/compiler/api/template/mtproto.txt b/compiler/api/template/mtproto.txt index 368d4712..9a65b52d 100644 --- a/compiler/api/template/mtproto.txt +++ b/compiler/api/template/mtproto.txt @@ -16,7 +16,6 @@ class {class_name}(Object): @staticmethod def read(b: BytesIO, *args) -> "{class_name}": - {read_flags} {read_types} return {class_name}({return_arguments}) @@ -24,6 +23,5 @@ class {class_name}(Object): b = BytesIO() b.write(Int(self.ID, False)) - {write_flags} {write_types} return b.getvalue() From 7cb1c99e286ddae9d4c604e3952e7d43e8d0b7f9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Jan 2019 18:29:48 +0100 Subject: [PATCH 193/326] Update copyright year --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index abeb8b43..abc81b3d 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -18,7 +18,7 @@ import sys -__copyright__ = "Copyright (C) 2017-2018 Dan Tès ".replace( +__copyright__ = "Copyright (C) 2017-2019 Dan Tès ".replace( "\xe8", "e" if sys.getfilesystemencoding() != "utf-8" else "\xe8" ) From 7d061a1b5c0d7155ac27bb8e567509d25085da7c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Jan 2019 23:11:39 +0100 Subject: [PATCH 194/326] Add Game type --- pyrogram/__init__.py | 2 +- pyrogram/client/types/__init__.py | 2 +- .../types/messages_and_media/__init__.py | 1 + .../client/types/messages_and_media/game.py | 98 +++++++++++++++++++ 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 pyrogram/client/types/messages_and_media/game.py diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index abc81b3d..281da114 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -32,7 +32,7 @@ from .client.types import ( Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, - Poll, PollOption, ChatPreview, StopPropagation + Poll, PollOption, ChatPreview, StopPropagation, Game ) from .client import ( Client, ChatAction, ParseMode, Emoji, diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index 0f4628ce..65a9165a 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -31,7 +31,7 @@ from .input_media import ( from .messages_and_media import ( Audio, Contact, Document, Animation, Location, Photo, PhotoSize, Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos, - Message, Messages, MessageEntity, Poll, PollOption + Message, Messages, MessageEntity, Poll, PollOption, Game ) from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/client/types/messages_and_media/__init__.py index b6847bdb..3c0b3c98 100644 --- a/pyrogram/client/types/messages_and_media/__init__.py +++ b/pyrogram/client/types/messages_and_media/__init__.py @@ -34,3 +34,4 @@ from .venue import Venue from .video import Video from .video_note import VideoNote from .voice import Voice +from .game import Game diff --git a/pyrogram/client/types/messages_and_media/game.py b/pyrogram/client/types/messages_and_media/game.py new file mode 100644 index 00000000..01af7226 --- /dev/null +++ b/pyrogram/client/types/messages_and_media/game.py @@ -0,0 +1,98 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram.api import types +from .animation import Animation +from .photo import Photo +from ..pyrogram_type import PyrogramType + + +class Game(PyrogramType): + """This object represents a game. + Use BotFather to create and edit games, their short names will act as unique identifiers. + + Args: + id (``int``): + Unique identifier of the game. + + title (``str``): + Title of the game. + + short_name (``str``): + Unique short name of the game. + + description (``str``): + Description of the game. + + photo (:obj:`Photo `): + Photo that will be displayed in the game message in chats. + + animation (:obj:`Animation `, *optional*): + Animation that will be displayed in the game message in chats. + Upload via BotFather. + """ + + def __init__(self, + *, + client: "pyrogram.client.ext.BaseClient", + id: int, + title: str, + short_name: str, + description: str, + photo: Photo, + animation: Animation = None): + super().__init__(client) + + self.id = id + self.title = title + self.short_name = short_name + self.description = description + self.photo = photo + self.animation = animation + + @staticmethod + def _parse(client, message: types.Message) -> "Game": + game = message.media.game # type: types.Game + animation = None + + if game.document: + attributes = {type(i): i for i in game.document.attributes} + + file_name = getattr( + attributes.get( + types.DocumentAttributeFilename, None + ), "file_name", None + ) + + animation = Animation._parse( + client, + game.document, + attributes.get(types.DocumentAttributeVideo, None), + file_name + ) + + return Game( + id=game.id, + title=game.title, + short_name=game.short_name, + description=game.description, + photo=Photo._parse(client, game.photo), + animation=animation, + client=client + ) From d5303285d66a7693728200dcb6a372937e7adb5c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Jan 2019 23:12:29 +0100 Subject: [PATCH 195/326] Add support for Game inline buttons. Add CallbackGame type --- pyrogram/client/types/bots/__init__.py | 1 + pyrogram/client/types/bots/callback_game.py | 29 +++++++++++++++++++ .../types/bots/inline_keyboard_button.py | 17 +++++++++-- 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 pyrogram/client/types/bots/callback_game.py diff --git a/pyrogram/client/types/bots/__init__.py b/pyrogram/client/types/bots/__init__.py index 28cfe724..804701dd 100644 --- a/pyrogram/client/types/bots/__init__.py +++ b/pyrogram/client/types/bots/__init__.py @@ -23,3 +23,4 @@ from .inline_keyboard_markup import InlineKeyboardMarkup from .keyboard_button import KeyboardButton from .reply_keyboard_markup import ReplyKeyboardMarkup from .reply_keyboard_remove import ReplyKeyboardRemove +from .callback_game import CallbackGame \ No newline at end of file diff --git a/pyrogram/client/types/bots/callback_game.py b/pyrogram/client/types/bots/callback_game.py new file mode 100644 index 00000000..be026360 --- /dev/null +++ b/pyrogram/client/types/bots/callback_game.py @@ -0,0 +1,29 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from ..pyrogram_type import PyrogramType + + +class CallbackGame(PyrogramType): + """A placeholder, currently holds no information. + + Use BotFather to set up your game. + """ + + def __init__(self): + super().__init__(None) diff --git a/pyrogram/client/types/bots/inline_keyboard_button.py b/pyrogram/client/types/bots/inline_keyboard_button.py index f9c1267a..cd30f373 100644 --- a/pyrogram/client/types/bots/inline_keyboard_button.py +++ b/pyrogram/client/types/bots/inline_keyboard_button.py @@ -18,8 +18,9 @@ from pyrogram.api.types import ( KeyboardButtonUrl, KeyboardButtonCallback, - KeyboardButtonSwitchInline + KeyboardButtonSwitchInline, KeyboardButtonGame ) +from .callback_game import CallbackGame from ..pyrogram_type import PyrogramType @@ -58,7 +59,8 @@ class InlineKeyboardButton(PyrogramType): callback_data: bytes = None, url: str = None, switch_inline_query: str = None, - switch_inline_query_current_chat: str = None): + switch_inline_query_current_chat: str = None, + callback_game: CallbackGame = None): super().__init__(None) self.text = text @@ -66,7 +68,7 @@ class InlineKeyboardButton(PyrogramType): self.callback_data = callback_data self.switch_inline_query = switch_inline_query self.switch_inline_query_current_chat = switch_inline_query_current_chat - # self.callback_game = callback_game + self.callback_game = callback_game # self.pay = pay @staticmethod @@ -95,6 +97,12 @@ class InlineKeyboardButton(PyrogramType): switch_inline_query=o.query ) + if isinstance(o, KeyboardButtonGame): + return InlineKeyboardButton( + text=o.text, + callback_game=CallbackGame() + ) + def write(self): if self.callback_data: return KeyboardButtonCallback(self.text, self.callback_data) @@ -107,3 +115,6 @@ class InlineKeyboardButton(PyrogramType): if self.switch_inline_query_current_chat: return KeyboardButtonSwitchInline(self.text, self.switch_inline_query_current_chat, same_peer=True) + + if self.callback_game: + return KeyboardButtonGame(self.text) From 50e5692eae1ad7708ea5c20d6c74d2f2bdf417ae Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Jan 2019 23:12:59 +0100 Subject: [PATCH 196/326] Add Filters.game and Filters.game_score. Also fix Filters.animation name --- pyrogram/client/filters/filters.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index ea6d7202..01ffe434 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -100,9 +100,12 @@ class Filters: sticker = create("Sticker", lambda _, m: bool(m.sticker)) """Filter messages that contain :obj:`Sticker ` objects.""" - animation = create("GIF", lambda _, m: bool(m.animation)) + animation = create("Animation", lambda _, m: bool(m.animation)) """Filter messages that contain :obj:`Animation ` objects.""" + game = create("Game", lambda _, m: bool(m.game)) + """Filter messages that contain :obj:`Game ` objects.""" + video = create("Video", lambda _, m: bool(m.video)) """Filter messages that contain :obj:`Video ` objects.""" @@ -169,6 +172,9 @@ class Filters: pinned_message = create("PinnedMessage", lambda _, m: bool(m.pinned_message)) """Filter service messages for pinned messages.""" + game_score = create("GameScore", lambda _, m: bool(m.game_score)) + """Filter service messages for game scores.""" + reply_keyboard = create("ReplyKeyboard", lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup)) """Filter messages containing reply keyboard markups""" @@ -193,7 +199,8 @@ class Filters: - channel_chat_created - migrate_to_chat_id - migrate_from_chat_id - - pinned_message""" + - pinned_message + - game_score""" media = create("Media", lambda _, m: bool(m.media)) """Filter media messages. A media message contains any of the following fields set @@ -208,7 +215,8 @@ class Filters: - video_note - contact - location - - venue""" + - venue + - poll""" @staticmethod def command(command: str or list, From bb27633da6d831b71723a37953dd00edd927e37c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Jan 2019 23:13:47 +0100 Subject: [PATCH 197/326] Add game and game_score in Message --- .../types/messages_and_media/message.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 830a24de..fddc4d0b 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -121,6 +121,9 @@ class Message(PyrogramType, Update): animation (:obj:`Animation `, *optional*): Message is an animation, information about the animation. + game (:obj:`Game `, *optional*): + Message is a game, information about the game. + video (:obj:`Video `, *optional*): Message is a video, information about the video. @@ -199,6 +202,10 @@ class Message(PyrogramType, Update): Note that the Message object in this field will not contain further reply_to_message fields even if it is itself a reply. + game_score (``int``, *optional*): + The game score for a user. + The reply_to_message field will contain the game Message. + views (``int``, *optional*): Channel post views. @@ -255,6 +262,7 @@ class Message(PyrogramType, Update): photo: "pyrogram.Photo" = None, sticker: "pyrogram.Sticker" = None, animation: "pyrogram.Animation" = None, + game: "pyrogram.Game" = None, video: "pyrogram.Video" = None, voice: "pyrogram.Voice" = None, video_note: "pyrogram.VideoNote" = None, @@ -275,6 +283,7 @@ class Message(PyrogramType, Update): migrate_to_chat_id: int = None, migrate_from_chat_id: int = None, pinned_message: "Message" = None, + game_score: int = None, views: int = None, via_bot: User = None, outgoing: bool = None, @@ -311,6 +320,7 @@ class Message(PyrogramType, Update): self.photo = photo self.sticker = sticker self.animation = animation + self.game = game self.video = video self.voice = voice self.video_note = video_note @@ -331,6 +341,7 @@ class Message(PyrogramType, Update): self.migrate_to_chat_id = migrate_to_chat_id self.migrate_from_chat_id = migrate_from_chat_id self.pinned_message = pinned_message + self.game_score = game_score self.views = views self.via_bot = via_bot self.outgoing = outgoing @@ -407,6 +418,19 @@ class Message(PyrogramType, Update): except MessageIdsEmpty: pass + if isinstance(action, types.MessageActionGameScore): + parsed_message.game_score = action.score + + if message.reply_to_msg_id and replies: + try: + parsed_message.reply_to_message = client.get_messages( + parsed_message.chat.id, + reply_to_message_ids=message.id, + replies=0 + ) + except MessageIdsEmpty: + pass + return parsed_message if isinstance(message, types.Message): @@ -435,6 +459,7 @@ class Message(PyrogramType, Update): location = None contact = None venue = None + game = None audio = None voice = None animation = None @@ -456,6 +481,8 @@ class Message(PyrogramType, Update): contact = Contact._parse(client, media) elif isinstance(media, types.MessageMediaVenue): venue = pyrogram.Venue._parse(client, media) + elif isinstance(media, types.MessageMediaGame): + game = pyrogram.Game._parse(client, message) elif isinstance(media, types.MessageMediaDocument): doc = media.document @@ -543,6 +570,7 @@ class Message(PyrogramType, Update): audio=audio, voice=voice, animation=animation, + game=game, video=video, video_note=video_note, sticker=sticker, From 6451d599b264ce0ea39779a2fbb4618d571a34c5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 08:30:40 +0100 Subject: [PATCH 198/326] Fix typo --- pyrogram/client/types/input_media/input_media_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index 5034ed06..a2dc18db 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -20,7 +20,7 @@ from . import InputMedia class InputMediaAudio(InputMedia): - """This object represents a video to be sent inside an album. + """This object represents an audio to be sent inside an album. It is intended to be used with :obj:`send_media_group() `. Args: From 491b96c9f671405fb3b2f9ce3600d3de5603bae4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 10:00:42 +0100 Subject: [PATCH 199/326] Use "recent" filter for up to 10k members --- pyrogram/client/methods/chats/iter_chat_members.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index bdd8d117..cd93fc46 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -86,6 +86,7 @@ class IterChatMembers(BaseClient): yielded = set() queries = [query] if query else QUERIES total = limit or (1 << 31) - 1 + filter = Filters.RECENT if total <= 10000 and filter == Filters.ALL else filter limit = min(200, total) if filter not in QUERYABLE_FILTERS: From 7ae9a065b81d7e5af1d1964ea7ea1acd40a8bc68 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 10:33:09 +0100 Subject: [PATCH 200/326] Update examples --- examples/README.md | 24 ++++---- ...k_query_handler.py => callback_queries.py} | 0 examples/chat_members.py | 10 ++++ examples/dialogs.py | 9 +++ examples/{echo_bot.py => echo.py} | 2 +- examples/get_chat_members.py | 31 ---------- examples/get_chat_members2.py | 50 ---------------- examples/get_history.py | 31 ---------- examples/hello.py | 16 +++++ examples/hello_world.py | 18 ------ examples/history.py | 10 ++++ .../{query_inline_bots.py => inline_bots.py} | 0 examples/keyboards.py | 59 +++++++++++++++++++ .../{raw_update_handler.py => raw_updates.py} | 0 examples/send_bot_keyboards.py | 51 ---------------- examples/welcome.py | 27 +++++++++ examples/welcome_bot.py | 45 -------------- .../types/bots/reply_keyboard_markup.py | 4 +- 18 files changed, 146 insertions(+), 241 deletions(-) rename examples/{callback_query_handler.py => callback_queries.py} (100%) create mode 100644 examples/chat_members.py create mode 100644 examples/dialogs.py rename examples/{echo_bot.py => echo.py} (90%) delete mode 100644 examples/get_chat_members.py delete mode 100644 examples/get_chat_members2.py delete mode 100644 examples/get_history.py create mode 100644 examples/hello.py delete mode 100644 examples/hello_world.py create mode 100644 examples/history.py rename examples/{query_inline_bots.py => inline_bots.py} (100%) create mode 100644 examples/keyboards.py rename examples/{raw_update_handler.py => raw_updates.py} (100%) delete mode 100644 examples/send_bot_keyboards.py create mode 100644 examples/welcome.py delete mode 100644 examples/welcome_bot.py diff --git a/examples/README.md b/examples/README.md index 763db699..6f56ab89 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,21 +2,21 @@ This folder contains example scripts to show you how **Pyrogram** looks like. -Every script is working right away (provided you correctly set up your credentials), meaning -you can simply copy-paste and run. The only things you have to change are session names and target chats. +Every script is working right away (provided you correctly set up your credentials), meaning you can simply copy-paste +and run. The only things you have to change are session names and target chats. All the examples listed in this directory are licensed under the terms of the [CC0 1.0 Universal](LICENSE) license and can be freely used as basic building blocks for your own applications without worrying about copyrights. Example | Description ---: | :--- -[**hello_world**](hello_world.py) | Demonstration of basic API usages -[**echo_bot**](echo_bot.py) | Echo bot that replies to every private text message -[**welcome_bot**](welcome_bot.py) | The Welcome Bot source code in [@PyrogramChat](https://t.me/pyrogramchat) -[**get_history**](get_history.py) | How to retrieve the full message history of a chat -[**get_chat_members**](get_chat_members.py) | How to get the first 10.000 members of a supergroup/channel -[**get_chat_members2**](get_chat_members2.py) | Improved version to get more than 10.000 members -[**query_inline_bots**](query_inline_bots.py) | How to query an inline bot and send a result to a chat -[**send_bot_keyboards**](send_bot_keyboards.py) | How to send normal and inline keyboards using regular bots -[**callback_query_handler**](callback_query_handler.py) | How to handle queries coming from inline button presses -[**raw_update_handler**](raw_update_handler.py) | How to handle raw updates (old, should be avoided) +[**hello**](hello.py) | Demonstration of basic API usage +[**echo**](echo.py) | Reply to every private text message +[**welcome**](welcome.py) | The Welcome Bot in [@PyrogramChat](https://t.me/pyrogramchat) +[**history**](history.py) | Get the full message history of a chat +[**chat_members**](chat_members.py) | Get all the members of a chat +[**dialogs**](dialogs.py) | Get all of your dialog chats +[**inline_bots**](inline_bots.py) | Query an inline bot and send a result to a chat +[**keyboards**](keyboards.py) | Send normal and inline keyboards using regular bots +[**callback_queries**](callback_queries.py) | Handle queries coming from inline button presses +[**raw_updates**](raw_updates.py) | Handle raw updates (old, should be avoided) diff --git a/examples/callback_query_handler.py b/examples/callback_queries.py similarity index 100% rename from examples/callback_query_handler.py rename to examples/callback_queries.py diff --git a/examples/chat_members.py b/examples/chat_members.py new file mode 100644 index 00000000..87f8613d --- /dev/null +++ b/examples/chat_members.py @@ -0,0 +1,10 @@ +"""This example shows how to get all the members of a chat.""" + +from pyrogram import Client + +app = Client("my_count") +target = "pyrogramchat" # Target channel/supergroup + +with app: + for member in app.iter_chat_members(target): + print(member.user.first_name) diff --git a/examples/dialogs.py b/examples/dialogs.py new file mode 100644 index 00000000..08c769e2 --- /dev/null +++ b/examples/dialogs.py @@ -0,0 +1,9 @@ +"""This example shows how to get the full dialogs list of a user.""" + +from pyrogram import Client + +app = Client("my_account") + +with app: + for dialog in app.iter_dialogs(): + print(dialog.chat.title or dialog.chat.first_name) diff --git a/examples/echo_bot.py b/examples/echo.py similarity index 90% rename from examples/echo_bot.py rename to examples/echo.py index 7a2b0aa7..c60ae291 100644 --- a/examples/echo_bot.py +++ b/examples/echo.py @@ -11,7 +11,7 @@ app = Client("my_account") @app.on_message(Filters.text & Filters.private) def echo(client, message): - message.reply(message.text, quote=True) + message.reply(message.text) app.run() # Automatically start() and idle() diff --git a/examples/get_chat_members.py b/examples/get_chat_members.py deleted file mode 100644 index e0f8c3fa..00000000 --- a/examples/get_chat_members.py +++ /dev/null @@ -1,31 +0,0 @@ -"""This example shows you how to get the first 10.000 members of a chat. -Refer to get_chat_members2.py for more than 10.000 members. -""" - -import time - -from pyrogram import Client -from pyrogram.api.errors import FloodWait - -app = Client("my_account") - -target = "pyrogramchat" # Target channel/supergroup -members = [] # List that will contain all the members of the target chat -offset = 0 # Offset starts at 0 -limit = 200 # Amount of users to retrieve for each API call (max 200) - -with app: - while True: - try: - chunk = app.get_chat_members(target, offset) - except FloodWait as e: # Very large chats could trigger FloodWait - time.sleep(e.x) # When it happens, wait X seconds and try again - continue - - if not chunk.chat_members: - break # No more members left - - members.extend(chunk.chat_members) - offset += len(chunk.chat_members) - -# Now the "members" list contains all the members of the target chat diff --git a/examples/get_chat_members2.py b/examples/get_chat_members2.py deleted file mode 100644 index a4fa9daa..00000000 --- a/examples/get_chat_members2.py +++ /dev/null @@ -1,50 +0,0 @@ -"""This is an improved version of get_chat_members.py - -Since Telegram will return at most 10.000 members for a single query, this script -repeats the search using numbers ("0" to "9") and all the available ascii letters ("a" to "z"). - -This can be further improved by also searching for non-ascii characters (e.g.: Japanese script), -as some user names may not contain ascii letters at all. -""" - -import time -from string import ascii_lowercase - -from pyrogram import Client -from pyrogram.api.errors import FloodWait - -app = Client("my_account") - -target = "pyrogramchat" # Target channel/supergroup -members = {} # List that will contain all the members of the target chat -limit = 200 # Amount of users to retrieve for each API call (max 200) - -# "" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" (as list) -queries = [""] + [str(i) for i in range(10)] + list(ascii_lowercase) - -with app: - for q in queries: - print('Searching for "{}"'.format(q)) - offset = 0 # For each query, offset restarts from 0 - - while True: - try: - chunk = app.get_chat_members(target, offset, query=q) - except FloodWait as e: # Very large chats could trigger FloodWait - print("Flood wait: {} seconds".format(e.x)) - time.sleep(e.x) # When it happens, wait X seconds and try again - continue - - if not chunk.chat_members: - print('Done searching for "{}"'.format(q)) - print() - break # No more members left - - members.update({i.user.id: i for i in chunk.chat_members}) - offset += len(chunk.chat_members) - - print("Total members: {}".format(len(members))) - - print("Grand total: {}".format(len(members))) - -# Now the "members" list contains all the members of the target chat diff --git a/examples/get_history.py b/examples/get_history.py deleted file mode 100644 index 628b5692..00000000 --- a/examples/get_history.py +++ /dev/null @@ -1,31 +0,0 @@ -"""This example shows how to retrieve the full message history of a chat""" - -import time - -from pyrogram import Client -from pyrogram.api.errors import FloodWait - -app = Client("my_account") -target = "me" # "me" refers to your own chat (Saved Messages) -messages = [] # List that will contain all the messages of the target chat -offset_id = 0 # ID of the last message of the chunk - -with app: - while True: - try: - m = app.get_history(target, offset_id=offset_id) - except FloodWait as e: # For very large chats the method call can raise a FloodWait - print("waiting {}".format(e.x)) - time.sleep(e.x) # Sleep X seconds before continuing - continue - - if not m.messages: - break - - messages += m.messages - offset_id = m.messages[-1].message_id - - print("Messages: {}".format(len(messages))) - -# Now the "messages" list contains all the messages sorted by date in -# descending order (from the most recent to the oldest one) diff --git a/examples/hello.py b/examples/hello.py new file mode 100644 index 00000000..54e86812 --- /dev/null +++ b/examples/hello.py @@ -0,0 +1,16 @@ +"""This example demonstrates a basic API usage""" + +from pyrogram import Client + +# Create a new Client instance +app = Client("my_account") + +with app: + # Send a message, Markdown is enabled by default + app.send_message("me", "Hi there! I'm using **Pyrogram**") + + # Send a location + app.send_location("me", 51.500729, -0.124583) + + # Send a sticker + app.send_sticker("me", "CAADBAADhw4AAvLQYAHICbZ5SUs_jwI") diff --git a/examples/hello_world.py b/examples/hello_world.py deleted file mode 100644 index 010725ef..00000000 --- a/examples/hello_world.py +++ /dev/null @@ -1,18 +0,0 @@ -"""This example demonstrates a basic API usage""" - -from pyrogram import Client - -# Create a new Client instance -app = Client("my_account") - -# Start the Client before calling any API method -app.start() - -# Send a message to yourself, Markdown is enabled by default -app.send_message("me", "Hi there! I'm using **Pyrogram**") - -# Send a location to yourself -app.send_location("me", 51.500729, -0.124583) - -# Stop the client when you're done -app.stop() diff --git a/examples/history.py b/examples/history.py new file mode 100644 index 00000000..e8bb14e3 --- /dev/null +++ b/examples/history.py @@ -0,0 +1,10 @@ +"""This example shows how to get the full message history of a chat, starting from the latest message""" + +from pyrogram import Client + +app = Client("my_account") +target = "me" # "me" refers to your own chat (Saved Messages) + +with app: + for message in app.iter_history(target): + print(message.text) diff --git a/examples/query_inline_bots.py b/examples/inline_bots.py similarity index 100% rename from examples/query_inline_bots.py rename to examples/inline_bots.py diff --git a/examples/keyboards.py b/examples/keyboards.py new file mode 100644 index 00000000..147154a3 --- /dev/null +++ b/examples/keyboards.py @@ -0,0 +1,59 @@ +"""This example will show you how to send normal and inline keyboards. + +You must log-in as a regular bot in order to send keyboards (use the token from @BotFather). +Any attempt in sending keyboards with a user account will be simply ignored by the server. + +send_message() is used as example, but a keyboard can be sent with any other send_* methods, +like send_audio(), send_document(), send_location(), etc... +""" + +from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton + +# Create a client using your bot token +app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") + +with app: + app.send_message( + "haskell", # Edit this + "This is a ReplyKeyboardMarkup example", + reply_markup=ReplyKeyboardMarkup( + [ + ["A", "B", "C", "D"], # First row + ["E", "F", "G"], # Second row + ["H", "I"], # Third row + ["J"] # Fourth row + ], + resize_keyboard=True # Make the keyboard smaller + ) + ) + + app.send_message( + "haskell", # Edit this + "This is a InlineKeyboardMarkup example", + reply_markup=InlineKeyboardMarkup( + [ + [ # First row + + InlineKeyboardButton( # Generates a callback query when pressed + "Button", + callback_data=b"data" + ), # Note how callback_data must be bytes + InlineKeyboardButton( # Opens a web URL + "URL", + url="https://docs.pyrogram.ml" + ), + ], + [ # Second row + # Opens the inline interface + InlineKeyboardButton( + "Choose chat", + switch_inline_query="pyrogram" + ), + InlineKeyboardButton( # Opens the inline interface in the current chat + "Inline here", + switch_inline_query_current_chat="pyrogram" + ) + ] + ] + ) + ) diff --git a/examples/raw_update_handler.py b/examples/raw_updates.py similarity index 100% rename from examples/raw_update_handler.py rename to examples/raw_updates.py diff --git a/examples/send_bot_keyboards.py b/examples/send_bot_keyboards.py deleted file mode 100644 index 3a15a23a..00000000 --- a/examples/send_bot_keyboards.py +++ /dev/null @@ -1,51 +0,0 @@ -"""This example will show you how to send normal and inline keyboards. - -You must log-in as a regular bot in order to send keyboards (use the token from @BotFather). -Any attempt in sending keyboards with a user account will be simply ignored by the server. - -send_message() is used as example, but a keyboard can be sent with any other send_* methods, -like send_audio(), send_document(), send_location(), etc... -""" - -from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton - -# Create a client using your bot token -app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") -app.start() - -app.send_message( - "haskell", # Edit this - "This is a ReplyKeyboardMarkup example", - reply_markup=ReplyKeyboardMarkup( - [ - ["A", "B", "C", "D"], # First row - ["E", "F", "G"], # Second row - ["H", "I"], # Third row - ["J"] # Fourth row - ], - resize_keyboard=True # Make the keyboard smaller - ) -) - -app.send_message( - "haskell", # Edit this - "This is a InlineKeyboardMarkup example", - reply_markup=InlineKeyboardMarkup( - [ - [ # First row - # Generates a callback query when pressed - InlineKeyboardButton("Button", callback_data="data"), - # Opens a web URL - InlineKeyboardButton("URL", url="https://docs.pyrogram.ml"), - ], - [ # Second row - # Opens the inline interface of a bot in another chat with a pre-defined query - InlineKeyboardButton("Choose chat", switch_inline_query="pyrogram"), - # Same as the button above, but the inline interface is opened in the current chat - InlineKeyboardButton("Inline here", switch_inline_query_current_chat="pyrogram"), - ] - ] - ) -) - -app.stop() diff --git a/examples/welcome.py b/examples/welcome.py new file mode 100644 index 00000000..06a38cb7 --- /dev/null +++ b/examples/welcome.py @@ -0,0 +1,27 @@ +"""This is the Welcome Bot in @PyrogramChat. + +It uses the Emoji module to easily add emojis in your text messages and Filters +to make it only work for specific messages in a specific chat. +""" + +from pyrogram import Client, Emoji, Filters + +MENTION = "[{}](tg://user?id={})" +MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!" + +app = Client("my_account") + + +@app.on_message(Filters.chat("PyrogramChat") & Filters.new_chat_members) +def welcome(client, message): + # Build the new members list (with mentions) by using their first_name + new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members] + + # Build the welcome message by using an emoji and the list we built above + text = MESSAGE.format(Emoji.SPARKLES, ", ".join(new_members)) + + # Send the welcome message, without the web page preview + message.reply(text, disable_web_page_preview=True) + + +app.run() # Automatically start() and idle() diff --git a/examples/welcome_bot.py b/examples/welcome_bot.py deleted file mode 100644 index 4326ed6c..00000000 --- a/examples/welcome_bot.py +++ /dev/null @@ -1,45 +0,0 @@ -"""This is the Welcome Bot in @PyrogramChat. - -It uses the Emoji module to easily add emojis in your text messages and Filters -to make it only work for specific messages in a specific chat. -""" - -from pyrogram import Client, Emoji, Filters - -USER = "**{}**" -MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {{}}!".format(Emoji.SPARKLES) - -enabled_groups = Filters.chat("PyrogramChat") -last_welcomes = {} - -app = Client("my_account") - - -@app.on_message(enabled_groups & Filters.new_chat_members) -def welcome(client, message): - chat_id = message.chat.id - - # Get the previous welcome message and members, if any - previous_welcome, previous_members = last_welcomes.pop(chat_id, (None, [])) - - # Delete the previous message, if exists - if previous_welcome: - previous_welcome.delete() - - # Build the new members list by using their first_name. Also append the previous members, if any - new_members = [USER.format(i.first_name) for i in message.new_chat_members] + previous_members - - # Build the welcome message by using an emoji and the list we created above - text = MESSAGE.format(", ".join(new_members)) - - # Actually send the welcome and save the new message and the new members list - last_welcomes[message.chat.id] = message.reply(text, disable_web_page_preview=True), new_members - - -@app.on_message(enabled_groups) -def reset(client, message): - # Don't make the bot delete the previous welcome in case someone talks in the middle - last_welcomes.pop(message.chat.id, None) - - -app.run() # Automatically start() and idle() diff --git a/pyrogram/client/types/bots/reply_keyboard_markup.py b/pyrogram/client/types/bots/reply_keyboard_markup.py index 7840dbe5..afae236d 100644 --- a/pyrogram/client/types/bots/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots/reply_keyboard_markup.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import List +from typing import List, Union from pyrogram.api.types import KeyboardButtonRow from pyrogram.api.types import ReplyKeyboardMarkup as RawReplyKeyboardMarkup @@ -50,7 +50,7 @@ class ReplyKeyboardMarkup(PyrogramType): """ def __init__(self, - keyboard: List[List[KeyboardButton]], + keyboard: List[List[Union[KeyboardButton, str]]], resize_keyboard: bool = None, one_time_keyboard: bool = None, selective: bool = None): From ebb2ad5aabda184a5112ee800a25ff0bf2fd02c9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 11:47:59 +0100 Subject: [PATCH 201/326] Add BOT_SCORE_NOT_MODIFIED error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index f01439f5..1caec874 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -84,4 +84,5 @@ PHONE_NUMBER_FLOOD This number has tried to login too many times TAKEOUT_INVALID The takeout id is invalid TAKEOUT_REQUIRED The method must be invoked inside a takeout session MESSAGE_POLL_CLOSED You can't interact with a closed poll -MEDIA_INVALID The media is invalid \ No newline at end of file +MEDIA_INVALID The media is invalid +BOT_SCORE_NOT_MODIFIED The bot score was not modified \ No newline at end of file From 1bbf048b7a027fe125350684ad25b375adf68bfb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 21:49:25 +0100 Subject: [PATCH 202/326] Remove duplicated references --- pyrogram/client/methods/chats/iter_chat_members.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index cd93fc46..963081f8 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -58,7 +58,7 @@ class IterChatMembers(BaseClient): query (``str``, *optional*): Query string to filter members based on their display names and usernames. - Defaults to "" (empty string) [2]_. + Defaults to "" (empty string). filter (``str``, *optional*): Filter used to select the kind of members you want to retrieve. Only applicable for supergroups @@ -71,11 +71,6 @@ class IterChatMembers(BaseClient): *"administrators"* - chat administrators only. Defaults to *"all"*. - .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members - on channels. - - .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. - Returns: A generator yielding :obj:`ChatMember ` objects. From 9771be9c2aa970cb8fdec5c1bb1c347c0ca7c0ee Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 21:49:58 +0100 Subject: [PATCH 203/326] Add send_game and set_game_score methods --- docs/source/pyrogram/Client.rst | 2 + docs/source/pyrogram/Types.rst | 4 + pyrogram/client/methods/messages/__init__.py | 6 +- pyrogram/client/methods/messages/send_game.py | 87 ++++++++++++++++++ .../client/methods/messages/set_game_score.py | 90 +++++++++++++++++++ 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/messages/send_game.py create mode 100644 pyrogram/client/methods/messages/set_game_score.py diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index 0d49068a..795d1cef 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -67,6 +67,8 @@ Messages vote_poll retract_vote download_media + send_game + set_game_score Chats ----- diff --git a/docs/source/pyrogram/Types.rst b/docs/source/pyrogram/Types.rst index bf1bf937..68659aa2 100644 --- a/docs/source/pyrogram/Types.rst +++ b/docs/source/pyrogram/Types.rst @@ -57,6 +57,7 @@ Bots InlineKeyboardButton ForceReply CallbackQuery + Game Input Media ----------- @@ -182,6 +183,9 @@ Input Media .. autoclass:: CallbackQuery :members: +.. autoclass:: Game + :members: + .. Input Media ----------- diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index f76d0a22..252bf1db 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -33,6 +33,7 @@ from .send_audio import SendAudio from .send_chat_action import SendChatAction from .send_contact import SendContact from .send_document import SendDocument +from .send_game import SendGame from .send_location import SendLocation from .send_media_group import SendMediaGroup from .send_message import SendMessage @@ -44,6 +45,7 @@ from .send_video import SendVideo from .send_video_note import SendVideoNote from .send_voice import SendVoice from .vote_poll import VotePoll +from .set_game_score import SetGameScore class Messages( @@ -74,6 +76,8 @@ class Messages( ClosePoll, RetractVote, DownloadMedia, - IterHistory + IterHistory, + SendGame, + SetGameScore ): pass diff --git a/pyrogram/client/methods/messages/send_game.py b/pyrogram/client/methods/messages/send_game.py new file mode 100644 index 00000000..7cfa33a0 --- /dev/null +++ b/pyrogram/client/methods/messages/send_game.py @@ -0,0 +1,87 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class SendGame(BaseClient): + def send_game(self, + chat_id: Union[int, str], + game_short_name: str, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None) -> "pyrogram.Message": + """Use this method to send a game. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + game_short_name (``str``): + Short name of the game, serves as the unique identifier for the game. Set up your games via Botfather. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An object for an inline keyboard. If empty, one ‘Play game_title’ button will be shown automatically. + If not empty, the first button must launch the game. + + Returns: + On success, the sent :obj:`Message` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + r = self.send( + functions.messages.SendMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaGame( + id=types.InputGameShortName( + bot_id=types.InputUserSelf(), + short_name=game_short_name + ), + ), + message="", + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None + ) + ) + + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) diff --git a/pyrogram/client/methods/messages/set_game_score.py b/pyrogram/client/methods/messages/set_game_score.py new file mode 100644 index 00000000..7f5f92eb --- /dev/null +++ b/pyrogram/client/methods/messages/set_game_score.py @@ -0,0 +1,90 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class SetGameScore(BaseClient): + def set_game_score(self, + user_id: Union[int, str], + score: int, + force: bool = None, + disable_edit_message: bool = None, + chat_id: Union[int, str] = None, + message_id: int = None): + # inline_message_id: str = None): TODO Add inline_message_id + """Use this method to set the score of the specified user in a game. + + Args: + user_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + score (``int``): + New score, must be non-negative. + + force (``bool``, *optional*): + Pass True, if the high score is allowed to decrease. + This can be useful when fixing mistakes or banning cheaters. + + disable_edit_message (``bool``, *optional*): + Pass True, if the game message should not be automatically edited to include the current scoreboard. + + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + Required if inline_message_id is not specified. + + message_id (``int``, *optional*): + Identifier of the sent message. + Required if inline_message_id is not specified. + + Returns: + On success, if the message was sent by the bot, returns the edited :obj:`Message `, + otherwise returns True. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + :class:`BotScoreNotModified` if the new score is not greater than the user's current score in the chat and force is False. + """ + r = self.send( + functions.messages.SetGameScore( + peer=self.resolve_peer(chat_id), + score=score, + id=message_id, + user_id=self.resolve_peer(user_id), + force=force or None, + edit_message=not disable_edit_message or None + ) + ) + + for i in r.updates: + if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): + return pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) + + return True From 33e83bf6351b8c40f8fe570b3faaf4897e269e9f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 22:19:21 +0100 Subject: [PATCH 204/326] Update set_game_score docstrings --- pyrogram/client/methods/messages/set_game_score.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/set_game_score.py b/pyrogram/client/methods/messages/set_game_score.py index 7f5f92eb..7352aea9 100644 --- a/pyrogram/client/methods/messages/set_game_score.py +++ b/pyrogram/client/methods/messages/set_game_score.py @@ -50,7 +50,7 @@ class SetGameScore(BaseClient): disable_edit_message (``bool``, *optional*): Pass True, if the game message should not be automatically edited to include the current scoreboard. - chat_id (``int`` | ``str``): + chat_id (``int`` | ``str``, *optional*): Unique identifier (int) or username (str) of the target chat. For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). From 65bdf31ce19af79d4b8f1e0037cfe195cf8129c4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 22:26:52 +0100 Subject: [PATCH 205/326] Move send_game and set_game_score into bots folder --- docs/source/pyrogram/Client.rst | 4 ++-- pyrogram/client/methods/bots/__init__.py | 6 +++++- pyrogram/client/methods/{messages => bots}/send_game.py | 2 +- .../client/methods/{messages => bots}/set_game_score.py | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) rename pyrogram/client/methods/{messages => bots}/send_game.py (98%) rename pyrogram/client/methods/{messages => bots}/set_game_score.py (98%) diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index 795d1cef..2e2e1d96 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -67,8 +67,6 @@ Messages vote_poll retract_vote download_media - send_game - set_game_score Chats ----- @@ -140,6 +138,8 @@ Bots send_inline_bot_result answer_callback_query request_callback_answer + send_game + set_game_score .. autoclass:: pyrogram.Client diff --git a/pyrogram/client/methods/bots/__init__.py b/pyrogram/client/methods/bots/__init__.py index b0430efe..28ba49d8 100644 --- a/pyrogram/client/methods/bots/__init__.py +++ b/pyrogram/client/methods/bots/__init__.py @@ -19,13 +19,17 @@ from .answer_callback_query import AnswerCallbackQuery from .get_inline_bot_results import GetInlineBotResults from .request_callback_answer import RequestCallbackAnswer +from .send_game import SendGame from .send_inline_bot_result import SendInlineBotResult +from .set_game_score import SetGameScore class Bots( AnswerCallbackQuery, GetInlineBotResults, RequestCallbackAnswer, - SendInlineBotResult + SendInlineBotResult, + SendGame, + SetGameScore ): pass diff --git a/pyrogram/client/methods/messages/send_game.py b/pyrogram/client/methods/bots/send_game.py similarity index 98% rename from pyrogram/client/methods/messages/send_game.py rename to pyrogram/client/methods/bots/send_game.py index 7cfa33a0..401a5aa6 100644 --- a/pyrogram/client/methods/messages/send_game.py +++ b/pyrogram/client/methods/bots/send_game.py @@ -20,7 +20,7 @@ from typing import Union import pyrogram from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram.client.ext import BaseClient class SendGame(BaseClient): diff --git a/pyrogram/client/methods/messages/set_game_score.py b/pyrogram/client/methods/bots/set_game_score.py similarity index 98% rename from pyrogram/client/methods/messages/set_game_score.py rename to pyrogram/client/methods/bots/set_game_score.py index 7352aea9..e9d20844 100644 --- a/pyrogram/client/methods/messages/set_game_score.py +++ b/pyrogram/client/methods/bots/set_game_score.py @@ -20,7 +20,7 @@ from typing import Union import pyrogram from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram.client.ext import BaseClient class SetGameScore(BaseClient): From 633fefe1785f5b50a664462e766ac6f3e80a52ef Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 22:28:41 +0100 Subject: [PATCH 206/326] Add get_game_high_scores method --- docs/source/pyrogram/Client.rst | 1 + pyrogram/client/methods/messages/__init__.py | 8 ++- .../methods/messages/get_game_high_scores.py | 60 +++++++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 pyrogram/client/methods/messages/get_game_high_scores.py diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index 2e2e1d96..9a1b0509 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -140,6 +140,7 @@ Bots request_callback_answer send_game set_game_score + get_game_high_scores .. autoclass:: pyrogram.Client diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index 252bf1db..a46b3c0c 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -24,6 +24,7 @@ from .edit_message_media import EditMessageMedia from .edit_message_reply_markup import EditMessageReplyMarkup from .edit_message_text import EditMessageText from .forward_messages import ForwardMessages +from .get_game_high_scores import GetGameHighScores from .get_history import GetHistory from .get_messages import GetMessages from .iter_history import IterHistory @@ -33,7 +34,7 @@ from .send_audio import SendAudio from .send_chat_action import SendChatAction from .send_contact import SendContact from .send_document import SendDocument -from .send_game import SendGame +from pyrogram.client.methods.bots.send_game import SendGame from .send_location import SendLocation from .send_media_group import SendMediaGroup from .send_message import SendMessage @@ -44,8 +45,8 @@ from .send_venue import SendVenue from .send_video import SendVideo from .send_video_note import SendVideoNote from .send_voice import SendVoice +from pyrogram.client.methods.bots.set_game_score import SetGameScore from .vote_poll import VotePoll -from .set_game_score import SetGameScore class Messages( @@ -78,6 +79,7 @@ class Messages( DownloadMedia, IterHistory, SendGame, - SetGameScore + SetGameScore, + GetGameHighScores ): pass diff --git a/pyrogram/client/methods/messages/get_game_high_scores.py b/pyrogram/client/methods/messages/get_game_high_scores.py new file mode 100644 index 00000000..9816c38e --- /dev/null +++ b/pyrogram/client/methods/messages/get_game_high_scores.py @@ -0,0 +1,60 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram.api import functions +from ...ext import BaseClient + + +class GetGameHighScores(BaseClient): + def get_game_high_scores(self, + user_id: Union[int, str], + chat_id: Union[int, str], + message_id: int = None): + """Use this method to get data for high score tables. + + Args: + user_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + chat_id (``int`` | ``str``, *optional*): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + Required if inline_message_id is not specified. + + message_id (``int``, *optional*): + Identifier of the sent message. + Required if inline_message_id is not specified. + """ + # TODO: inline_message_id + + return pyrogram.GameHighScores._parse( + self, + self.send( + functions.messages.GetGameHighScores( + peer=self.resolve_peer(chat_id), + id=message_id, + user_id=self.resolve_peer(user_id) + ) + ) + ) From dc737ab7bb633b08c3b1a84203fff5109479e653 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 22:30:33 +0100 Subject: [PATCH 207/326] Add GameHighScore and GameHighScores types --- docs/source/pyrogram/Types.rst | 6 ++ pyrogram/__init__.py | 2 +- pyrogram/client/types/__init__.py | 9 +-- pyrogram/client/types/bots/__init__.py | 4 +- pyrogram/client/types/bots/game_high_score.py | 61 +++++++++++++++++++ .../client/types/bots/game_high_scores.py | 56 +++++++++++++++++ .../types/messages_and_media/__init__.py | 2 +- 7 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 pyrogram/client/types/bots/game_high_score.py create mode 100644 pyrogram/client/types/bots/game_high_scores.py diff --git a/docs/source/pyrogram/Types.rst b/docs/source/pyrogram/Types.rst index 68659aa2..6e0a14db 100644 --- a/docs/source/pyrogram/Types.rst +++ b/docs/source/pyrogram/Types.rst @@ -186,6 +186,12 @@ Input Media .. autoclass:: Game :members: +.. autoclass:: GameHighScore + :members: + +.. autoclass:: GameHighScores + :members: + .. Input Media ----------- diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 281da114..7330001d 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -32,7 +32,7 @@ from .client.types import ( Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, - Poll, PollOption, ChatPreview, StopPropagation, Game + Poll, PollOption, ChatPreview, StopPropagation, Game, CallbackGame, GameHighScore, GameHighScores ) from .client import ( Client, ChatAction, ParseMode, Emoji, diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index 65a9165a..ca332a22 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -18,11 +18,8 @@ from .bots import ( CallbackQuery, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, - KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove -) -from .bots import ( - ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, - KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove + KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, CallbackGame, + GameHighScore, GameHighScores ) from .input_media import ( InputMediaAudio, InputPhoneContact, InputMediaVideo, InputMediaPhoto, @@ -33,8 +30,8 @@ from .messages_and_media import ( Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos, Message, Messages, MessageEntity, Poll, PollOption, Game ) +from .update import StopPropagation from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, Dialog, Dialogs, User, UserStatus, ChatPreview ) -from .update import StopPropagation diff --git a/pyrogram/client/types/bots/__init__.py b/pyrogram/client/types/bots/__init__.py index 804701dd..81767945 100644 --- a/pyrogram/client/types/bots/__init__.py +++ b/pyrogram/client/types/bots/__init__.py @@ -16,11 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .callback_game import CallbackGame from .callback_query import CallbackQuery from .force_reply import ForceReply +from .game_high_score import GameHighScore +from .game_high_scores import GameHighScores from .inline_keyboard_button import InlineKeyboardButton from .inline_keyboard_markup import InlineKeyboardMarkup from .keyboard_button import KeyboardButton from .reply_keyboard_markup import ReplyKeyboardMarkup from .reply_keyboard_remove import ReplyKeyboardRemove -from .callback_game import CallbackGame \ No newline at end of file diff --git a/pyrogram/client/types/bots/game_high_score.py b/pyrogram/client/types/bots/game_high_score.py new file mode 100644 index 00000000..1297d8a9 --- /dev/null +++ b/pyrogram/client/types/bots/game_high_score.py @@ -0,0 +1,61 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram + +from pyrogram.api import types +from pyrogram.client.types.pyrogram_type import PyrogramType +from pyrogram.client.types.user_and_chats import User + + +class GameHighScore(PyrogramType): + """This object represents one row of the high scores table for a game. + + Args: + user (:obj:`User`): + User. + + score (``int``): + Score. + + position (``position``): + Position in high score table for the game. + """ + + def __init__(self, + *, + client: "pyrogram.client.ext.BaseClient", + user: User, + score: int, + position: int): + super().__init__(client) + + self.user = user + self.score = score + self.position = position + + @staticmethod + def _parse(client, game_high_score: types.HighScore, users: dict) -> "GameHighScore": + users = {i.id: i for i in users} + + return GameHighScore( + user=User._parse(client, users[game_high_score.user_id]), + score=game_high_score.score, + position=game_high_score.pos, + client=client + ) diff --git a/pyrogram/client/types/bots/game_high_scores.py b/pyrogram/client/types/bots/game_high_scores.py new file mode 100644 index 00000000..1717effa --- /dev/null +++ b/pyrogram/client/types/bots/game_high_scores.py @@ -0,0 +1,56 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import List + +import pyrogram +from pyrogram.api import types +from pyrogram.client.types.pyrogram_type import PyrogramType +from .game_high_score import GameHighScore + + +class GameHighScores(PyrogramType): + """This object represents the high scores table for a game. + + Args: + total_count (``int``): + Total number of scores the target game has. + + game_high_scores (List of :obj:`GameHighScore `): + Game scores. + """ + + def __init__(self, + *, + client: "pyrogram.client.ext.BaseClient", + total_count: int, + game_high_scores: List[GameHighScore]): + super().__init__(client) + + self.total_count = total_count + self.game_high_scores = game_high_scores + + @staticmethod + def _parse(client, game_high_scores: types.messages.HighScores) -> "GameHighScores": + return GameHighScores( + total_count=len(game_high_scores.scores), + game_high_scores=[ + GameHighScore._parse(client, score, game_high_scores.users) + for score in game_high_scores.scores], + client=client + ) diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/client/types/messages_and_media/__init__.py index 3c0b3c98..604b68b9 100644 --- a/pyrogram/client/types/messages_and_media/__init__.py +++ b/pyrogram/client/types/messages_and_media/__init__.py @@ -20,6 +20,7 @@ from .animation import Animation from .audio import Audio from .contact import Contact from .document import Document +from .game import Game from .location import Location from .message import Message from .message_entity import MessageEntity @@ -34,4 +35,3 @@ from .venue import Venue from .video import Video from .video_note import VideoNote from .voice import Voice -from .game import Game From ee472329a20fbc503e0a59639abb6cf776398696 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 22:35:17 +0100 Subject: [PATCH 208/326] Move get_game_high_scores method into bots folder --- pyrogram/client/methods/bots/__init__.py | 4 +++- .../methods/{messages => bots}/get_game_high_scores.py | 8 +++++++- pyrogram/client/methods/messages/__init__.py | 8 +------- 3 files changed, 11 insertions(+), 9 deletions(-) rename pyrogram/client/methods/{messages => bots}/get_game_high_scores.py (90%) diff --git a/pyrogram/client/methods/bots/__init__.py b/pyrogram/client/methods/bots/__init__.py index 28ba49d8..65d132a0 100644 --- a/pyrogram/client/methods/bots/__init__.py +++ b/pyrogram/client/methods/bots/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .answer_callback_query import AnswerCallbackQuery +from .get_game_high_scores import GetGameHighScores from .get_inline_bot_results import GetInlineBotResults from .request_callback_answer import RequestCallbackAnswer from .send_game import SendGame @@ -30,6 +31,7 @@ class Bots( RequestCallbackAnswer, SendInlineBotResult, SendGame, - SetGameScore + SetGameScore, + GetGameHighScores ): pass diff --git a/pyrogram/client/methods/messages/get_game_high_scores.py b/pyrogram/client/methods/bots/get_game_high_scores.py similarity index 90% rename from pyrogram/client/methods/messages/get_game_high_scores.py rename to pyrogram/client/methods/bots/get_game_high_scores.py index 9816c38e..ad4f8b4a 100644 --- a/pyrogram/client/methods/messages/get_game_high_scores.py +++ b/pyrogram/client/methods/bots/get_game_high_scores.py @@ -20,7 +20,7 @@ from typing import Union import pyrogram from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram.client.ext import BaseClient class GetGameHighScores(BaseClient): @@ -45,6 +45,12 @@ class GetGameHighScores(BaseClient): message_id (``int``, *optional*): Identifier of the sent message. Required if inline_message_id is not specified. + + Returns: + On success, a :obj:`GameHighScores ` object is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. """ # TODO: inline_message_id diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index a46b3c0c..f76d0a22 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -24,7 +24,6 @@ from .edit_message_media import EditMessageMedia from .edit_message_reply_markup import EditMessageReplyMarkup from .edit_message_text import EditMessageText from .forward_messages import ForwardMessages -from .get_game_high_scores import GetGameHighScores from .get_history import GetHistory from .get_messages import GetMessages from .iter_history import IterHistory @@ -34,7 +33,6 @@ from .send_audio import SendAudio from .send_chat_action import SendChatAction from .send_contact import SendContact from .send_document import SendDocument -from pyrogram.client.methods.bots.send_game import SendGame from .send_location import SendLocation from .send_media_group import SendMediaGroup from .send_message import SendMessage @@ -45,7 +43,6 @@ from .send_venue import SendVenue from .send_video import SendVideo from .send_video_note import SendVideoNote from .send_voice import SendVoice -from pyrogram.client.methods.bots.set_game_score import SetGameScore from .vote_poll import VotePoll @@ -77,9 +74,6 @@ class Messages( ClosePoll, RetractVote, DownloadMedia, - IterHistory, - SendGame, - SetGameScore, - GetGameHighScores + IterHistory ): pass From 8928ca34336a1ca5e1c8f5172e1a5dd3c62d9db5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 22:50:54 +0100 Subject: [PATCH 209/326] Rename game_score to game_high_score --- pyrogram/client/filters/filters.py | 4 ++-- pyrogram/client/types/bots/game_high_score.py | 12 ++++++++++-- pyrogram/client/types/messages_and_media/message.py | 8 ++++---- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 01ffe434..77492c6a 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -172,8 +172,8 @@ class Filters: pinned_message = create("PinnedMessage", lambda _, m: bool(m.pinned_message)) """Filter service messages for pinned messages.""" - game_score = create("GameScore", lambda _, m: bool(m.game_score)) - """Filter service messages for game scores.""" + game_high_score = create("GameHighScore", lambda _, m: bool(m.game_high_score)) + """Filter service messages for game high scores.""" reply_keyboard = create("ReplyKeyboard", lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup)) """Filter messages containing reply keyboard markups""" diff --git a/pyrogram/client/types/bots/game_high_score.py b/pyrogram/client/types/bots/game_high_score.py index 1297d8a9..0541c18c 100644 --- a/pyrogram/client/types/bots/game_high_score.py +++ b/pyrogram/client/types/bots/game_high_score.py @@ -33,7 +33,7 @@ class GameHighScore(PyrogramType): score (``int``): Score. - position (``position``): + position (``position``, *optional*): Position in high score table for the game. """ @@ -42,7 +42,7 @@ class GameHighScore(PyrogramType): client: "pyrogram.client.ext.BaseClient", user: User, score: int, - position: int): + position: int = None): super().__init__(client) self.user = user @@ -59,3 +59,11 @@ class GameHighScore(PyrogramType): position=game_high_score.pos, client=client ) + + @staticmethod + def _parse_action(client, service: types.MessageService, users: dict): + return GameHighScore( + user=User._parse(client, users[service.from_id]), + score=service.action.score, + client=client + ) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index fddc4d0b..a323898c 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -202,7 +202,7 @@ class Message(PyrogramType, Update): Note that the Message object in this field will not contain further reply_to_message fields even if it is itself a reply. - game_score (``int``, *optional*): + game_high_score (:obj:`GameHighScore `, *optional*): The game score for a user. The reply_to_message field will contain the game Message. @@ -283,7 +283,7 @@ class Message(PyrogramType, Update): migrate_to_chat_id: int = None, migrate_from_chat_id: int = None, pinned_message: "Message" = None, - game_score: int = None, + game_high_score: int = None, views: int = None, via_bot: User = None, outgoing: bool = None, @@ -341,7 +341,7 @@ class Message(PyrogramType, Update): self.migrate_to_chat_id = migrate_to_chat_id self.migrate_from_chat_id = migrate_from_chat_id self.pinned_message = pinned_message - self.game_score = game_score + self.game_high_score = game_high_score self.views = views self.via_bot = via_bot self.outgoing = outgoing @@ -419,7 +419,7 @@ class Message(PyrogramType, Update): pass if isinstance(action, types.MessageActionGameScore): - parsed_message.game_score = action.score + parsed_message.game_high_score = pyrogram.GameHighScore._parse_action(client, message, users) if message.reply_to_msg_id and replies: try: From 4db826615b5521e5db193f8168b93017f89fd48c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 22:58:14 +0100 Subject: [PATCH 210/326] Add USER_BOT_REQUIRED error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 1caec874..0f174c66 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -85,4 +85,5 @@ TAKEOUT_INVALID The takeout id is invalid TAKEOUT_REQUIRED The method must be invoked inside a takeout session MESSAGE_POLL_CLOSED You can't interact with a closed poll MEDIA_INVALID The media is invalid -BOT_SCORE_NOT_MODIFIED The bot score was not modified \ No newline at end of file +BOT_SCORE_NOT_MODIFIED The bot score was not modified +USER_BOT_REQUIRED The method can be used by bots only \ No newline at end of file From 36681c8c5b8135144fc6546a33396e4781d2f5d4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 22:59:22 +0100 Subject: [PATCH 211/326] Update dev version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 7330001d..db3d8674 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2019 Dan Tès Date: Tue, 8 Jan 2019 14:28:52 +0100 Subject: [PATCH 212/326] Allow phone_number, phone_code and password to also be functions Also add recovery_code References #163 --- pyrogram/client/client.py | 161 ++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 66 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index bd72c582..7b5c1aea 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -111,18 +111,28 @@ class Client(Methods, BaseClient): Only applicable for new sessions and will be ignored in case previously created sessions are loaded. - phone_number (``str``, *optional*): - Pass your phone number (with your Country Code prefix included) to avoid - entering it manually. Only applicable for new sessions. + phone_number (``str`` | ``callable``, *optional*): + Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually. + Or pass a callback function which accepts no arguments and must return the correct phone number as string + (e.g., "391234567890"). + Only applicable for new sessions. phone_code (``str`` | ``callable``, *optional*): - Pass the phone code as string (for test numbers only), or pass a callback function which accepts - a single positional argument *(phone_number)* and must return the correct phone code (e.g., "12345"). + Pass the phone code as string (for test numbers only) to avoid entering it manually. Or pass a callback + function which accepts a single positional argument *(phone_number)* and must return the correct phone code + as string (e.g., "12345"). Only applicable for new sessions. password (``str``, *optional*): - Pass your Two-Step Verification password (if you have one) to avoid entering it - manually. Only applicable for new sessions. + Pass your Two-Step Verification password as string (if you have one) to avoid entering it manually. + Or pass a callback function which accepts a single positional argument *(password_hint)* and must return + the correct password as string (e.g., "password"). + Only applicable for new sessions. + + recovery_code (``callable``, *optional*): + Pass a callback function which accepts a single positional argument *(email_pattern)* and must return the + correct password recovery code as string (e.g., "987654"). + Only applicable for new sessions. force_sms (``str``, *optional*): Pass True to force Telegram sending the authorization code via SMS. @@ -180,6 +190,7 @@ class Client(Methods, BaseClient): phone_number: str = None, phone_code: Union[str, callable] = None, password: str = None, + recovery_code: callable = None, force_sms: bool = False, first_name: str = None, last_name: str = None, @@ -205,6 +216,7 @@ class Client(Methods, BaseClient): self.phone_number = phone_number self.phone_code = phone_code self.password = password + self.recovery_code = recovery_code self.force_sms = force_sms self.first_name = first_name self.last_name = last_name @@ -470,20 +482,25 @@ class Client(Methods, BaseClient): def authorize_user(self): phone_number_invalid_raises = self.phone_number is not None phone_code_invalid_raises = self.phone_code is not None - password_hash_invalid_raises = self.password is not None + password_invalid_raises = self.password is not None first_name_invalid_raises = self.first_name is not None + def default_phone_number_callback(): + while True: + phone_number = input("Enter phone number: ") + confirm = input("Is \"{}\" correct? (y/n): ".format(phone_number)) + + if confirm in ("y", "1"): + return phone_number + elif confirm in ("n", "2"): + continue + while True: - if self.phone_number is None: - self.phone_number = input("Enter phone number: ") - - while True: - confirm = input("Is \"{}\" correct? (y/n): ".format(self.phone_number)) - - if confirm in ("y", "1"): - break - elif confirm in ("n", "2"): - self.phone_number = input("Enter phone number: ") + self.phone_number = ( + default_phone_number_callback() if self.phone_number is None + else str(self.phone_number()) if callable(self.phone_number) + else str(self.phone_number) + ) self.phone_number = self.phone_number.strip("+") @@ -499,23 +516,21 @@ class Client(Methods, BaseClient): self.session.stop() self.dc_id = e.x - self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() + + self.auth_key = Auth( + self.dc_id, + self.test_mode, + self.ipv6, + self._proxy + ).create() self.session = Session( self, self.dc_id, self.auth_key ) - self.session.start() - r = self.send( - functions.auth.SendCode( - self.phone_number, - self.api_id, - self.api_hash - ) - ) - break + self.session.start() except (PhoneNumberInvalid, PhoneNumberBanned) as e: if phone_number_invalid_raises: raise @@ -530,6 +545,7 @@ class Client(Methods, BaseClient): time.sleep(e.x) except Exception as e: log.error(e, exc_info=True) + raise else: break @@ -549,10 +565,23 @@ class Client(Methods, BaseClient): ) while True: + if not phone_registered: + self.first_name = ( + input("First name: ") if self.first_name is None + else str(self.first_name) if callable(self.first_name) + else str(self.first_name) + ) + + self.last_name = ( + input("Last name: ") if self.last_name is None + else str(self.last_name) if callable(self.last_name) + else str(self.last_name) + ) + self.phone_code = ( input("Enter phone code: ") if self.phone_code is None - else self.phone_code if type(self.phone_code) is str - else str(self.phone_code(self.phone_number)) + else str(self.phone_code(self.phone_number)) if callable(self.phone_code) + else str(self.phone_code) ) try: @@ -570,9 +599,6 @@ class Client(Methods, BaseClient): phone_registered = False continue else: - self.first_name = self.first_name if self.first_name is not None else input("First name: ") - self.last_name = self.last_name if self.last_name is not None else input("Last name: ") - try: r = self.send( functions.auth.SignUp( @@ -602,60 +628,62 @@ class Client(Methods, BaseClient): except SessionPasswordNeeded as e: print(e.MESSAGE) + def default_password_callback(password_hint: str) -> str: + print("Hint: {}".format(password_hint)) + return input("Enter password (empty to recover): ") + + def default_recovery_callback(email_pattern: str) -> str: + print("An e-mail containing the recovery code has been sent to {}".format(email_pattern)) + return input("Enter password recovery code: ") + while True: try: r = self.send(functions.account.GetPassword()) - if self.password is None: - print("Hint: {}".format(r.hint)) + self.password = ( + default_password_callback(r.hint) if self.password is None + else str(self.password(r.hint) or "") if callable(self.password) + else str(self.password) + ) - self.password = input("Enter password (empty to recover): ") + if self.password == "": + r = self.send(functions.auth.RequestPasswordRecovery()) - if self.password == "": - r = self.send(functions.auth.RequestPasswordRecovery()) + self.recovery_code = ( + default_recovery_callback(r.email_pattern) if self.recovery_code is None + else str(self.recovery_code(r.email_pattern)) if callable(self.recovery_code) + else str(self.recovery_code) + ) - print("An e-mail containing the recovery code has been sent to {}".format( - r.email_pattern - )) - - r = self.send( - functions.auth.RecoverPassword( - code=input("Enter password recovery code: ") - ) + r = self.send( + functions.auth.RecoverPassword( + code=self.recovery_code ) - else: - r = self.send( - functions.auth.CheckPassword( - password=compute_check(r, self.password) - ) + ) + else: + r = self.send( + functions.auth.CheckPassword( + password=compute_check(r, self.password) ) - except PasswordEmpty as e: - if password_hash_invalid_raises: - raise - else: - print(e.MESSAGE) - self.password = None - except PasswordRecoveryNa as e: - if password_hash_invalid_raises: - raise - else: - print(e.MESSAGE) - self.password = None - except PasswordHashInvalid as e: - if password_hash_invalid_raises: + ) + except (PasswordEmpty, PasswordRecoveryNa, PasswordHashInvalid) as e: + if password_invalid_raises: raise else: print(e.MESSAGE) self.password = None + self.recovery_code = None except FloodWait as e: - if password_hash_invalid_raises: + if password_invalid_raises: raise else: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) self.password = None + self.recovery_code = None except Exception as e: log.error(e, exc_info=True) + raise else: break break @@ -667,6 +695,7 @@ class Client(Methods, BaseClient): time.sleep(e.x) except Exception as e: log.error(e, exc_info=True) + raise else: break From 19b8f648d2a7b18de7bf6446367e6001b53a7681 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 10 Jan 2019 18:22:37 +0100 Subject: [PATCH 213/326] Fix bad behaviours for Python <3.6 Pyrogram was relying on dict keys being "ordered" (keys keeping insertion order). --- pyrogram/client/methods/messages/send_message.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index c25ec570..6589fcd6 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -88,10 +88,18 @@ class SendMessage(BaseClient): ) if isinstance(r, types.UpdateShortSentMessage): + peer = self.resolve_peer(chat_id) + + peer_id = ( + peer.user_id + if isinstance(peer, types.InputPeerUser) + else -peer.chat_id + ) + return pyrogram.Message( message_id=r.id, chat=pyrogram.Chat( - id=list(self.resolve_peer(chat_id).__dict__.values())[0], + id=peer_id, type="private", client=self ), From 07276e31b96a029d8789dd98ede1407adc52f771 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 11 Jan 2019 12:36:37 +0100 Subject: [PATCH 214/326] Add restart method --- docs/source/pyrogram/Client.rst | 1 + pyrogram/client/client.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index 9a1b0509..f9e50d3e 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -13,6 +13,7 @@ Utilities start stop + restart idle run add_handler diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 7b5c1aea..61a3775b 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -374,6 +374,16 @@ class Client(Methods, BaseClient): return self + def restart(self): + """Use this method to restart the Client. + Requires no parameters. + + Raises: + ``ConnectionError`` in case you try to restart a stopped Client. + """ + self.stop() + self.start() + def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): """Blocks the program execution until one of the signals are received, then gently stop the Client by closing the underlying connection. From 1d8fd0b836184204d040f19ba5cb8d9b9674a58b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 11 Jan 2019 12:46:41 +0100 Subject: [PATCH 215/326] Make Filters.regex work on message captions too --- pyrogram/client/filters/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 77492c6a..c54960d3 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -287,7 +287,7 @@ class Filters: """ def f(_, m): - m.matches = [i for i in _.p.finditer(m.text or "")] + m.matches = [i for i in _.p.finditer(m.text or m.caption or "")] return bool(m.matches) return create("Regex", f, p=re.compile(pattern, flags)) From 161ab79eb356256b8041d78073f5ac47ebdf488c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 11 Jan 2019 12:51:01 +0100 Subject: [PATCH 216/326] Add Filters.media_group for photos or videos being part of an album. --- pyrogram/client/filters/filters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index c54960d3..57a21045 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -109,6 +109,9 @@ class Filters: video = create("Video", lambda _, m: bool(m.video)) """Filter messages that contain :obj:`Video ` objects.""" + media_group = create("MediaGroup", lambda _, m: bool(m.media_group_id)) + """Filter messages containing photos or videos being part of an album.""" + voice = create("Voice", lambda _, m: bool(m.voice)) """Filter messages that contain :obj:`Voice ` note objects.""" From 200ed844fe24333a4ac21f2b98fa0a845a103d04 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 11 Jan 2019 13:02:19 +0100 Subject: [PATCH 217/326] Fix first_name and last_name not being called if they are callable --- pyrogram/client/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 61a3775b..2a3a420d 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -578,13 +578,13 @@ class Client(Methods, BaseClient): if not phone_registered: self.first_name = ( input("First name: ") if self.first_name is None - else str(self.first_name) if callable(self.first_name) + else str(self.first_name()) if callable(self.first_name) else str(self.first_name) ) self.last_name = ( input("Last name: ") if self.last_name is None - else str(self.last_name) if callable(self.last_name) + else str(self.last_name()) if callable(self.last_name) else str(self.last_name) ) From d5ed47f4e963700bc2a9d425e31a86ecce57a1b7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 11 Jan 2019 13:59:18 +0100 Subject: [PATCH 218/326] Fix Message.download() not working when using the progress callback --- pyrogram/client/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index a323898c..badd3689 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -912,7 +912,7 @@ class Message(PyrogramType, Update): else: raise ValueError("The message doesn't contain any keyboard") - def download(self, file_name: str = "", block: bool = True, progress: callable = None, progress_args: tuple = None): + def download(self, file_name: str = "", block: bool = True, progress: callable = None, progress_args: tuple = ()): """Bound method *download* of :obj:`Message `. Use as a shortcut for: From c28b9f9a2cbb9db8602e798bc68cd725f0b9cfd7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 11 Jan 2019 14:00:03 +0100 Subject: [PATCH 219/326] Add StopTransmission custom exception Useful for stopping up/downloads after they started --- pyrogram/client/ext/base_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index aaed5786..7b94ae6e 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -27,6 +27,9 @@ from ...session.internals import MsgId class BaseClient: + class StopTransmission(StopIteration): + pass + APP_VERSION = "Pyrogram \U0001f525 {}".format(__version__) DEVICE_MODEL = "{} {}".format( From 6b63e88de7e42ebfc1aa20433154750d82533ce5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 11 Jan 2019 14:02:40 +0100 Subject: [PATCH 220/326] Add Client.stop_transmission() method As a wrapper for raise StopTransmission --- pyrogram/client/client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 2a3a420d..b8e310a5 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -460,6 +460,12 @@ class Client(Methods, BaseClient): else: self.dispatcher.remove_handler(handler, group) + def stop_transmission(self): + """Use this method to stop downloading or uploading a file. + Must be called inside a progress callback function. + """ + raise Client.StopTransmission + def authorize_bot(self): try: r = self.send( From b37d4dc7ec78d0010c6ab35900244d40649a7299 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 11 Jan 2019 14:03:16 +0100 Subject: [PATCH 221/326] Make get_file and save_file handle StopTransmission errors --- pyrogram/client/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index b8e310a5..2912072d 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1368,6 +1368,8 @@ class Client(Methods, BaseClient): if progress: progress(self, min(file_part * part_size, file_size), file_size, *progress_args) + except Client.StopTransmission: + raise except Exception as e: log.error(e, exc_info=True) else: @@ -1569,7 +1571,8 @@ class Client(Methods, BaseClient): except Exception as e: raise e except Exception as e: - log.error(e, exc_info=True) + if not isinstance(e, Client.StopTransmission): + log.error(e, exc_info=True) try: os.remove(file_name) From 2791600926285043128729c9357473b780fab47e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 11 Jan 2019 14:12:53 +0100 Subject: [PATCH 222/326] Hint about the returned value in case of stopped downloads --- pyrogram/client/methods/messages/download_media.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index cfdcfce7..181daa14 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -72,6 +72,7 @@ class DownloadMedia(BaseClient): Returns: On success, the absolute path of the downloaded file as string is returned, None otherwise. + In case the download is deliberately stopped with :meth:`stop_transmission`, None is returned as well. Raises: :class:`Error ` in case of a Telegram RPC error. From 4e02cd23a8564cb6ac8a112684561feaafc8a5ed Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 11 Jan 2019 14:13:23 +0100 Subject: [PATCH 223/326] Make all send_* methods dealing with files aware of StopTransmission --- .../client/methods/messages/send_animation.py | 136 +++++++++--------- .../client/methods/messages/send_audio.py | 132 ++++++++--------- .../client/methods/messages/send_document.py | 122 ++++++++-------- .../client/methods/messages/send_photo.py | 114 ++++++++------- .../client/methods/messages/send_sticker.py | 118 +++++++-------- .../client/methods/messages/send_video.py | 134 ++++++++--------- .../methods/messages/send_video_note.py | 130 +++++++++-------- .../client/methods/messages/send_voice.py | 130 +++++++++-------- 8 files changed, 524 insertions(+), 492 deletions(-) diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 08b69c17..5b27c914 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -45,7 +45,7 @@ class SendAnimation(BaseClient): "pyrogram.ReplyKeyboardRemove", "pyrogram.ForceReply"] = None, progress: callable = None, - progress_args: tuple = ()) -> "pyrogram.Message": + progress_args: tuple = ()) -> Union["pyrogram.Message", None]: """Use this method to send animation files (animation or H.264/MPEG-4 AVC video without sound). Args: @@ -119,6 +119,7 @@ class SendAnimation(BaseClient): Returns: On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. Raises: :class:`Error ` in case of a Telegram RPC error. @@ -126,72 +127,75 @@ class SendAnimation(BaseClient): file = None style = self.html if parse_mode.lower() == "html" else self.markdown - if os.path.exists(animation): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(animation, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map[".mp4"], - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeVideo( - supports_streaming=True, - duration=duration, - w=width, - h=height - ), - types.DocumentAttributeFilename(os.path.basename(animation)), - types.DocumentAttributeAnimated() - ] - ) - elif animation.startswith("http"): - media = types.InputMediaDocumentExternal( - url=animation - ) - else: - try: - decoded = utils.decode(animation) - fmt = " 24 else " 24 else " "pyrogram.Message": + progress_args: tuple = ()) -> Union["pyrogram.Message", None]: """Use this method to send audio files. For sending voice messages, use the :obj:`send_voice()` method instead. @@ -121,6 +121,7 @@ class SendAudio(BaseClient): Returns: On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. Raises: :class:`Error ` in case of a Telegram RPC error. @@ -128,70 +129,73 @@ class SendAudio(BaseClient): file = None style = self.html if parse_mode.lower() == "html" else self.markdown - if os.path.exists(audio): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(audio, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"), - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeAudio( - duration=duration, - performer=performer, - title=title - ), - types.DocumentAttributeFilename(os.path.basename(audio)) - ] - ) - elif audio.startswith("http"): - media = types.InputMediaDocumentExternal( - url=audio - ) - else: - try: - decoded = utils.decode(audio) - fmt = " 24 else " 24 else " "pyrogram.Message": + progress_args: tuple = ()) -> Union["pyrogram.Message", None]: """Use this method to send general files. Args: @@ -107,6 +107,7 @@ class SendDocument(BaseClient): Returns: On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. Raises: :class:`Error ` in case of a Telegram RPC error. @@ -114,65 +115,68 @@ class SendDocument(BaseClient): file = None style = self.html if parse_mode.lower() == "html" else self.markdown - if os.path.exists(document): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(document, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"), - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeFilename(os.path.basename(document)) - ] - ) - elif document.startswith("http"): - media = types.InputMediaDocumentExternal( - url=document - ) - else: - try: - decoded = utils.decode(document) - fmt = " 24 else " 24 else " "pyrogram.Message": + progress_args: tuple = ()) -> Union["pyrogram.Message", None]: """Use this method to send photos. Args: @@ -105,6 +105,7 @@ class SendPhoto(BaseClient): Returns: On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. Raises: :class:`Error ` in case of a Telegram RPC error. @@ -112,62 +113,65 @@ class SendPhoto(BaseClient): file = None style = self.html if parse_mode.lower() == "html" else self.markdown - if os.path.exists(photo): - file = self.save_file(photo, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedPhoto( - file=file, - ttl_seconds=ttl_seconds - ) - elif photo.startswith("http"): - media = types.InputMediaPhotoExternal( - url=photo, - ttl_seconds=ttl_seconds - ) - else: - try: - decoded = utils.decode(photo) - fmt = " 24 else " 24 else " "pyrogram.Message": + progress_args: tuple = ()) -> Union["pyrogram.Message", None]: """Use this method to send .webp stickers. Args: @@ -89,69 +89,73 @@ class SendSticker(BaseClient): Returns: On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. Raises: :class:`Error ` in case of a Telegram RPC error. """ file = None - if os.path.exists(sticker): - file = self.save_file(sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type="image/webp", - file=file, - attributes=[ - types.DocumentAttributeFilename(os.path.basename(sticker)) - ] - ) - elif sticker.startswith("http"): - media = types.InputMediaDocumentExternal( - url=sticker - ) - else: - try: - decoded = utils.decode(sticker) - fmt = " 24 else " 24 else " "pyrogram.Message": + progress_args: tuple = ()) -> Union["pyrogram.Message", None]: """Use this method to send video files. Args: @@ -123,6 +123,7 @@ class SendVideo(BaseClient): Returns: On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. Raises: :class:`Error ` in case of a Telegram RPC error. @@ -130,71 +131,74 @@ class SendVideo(BaseClient): file = None style = self.html if parse_mode.lower() == "html" else self.markdown - if os.path.exists(video): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(video, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map[".mp4"], - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeVideo( - supports_streaming=supports_streaming or None, - duration=duration, - w=width, - h=height - ), - types.DocumentAttributeFilename(os.path.basename(video)) - ] - ) - elif video.startswith("http"): - media = types.InputMediaDocumentExternal( - url=video - ) - else: - try: - decoded = utils.decode(video) - fmt = " 24 else " 24 else " "pyrogram.Message": + progress_args: tuple = ()) -> Union["pyrogram.Message", None]: """Use this method to send video messages. Args: @@ -105,72 +105,76 @@ class SendVideoNote(BaseClient): Returns: On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. Raises: :class:`Error ` in case of a Telegram RPC error. """ file = None - if os.path.exists(video_note): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(video_note, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map[".mp4"], - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeVideo( - round_message=True, - duration=duration, - w=length, - h=length - ) - ] - ) - else: - try: - decoded = utils.decode(video_note) - fmt = " 24 else " 24 else " "pyrogram.Message": + progress_args: tuple = ()) -> Union["pyrogram.Message", None]: """Use this method to send audio files. Args: @@ -104,6 +104,7 @@ class SendVoice(BaseClient): Returns: On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. Raises: :class:`Error ` in case of a Telegram RPC error. @@ -111,66 +112,69 @@ class SendVoice(BaseClient): file = None style = self.html if parse_mode.lower() == "html" else self.markdown - if os.path.exists(voice): - file = self.save_file(voice, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map.get("." + voice.split(".")[-1], "audio/mpeg"), - file=file, - attributes=[ - types.DocumentAttributeAudio( - voice=True, - duration=duration - ) - ] - ) - elif voice.startswith("http"): - media = types.InputMediaDocumentExternal( - url=voice - ) - else: - try: - decoded = utils.decode(voice) - fmt = " 24 else " 24 else " Date: Fri, 11 Jan 2019 14:20:01 +0100 Subject: [PATCH 224/326] Add stop_transmission to docs --- docs/source/pyrogram/Client.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index f9e50d3e..548f1c5b 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -21,6 +21,7 @@ Utilities send resolve_peer save_file + stop_transmission Decorators ---------- From 4cf1208c96d72c76b8f5cb47874418b834cb2cb8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 13 Jan 2019 06:52:25 +0100 Subject: [PATCH 225/326] Update media caption maximum length --- compiler/error/source/400_BAD_REQUEST.tsv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 0f174c66..82474096 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -62,7 +62,7 @@ USER_IS_BOT A bot cannot send messages to other bots or to itself WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL STICKERSET_INVALID The requested sticker set is invalid PEER_FLOOD The method can't be used because your account is limited -MEDIA_CAPTION_TOO_LONG The media caption is longer than 200 characters +MEDIA_CAPTION_TOO_LONG The media caption is longer than 1024 characters USER_NOT_MUTUAL_CONTACT The user is not a mutual contact USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side @@ -86,4 +86,4 @@ TAKEOUT_REQUIRED The method must be invoked inside a takeout session MESSAGE_POLL_CLOSED You can't interact with a closed poll MEDIA_INVALID The media is invalid BOT_SCORE_NOT_MODIFIED The bot score was not modified -USER_BOT_REQUIRED The method can be used by bots only \ No newline at end of file +USER_BOT_REQUIRED The method can be used by bots only From 6df7788379f6a63aa1248ca27a6a469fce9f03ee Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 16 Jan 2019 13:10:01 +0100 Subject: [PATCH 226/326] Enhance proxy settings - Allow proxy settings to omit "enabled" key - Allow setting proxy to None in order to disable it --- pyrogram/client/client.py | 1585 +------------------------------------ 1 file changed, 1 insertion(+), 1584 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 2912072d..5d5bf945 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1,1584 +1 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2019 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import base64 -import binascii -import json -import logging -import math -import mimetypes -import os -import re -import shutil -import struct -import tempfile -import threading -import time -from configparser import ConfigParser -from datetime import datetime -from hashlib import sha256, md5 -from importlib import import_module -from pathlib import Path -from signal import signal, SIGINT, SIGTERM, SIGABRT -from threading import Thread -from typing import Union, List - -from pyrogram.api import functions, types -from pyrogram.api.core import Object -from pyrogram.api.errors import ( - PhoneMigrate, NetworkMigrate, PhoneNumberInvalid, - PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty, - PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, - PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, - VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied, - PasswordRecoveryNa, PasswordEmpty -) -from pyrogram.client.handlers import DisconnectHandler -from pyrogram.client.handlers.handler import Handler -from pyrogram.client.methods.password.utils import compute_check -from pyrogram.crypto import AES -from pyrogram.session import Auth, Session -from .dispatcher import Dispatcher -from .ext import utils, Syncer, BaseClient -from .methods import Methods - -log = logging.getLogger(__name__) - - -class Client(Methods, BaseClient): - """This class represents a Client, the main mean for interacting with Telegram. - It exposes bot-like methods for an easy access to the API as well as a simple way to - invoke every single Telegram API method available. - - Args: - session_name (``str``): - Name to uniquely identify a session of either a User or a Bot. - For Users: pass a string of your choice, e.g.: "my_main_account". - For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" - Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number. - - api_id (``int``, *optional*): - The *api_id* part of your Telegram API Key, as integer. E.g.: 12345 - This is an alternative way to pass it if you don't want to use the *config.ini* file. - - api_hash (``str``, *optional*): - The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef" - This is an alternative way to pass it if you don't want to use the *config.ini* file. - - app_version (``str``, *optional*): - Application version. Defaults to "Pyrogram \U0001f525 vX.Y.Z" - This is an alternative way to set it if you don't want to use the *config.ini* file. - - device_model (``str``, *optional*): - Device model. Defaults to *platform.python_implementation() + " " + platform.python_version()* - This is an alternative way to set it if you don't want to use the *config.ini* file. - - system_version (``str``, *optional*): - Operating System version. Defaults to *platform.system() + " " + platform.release()* - This is an alternative way to set it if you don't want to use the *config.ini* file. - - lang_code (``str``, *optional*): - Code of the language used on the client, in ISO 639-1 standard. Defaults to "en". - This is an alternative way to set it if you don't want to use the *config.ini* file. - - ipv6 (``bool``, *optional*): - Pass True to connect to Telegram using IPv6. - Defaults to False (IPv4). - - proxy (``dict``, *optional*): - Your SOCKS5 Proxy settings as dict, - e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*. - *username* and *password* can be omitted if your proxy doesn't require authorization. - This is an alternative way to setup a proxy if you don't want to use the *config.ini* file. - - test_mode (``bool``, *optional*): - Enable or disable log-in to testing servers. Defaults to False. - Only applicable for new sessions and will be ignored in case previously - created sessions are loaded. - - phone_number (``str`` | ``callable``, *optional*): - Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually. - Or pass a callback function which accepts no arguments and must return the correct phone number as string - (e.g., "391234567890"). - Only applicable for new sessions. - - phone_code (``str`` | ``callable``, *optional*): - Pass the phone code as string (for test numbers only) to avoid entering it manually. Or pass a callback - function which accepts a single positional argument *(phone_number)* and must return the correct phone code - as string (e.g., "12345"). - Only applicable for new sessions. - - password (``str``, *optional*): - Pass your Two-Step Verification password as string (if you have one) to avoid entering it manually. - Or pass a callback function which accepts a single positional argument *(password_hint)* and must return - the correct password as string (e.g., "password"). - Only applicable for new sessions. - - recovery_code (``callable``, *optional*): - Pass a callback function which accepts a single positional argument *(email_pattern)* and must return the - correct password recovery code as string (e.g., "987654"). - Only applicable for new sessions. - - force_sms (``str``, *optional*): - Pass True to force Telegram sending the authorization code via SMS. - Only applicable for new sessions. - - first_name (``str``, *optional*): - Pass a First Name to avoid entering it manually. It will be used to automatically - create a new Telegram account in case the phone number you passed is not registered yet. - Only applicable for new sessions. - - last_name (``str``, *optional*): - Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can - be an empty string: "". Only applicable for new sessions. - - workers (``int``, *optional*): - Thread pool size for handling incoming updates. Defaults to 4. - - workdir (``str``, *optional*): - Define a custom working directory. The working directory is the location in your filesystem - where Pyrogram will store your session files. Defaults to "." (current directory). - - config_file (``str``, *optional*): - Path of the configuration file. Defaults to ./config.ini - - plugins_dir (``str``, *optional*): - Define a custom directory for your plugins. The plugins directory is the location in your - filesystem where Pyrogram will automatically load your update handlers. - Defaults to None (plugins disabled). - - no_updates (``bool``, *optional*): - Pass True to completely disable incoming updates for the current session. - When updates are disabled your client can't receive any new message. - Useful for batch programs that don't need to deal with updates. - Defaults to False (updates enabled and always received). - - takeout (``bool``, *optional*): - Pass True to let the client use a takeout session instead of a normal one, implies no_updates. - Useful for exporting your Telegram data. Methods invoked inside a takeout session (such as get_history, - download_media, ...) are less prone to throw FloodWait exceptions. - Only available for users, bots will ignore this parameter. - Defaults to False (normal session). - """ - - def __init__(self, - session_name: str, - api_id: Union[int, str] = None, - api_hash: str = None, - app_version: str = None, - device_model: str = None, - system_version: str = None, - lang_code: str = None, - ipv6: bool = False, - proxy: dict = None, - test_mode: bool = False, - phone_number: str = None, - phone_code: Union[str, callable] = None, - password: str = None, - recovery_code: callable = None, - force_sms: bool = False, - first_name: str = None, - last_name: str = None, - workers: int = BaseClient.WORKERS, - workdir: str = BaseClient.WORKDIR, - config_file: str = BaseClient.CONFIG_FILE, - plugins_dir: str = None, - no_updates: bool = None, - takeout: bool = None): - super().__init__() - - self.session_name = session_name - self.api_id = int(api_id) if api_id else None - self.api_hash = api_hash - self.app_version = app_version - self.device_model = device_model - self.system_version = system_version - self.lang_code = lang_code - self.ipv6 = ipv6 - # TODO: Make code consistent, use underscore for private/protected fields - self._proxy = proxy - self.test_mode = test_mode - self.phone_number = phone_number - self.phone_code = phone_code - self.password = password - self.recovery_code = recovery_code - self.force_sms = force_sms - self.first_name = first_name - self.last_name = last_name - self.workers = workers - self.workdir = workdir - self.config_file = config_file - self.plugins_dir = plugins_dir - self.no_updates = no_updates - self.takeout = takeout - - self.dispatcher = Dispatcher(self, workers) - - def __enter__(self): - return self.start() - - def __exit__(self, *args): - self.stop() - - @property - def proxy(self): - return self._proxy - - @proxy.setter - def proxy(self, value): - self._proxy["enabled"] = True - self._proxy.update(value) - - def start(self): - """Use this method to start the Client after creating it. - Requires no parameters. - - Raises: - :class:`Error ` in case of a Telegram RPC error. - ``ConnectionError`` in case you try to start an already started Client. - """ - if self.is_started: - raise ConnectionError("Client has already been started") - - if self.BOT_TOKEN_RE.match(self.session_name): - self.bot_token = self.session_name - self.session_name = self.session_name.split(":")[0] - - self.load_config() - self.load_session() - self.load_plugins() - - self.session = Session( - self, - self.dc_id, - self.auth_key - ) - - self.session.start() - self.is_started = True - - try: - if self.user_id is None: - if self.bot_token is None: - self.authorize_user() - else: - self.authorize_bot() - - self.save_session() - - if self.bot_token is None: - if self.takeout: - self.takeout_id = self.send(functions.account.InitTakeoutSession()).id - log.warning("Takeout session {} initiated".format(self.takeout_id)) - - now = time.time() - - if abs(now - self.date) > Client.OFFLINE_SLEEP: - self.peers_by_username = {} - self.peers_by_phone = {} - - self.get_initial_dialogs() - self.get_contacts() - else: - self.send(functions.messages.GetPinnedDialogs()) - self.get_initial_dialogs_chunk() - else: - self.send(functions.updates.GetState()) - except Exception as e: - self.is_started = False - self.session.stop() - raise e - - for i in range(self.UPDATES_WORKERS): - self.updates_workers_list.append( - Thread( - target=self.updates_worker, - name="UpdatesWorker#{}".format(i + 1) - ) - ) - - self.updates_workers_list[-1].start() - - for i in range(self.DOWNLOAD_WORKERS): - self.download_workers_list.append( - Thread( - target=self.download_worker, - name="DownloadWorker#{}".format(i + 1) - ) - ) - - self.download_workers_list[-1].start() - - self.dispatcher.start() - - mimetypes.init() - Syncer.add(self) - - return self - - def stop(self): - """Use this method to manually stop the Client. - Requires no parameters. - - Raises: - ``ConnectionError`` in case you try to stop an already stopped Client. - """ - if not self.is_started: - raise ConnectionError("Client is already stopped") - - if self.takeout_id: - self.send(functions.account.FinishTakeoutSession()) - log.warning("Takeout session {} finished".format(self.takeout_id)) - - Syncer.remove(self) - self.dispatcher.stop() - - for _ in range(self.DOWNLOAD_WORKERS): - self.download_queue.put(None) - - for i in self.download_workers_list: - i.join() - - self.download_workers_list.clear() - - for _ in range(self.UPDATES_WORKERS): - self.updates_queue.put(None) - - for i in self.updates_workers_list: - i.join() - - self.updates_workers_list.clear() - - for i in self.media_sessions.values(): - i.stop() - - self.media_sessions.clear() - - self.is_started = False - self.session.stop() - - return self - - def restart(self): - """Use this method to restart the Client. - Requires no parameters. - - Raises: - ``ConnectionError`` in case you try to restart a stopped Client. - """ - self.stop() - self.start() - - def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): - """Blocks the program execution until one of the signals are received, - then gently stop the Client by closing the underlying connection. - - Args: - stop_signals (``tuple``, *optional*): - Iterable containing signals the signal handler will listen to. - Defaults to (SIGINT, SIGTERM, SIGABRT). - """ - - def signal_handler(*args): - self.is_idle = False - - for s in stop_signals: - signal(s, signal_handler) - - self.is_idle = True - - while self.is_idle: - time.sleep(1) - - self.stop() - - def run(self): - """Use this method to automatically start and idle a Client. - Requires no parameters. - - Raises: - :class:`Error ` in case of a Telegram RPC error. - """ - self.start() - self.idle() - - def add_handler(self, handler: Handler, group: int = 0): - """Use this method to register an update handler. - - You can register multiple handlers, but at most one handler within a group - will be used for a single update. To handle the same update more than once, register - your handler using a different group id (lower group id == higher priority). - - Args: - handler (``Handler``): - The handler to be registered. - - group (``int``, *optional*): - The group identifier, defaults to 0. - - Returns: - A tuple of (handler, group) - """ - if isinstance(handler, DisconnectHandler): - self.disconnect_handler = handler.callback - else: - self.dispatcher.add_handler(handler, group) - - return handler, group - - def remove_handler(self, handler: Handler, group: int = 0): - """Removes a previously-added update handler. - - Make sure to provide the right group that the handler was added in. You can use - the return value of the :meth:`add_handler` method, a tuple of (handler, group), and - pass it directly. - - Args: - handler (``Handler``): - The handler to be removed. - - group (``int``, *optional*): - The group identifier, defaults to 0. - """ - if isinstance(handler, DisconnectHandler): - self.disconnect_handler = None - else: - self.dispatcher.remove_handler(handler, group) - - def stop_transmission(self): - """Use this method to stop downloading or uploading a file. - Must be called inside a progress callback function. - """ - raise Client.StopTransmission - - def authorize_bot(self): - try: - r = self.send( - functions.auth.ImportBotAuthorization( - flags=0, - api_id=self.api_id, - api_hash=self.api_hash, - bot_auth_token=self.bot_token - ) - ) - except UserMigrate as e: - self.session.stop() - - self.dc_id = e.x - self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() - - self.session = Session( - self, - self.dc_id, - self.auth_key - ) - - self.session.start() - self.authorize_bot() - else: - self.user_id = r.user.id - - print("Logged in successfully as @{}".format(r.user.username)) - - def authorize_user(self): - phone_number_invalid_raises = self.phone_number is not None - phone_code_invalid_raises = self.phone_code is not None - password_invalid_raises = self.password is not None - first_name_invalid_raises = self.first_name is not None - - def default_phone_number_callback(): - while True: - phone_number = input("Enter phone number: ") - confirm = input("Is \"{}\" correct? (y/n): ".format(phone_number)) - - if confirm in ("y", "1"): - return phone_number - elif confirm in ("n", "2"): - continue - - while True: - self.phone_number = ( - default_phone_number_callback() if self.phone_number is None - else str(self.phone_number()) if callable(self.phone_number) - else str(self.phone_number) - ) - - self.phone_number = self.phone_number.strip("+") - - try: - r = self.send( - functions.auth.SendCode( - self.phone_number, - self.api_id, - self.api_hash - ) - ) - except (PhoneMigrate, NetworkMigrate) as e: - self.session.stop() - - self.dc_id = e.x - - self.auth_key = Auth( - self.dc_id, - self.test_mode, - self.ipv6, - self._proxy - ).create() - - self.session = Session( - self, - self.dc_id, - self.auth_key - ) - - self.session.start() - except (PhoneNumberInvalid, PhoneNumberBanned) as e: - if phone_number_invalid_raises: - raise - else: - print(e.MESSAGE) - self.phone_number = None - except FloodWait as e: - if phone_number_invalid_raises: - raise - else: - print(e.MESSAGE.format(x=e.x)) - time.sleep(e.x) - except Exception as e: - log.error(e, exc_info=True) - raise - else: - break - - phone_registered = r.phone_registered - phone_code_hash = r.phone_code_hash - terms_of_service = r.terms_of_service - - if terms_of_service: - print("\n" + terms_of_service.text + "\n") - - if self.force_sms: - self.send( - functions.auth.ResendCode( - phone_number=self.phone_number, - phone_code_hash=phone_code_hash - ) - ) - - while True: - if not phone_registered: - self.first_name = ( - input("First name: ") if self.first_name is None - else str(self.first_name()) if callable(self.first_name) - else str(self.first_name) - ) - - self.last_name = ( - input("Last name: ") if self.last_name is None - else str(self.last_name()) if callable(self.last_name) - else str(self.last_name) - ) - - self.phone_code = ( - input("Enter phone code: ") if self.phone_code is None - else str(self.phone_code(self.phone_number)) if callable(self.phone_code) - else str(self.phone_code) - ) - - try: - if phone_registered: - try: - r = self.send( - functions.auth.SignIn( - self.phone_number, - phone_code_hash, - self.phone_code - ) - ) - except PhoneNumberUnoccupied: - log.warning("Phone number unregistered") - phone_registered = False - continue - else: - try: - r = self.send( - functions.auth.SignUp( - self.phone_number, - phone_code_hash, - self.phone_code, - self.first_name, - self.last_name - ) - ) - except PhoneNumberOccupied: - log.warning("Phone number already registered") - phone_registered = True - continue - except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e: - if phone_code_invalid_raises: - raise - else: - print(e.MESSAGE) - self.phone_code = None - except FirstnameInvalid as e: - if first_name_invalid_raises: - raise - else: - print(e.MESSAGE) - self.first_name = None - except SessionPasswordNeeded as e: - print(e.MESSAGE) - - def default_password_callback(password_hint: str) -> str: - print("Hint: {}".format(password_hint)) - return input("Enter password (empty to recover): ") - - def default_recovery_callback(email_pattern: str) -> str: - print("An e-mail containing the recovery code has been sent to {}".format(email_pattern)) - return input("Enter password recovery code: ") - - while True: - try: - r = self.send(functions.account.GetPassword()) - - self.password = ( - default_password_callback(r.hint) if self.password is None - else str(self.password(r.hint) or "") if callable(self.password) - else str(self.password) - ) - - if self.password == "": - r = self.send(functions.auth.RequestPasswordRecovery()) - - self.recovery_code = ( - default_recovery_callback(r.email_pattern) if self.recovery_code is None - else str(self.recovery_code(r.email_pattern)) if callable(self.recovery_code) - else str(self.recovery_code) - ) - - r = self.send( - functions.auth.RecoverPassword( - code=self.recovery_code - ) - ) - else: - r = self.send( - functions.auth.CheckPassword( - password=compute_check(r, self.password) - ) - ) - except (PasswordEmpty, PasswordRecoveryNa, PasswordHashInvalid) as e: - if password_invalid_raises: - raise - else: - print(e.MESSAGE) - self.password = None - self.recovery_code = None - except FloodWait as e: - if password_invalid_raises: - raise - else: - print(e.MESSAGE.format(x=e.x)) - time.sleep(e.x) - self.password = None - self.recovery_code = None - except Exception as e: - log.error(e, exc_info=True) - raise - else: - break - break - except FloodWait as e: - if phone_code_invalid_raises or first_name_invalid_raises: - raise - else: - print(e.MESSAGE.format(x=e.x)) - time.sleep(e.x) - except Exception as e: - log.error(e, exc_info=True) - raise - else: - break - - if terms_of_service: - assert self.send(functions.help.AcceptTermsOfService(terms_of_service.id)) - - self.password = None - self.user_id = r.user.id - - print("Logged in successfully as {}".format(r.user.first_name)) - - def fetch_peers(self, entities: List[Union[types.User, - types.Chat, types.ChatForbidden, - types.Channel, types.ChannelForbidden]]): - for entity in entities: - if isinstance(entity, types.User): - user_id = entity.id - - access_hash = entity.access_hash - - if access_hash is None: - continue - - username = entity.username - phone = entity.phone - - input_peer = types.InputPeerUser( - user_id=user_id, - access_hash=access_hash - ) - - self.peers_by_id[user_id] = input_peer - - if username is not None: - self.peers_by_username[username.lower()] = input_peer - - if phone is not None: - self.peers_by_phone[phone] = input_peer - - if isinstance(entity, (types.Chat, types.ChatForbidden)): - chat_id = entity.id - peer_id = -chat_id - - input_peer = types.InputPeerChat( - chat_id=chat_id - ) - - self.peers_by_id[peer_id] = input_peer - - if isinstance(entity, (types.Channel, types.ChannelForbidden)): - channel_id = entity.id - peer_id = int("-100" + str(channel_id)) - - access_hash = entity.access_hash - - if access_hash is None: - continue - - username = getattr(entity, "username", None) - - input_peer = types.InputPeerChannel( - channel_id=channel_id, - access_hash=access_hash - ) - - self.peers_by_id[peer_id] = input_peer - - if username is not None: - self.peers_by_username[username.lower()] = input_peer - - def download_worker(self): - name = threading.current_thread().name - log.debug("{} started".format(name)) - - while True: - media = self.download_queue.get() - - if media is None: - break - - temp_file_path = "" - final_file_path = "" - - try: - media, file_name, done, progress, progress_args, path = media - - file_id = media.file_id - size = media.file_size - - directory, file_name = os.path.split(file_name) - directory = directory or "downloads" - - try: - decoded = utils.decode(file_id) - fmt = " 24 else " 24: - volume_id = unpacked[4] - secret = unpacked[5] - local_id = unpacked[6] - - media_type_str = Client.MEDIA_TYPE_ID.get(media_type, None) - - if media_type_str is None: - raise FileIdInvalid("Unknown media type: {}".format(unpacked[0])) - - file_name = file_name or getattr(media, "file_name", None) - - if not file_name: - if media_type == 3: - extension = ".ogg" - elif media_type in (4, 10, 13): - extension = mimetypes.guess_extension(media.mime_type) or ".mp4" - elif media_type == 5: - extension = mimetypes.guess_extension(media.mime_type) or ".unknown" - elif media_type == 8: - extension = ".webp" - elif media_type == 9: - extension = mimetypes.guess_extension(media.mime_type) or ".mp3" - elif media_type in (0, 1, 2): - extension = ".jpg" - else: - continue - - file_name = "{}_{}_{}{}".format( - media_type_str, - datetime.fromtimestamp( - getattr(media, "date", None) or time.time() - ).strftime("%Y-%m-%d_%H-%M-%S"), - self.rnd_id(), - extension - ) - - temp_file_path = self.get_file( - dc_id=dc_id, - id=id, - access_hash=access_hash, - volume_id=volume_id, - local_id=local_id, - secret=secret, - size=size, - progress=progress, - progress_args=progress_args - ) - - if temp_file_path: - final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) - os.makedirs(directory, exist_ok=True) - shutil.move(temp_file_path, final_file_path) - except Exception as e: - log.error(e, exc_info=True) - - try: - os.remove(temp_file_path) - except OSError: - pass - else: - # TODO: "" or None for faulty download, which is better? - # os.path methods return "" in case something does not exist, I prefer this. - # For now let's keep None - path[0] = final_file_path or None - finally: - done.set() - - log.debug("{} stopped".format(name)) - - def updates_worker(self): - name = threading.current_thread().name - log.debug("{} started".format(name)) - - while True: - updates = self.updates_queue.get() - - if updates is None: - break - - try: - if isinstance(updates, (types.Update, types.UpdatesCombined)): - self.fetch_peers(updates.users) - self.fetch_peers(updates.chats) - - for update in updates.updates: - channel_id = getattr( - getattr( - getattr( - update, "message", None - ), "to_id", None - ), "channel_id", None - ) or getattr(update, "channel_id", None) - - pts = getattr(update, "pts", None) - pts_count = getattr(update, "pts_count", None) - - if isinstance(update, types.UpdateChannelTooLong): - log.warning(update) - - if isinstance(update, types.UpdateNewChannelMessage): - message = update.message - - if not isinstance(message, types.MessageEmpty): - try: - diff = self.send( - functions.updates.GetChannelDifference( - channel=self.resolve_peer(int("-100" + str(channel_id))), - filter=types.ChannelMessagesFilter( - ranges=[types.MessageRange( - min_id=update.message.id, - max_id=update.message.id - )] - ), - pts=pts - pts_count, - limit=pts - ) - ) - except ChannelPrivate: - pass - else: - if not isinstance(diff, types.updates.ChannelDifferenceEmpty): - updates.users += diff.users - updates.chats += diff.chats - - if channel_id and pts: - if channel_id not in self.channels_pts: - self.channels_pts[channel_id] = [] - - if pts in self.channels_pts[channel_id]: - continue - - self.channels_pts[channel_id].append(pts) - - if len(self.channels_pts[channel_id]) > 50: - self.channels_pts[channel_id] = self.channels_pts[channel_id][25:] - - self.dispatcher.updates_queue.put((update, updates.users, updates.chats)) - elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)): - diff = self.send( - functions.updates.GetDifference( - pts=updates.pts - updates.pts_count, - date=updates.date, - qts=-1 - ) - ) - - if diff.new_messages: - self.dispatcher.updates_queue.put(( - types.UpdateNewMessage( - message=diff.new_messages[0], - pts=updates.pts, - pts_count=updates.pts_count - ), - diff.users, - diff.chats - )) - else: - self.dispatcher.updates_queue.put((diff.other_updates[0], [], [])) - elif isinstance(updates, types.UpdateShort): - self.dispatcher.updates_queue.put((updates.update, [], [])) - elif isinstance(updates, types.UpdatesTooLong): - log.warning(updates) - except Exception as e: - log.error(e, exc_info=True) - - log.debug("{} stopped".format(name)) - - def send(self, - data: Object, - retries: int = Session.MAX_RETRIES, - timeout: float = Session.WAIT_TIMEOUT): - """Use this method to send Raw Function queries. - - This method makes possible to manually call every single Telegram API method in a low-level manner. - Available functions are listed in the :obj:`functions ` package and may accept compound - data types from :obj:`types ` as well as bare types such as ``int``, ``str``, etc... - - Args: - data (``Object``): - The API Schema function filled with proper arguments. - - retries (``int``): - Number of retries. - - timeout (``float``): - Timeout in seconds. - - Raises: - :class:`Error ` in case of a Telegram RPC error. - """ - if not self.is_started: - raise ConnectionError("Client has not been started") - - if self.no_updates: - data = functions.InvokeWithoutUpdates(data) - - if self.takeout_id: - data = functions.InvokeWithTakeout(self.takeout_id, data) - - r = self.session.send(data, retries, timeout) - - self.fetch_peers(getattr(r, "users", [])) - self.fetch_peers(getattr(r, "chats", [])) - - return r - - def load_config(self): - parser = ConfigParser() - parser.read(self.config_file) - - if self.api_id and self.api_hash: - pass - else: - if parser.has_section("pyrogram"): - self.api_id = parser.getint("pyrogram", "api_id") - self.api_hash = parser.get("pyrogram", "api_hash") - else: - raise AttributeError( - "No API Key found. " - "More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration" - ) - - for option in ["app_version", "device_model", "system_version", "lang_code"]: - if getattr(self, option): - pass - else: - if parser.has_section("pyrogram"): - setattr(self, option, parser.get( - "pyrogram", - option, - fallback=getattr(Client, option.upper()) - )) - else: - setattr(self, option, getattr(Client, option.upper())) - - if self._proxy: - self._proxy["enabled"] = True - else: - self._proxy = {} - - if parser.has_section("proxy"): - self._proxy["enabled"] = parser.getboolean("proxy", "enabled") - self._proxy["hostname"] = parser.get("proxy", "hostname") - self._proxy["port"] = parser.getint("proxy", "port") - self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None - self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None - - def load_session(self): - try: - with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f: - s = json.load(f) - except FileNotFoundError: - self.dc_id = 1 - self.date = 0 - self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() - else: - self.dc_id = s["dc_id"] - self.test_mode = s["test_mode"] - self.auth_key = base64.b64decode("".join(s["auth_key"])) - self.user_id = s["user_id"] - self.date = s.get("date", 0) - - for k, v in s.get("peers_by_id", {}).items(): - self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) - - for k, v in s.get("peers_by_username", {}).items(): - peer = self.peers_by_id.get(v, None) - - if peer: - self.peers_by_username[k] = peer - - for k, v in s.get("peers_by_phone", {}).items(): - peer = self.peers_by_id.get(v, None) - - if peer: - self.peers_by_phone[k] = peer - - def load_plugins(self): - if self.plugins_dir is not None: - plugins_count = 0 - - for path in Path(self.plugins_dir).rglob("*.py"): - file_path = os.path.splitext(str(path))[0] - import_path = [] - - while file_path: - file_path, tail = os.path.split(file_path) - import_path.insert(0, tail) - - import_path = ".".join(import_path) - module = import_module(import_path) - - for name in dir(module): - # noinspection PyBroadException - try: - handler, group = getattr(module, name) - - if isinstance(handler, Handler) and isinstance(group, int): - self.add_handler(handler, group) - - log.info('{}("{}") from "{}" loaded in group {}'.format( - type(handler).__name__, name, import_path, group)) - - plugins_count += 1 - except Exception: - pass - - if plugins_count > 0: - log.warning('Successfully loaded {} plugin{} from "{}"'.format( - plugins_count, - "s" if plugins_count > 1 else "", - self.plugins_dir - )) - else: - log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(self.plugins_dir)) - - def save_session(self): - auth_key = base64.b64encode(self.auth_key).decode() - auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] - - os.makedirs(self.workdir, exist_ok=True) - - with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), "w", encoding="utf-8") as f: - json.dump( - dict( - dc_id=self.dc_id, - test_mode=self.test_mode, - auth_key=auth_key, - user_id=self.user_id, - date=self.date - ), - f, - indent=4 - ) - - def get_initial_dialogs_chunk(self, - offset_date: int = 0): - while True: - try: - r = self.send( - functions.messages.GetDialogs( - offset_date=offset_date, - offset_id=0, - offset_peer=types.InputPeerEmpty(), - limit=self.DIALOGS_AT_ONCE, - hash=0, - exclude_pinned=True - ) - ) - except FloodWait as e: - log.warning("get_dialogs flood: waiting {} seconds".format(e.x)) - time.sleep(e.x) - else: - log.info("Total peers: {}".format(len(self.peers_by_id))) - return r - - def get_initial_dialogs(self): - self.send(functions.messages.GetPinnedDialogs()) - - dialogs = self.get_initial_dialogs_chunk() - offset_date = utils.get_offset_date(dialogs) - - while len(dialogs.dialogs) == self.DIALOGS_AT_ONCE: - dialogs = self.get_initial_dialogs_chunk(offset_date) - offset_date = utils.get_offset_date(dialogs) - - self.get_initial_dialogs_chunk() - - def resolve_peer(self, - peer_id: Union[int, str]): - """Use this method to get the InputPeer of a known peer_id. - - This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API - method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an - InputPeer type is required. - - Args: - peer_id (``int`` | ``str``): - The peer id you want to extract the InputPeer from. - Can be a direct id (int), a username (str) or a phone number (str). - - Returns: - On success, the resolved peer id is returned in form of an InputPeer object. - - Raises: - :class:`Error ` in case of a Telegram RPC error. - ``KeyError`` in case the peer doesn't exist in the internal database. - """ - try: - return self.peers_by_id[peer_id] - except KeyError: - if type(peer_id) is str: - if peer_id in ("self", "me"): - return types.InputPeerSelf() - - peer_id = re.sub(r"[@+\s]", "", peer_id.lower()) - - try: - int(peer_id) - except ValueError: - if peer_id not in self.peers_by_username: - self.send( - functions.contacts.ResolveUsername( - username=peer_id - ) - ) - - return self.peers_by_username[peer_id] - else: - try: - return self.peers_by_phone[peer_id] - except KeyError: - raise PeerIdInvalid - - if peer_id > 0: - self.fetch_peers( - self.send( - functions.users.GetUsers( - id=[types.InputUser(peer_id, 0)] - ) - ) - ) - else: - if str(peer_id).startswith("-100"): - self.send( - functions.channels.GetChannels( - id=[types.InputChannel(int(str(peer_id)[4:]), 0)] - ) - ) - else: - self.send( - functions.messages.GetChats( - id=[-peer_id] - ) - ) - - try: - return self.peers_by_id[peer_id] - except KeyError: - raise PeerIdInvalid - - def save_file(self, - path: str, - file_id: int = None, - file_part: int = 0, - progress: callable = None, - progress_args: tuple = ()): - """Use this method to upload a file onto Telegram servers, without actually sending the message to anyone. - - This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API - method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an - InputFile type is required. - - Args: - path (``str``): - The path of the file you want to upload that exists on your local machine. - - file_id (``int``, *optional*): - In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. - - file_part (``int``, *optional*): - In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. - - progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). - - progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. - - Other Parameters: - client (:obj:`Client `): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - On success, the uploaded file is returned in form of an InputFile object. - - Raises: - :class:`Error ` in case of a Telegram RPC error. - """ - part_size = 512 * 1024 - file_size = os.path.getsize(path) - - if file_size == 0: - raise ValueError("File size equals to 0 B") - - if file_size > 1500 * 1024 * 1024: - raise ValueError("Telegram doesn't support uploading files bigger than 1500 MiB") - - file_total_parts = int(math.ceil(file_size / part_size)) - is_big = True if file_size > 10 * 1024 * 1024 else False - is_missing_part = True if file_id is not None else False - file_id = file_id or self.rnd_id() - md5_sum = md5() if not is_big and not is_missing_part else None - - session = Session(self, self.dc_id, self.auth_key, is_media=True) - session.start() - - try: - with open(path, "rb") as f: - f.seek(part_size * file_part) - - while True: - chunk = f.read(part_size) - - if not chunk: - if not is_big: - md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) - break - - if is_big: - rpc = functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk - ) - else: - rpc = functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk - ) - - assert session.send(rpc), "Couldn't upload file" - - if is_missing_part: - return - - if not is_big: - md5_sum.update(chunk) - - file_part += 1 - - if progress: - progress(self, min(file_part * part_size, file_size), file_size, *progress_args) - except Client.StopTransmission: - raise - except Exception as e: - log.error(e, exc_info=True) - else: - if is_big: - return types.InputFileBig( - id=file_id, - parts=file_total_parts, - name=os.path.basename(path), - - ) - else: - return types.InputFile( - id=file_id, - parts=file_total_parts, - name=os.path.basename(path), - md5_checksum=md5_sum - ) - finally: - session.stop() - - def get_file(self, - dc_id: int, - id: int = None, - access_hash: int = None, - volume_id: int = None, - local_id: int = None, - secret: int = None, - size: int = None, - progress: callable = None, - progress_args: tuple = ()) -> str: - with self.media_sessions_lock: - session = self.media_sessions.get(dc_id, None) - - if session is None: - if dc_id != self.dc_id: - exported_auth = self.send( - functions.auth.ExportAuthorization( - dc_id=dc_id - ) - ) - - session = Session( - self, - dc_id, - Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(), - is_media=True - ) - - session.start() - - self.media_sessions[dc_id] = session - - session.send( - functions.auth.ImportAuthorization( - id=exported_auth.id, - bytes=exported_auth.bytes - ) - ) - else: - session = Session( - self, - dc_id, - self.auth_key, - is_media=True - ) - - session.start() - - self.media_sessions[dc_id] = session - - if volume_id: # Photos are accessed by volume_id, local_id, secret - location = types.InputFileLocation( - volume_id=volume_id, - local_id=local_id, - secret=secret, - file_reference=b"" - ) - else: # Any other file can be more easily accessed by id and access_hash - location = types.InputDocumentFileLocation( - id=id, - access_hash=access_hash, - file_reference=b"" - ) - - limit = 1024 * 1024 - offset = 0 - file_name = "" - - try: - r = session.send( - functions.upload.GetFile( - location=location, - offset=offset, - limit=limit - ) - ) - - if isinstance(r, types.upload.File): - with tempfile.NamedTemporaryFile("wb", delete=False) as f: - file_name = f.name - - while True: - chunk = r.bytes - - if not chunk: - break - - f.write(chunk) - - offset += limit - - if progress: - progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) - - r = session.send( - functions.upload.GetFile( - location=location, - offset=offset, - limit=limit - ) - ) - - elif isinstance(r, types.upload.FileCdnRedirect): - with self.media_sessions_lock: - cdn_session = self.media_sessions.get(r.dc_id, None) - - if cdn_session is None: - cdn_session = Session( - self, - r.dc_id, - Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(), - is_media=True, - is_cdn=True - ) - - cdn_session.start() - - self.media_sessions[r.dc_id] = cdn_session - - try: - with tempfile.NamedTemporaryFile("wb", delete=False) as f: - file_name = f.name - - while True: - r2 = cdn_session.send( - functions.upload.GetCdnFile( - file_token=r.file_token, - offset=offset, - limit=limit - ) - ) - - if isinstance(r2, types.upload.CdnFileReuploadNeeded): - try: - session.send( - functions.upload.ReuploadCdnFile( - file_token=r.file_token, - request_token=r2.request_token - ) - ) - except VolumeLocNotFound: - break - else: - continue - - chunk = r2.bytes - - # https://core.telegram.org/cdn#decrypting-files - decrypted_chunk = AES.ctr256_decrypt( - chunk, - r.encryption_key, - bytearray( - r.encryption_iv[:-4] - + (offset // 16).to_bytes(4, "big") - ) - ) - - hashes = session.send( - functions.upload.GetCdnFileHashes( - r.file_token, - offset - ) - ) - - # https://core.telegram.org/cdn#verifying-files - for i, h in enumerate(hashes): - cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] - assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i) - - f.write(decrypted_chunk) - - offset += limit - - if progress: - progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) - - if len(chunk) < limit: - break - except Exception as e: - raise e - except Exception as e: - if not isinstance(e, Client.StopTransmission): - log.error(e, exc_info=True) - - try: - os.remove(file_name) - except OSError: - pass - - return "" - else: - return file_name +# Pyrogram - Telegram MTProto API Client Library for Python # Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # # Pyrogram is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Pyrogram is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . import base64 import binascii import json import logging import math import mimetypes import os import re import shutil import struct import tempfile import threading import time from configparser import ConfigParser from datetime import datetime from hashlib import sha256, md5 from importlib import import_module from pathlib import Path from signal import signal, SIGINT, SIGTERM, SIGABRT from threading import Thread from typing import Union, List from pyrogram.api import functions, types from pyrogram.api.core import Object from pyrogram.api.errors import ( PhoneMigrate, NetworkMigrate, PhoneNumberInvalid, PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty, PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied, PasswordRecoveryNa, PasswordEmpty ) from pyrogram.client.handlers import DisconnectHandler from pyrogram.client.handlers.handler import Handler from pyrogram.client.methods.password.utils import compute_check from pyrogram.crypto import AES from pyrogram.session import Auth, Session from .dispatcher import Dispatcher from .ext import utils, Syncer, BaseClient from .methods import Methods log = logging.getLogger(__name__) class Client(Methods, BaseClient): """This class represents a Client, the main mean for interacting with Telegram. It exposes bot-like methods for an easy access to the API as well as a simple way to invoke every single Telegram API method available. Args: session_name (``str``): Name to uniquely identify a session of either a User or a Bot. For Users: pass a string of your choice, e.g.: "my_main_account". For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number. api_id (``int``, *optional*): The *api_id* part of your Telegram API Key, as integer. E.g.: 12345 This is an alternative way to pass it if you don't want to use the *config.ini* file. api_hash (``str``, *optional*): The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef" This is an alternative way to pass it if you don't want to use the *config.ini* file. app_version (``str``, *optional*): Application version. Defaults to "Pyrogram \U0001f525 vX.Y.Z" This is an alternative way to set it if you don't want to use the *config.ini* file. device_model (``str``, *optional*): Device model. Defaults to *platform.python_implementation() + " " + platform.python_version()* This is an alternative way to set it if you don't want to use the *config.ini* file. system_version (``str``, *optional*): Operating System version. Defaults to *platform.system() + " " + platform.release()* This is an alternative way to set it if you don't want to use the *config.ini* file. lang_code (``str``, *optional*): Code of the language used on the client, in ISO 639-1 standard. Defaults to "en". This is an alternative way to set it if you don't want to use the *config.ini* file. ipv6 (``bool``, *optional*): Pass True to connect to Telegram using IPv6. Defaults to False (IPv4). proxy (``dict``, *optional*): Your SOCKS5 Proxy settings as dict, e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*. *username* and *password* can be omitted if your proxy doesn't require authorization. This is an alternative way to setup a proxy if you don't want to use the *config.ini* file. test_mode (``bool``, *optional*): Enable or disable log-in to testing servers. Defaults to False. Only applicable for new sessions and will be ignored in case previously created sessions are loaded. phone_number (``str`` | ``callable``, *optional*): Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually. Or pass a callback function which accepts no arguments and must return the correct phone number as string (e.g., "391234567890"). Only applicable for new sessions. phone_code (``str`` | ``callable``, *optional*): Pass the phone code as string (for test numbers only) to avoid entering it manually. Or pass a callback function which accepts a single positional argument *(phone_number)* and must return the correct phone code as string (e.g., "12345"). Only applicable for new sessions. password (``str``, *optional*): Pass your Two-Step Verification password as string (if you have one) to avoid entering it manually. Or pass a callback function which accepts a single positional argument *(password_hint)* and must return the correct password as string (e.g., "password"). Only applicable for new sessions. recovery_code (``callable``, *optional*): Pass a callback function which accepts a single positional argument *(email_pattern)* and must return the correct password recovery code as string (e.g., "987654"). Only applicable for new sessions. force_sms (``str``, *optional*): Pass True to force Telegram sending the authorization code via SMS. Only applicable for new sessions. first_name (``str``, *optional*): Pass a First Name to avoid entering it manually. It will be used to automatically create a new Telegram account in case the phone number you passed is not registered yet. Only applicable for new sessions. last_name (``str``, *optional*): Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can be an empty string: "". Only applicable for new sessions. workers (``int``, *optional*): Thread pool size for handling incoming updates. Defaults to 4. workdir (``str``, *optional*): Define a custom working directory. The working directory is the location in your filesystem where Pyrogram will store your session files. Defaults to "." (current directory). config_file (``str``, *optional*): Path of the configuration file. Defaults to ./config.ini plugins_dir (``str``, *optional*): Define a custom directory for your plugins. The plugins directory is the location in your filesystem where Pyrogram will automatically load your update handlers. Defaults to None (plugins disabled). no_updates (``bool``, *optional*): Pass True to completely disable incoming updates for the current session. When updates are disabled your client can't receive any new message. Useful for batch programs that don't need to deal with updates. Defaults to False (updates enabled and always received). takeout (``bool``, *optional*): Pass True to let the client use a takeout session instead of a normal one, implies no_updates. Useful for exporting your Telegram data. Methods invoked inside a takeout session (such as get_history, download_media, ...) are less prone to throw FloodWait exceptions. Only available for users, bots will ignore this parameter. Defaults to False (normal session). """ def __init__(self, session_name: str, api_id: Union[int, str] = None, api_hash: str = None, app_version: str = None, device_model: str = None, system_version: str = None, lang_code: str = None, ipv6: bool = False, proxy: dict = None, test_mode: bool = False, phone_number: str = None, phone_code: Union[str, callable] = None, password: str = None, recovery_code: callable = None, force_sms: bool = False, first_name: str = None, last_name: str = None, workers: int = BaseClient.WORKERS, workdir: str = BaseClient.WORKDIR, config_file: str = BaseClient.CONFIG_FILE, plugins_dir: str = None, no_updates: bool = None, takeout: bool = None): super().__init__() self.session_name = session_name self.api_id = int(api_id) if api_id else None self.api_hash = api_hash self.app_version = app_version self.device_model = device_model self.system_version = system_version self.lang_code = lang_code self.ipv6 = ipv6 # TODO: Make code consistent, use underscore for private/protected fields self._proxy = proxy self.test_mode = test_mode self.phone_number = phone_number self.phone_code = phone_code self.password = password self.recovery_code = recovery_code self.force_sms = force_sms self.first_name = first_name self.last_name = last_name self.workers = workers self.workdir = workdir self.config_file = config_file self.plugins_dir = plugins_dir self.no_updates = no_updates self.takeout = takeout self.dispatcher = Dispatcher(self, workers) def __enter__(self): return self.start() def __exit__(self, *args): self.stop() @property def proxy(self): return self._proxy @proxy.setter def proxy(self, value): if value is None: self._proxy = None return if self._proxy is None: self._proxy = {} self._proxy["enabled"] = bool(value.get("enabled", True)) self._proxy.update(value) def start(self): """Use this method to start the Client after creating it. Requires no parameters. Raises: :class:`Error ` in case of a Telegram RPC error. ``ConnectionError`` in case you try to start an already started Client. """ if self.is_started: raise ConnectionError("Client has already been started") if self.BOT_TOKEN_RE.match(self.session_name): self.bot_token = self.session_name self.session_name = self.session_name.split(":")[0] self.load_config() self.load_session() self.load_plugins() self.session = Session( self, self.dc_id, self.auth_key ) self.session.start() self.is_started = True try: if self.user_id is None: if self.bot_token is None: self.authorize_user() else: self.authorize_bot() self.save_session() if self.bot_token is None: if self.takeout: self.takeout_id = self.send(functions.account.InitTakeoutSession()).id log.warning("Takeout session {} initiated".format(self.takeout_id)) now = time.time() if abs(now - self.date) > Client.OFFLINE_SLEEP: self.peers_by_username = {} self.peers_by_phone = {} self.get_initial_dialogs() self.get_contacts() else: self.send(functions.messages.GetPinnedDialogs()) self.get_initial_dialogs_chunk() else: self.send(functions.updates.GetState()) except Exception as e: self.is_started = False self.session.stop() raise e for i in range(self.UPDATES_WORKERS): self.updates_workers_list.append( Thread( target=self.updates_worker, name="UpdatesWorker#{}".format(i + 1) ) ) self.updates_workers_list[-1].start() for i in range(self.DOWNLOAD_WORKERS): self.download_workers_list.append( Thread( target=self.download_worker, name="DownloadWorker#{}".format(i + 1) ) ) self.download_workers_list[-1].start() self.dispatcher.start() mimetypes.init() Syncer.add(self) return self def stop(self): """Use this method to manually stop the Client. Requires no parameters. Raises: ``ConnectionError`` in case you try to stop an already stopped Client. """ if not self.is_started: raise ConnectionError("Client is already stopped") if self.takeout_id: self.send(functions.account.FinishTakeoutSession()) log.warning("Takeout session {} finished".format(self.takeout_id)) Syncer.remove(self) self.dispatcher.stop() for _ in range(self.DOWNLOAD_WORKERS): self.download_queue.put(None) for i in self.download_workers_list: i.join() self.download_workers_list.clear() for _ in range(self.UPDATES_WORKERS): self.updates_queue.put(None) for i in self.updates_workers_list: i.join() self.updates_workers_list.clear() for i in self.media_sessions.values(): i.stop() self.media_sessions.clear() self.is_started = False self.session.stop() return self def restart(self): """Use this method to restart the Client. Requires no parameters. Raises: ``ConnectionError`` in case you try to restart a stopped Client. """ self.stop() self.start() def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): """Blocks the program execution until one of the signals are received, then gently stop the Client by closing the underlying connection. Args: stop_signals (``tuple``, *optional*): Iterable containing signals the signal handler will listen to. Defaults to (SIGINT, SIGTERM, SIGABRT). """ def signal_handler(*args): self.is_idle = False for s in stop_signals: signal(s, signal_handler) self.is_idle = True while self.is_idle: time.sleep(1) self.stop() def run(self): """Use this method to automatically start and idle a Client. Requires no parameters. Raises: :class:`Error ` in case of a Telegram RPC error. """ self.start() self.idle() def add_handler(self, handler: Handler, group: int = 0): """Use this method to register an update handler. You can register multiple handlers, but at most one handler within a group will be used for a single update. To handle the same update more than once, register your handler using a different group id (lower group id == higher priority). Args: handler (``Handler``): The handler to be registered. group (``int``, *optional*): The group identifier, defaults to 0. Returns: A tuple of (handler, group) """ if isinstance(handler, DisconnectHandler): self.disconnect_handler = handler.callback else: self.dispatcher.add_handler(handler, group) return handler, group def remove_handler(self, handler: Handler, group: int = 0): """Removes a previously-added update handler. Make sure to provide the right group that the handler was added in. You can use the return value of the :meth:`add_handler` method, a tuple of (handler, group), and pass it directly. Args: handler (``Handler``): The handler to be removed. group (``int``, *optional*): The group identifier, defaults to 0. """ if isinstance(handler, DisconnectHandler): self.disconnect_handler = None else: self.dispatcher.remove_handler(handler, group) def stop_transmission(self): """Use this method to stop downloading or uploading a file. Must be called inside a progress callback function. """ raise Client.StopTransmission def authorize_bot(self): try: r = self.send( functions.auth.ImportBotAuthorization( flags=0, api_id=self.api_id, api_hash=self.api_hash, bot_auth_token=self.bot_token ) ) except UserMigrate as e: self.session.stop() self.dc_id = e.x self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() self.session = Session( self, self.dc_id, self.auth_key ) self.session.start() self.authorize_bot() else: self.user_id = r.user.id print("Logged in successfully as @{}".format(r.user.username)) def authorize_user(self): phone_number_invalid_raises = self.phone_number is not None phone_code_invalid_raises = self.phone_code is not None password_invalid_raises = self.password is not None first_name_invalid_raises = self.first_name is not None def default_phone_number_callback(): while True: phone_number = input("Enter phone number: ") confirm = input("Is \"{}\" correct? (y/n): ".format(phone_number)) if confirm in ("y", "1"): return phone_number elif confirm in ("n", "2"): continue while True: self.phone_number = ( default_phone_number_callback() if self.phone_number is None else str(self.phone_number()) if callable(self.phone_number) else str(self.phone_number) ) self.phone_number = self.phone_number.strip("+") try: r = self.send( functions.auth.SendCode( self.phone_number, self.api_id, self.api_hash ) ) except (PhoneMigrate, NetworkMigrate) as e: self.session.stop() self.dc_id = e.x self.auth_key = Auth( self.dc_id, self.test_mode, self.ipv6, self._proxy ).create() self.session = Session( self, self.dc_id, self.auth_key ) self.session.start() except (PhoneNumberInvalid, PhoneNumberBanned) as e: if phone_number_invalid_raises: raise else: print(e.MESSAGE) self.phone_number = None except FloodWait as e: if phone_number_invalid_raises: raise else: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) except Exception as e: log.error(e, exc_info=True) raise else: break phone_registered = r.phone_registered phone_code_hash = r.phone_code_hash terms_of_service = r.terms_of_service if terms_of_service: print("\n" + terms_of_service.text + "\n") if self.force_sms: self.send( functions.auth.ResendCode( phone_number=self.phone_number, phone_code_hash=phone_code_hash ) ) while True: if not phone_registered: self.first_name = ( input("First name: ") if self.first_name is None else str(self.first_name()) if callable(self.first_name) else str(self.first_name) ) self.last_name = ( input("Last name: ") if self.last_name is None else str(self.last_name()) if callable(self.last_name) else str(self.last_name) ) self.phone_code = ( input("Enter phone code: ") if self.phone_code is None else str(self.phone_code(self.phone_number)) if callable(self.phone_code) else str(self.phone_code) ) try: if phone_registered: try: r = self.send( functions.auth.SignIn( self.phone_number, phone_code_hash, self.phone_code ) ) except PhoneNumberUnoccupied: log.warning("Phone number unregistered") phone_registered = False continue else: try: r = self.send( functions.auth.SignUp( self.phone_number, phone_code_hash, self.phone_code, self.first_name, self.last_name ) ) except PhoneNumberOccupied: log.warning("Phone number already registered") phone_registered = True continue except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e: if phone_code_invalid_raises: raise else: print(e.MESSAGE) self.phone_code = None except FirstnameInvalid as e: if first_name_invalid_raises: raise else: print(e.MESSAGE) self.first_name = None except SessionPasswordNeeded as e: print(e.MESSAGE) def default_password_callback(password_hint: str) -> str: print("Hint: {}".format(password_hint)) return input("Enter password (empty to recover): ") def default_recovery_callback(email_pattern: str) -> str: print("An e-mail containing the recovery code has been sent to {}".format(email_pattern)) return input("Enter password recovery code: ") while True: try: r = self.send(functions.account.GetPassword()) self.password = ( default_password_callback(r.hint) if self.password is None else str(self.password(r.hint) or "") if callable(self.password) else str(self.password) ) if self.password == "": r = self.send(functions.auth.RequestPasswordRecovery()) self.recovery_code = ( default_recovery_callback(r.email_pattern) if self.recovery_code is None else str(self.recovery_code(r.email_pattern)) if callable(self.recovery_code) else str(self.recovery_code) ) r = self.send( functions.auth.RecoverPassword( code=self.recovery_code ) ) else: r = self.send( functions.auth.CheckPassword( password=compute_check(r, self.password) ) ) except (PasswordEmpty, PasswordRecoveryNa, PasswordHashInvalid) as e: if password_invalid_raises: raise else: print(e.MESSAGE) self.password = None self.recovery_code = None except FloodWait as e: if password_invalid_raises: raise else: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) self.password = None self.recovery_code = None except Exception as e: log.error(e, exc_info=True) raise else: break break except FloodWait as e: if phone_code_invalid_raises or first_name_invalid_raises: raise else: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) except Exception as e: log.error(e, exc_info=True) raise else: break if terms_of_service: assert self.send(functions.help.AcceptTermsOfService(terms_of_service.id)) self.password = None self.user_id = r.user.id print("Logged in successfully as {}".format(r.user.first_name)) def fetch_peers(self, entities: List[Union[types.User, types.Chat, types.ChatForbidden, types.Channel, types.ChannelForbidden]]): for entity in entities: if isinstance(entity, types.User): user_id = entity.id access_hash = entity.access_hash if access_hash is None: continue username = entity.username phone = entity.phone input_peer = types.InputPeerUser( user_id=user_id, access_hash=access_hash ) self.peers_by_id[user_id] = input_peer if username is not None: self.peers_by_username[username.lower()] = input_peer if phone is not None: self.peers_by_phone[phone] = input_peer if isinstance(entity, (types.Chat, types.ChatForbidden)): chat_id = entity.id peer_id = -chat_id input_peer = types.InputPeerChat( chat_id=chat_id ) self.peers_by_id[peer_id] = input_peer if isinstance(entity, (types.Channel, types.ChannelForbidden)): channel_id = entity.id peer_id = int("-100" + str(channel_id)) access_hash = entity.access_hash if access_hash is None: continue username = getattr(entity, "username", None) input_peer = types.InputPeerChannel( channel_id=channel_id, access_hash=access_hash ) self.peers_by_id[peer_id] = input_peer if username is not None: self.peers_by_username[username.lower()] = input_peer def download_worker(self): name = threading.current_thread().name log.debug("{} started".format(name)) while True: media = self.download_queue.get() if media is None: break temp_file_path = "" final_file_path = "" try: media, file_name, done, progress, progress_args, path = media file_id = media.file_id size = media.file_size directory, file_name = os.path.split(file_name) directory = directory or "downloads" try: decoded = utils.decode(file_id) fmt = " 24 else " 24: volume_id = unpacked[4] secret = unpacked[5] local_id = unpacked[6] media_type_str = Client.MEDIA_TYPE_ID.get(media_type, None) if media_type_str is None: raise FileIdInvalid("Unknown media type: {}".format(unpacked[0])) file_name = file_name or getattr(media, "file_name", None) if not file_name: if media_type == 3: extension = ".ogg" elif media_type in (4, 10, 13): extension = mimetypes.guess_extension(media.mime_type) or ".mp4" elif media_type == 5: extension = mimetypes.guess_extension(media.mime_type) or ".unknown" elif media_type == 8: extension = ".webp" elif media_type == 9: extension = mimetypes.guess_extension(media.mime_type) or ".mp3" elif media_type in (0, 1, 2): extension = ".jpg" else: continue file_name = "{}_{}_{}{}".format( media_type_str, datetime.fromtimestamp( getattr(media, "date", None) or time.time() ).strftime("%Y-%m-%d_%H-%M-%S"), self.rnd_id(), extension ) temp_file_path = self.get_file( dc_id=dc_id, id=id, access_hash=access_hash, volume_id=volume_id, local_id=local_id, secret=secret, size=size, progress=progress, progress_args=progress_args ) if temp_file_path: final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) os.makedirs(directory, exist_ok=True) shutil.move(temp_file_path, final_file_path) except Exception as e: log.error(e, exc_info=True) try: os.remove(temp_file_path) except OSError: pass else: # TODO: "" or None for faulty download, which is better? # os.path methods return "" in case something does not exist, I prefer this. # For now let's keep None path[0] = final_file_path or None finally: done.set() log.debug("{} stopped".format(name)) def updates_worker(self): name = threading.current_thread().name log.debug("{} started".format(name)) while True: updates = self.updates_queue.get() if updates is None: break try: if isinstance(updates, (types.Update, types.UpdatesCombined)): self.fetch_peers(updates.users) self.fetch_peers(updates.chats) for update in updates.updates: channel_id = getattr( getattr( getattr( update, "message", None ), "to_id", None ), "channel_id", None ) or getattr(update, "channel_id", None) pts = getattr(update, "pts", None) pts_count = getattr(update, "pts_count", None) if isinstance(update, types.UpdateChannelTooLong): log.warning(update) if isinstance(update, types.UpdateNewChannelMessage): message = update.message if not isinstance(message, types.MessageEmpty): try: diff = self.send( functions.updates.GetChannelDifference( channel=self.resolve_peer(int("-100" + str(channel_id))), filter=types.ChannelMessagesFilter( ranges=[types.MessageRange( min_id=update.message.id, max_id=update.message.id )] ), pts=pts - pts_count, limit=pts ) ) except ChannelPrivate: pass else: if not isinstance(diff, types.updates.ChannelDifferenceEmpty): updates.users += diff.users updates.chats += diff.chats if channel_id and pts: if channel_id not in self.channels_pts: self.channels_pts[channel_id] = [] if pts in self.channels_pts[channel_id]: continue self.channels_pts[channel_id].append(pts) if len(self.channels_pts[channel_id]) > 50: self.channels_pts[channel_id] = self.channels_pts[channel_id][25:] self.dispatcher.updates_queue.put((update, updates.users, updates.chats)) elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)): diff = self.send( functions.updates.GetDifference( pts=updates.pts - updates.pts_count, date=updates.date, qts=-1 ) ) if diff.new_messages: self.dispatcher.updates_queue.put(( types.UpdateNewMessage( message=diff.new_messages[0], pts=updates.pts, pts_count=updates.pts_count ), diff.users, diff.chats )) else: self.dispatcher.updates_queue.put((diff.other_updates[0], [], [])) elif isinstance(updates, types.UpdateShort): self.dispatcher.updates_queue.put((updates.update, [], [])) elif isinstance(updates, types.UpdatesTooLong): log.warning(updates) except Exception as e: log.error(e, exc_info=True) log.debug("{} stopped".format(name)) def send(self, data: Object, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT): """Use this method to send Raw Function queries. This method makes possible to manually call every single Telegram API method in a low-level manner. Available functions are listed in the :obj:`functions ` package and may accept compound data types from :obj:`types ` as well as bare types such as ``int``, ``str``, etc... Args: data (``Object``): The API Schema function filled with proper arguments. retries (``int``): Number of retries. timeout (``float``): Timeout in seconds. Raises: :class:`Error ` in case of a Telegram RPC error. """ if not self.is_started: raise ConnectionError("Client has not been started") if self.no_updates: data = functions.InvokeWithoutUpdates(data) if self.takeout_id: data = functions.InvokeWithTakeout(self.takeout_id, data) r = self.session.send(data, retries, timeout) self.fetch_peers(getattr(r, "users", [])) self.fetch_peers(getattr(r, "chats", [])) return r def load_config(self): parser = ConfigParser() parser.read(self.config_file) if self.api_id and self.api_hash: pass else: if parser.has_section("pyrogram"): self.api_id = parser.getint("pyrogram", "api_id") self.api_hash = parser.get("pyrogram", "api_hash") else: raise AttributeError( "No API Key found. " "More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration" ) for option in ["app_version", "device_model", "system_version", "lang_code"]: if getattr(self, option): pass else: if parser.has_section("pyrogram"): setattr(self, option, parser.get( "pyrogram", option, fallback=getattr(Client, option.upper()) )) else: setattr(self, option, getattr(Client, option.upper())) if self._proxy: self._proxy["enabled"] = bool(self._proxy.get("enabled", True)) else: self._proxy = {} if parser.has_section("proxy"): self._proxy["enabled"] = parser.getboolean("proxy", "enabled", fallback=True) self._proxy["hostname"] = parser.get("proxy", "hostname") self._proxy["port"] = parser.getint("proxy", "port") self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None def load_session(self): try: with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f: s = json.load(f) except FileNotFoundError: self.dc_id = 1 self.date = 0 self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() else: self.dc_id = s["dc_id"] self.test_mode = s["test_mode"] self.auth_key = base64.b64decode("".join(s["auth_key"])) self.user_id = s["user_id"] self.date = s.get("date", 0) for k, v in s.get("peers_by_id", {}).items(): self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) for k, v in s.get("peers_by_username", {}).items(): peer = self.peers_by_id.get(v, None) if peer: self.peers_by_username[k] = peer for k, v in s.get("peers_by_phone", {}).items(): peer = self.peers_by_id.get(v, None) if peer: self.peers_by_phone[k] = peer def load_plugins(self): if self.plugins_dir is not None: plugins_count = 0 for path in Path(self.plugins_dir).rglob("*.py"): file_path = os.path.splitext(str(path))[0] import_path = [] while file_path: file_path, tail = os.path.split(file_path) import_path.insert(0, tail) import_path = ".".join(import_path) module = import_module(import_path) for name in dir(module): # noinspection PyBroadException try: handler, group = getattr(module, name) if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) log.info('{}("{}") from "{}" loaded in group {}'.format( type(handler).__name__, name, import_path, group)) plugins_count += 1 except Exception: pass if plugins_count > 0: log.warning('Successfully loaded {} plugin{} from "{}"'.format( plugins_count, "s" if plugins_count > 1 else "", self.plugins_dir )) else: log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(self.plugins_dir)) def save_session(self): auth_key = base64.b64encode(self.auth_key).decode() auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] os.makedirs(self.workdir, exist_ok=True) with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), "w", encoding="utf-8") as f: json.dump( dict( dc_id=self.dc_id, test_mode=self.test_mode, auth_key=auth_key, user_id=self.user_id, date=self.date ), f, indent=4 ) def get_initial_dialogs_chunk(self, offset_date: int = 0): while True: try: r = self.send( functions.messages.GetDialogs( offset_date=offset_date, offset_id=0, offset_peer=types.InputPeerEmpty(), limit=self.DIALOGS_AT_ONCE, hash=0, exclude_pinned=True ) ) except FloodWait as e: log.warning("get_dialogs flood: waiting {} seconds".format(e.x)) time.sleep(e.x) else: log.info("Total peers: {}".format(len(self.peers_by_id))) return r def get_initial_dialogs(self): self.send(functions.messages.GetPinnedDialogs()) dialogs = self.get_initial_dialogs_chunk() offset_date = utils.get_offset_date(dialogs) while len(dialogs.dialogs) == self.DIALOGS_AT_ONCE: dialogs = self.get_initial_dialogs_chunk(offset_date) offset_date = utils.get_offset_date(dialogs) self.get_initial_dialogs_chunk() def resolve_peer(self, peer_id: Union[int, str]): """Use this method to get the InputPeer of a known peer_id. This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an InputPeer type is required. Args: peer_id (``int`` | ``str``): The peer id you want to extract the InputPeer from. Can be a direct id (int), a username (str) or a phone number (str). Returns: On success, the resolved peer id is returned in form of an InputPeer object. Raises: :class:`Error ` in case of a Telegram RPC error. ``KeyError`` in case the peer doesn't exist in the internal database. """ try: return self.peers_by_id[peer_id] except KeyError: if type(peer_id) is str: if peer_id in ("self", "me"): return types.InputPeerSelf() peer_id = re.sub(r"[@+\s]", "", peer_id.lower()) try: int(peer_id) except ValueError: if peer_id not in self.peers_by_username: self.send( functions.contacts.ResolveUsername( username=peer_id ) ) return self.peers_by_username[peer_id] else: try: return self.peers_by_phone[peer_id] except KeyError: raise PeerIdInvalid if peer_id > 0: self.fetch_peers( self.send( functions.users.GetUsers( id=[types.InputUser(peer_id, 0)] ) ) ) else: if str(peer_id).startswith("-100"): self.send( functions.channels.GetChannels( id=[types.InputChannel(int(str(peer_id)[4:]), 0)] ) ) else: self.send( functions.messages.GetChats( id=[-peer_id] ) ) try: return self.peers_by_id[peer_id] except KeyError: raise PeerIdInvalid def save_file(self, path: str, file_id: int = None, file_part: int = 0, progress: callable = None, progress_args: tuple = ()): """Use this method to upload a file onto Telegram servers, without actually sending the message to anyone. This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an InputFile type is required. Args: path (``str``): The path of the file you want to upload that exists on your local machine. file_id (``int``, *optional*): In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. file_part (``int``, *optional*): In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. progress (``callable``, *optional*): Pass a callback function to view the upload progress. The function must take *(client, current, total, \*args)* as positional arguments (look at the section below for a detailed description). progress_args (``tuple``, *optional*): Extra custom arguments for the progress callback function. Useful, for example, if you want to pass a chat_id and a message_id in order to edit a message with the updated progress. Other Parameters: client (:obj:`Client `): The Client itself, useful when you want to call other API methods inside the callback function. current (``int``): The amount of bytes uploaded so far. total (``int``): The size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. You can either keep *\*args* or add every single extra argument in your function signature. Returns: On success, the uploaded file is returned in form of an InputFile object. Raises: :class:`Error ` in case of a Telegram RPC error. """ part_size = 512 * 1024 file_size = os.path.getsize(path) if file_size == 0: raise ValueError("File size equals to 0 B") if file_size > 1500 * 1024 * 1024: raise ValueError("Telegram doesn't support uploading files bigger than 1500 MiB") file_total_parts = int(math.ceil(file_size / part_size)) is_big = True if file_size > 10 * 1024 * 1024 else False is_missing_part = True if file_id is not None else False file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None session = Session(self, self.dc_id, self.auth_key, is_media=True) session.start() try: with open(path, "rb") as f: f.seek(part_size * file_part) while True: chunk = f.read(part_size) if not chunk: if not is_big: md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) break if is_big: rpc = functions.upload.SaveBigFilePart( file_id=file_id, file_part=file_part, file_total_parts=file_total_parts, bytes=chunk ) else: rpc = functions.upload.SaveFilePart( file_id=file_id, file_part=file_part, bytes=chunk ) assert session.send(rpc), "Couldn't upload file" if is_missing_part: return if not is_big: md5_sum.update(chunk) file_part += 1 if progress: progress(self, min(file_part * part_size, file_size), file_size, *progress_args) except Client.StopTransmission: raise except Exception as e: log.error(e, exc_info=True) else: if is_big: return types.InputFileBig( id=file_id, parts=file_total_parts, name=os.path.basename(path), ) else: return types.InputFile( id=file_id, parts=file_total_parts, name=os.path.basename(path), md5_checksum=md5_sum ) finally: session.stop() def get_file(self, dc_id: int, id: int = None, access_hash: int = None, volume_id: int = None, local_id: int = None, secret: int = None, size: int = None, progress: callable = None, progress_args: tuple = ()) -> str: with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) if session is None: if dc_id != self.dc_id: exported_auth = self.send( functions.auth.ExportAuthorization( dc_id=dc_id ) ) session = Session( self, dc_id, Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(), is_media=True ) session.start() self.media_sessions[dc_id] = session session.send( functions.auth.ImportAuthorization( id=exported_auth.id, bytes=exported_auth.bytes ) ) else: session = Session( self, dc_id, self.auth_key, is_media=True ) session.start() self.media_sessions[dc_id] = session if volume_id: # Photos are accessed by volume_id, local_id, secret location = types.InputFileLocation( volume_id=volume_id, local_id=local_id, secret=secret, file_reference=b"" ) else: # Any other file can be more easily accessed by id and access_hash location = types.InputDocumentFileLocation( id=id, access_hash=access_hash, file_reference=b"" ) limit = 1024 * 1024 offset = 0 file_name = "" try: r = session.send( functions.upload.GetFile( location=location, offset=offset, limit=limit ) ) if isinstance(r, types.upload.File): with tempfile.NamedTemporaryFile("wb", delete=False) as f: file_name = f.name while True: chunk = r.bytes if not chunk: break f.write(chunk) offset += limit if progress: progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) r = session.send( functions.upload.GetFile( location=location, offset=offset, limit=limit ) ) elif isinstance(r, types.upload.FileCdnRedirect): with self.media_sessions_lock: cdn_session = self.media_sessions.get(r.dc_id, None) if cdn_session is None: cdn_session = Session( self, r.dc_id, Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(), is_media=True, is_cdn=True ) cdn_session.start() self.media_sessions[r.dc_id] = cdn_session try: with tempfile.NamedTemporaryFile("wb", delete=False) as f: file_name = f.name while True: r2 = cdn_session.send( functions.upload.GetCdnFile( file_token=r.file_token, offset=offset, limit=limit ) ) if isinstance(r2, types.upload.CdnFileReuploadNeeded): try: session.send( functions.upload.ReuploadCdnFile( file_token=r.file_token, request_token=r2.request_token ) ) except VolumeLocNotFound: break else: continue chunk = r2.bytes # https://core.telegram.org/cdn#decrypting-files decrypted_chunk = AES.ctr256_decrypt( chunk, r.encryption_key, bytearray( r.encryption_iv[:-4] + (offset // 16).to_bytes(4, "big") ) ) hashes = session.send( functions.upload.GetCdnFileHashes( r.file_token, offset ) ) # https://core.telegram.org/cdn#verifying-files for i, h in enumerate(hashes): cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i) f.write(decrypted_chunk) offset += limit if progress: progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) if len(chunk) < limit: break except Exception as e: raise e except Exception as e: if not isinstance(e, Client.StopTransmission): log.error(e, exc_info=True) try: os.remove(file_name) except OSError: pass return "" else: return file_name \ No newline at end of file From 3d16a715ad5fd78734a48295cab9940343436667 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 16 Jan 2019 15:46:46 +0100 Subject: [PATCH 227/326] Fix file using wrong line separator --- pyrogram/client/client.py | 1592 ++++++++++++++++++++++++++++++++++++- 1 file changed, 1591 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 5d5bf945..576dea9a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1 +1,1591 @@ -# Pyrogram - Telegram MTProto API Client Library for Python # Copyright (C) 2017-2019 Dan Tès # # This file is part of Pyrogram. # # Pyrogram is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Pyrogram is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . import base64 import binascii import json import logging import math import mimetypes import os import re import shutil import struct import tempfile import threading import time from configparser import ConfigParser from datetime import datetime from hashlib import sha256, md5 from importlib import import_module from pathlib import Path from signal import signal, SIGINT, SIGTERM, SIGABRT from threading import Thread from typing import Union, List from pyrogram.api import functions, types from pyrogram.api.core import Object from pyrogram.api.errors import ( PhoneMigrate, NetworkMigrate, PhoneNumberInvalid, PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty, PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied, PasswordRecoveryNa, PasswordEmpty ) from pyrogram.client.handlers import DisconnectHandler from pyrogram.client.handlers.handler import Handler from pyrogram.client.methods.password.utils import compute_check from pyrogram.crypto import AES from pyrogram.session import Auth, Session from .dispatcher import Dispatcher from .ext import utils, Syncer, BaseClient from .methods import Methods log = logging.getLogger(__name__) class Client(Methods, BaseClient): """This class represents a Client, the main mean for interacting with Telegram. It exposes bot-like methods for an easy access to the API as well as a simple way to invoke every single Telegram API method available. Args: session_name (``str``): Name to uniquely identify a session of either a User or a Bot. For Users: pass a string of your choice, e.g.: "my_main_account". For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number. api_id (``int``, *optional*): The *api_id* part of your Telegram API Key, as integer. E.g.: 12345 This is an alternative way to pass it if you don't want to use the *config.ini* file. api_hash (``str``, *optional*): The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef" This is an alternative way to pass it if you don't want to use the *config.ini* file. app_version (``str``, *optional*): Application version. Defaults to "Pyrogram \U0001f525 vX.Y.Z" This is an alternative way to set it if you don't want to use the *config.ini* file. device_model (``str``, *optional*): Device model. Defaults to *platform.python_implementation() + " " + platform.python_version()* This is an alternative way to set it if you don't want to use the *config.ini* file. system_version (``str``, *optional*): Operating System version. Defaults to *platform.system() + " " + platform.release()* This is an alternative way to set it if you don't want to use the *config.ini* file. lang_code (``str``, *optional*): Code of the language used on the client, in ISO 639-1 standard. Defaults to "en". This is an alternative way to set it if you don't want to use the *config.ini* file. ipv6 (``bool``, *optional*): Pass True to connect to Telegram using IPv6. Defaults to False (IPv4). proxy (``dict``, *optional*): Your SOCKS5 Proxy settings as dict, e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*. *username* and *password* can be omitted if your proxy doesn't require authorization. This is an alternative way to setup a proxy if you don't want to use the *config.ini* file. test_mode (``bool``, *optional*): Enable or disable log-in to testing servers. Defaults to False. Only applicable for new sessions and will be ignored in case previously created sessions are loaded. phone_number (``str`` | ``callable``, *optional*): Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually. Or pass a callback function which accepts no arguments and must return the correct phone number as string (e.g., "391234567890"). Only applicable for new sessions. phone_code (``str`` | ``callable``, *optional*): Pass the phone code as string (for test numbers only) to avoid entering it manually. Or pass a callback function which accepts a single positional argument *(phone_number)* and must return the correct phone code as string (e.g., "12345"). Only applicable for new sessions. password (``str``, *optional*): Pass your Two-Step Verification password as string (if you have one) to avoid entering it manually. Or pass a callback function which accepts a single positional argument *(password_hint)* and must return the correct password as string (e.g., "password"). Only applicable for new sessions. recovery_code (``callable``, *optional*): Pass a callback function which accepts a single positional argument *(email_pattern)* and must return the correct password recovery code as string (e.g., "987654"). Only applicable for new sessions. force_sms (``str``, *optional*): Pass True to force Telegram sending the authorization code via SMS. Only applicable for new sessions. first_name (``str``, *optional*): Pass a First Name to avoid entering it manually. It will be used to automatically create a new Telegram account in case the phone number you passed is not registered yet. Only applicable for new sessions. last_name (``str``, *optional*): Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can be an empty string: "". Only applicable for new sessions. workers (``int``, *optional*): Thread pool size for handling incoming updates. Defaults to 4. workdir (``str``, *optional*): Define a custom working directory. The working directory is the location in your filesystem where Pyrogram will store your session files. Defaults to "." (current directory). config_file (``str``, *optional*): Path of the configuration file. Defaults to ./config.ini plugins_dir (``str``, *optional*): Define a custom directory for your plugins. The plugins directory is the location in your filesystem where Pyrogram will automatically load your update handlers. Defaults to None (plugins disabled). no_updates (``bool``, *optional*): Pass True to completely disable incoming updates for the current session. When updates are disabled your client can't receive any new message. Useful for batch programs that don't need to deal with updates. Defaults to False (updates enabled and always received). takeout (``bool``, *optional*): Pass True to let the client use a takeout session instead of a normal one, implies no_updates. Useful for exporting your Telegram data. Methods invoked inside a takeout session (such as get_history, download_media, ...) are less prone to throw FloodWait exceptions. Only available for users, bots will ignore this parameter. Defaults to False (normal session). """ def __init__(self, session_name: str, api_id: Union[int, str] = None, api_hash: str = None, app_version: str = None, device_model: str = None, system_version: str = None, lang_code: str = None, ipv6: bool = False, proxy: dict = None, test_mode: bool = False, phone_number: str = None, phone_code: Union[str, callable] = None, password: str = None, recovery_code: callable = None, force_sms: bool = False, first_name: str = None, last_name: str = None, workers: int = BaseClient.WORKERS, workdir: str = BaseClient.WORKDIR, config_file: str = BaseClient.CONFIG_FILE, plugins_dir: str = None, no_updates: bool = None, takeout: bool = None): super().__init__() self.session_name = session_name self.api_id = int(api_id) if api_id else None self.api_hash = api_hash self.app_version = app_version self.device_model = device_model self.system_version = system_version self.lang_code = lang_code self.ipv6 = ipv6 # TODO: Make code consistent, use underscore for private/protected fields self._proxy = proxy self.test_mode = test_mode self.phone_number = phone_number self.phone_code = phone_code self.password = password self.recovery_code = recovery_code self.force_sms = force_sms self.first_name = first_name self.last_name = last_name self.workers = workers self.workdir = workdir self.config_file = config_file self.plugins_dir = plugins_dir self.no_updates = no_updates self.takeout = takeout self.dispatcher = Dispatcher(self, workers) def __enter__(self): return self.start() def __exit__(self, *args): self.stop() @property def proxy(self): return self._proxy @proxy.setter def proxy(self, value): if value is None: self._proxy = None return if self._proxy is None: self._proxy = {} self._proxy["enabled"] = bool(value.get("enabled", True)) self._proxy.update(value) def start(self): """Use this method to start the Client after creating it. Requires no parameters. Raises: :class:`Error ` in case of a Telegram RPC error. ``ConnectionError`` in case you try to start an already started Client. """ if self.is_started: raise ConnectionError("Client has already been started") if self.BOT_TOKEN_RE.match(self.session_name): self.bot_token = self.session_name self.session_name = self.session_name.split(":")[0] self.load_config() self.load_session() self.load_plugins() self.session = Session( self, self.dc_id, self.auth_key ) self.session.start() self.is_started = True try: if self.user_id is None: if self.bot_token is None: self.authorize_user() else: self.authorize_bot() self.save_session() if self.bot_token is None: if self.takeout: self.takeout_id = self.send(functions.account.InitTakeoutSession()).id log.warning("Takeout session {} initiated".format(self.takeout_id)) now = time.time() if abs(now - self.date) > Client.OFFLINE_SLEEP: self.peers_by_username = {} self.peers_by_phone = {} self.get_initial_dialogs() self.get_contacts() else: self.send(functions.messages.GetPinnedDialogs()) self.get_initial_dialogs_chunk() else: self.send(functions.updates.GetState()) except Exception as e: self.is_started = False self.session.stop() raise e for i in range(self.UPDATES_WORKERS): self.updates_workers_list.append( Thread( target=self.updates_worker, name="UpdatesWorker#{}".format(i + 1) ) ) self.updates_workers_list[-1].start() for i in range(self.DOWNLOAD_WORKERS): self.download_workers_list.append( Thread( target=self.download_worker, name="DownloadWorker#{}".format(i + 1) ) ) self.download_workers_list[-1].start() self.dispatcher.start() mimetypes.init() Syncer.add(self) return self def stop(self): """Use this method to manually stop the Client. Requires no parameters. Raises: ``ConnectionError`` in case you try to stop an already stopped Client. """ if not self.is_started: raise ConnectionError("Client is already stopped") if self.takeout_id: self.send(functions.account.FinishTakeoutSession()) log.warning("Takeout session {} finished".format(self.takeout_id)) Syncer.remove(self) self.dispatcher.stop() for _ in range(self.DOWNLOAD_WORKERS): self.download_queue.put(None) for i in self.download_workers_list: i.join() self.download_workers_list.clear() for _ in range(self.UPDATES_WORKERS): self.updates_queue.put(None) for i in self.updates_workers_list: i.join() self.updates_workers_list.clear() for i in self.media_sessions.values(): i.stop() self.media_sessions.clear() self.is_started = False self.session.stop() return self def restart(self): """Use this method to restart the Client. Requires no parameters. Raises: ``ConnectionError`` in case you try to restart a stopped Client. """ self.stop() self.start() def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): """Blocks the program execution until one of the signals are received, then gently stop the Client by closing the underlying connection. Args: stop_signals (``tuple``, *optional*): Iterable containing signals the signal handler will listen to. Defaults to (SIGINT, SIGTERM, SIGABRT). """ def signal_handler(*args): self.is_idle = False for s in stop_signals: signal(s, signal_handler) self.is_idle = True while self.is_idle: time.sleep(1) self.stop() def run(self): """Use this method to automatically start and idle a Client. Requires no parameters. Raises: :class:`Error ` in case of a Telegram RPC error. """ self.start() self.idle() def add_handler(self, handler: Handler, group: int = 0): """Use this method to register an update handler. You can register multiple handlers, but at most one handler within a group will be used for a single update. To handle the same update more than once, register your handler using a different group id (lower group id == higher priority). Args: handler (``Handler``): The handler to be registered. group (``int``, *optional*): The group identifier, defaults to 0. Returns: A tuple of (handler, group) """ if isinstance(handler, DisconnectHandler): self.disconnect_handler = handler.callback else: self.dispatcher.add_handler(handler, group) return handler, group def remove_handler(self, handler: Handler, group: int = 0): """Removes a previously-added update handler. Make sure to provide the right group that the handler was added in. You can use the return value of the :meth:`add_handler` method, a tuple of (handler, group), and pass it directly. Args: handler (``Handler``): The handler to be removed. group (``int``, *optional*): The group identifier, defaults to 0. """ if isinstance(handler, DisconnectHandler): self.disconnect_handler = None else: self.dispatcher.remove_handler(handler, group) def stop_transmission(self): """Use this method to stop downloading or uploading a file. Must be called inside a progress callback function. """ raise Client.StopTransmission def authorize_bot(self): try: r = self.send( functions.auth.ImportBotAuthorization( flags=0, api_id=self.api_id, api_hash=self.api_hash, bot_auth_token=self.bot_token ) ) except UserMigrate as e: self.session.stop() self.dc_id = e.x self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() self.session = Session( self, self.dc_id, self.auth_key ) self.session.start() self.authorize_bot() else: self.user_id = r.user.id print("Logged in successfully as @{}".format(r.user.username)) def authorize_user(self): phone_number_invalid_raises = self.phone_number is not None phone_code_invalid_raises = self.phone_code is not None password_invalid_raises = self.password is not None first_name_invalid_raises = self.first_name is not None def default_phone_number_callback(): while True: phone_number = input("Enter phone number: ") confirm = input("Is \"{}\" correct? (y/n): ".format(phone_number)) if confirm in ("y", "1"): return phone_number elif confirm in ("n", "2"): continue while True: self.phone_number = ( default_phone_number_callback() if self.phone_number is None else str(self.phone_number()) if callable(self.phone_number) else str(self.phone_number) ) self.phone_number = self.phone_number.strip("+") try: r = self.send( functions.auth.SendCode( self.phone_number, self.api_id, self.api_hash ) ) except (PhoneMigrate, NetworkMigrate) as e: self.session.stop() self.dc_id = e.x self.auth_key = Auth( self.dc_id, self.test_mode, self.ipv6, self._proxy ).create() self.session = Session( self, self.dc_id, self.auth_key ) self.session.start() except (PhoneNumberInvalid, PhoneNumberBanned) as e: if phone_number_invalid_raises: raise else: print(e.MESSAGE) self.phone_number = None except FloodWait as e: if phone_number_invalid_raises: raise else: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) except Exception as e: log.error(e, exc_info=True) raise else: break phone_registered = r.phone_registered phone_code_hash = r.phone_code_hash terms_of_service = r.terms_of_service if terms_of_service: print("\n" + terms_of_service.text + "\n") if self.force_sms: self.send( functions.auth.ResendCode( phone_number=self.phone_number, phone_code_hash=phone_code_hash ) ) while True: if not phone_registered: self.first_name = ( input("First name: ") if self.first_name is None else str(self.first_name()) if callable(self.first_name) else str(self.first_name) ) self.last_name = ( input("Last name: ") if self.last_name is None else str(self.last_name()) if callable(self.last_name) else str(self.last_name) ) self.phone_code = ( input("Enter phone code: ") if self.phone_code is None else str(self.phone_code(self.phone_number)) if callable(self.phone_code) else str(self.phone_code) ) try: if phone_registered: try: r = self.send( functions.auth.SignIn( self.phone_number, phone_code_hash, self.phone_code ) ) except PhoneNumberUnoccupied: log.warning("Phone number unregistered") phone_registered = False continue else: try: r = self.send( functions.auth.SignUp( self.phone_number, phone_code_hash, self.phone_code, self.first_name, self.last_name ) ) except PhoneNumberOccupied: log.warning("Phone number already registered") phone_registered = True continue except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e: if phone_code_invalid_raises: raise else: print(e.MESSAGE) self.phone_code = None except FirstnameInvalid as e: if first_name_invalid_raises: raise else: print(e.MESSAGE) self.first_name = None except SessionPasswordNeeded as e: print(e.MESSAGE) def default_password_callback(password_hint: str) -> str: print("Hint: {}".format(password_hint)) return input("Enter password (empty to recover): ") def default_recovery_callback(email_pattern: str) -> str: print("An e-mail containing the recovery code has been sent to {}".format(email_pattern)) return input("Enter password recovery code: ") while True: try: r = self.send(functions.account.GetPassword()) self.password = ( default_password_callback(r.hint) if self.password is None else str(self.password(r.hint) or "") if callable(self.password) else str(self.password) ) if self.password == "": r = self.send(functions.auth.RequestPasswordRecovery()) self.recovery_code = ( default_recovery_callback(r.email_pattern) if self.recovery_code is None else str(self.recovery_code(r.email_pattern)) if callable(self.recovery_code) else str(self.recovery_code) ) r = self.send( functions.auth.RecoverPassword( code=self.recovery_code ) ) else: r = self.send( functions.auth.CheckPassword( password=compute_check(r, self.password) ) ) except (PasswordEmpty, PasswordRecoveryNa, PasswordHashInvalid) as e: if password_invalid_raises: raise else: print(e.MESSAGE) self.password = None self.recovery_code = None except FloodWait as e: if password_invalid_raises: raise else: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) self.password = None self.recovery_code = None except Exception as e: log.error(e, exc_info=True) raise else: break break except FloodWait as e: if phone_code_invalid_raises or first_name_invalid_raises: raise else: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) except Exception as e: log.error(e, exc_info=True) raise else: break if terms_of_service: assert self.send(functions.help.AcceptTermsOfService(terms_of_service.id)) self.password = None self.user_id = r.user.id print("Logged in successfully as {}".format(r.user.first_name)) def fetch_peers(self, entities: List[Union[types.User, types.Chat, types.ChatForbidden, types.Channel, types.ChannelForbidden]]): for entity in entities: if isinstance(entity, types.User): user_id = entity.id access_hash = entity.access_hash if access_hash is None: continue username = entity.username phone = entity.phone input_peer = types.InputPeerUser( user_id=user_id, access_hash=access_hash ) self.peers_by_id[user_id] = input_peer if username is not None: self.peers_by_username[username.lower()] = input_peer if phone is not None: self.peers_by_phone[phone] = input_peer if isinstance(entity, (types.Chat, types.ChatForbidden)): chat_id = entity.id peer_id = -chat_id input_peer = types.InputPeerChat( chat_id=chat_id ) self.peers_by_id[peer_id] = input_peer if isinstance(entity, (types.Channel, types.ChannelForbidden)): channel_id = entity.id peer_id = int("-100" + str(channel_id)) access_hash = entity.access_hash if access_hash is None: continue username = getattr(entity, "username", None) input_peer = types.InputPeerChannel( channel_id=channel_id, access_hash=access_hash ) self.peers_by_id[peer_id] = input_peer if username is not None: self.peers_by_username[username.lower()] = input_peer def download_worker(self): name = threading.current_thread().name log.debug("{} started".format(name)) while True: media = self.download_queue.get() if media is None: break temp_file_path = "" final_file_path = "" try: media, file_name, done, progress, progress_args, path = media file_id = media.file_id size = media.file_size directory, file_name = os.path.split(file_name) directory = directory or "downloads" try: decoded = utils.decode(file_id) fmt = " 24 else " 24: volume_id = unpacked[4] secret = unpacked[5] local_id = unpacked[6] media_type_str = Client.MEDIA_TYPE_ID.get(media_type, None) if media_type_str is None: raise FileIdInvalid("Unknown media type: {}".format(unpacked[0])) file_name = file_name or getattr(media, "file_name", None) if not file_name: if media_type == 3: extension = ".ogg" elif media_type in (4, 10, 13): extension = mimetypes.guess_extension(media.mime_type) or ".mp4" elif media_type == 5: extension = mimetypes.guess_extension(media.mime_type) or ".unknown" elif media_type == 8: extension = ".webp" elif media_type == 9: extension = mimetypes.guess_extension(media.mime_type) or ".mp3" elif media_type in (0, 1, 2): extension = ".jpg" else: continue file_name = "{}_{}_{}{}".format( media_type_str, datetime.fromtimestamp( getattr(media, "date", None) or time.time() ).strftime("%Y-%m-%d_%H-%M-%S"), self.rnd_id(), extension ) temp_file_path = self.get_file( dc_id=dc_id, id=id, access_hash=access_hash, volume_id=volume_id, local_id=local_id, secret=secret, size=size, progress=progress, progress_args=progress_args ) if temp_file_path: final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) os.makedirs(directory, exist_ok=True) shutil.move(temp_file_path, final_file_path) except Exception as e: log.error(e, exc_info=True) try: os.remove(temp_file_path) except OSError: pass else: # TODO: "" or None for faulty download, which is better? # os.path methods return "" in case something does not exist, I prefer this. # For now let's keep None path[0] = final_file_path or None finally: done.set() log.debug("{} stopped".format(name)) def updates_worker(self): name = threading.current_thread().name log.debug("{} started".format(name)) while True: updates = self.updates_queue.get() if updates is None: break try: if isinstance(updates, (types.Update, types.UpdatesCombined)): self.fetch_peers(updates.users) self.fetch_peers(updates.chats) for update in updates.updates: channel_id = getattr( getattr( getattr( update, "message", None ), "to_id", None ), "channel_id", None ) or getattr(update, "channel_id", None) pts = getattr(update, "pts", None) pts_count = getattr(update, "pts_count", None) if isinstance(update, types.UpdateChannelTooLong): log.warning(update) if isinstance(update, types.UpdateNewChannelMessage): message = update.message if not isinstance(message, types.MessageEmpty): try: diff = self.send( functions.updates.GetChannelDifference( channel=self.resolve_peer(int("-100" + str(channel_id))), filter=types.ChannelMessagesFilter( ranges=[types.MessageRange( min_id=update.message.id, max_id=update.message.id )] ), pts=pts - pts_count, limit=pts ) ) except ChannelPrivate: pass else: if not isinstance(diff, types.updates.ChannelDifferenceEmpty): updates.users += diff.users updates.chats += diff.chats if channel_id and pts: if channel_id not in self.channels_pts: self.channels_pts[channel_id] = [] if pts in self.channels_pts[channel_id]: continue self.channels_pts[channel_id].append(pts) if len(self.channels_pts[channel_id]) > 50: self.channels_pts[channel_id] = self.channels_pts[channel_id][25:] self.dispatcher.updates_queue.put((update, updates.users, updates.chats)) elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)): diff = self.send( functions.updates.GetDifference( pts=updates.pts - updates.pts_count, date=updates.date, qts=-1 ) ) if diff.new_messages: self.dispatcher.updates_queue.put(( types.UpdateNewMessage( message=diff.new_messages[0], pts=updates.pts, pts_count=updates.pts_count ), diff.users, diff.chats )) else: self.dispatcher.updates_queue.put((diff.other_updates[0], [], [])) elif isinstance(updates, types.UpdateShort): self.dispatcher.updates_queue.put((updates.update, [], [])) elif isinstance(updates, types.UpdatesTooLong): log.warning(updates) except Exception as e: log.error(e, exc_info=True) log.debug("{} stopped".format(name)) def send(self, data: Object, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT): """Use this method to send Raw Function queries. This method makes possible to manually call every single Telegram API method in a low-level manner. Available functions are listed in the :obj:`functions ` package and may accept compound data types from :obj:`types ` as well as bare types such as ``int``, ``str``, etc... Args: data (``Object``): The API Schema function filled with proper arguments. retries (``int``): Number of retries. timeout (``float``): Timeout in seconds. Raises: :class:`Error ` in case of a Telegram RPC error. """ if not self.is_started: raise ConnectionError("Client has not been started") if self.no_updates: data = functions.InvokeWithoutUpdates(data) if self.takeout_id: data = functions.InvokeWithTakeout(self.takeout_id, data) r = self.session.send(data, retries, timeout) self.fetch_peers(getattr(r, "users", [])) self.fetch_peers(getattr(r, "chats", [])) return r def load_config(self): parser = ConfigParser() parser.read(self.config_file) if self.api_id and self.api_hash: pass else: if parser.has_section("pyrogram"): self.api_id = parser.getint("pyrogram", "api_id") self.api_hash = parser.get("pyrogram", "api_hash") else: raise AttributeError( "No API Key found. " "More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration" ) for option in ["app_version", "device_model", "system_version", "lang_code"]: if getattr(self, option): pass else: if parser.has_section("pyrogram"): setattr(self, option, parser.get( "pyrogram", option, fallback=getattr(Client, option.upper()) )) else: setattr(self, option, getattr(Client, option.upper())) if self._proxy: self._proxy["enabled"] = bool(self._proxy.get("enabled", True)) else: self._proxy = {} if parser.has_section("proxy"): self._proxy["enabled"] = parser.getboolean("proxy", "enabled", fallback=True) self._proxy["hostname"] = parser.get("proxy", "hostname") self._proxy["port"] = parser.getint("proxy", "port") self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None def load_session(self): try: with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f: s = json.load(f) except FileNotFoundError: self.dc_id = 1 self.date = 0 self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() else: self.dc_id = s["dc_id"] self.test_mode = s["test_mode"] self.auth_key = base64.b64decode("".join(s["auth_key"])) self.user_id = s["user_id"] self.date = s.get("date", 0) for k, v in s.get("peers_by_id", {}).items(): self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) for k, v in s.get("peers_by_username", {}).items(): peer = self.peers_by_id.get(v, None) if peer: self.peers_by_username[k] = peer for k, v in s.get("peers_by_phone", {}).items(): peer = self.peers_by_id.get(v, None) if peer: self.peers_by_phone[k] = peer def load_plugins(self): if self.plugins_dir is not None: plugins_count = 0 for path in Path(self.plugins_dir).rglob("*.py"): file_path = os.path.splitext(str(path))[0] import_path = [] while file_path: file_path, tail = os.path.split(file_path) import_path.insert(0, tail) import_path = ".".join(import_path) module = import_module(import_path) for name in dir(module): # noinspection PyBroadException try: handler, group = getattr(module, name) if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) log.info('{}("{}") from "{}" loaded in group {}'.format( type(handler).__name__, name, import_path, group)) plugins_count += 1 except Exception: pass if plugins_count > 0: log.warning('Successfully loaded {} plugin{} from "{}"'.format( plugins_count, "s" if plugins_count > 1 else "", self.plugins_dir )) else: log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(self.plugins_dir)) def save_session(self): auth_key = base64.b64encode(self.auth_key).decode() auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] os.makedirs(self.workdir, exist_ok=True) with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), "w", encoding="utf-8") as f: json.dump( dict( dc_id=self.dc_id, test_mode=self.test_mode, auth_key=auth_key, user_id=self.user_id, date=self.date ), f, indent=4 ) def get_initial_dialogs_chunk(self, offset_date: int = 0): while True: try: r = self.send( functions.messages.GetDialogs( offset_date=offset_date, offset_id=0, offset_peer=types.InputPeerEmpty(), limit=self.DIALOGS_AT_ONCE, hash=0, exclude_pinned=True ) ) except FloodWait as e: log.warning("get_dialogs flood: waiting {} seconds".format(e.x)) time.sleep(e.x) else: log.info("Total peers: {}".format(len(self.peers_by_id))) return r def get_initial_dialogs(self): self.send(functions.messages.GetPinnedDialogs()) dialogs = self.get_initial_dialogs_chunk() offset_date = utils.get_offset_date(dialogs) while len(dialogs.dialogs) == self.DIALOGS_AT_ONCE: dialogs = self.get_initial_dialogs_chunk(offset_date) offset_date = utils.get_offset_date(dialogs) self.get_initial_dialogs_chunk() def resolve_peer(self, peer_id: Union[int, str]): """Use this method to get the InputPeer of a known peer_id. This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an InputPeer type is required. Args: peer_id (``int`` | ``str``): The peer id you want to extract the InputPeer from. Can be a direct id (int), a username (str) or a phone number (str). Returns: On success, the resolved peer id is returned in form of an InputPeer object. Raises: :class:`Error ` in case of a Telegram RPC error. ``KeyError`` in case the peer doesn't exist in the internal database. """ try: return self.peers_by_id[peer_id] except KeyError: if type(peer_id) is str: if peer_id in ("self", "me"): return types.InputPeerSelf() peer_id = re.sub(r"[@+\s]", "", peer_id.lower()) try: int(peer_id) except ValueError: if peer_id not in self.peers_by_username: self.send( functions.contacts.ResolveUsername( username=peer_id ) ) return self.peers_by_username[peer_id] else: try: return self.peers_by_phone[peer_id] except KeyError: raise PeerIdInvalid if peer_id > 0: self.fetch_peers( self.send( functions.users.GetUsers( id=[types.InputUser(peer_id, 0)] ) ) ) else: if str(peer_id).startswith("-100"): self.send( functions.channels.GetChannels( id=[types.InputChannel(int(str(peer_id)[4:]), 0)] ) ) else: self.send( functions.messages.GetChats( id=[-peer_id] ) ) try: return self.peers_by_id[peer_id] except KeyError: raise PeerIdInvalid def save_file(self, path: str, file_id: int = None, file_part: int = 0, progress: callable = None, progress_args: tuple = ()): """Use this method to upload a file onto Telegram servers, without actually sending the message to anyone. This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an InputFile type is required. Args: path (``str``): The path of the file you want to upload that exists on your local machine. file_id (``int``, *optional*): In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. file_part (``int``, *optional*): In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. progress (``callable``, *optional*): Pass a callback function to view the upload progress. The function must take *(client, current, total, \*args)* as positional arguments (look at the section below for a detailed description). progress_args (``tuple``, *optional*): Extra custom arguments for the progress callback function. Useful, for example, if you want to pass a chat_id and a message_id in order to edit a message with the updated progress. Other Parameters: client (:obj:`Client `): The Client itself, useful when you want to call other API methods inside the callback function. current (``int``): The amount of bytes uploaded so far. total (``int``): The size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. You can either keep *\*args* or add every single extra argument in your function signature. Returns: On success, the uploaded file is returned in form of an InputFile object. Raises: :class:`Error ` in case of a Telegram RPC error. """ part_size = 512 * 1024 file_size = os.path.getsize(path) if file_size == 0: raise ValueError("File size equals to 0 B") if file_size > 1500 * 1024 * 1024: raise ValueError("Telegram doesn't support uploading files bigger than 1500 MiB") file_total_parts = int(math.ceil(file_size / part_size)) is_big = True if file_size > 10 * 1024 * 1024 else False is_missing_part = True if file_id is not None else False file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None session = Session(self, self.dc_id, self.auth_key, is_media=True) session.start() try: with open(path, "rb") as f: f.seek(part_size * file_part) while True: chunk = f.read(part_size) if not chunk: if not is_big: md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) break if is_big: rpc = functions.upload.SaveBigFilePart( file_id=file_id, file_part=file_part, file_total_parts=file_total_parts, bytes=chunk ) else: rpc = functions.upload.SaveFilePart( file_id=file_id, file_part=file_part, bytes=chunk ) assert session.send(rpc), "Couldn't upload file" if is_missing_part: return if not is_big: md5_sum.update(chunk) file_part += 1 if progress: progress(self, min(file_part * part_size, file_size), file_size, *progress_args) except Client.StopTransmission: raise except Exception as e: log.error(e, exc_info=True) else: if is_big: return types.InputFileBig( id=file_id, parts=file_total_parts, name=os.path.basename(path), ) else: return types.InputFile( id=file_id, parts=file_total_parts, name=os.path.basename(path), md5_checksum=md5_sum ) finally: session.stop() def get_file(self, dc_id: int, id: int = None, access_hash: int = None, volume_id: int = None, local_id: int = None, secret: int = None, size: int = None, progress: callable = None, progress_args: tuple = ()) -> str: with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) if session is None: if dc_id != self.dc_id: exported_auth = self.send( functions.auth.ExportAuthorization( dc_id=dc_id ) ) session = Session( self, dc_id, Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(), is_media=True ) session.start() self.media_sessions[dc_id] = session session.send( functions.auth.ImportAuthorization( id=exported_auth.id, bytes=exported_auth.bytes ) ) else: session = Session( self, dc_id, self.auth_key, is_media=True ) session.start() self.media_sessions[dc_id] = session if volume_id: # Photos are accessed by volume_id, local_id, secret location = types.InputFileLocation( volume_id=volume_id, local_id=local_id, secret=secret, file_reference=b"" ) else: # Any other file can be more easily accessed by id and access_hash location = types.InputDocumentFileLocation( id=id, access_hash=access_hash, file_reference=b"" ) limit = 1024 * 1024 offset = 0 file_name = "" try: r = session.send( functions.upload.GetFile( location=location, offset=offset, limit=limit ) ) if isinstance(r, types.upload.File): with tempfile.NamedTemporaryFile("wb", delete=False) as f: file_name = f.name while True: chunk = r.bytes if not chunk: break f.write(chunk) offset += limit if progress: progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) r = session.send( functions.upload.GetFile( location=location, offset=offset, limit=limit ) ) elif isinstance(r, types.upload.FileCdnRedirect): with self.media_sessions_lock: cdn_session = self.media_sessions.get(r.dc_id, None) if cdn_session is None: cdn_session = Session( self, r.dc_id, Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(), is_media=True, is_cdn=True ) cdn_session.start() self.media_sessions[r.dc_id] = cdn_session try: with tempfile.NamedTemporaryFile("wb", delete=False) as f: file_name = f.name while True: r2 = cdn_session.send( functions.upload.GetCdnFile( file_token=r.file_token, offset=offset, limit=limit ) ) if isinstance(r2, types.upload.CdnFileReuploadNeeded): try: session.send( functions.upload.ReuploadCdnFile( file_token=r.file_token, request_token=r2.request_token ) ) except VolumeLocNotFound: break else: continue chunk = r2.bytes # https://core.telegram.org/cdn#decrypting-files decrypted_chunk = AES.ctr256_decrypt( chunk, r.encryption_key, bytearray( r.encryption_iv[:-4] + (offset // 16).to_bytes(4, "big") ) ) hashes = session.send( functions.upload.GetCdnFileHashes( r.file_token, offset ) ) # https://core.telegram.org/cdn#verifying-files for i, h in enumerate(hashes): cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i) f.write(decrypted_chunk) offset += limit if progress: progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) if len(chunk) < limit: break except Exception as e: raise e except Exception as e: if not isinstance(e, Client.StopTransmission): log.error(e, exc_info=True) try: os.remove(file_name) except OSError: pass return "" else: return file_name \ No newline at end of file +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import base64 +import binascii +import json +import logging +import math +import mimetypes +import os +import re +import shutil +import struct +import tempfile +import threading +import time +from configparser import ConfigParser +from datetime import datetime +from hashlib import sha256, md5 +from importlib import import_module +from pathlib import Path +from signal import signal, SIGINT, SIGTERM, SIGABRT +from threading import Thread +from typing import Union, List + +from pyrogram.api import functions, types +from pyrogram.api.core import Object +from pyrogram.api.errors import ( + PhoneMigrate, NetworkMigrate, PhoneNumberInvalid, + PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty, + PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, + PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, + VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied, + PasswordRecoveryNa, PasswordEmpty +) +from pyrogram.client.handlers import DisconnectHandler +from pyrogram.client.handlers.handler import Handler +from pyrogram.client.methods.password.utils import compute_check +from pyrogram.crypto import AES +from pyrogram.session import Auth, Session +from .dispatcher import Dispatcher +from .ext import utils, Syncer, BaseClient +from .methods import Methods + +log = logging.getLogger(__name__) + + +class Client(Methods, BaseClient): + """This class represents a Client, the main mean for interacting with Telegram. + It exposes bot-like methods for an easy access to the API as well as a simple way to + invoke every single Telegram API method available. + + Args: + session_name (``str``): + Name to uniquely identify a session of either a User or a Bot. + For Users: pass a string of your choice, e.g.: "my_main_account". + For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" + Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number. + + api_id (``int``, *optional*): + The *api_id* part of your Telegram API Key, as integer. E.g.: 12345 + This is an alternative way to pass it if you don't want to use the *config.ini* file. + + api_hash (``str``, *optional*): + The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef" + This is an alternative way to pass it if you don't want to use the *config.ini* file. + + app_version (``str``, *optional*): + Application version. Defaults to "Pyrogram \U0001f525 vX.Y.Z" + This is an alternative way to set it if you don't want to use the *config.ini* file. + + device_model (``str``, *optional*): + Device model. Defaults to *platform.python_implementation() + " " + platform.python_version()* + This is an alternative way to set it if you don't want to use the *config.ini* file. + + system_version (``str``, *optional*): + Operating System version. Defaults to *platform.system() + " " + platform.release()* + This is an alternative way to set it if you don't want to use the *config.ini* file. + + lang_code (``str``, *optional*): + Code of the language used on the client, in ISO 639-1 standard. Defaults to "en". + This is an alternative way to set it if you don't want to use the *config.ini* file. + + ipv6 (``bool``, *optional*): + Pass True to connect to Telegram using IPv6. + Defaults to False (IPv4). + + proxy (``dict``, *optional*): + Your SOCKS5 Proxy settings as dict, + e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*. + *username* and *password* can be omitted if your proxy doesn't require authorization. + This is an alternative way to setup a proxy if you don't want to use the *config.ini* file. + + test_mode (``bool``, *optional*): + Enable or disable log-in to testing servers. Defaults to False. + Only applicable for new sessions and will be ignored in case previously + created sessions are loaded. + + phone_number (``str`` | ``callable``, *optional*): + Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually. + Or pass a callback function which accepts no arguments and must return the correct phone number as string + (e.g., "391234567890"). + Only applicable for new sessions. + + phone_code (``str`` | ``callable``, *optional*): + Pass the phone code as string (for test numbers only) to avoid entering it manually. Or pass a callback + function which accepts a single positional argument *(phone_number)* and must return the correct phone code + as string (e.g., "12345"). + Only applicable for new sessions. + + password (``str``, *optional*): + Pass your Two-Step Verification password as string (if you have one) to avoid entering it manually. + Or pass a callback function which accepts a single positional argument *(password_hint)* and must return + the correct password as string (e.g., "password"). + Only applicable for new sessions. + + recovery_code (``callable``, *optional*): + Pass a callback function which accepts a single positional argument *(email_pattern)* and must return the + correct password recovery code as string (e.g., "987654"). + Only applicable for new sessions. + + force_sms (``str``, *optional*): + Pass True to force Telegram sending the authorization code via SMS. + Only applicable for new sessions. + + first_name (``str``, *optional*): + Pass a First Name to avoid entering it manually. It will be used to automatically + create a new Telegram account in case the phone number you passed is not registered yet. + Only applicable for new sessions. + + last_name (``str``, *optional*): + Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can + be an empty string: "". Only applicable for new sessions. + + workers (``int``, *optional*): + Thread pool size for handling incoming updates. Defaults to 4. + + workdir (``str``, *optional*): + Define a custom working directory. The working directory is the location in your filesystem + where Pyrogram will store your session files. Defaults to "." (current directory). + + config_file (``str``, *optional*): + Path of the configuration file. Defaults to ./config.ini + + plugins_dir (``str``, *optional*): + Define a custom directory for your plugins. The plugins directory is the location in your + filesystem where Pyrogram will automatically load your update handlers. + Defaults to None (plugins disabled). + + no_updates (``bool``, *optional*): + Pass True to completely disable incoming updates for the current session. + When updates are disabled your client can't receive any new message. + Useful for batch programs that don't need to deal with updates. + Defaults to False (updates enabled and always received). + + takeout (``bool``, *optional*): + Pass True to let the client use a takeout session instead of a normal one, implies no_updates. + Useful for exporting your Telegram data. Methods invoked inside a takeout session (such as get_history, + download_media, ...) are less prone to throw FloodWait exceptions. + Only available for users, bots will ignore this parameter. + Defaults to False (normal session). + """ + + def __init__(self, + session_name: str, + api_id: Union[int, str] = None, + api_hash: str = None, + app_version: str = None, + device_model: str = None, + system_version: str = None, + lang_code: str = None, + ipv6: bool = False, + proxy: dict = None, + test_mode: bool = False, + phone_number: str = None, + phone_code: Union[str, callable] = None, + password: str = None, + recovery_code: callable = None, + force_sms: bool = False, + first_name: str = None, + last_name: str = None, + workers: int = BaseClient.WORKERS, + workdir: str = BaseClient.WORKDIR, + config_file: str = BaseClient.CONFIG_FILE, + plugins_dir: str = None, + no_updates: bool = None, + takeout: bool = None): + super().__init__() + + self.session_name = session_name + self.api_id = int(api_id) if api_id else None + self.api_hash = api_hash + self.app_version = app_version + self.device_model = device_model + self.system_version = system_version + self.lang_code = lang_code + self.ipv6 = ipv6 + # TODO: Make code consistent, use underscore for private/protected fields + self._proxy = proxy + self.test_mode = test_mode + self.phone_number = phone_number + self.phone_code = phone_code + self.password = password + self.recovery_code = recovery_code + self.force_sms = force_sms + self.first_name = first_name + self.last_name = last_name + self.workers = workers + self.workdir = workdir + self.config_file = config_file + self.plugins_dir = plugins_dir + self.no_updates = no_updates + self.takeout = takeout + + self.dispatcher = Dispatcher(self, workers) + + def __enter__(self): + return self.start() + + def __exit__(self, *args): + self.stop() + + @property + def proxy(self): + return self._proxy + + @proxy.setter + def proxy(self, value): + if value is None: + self._proxy = None + return + + if self._proxy is None: + self._proxy = {} + + self._proxy["enabled"] = bool(value.get("enabled", True)) + self._proxy.update(value) + + def start(self): + """Use this method to start the Client after creating it. + Requires no parameters. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + ``ConnectionError`` in case you try to start an already started Client. + """ + if self.is_started: + raise ConnectionError("Client has already been started") + + if self.BOT_TOKEN_RE.match(self.session_name): + self.bot_token = self.session_name + self.session_name = self.session_name.split(":")[0] + + self.load_config() + self.load_session() + self.load_plugins() + + self.session = Session( + self, + self.dc_id, + self.auth_key + ) + + self.session.start() + self.is_started = True + + try: + if self.user_id is None: + if self.bot_token is None: + self.authorize_user() + else: + self.authorize_bot() + + self.save_session() + + if self.bot_token is None: + if self.takeout: + self.takeout_id = self.send(functions.account.InitTakeoutSession()).id + log.warning("Takeout session {} initiated".format(self.takeout_id)) + + now = time.time() + + if abs(now - self.date) > Client.OFFLINE_SLEEP: + self.peers_by_username = {} + self.peers_by_phone = {} + + self.get_initial_dialogs() + self.get_contacts() + else: + self.send(functions.messages.GetPinnedDialogs()) + self.get_initial_dialogs_chunk() + else: + self.send(functions.updates.GetState()) + except Exception as e: + self.is_started = False + self.session.stop() + raise e + + for i in range(self.UPDATES_WORKERS): + self.updates_workers_list.append( + Thread( + target=self.updates_worker, + name="UpdatesWorker#{}".format(i + 1) + ) + ) + + self.updates_workers_list[-1].start() + + for i in range(self.DOWNLOAD_WORKERS): + self.download_workers_list.append( + Thread( + target=self.download_worker, + name="DownloadWorker#{}".format(i + 1) + ) + ) + + self.download_workers_list[-1].start() + + self.dispatcher.start() + + mimetypes.init() + Syncer.add(self) + + return self + + def stop(self): + """Use this method to manually stop the Client. + Requires no parameters. + + Raises: + ``ConnectionError`` in case you try to stop an already stopped Client. + """ + if not self.is_started: + raise ConnectionError("Client is already stopped") + + if self.takeout_id: + self.send(functions.account.FinishTakeoutSession()) + log.warning("Takeout session {} finished".format(self.takeout_id)) + + Syncer.remove(self) + self.dispatcher.stop() + + for _ in range(self.DOWNLOAD_WORKERS): + self.download_queue.put(None) + + for i in self.download_workers_list: + i.join() + + self.download_workers_list.clear() + + for _ in range(self.UPDATES_WORKERS): + self.updates_queue.put(None) + + for i in self.updates_workers_list: + i.join() + + self.updates_workers_list.clear() + + for i in self.media_sessions.values(): + i.stop() + + self.media_sessions.clear() + + self.is_started = False + self.session.stop() + + return self + + def restart(self): + """Use this method to restart the Client. + Requires no parameters. + + Raises: + ``ConnectionError`` in case you try to restart a stopped Client. + """ + self.stop() + self.start() + + def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): + """Blocks the program execution until one of the signals are received, + then gently stop the Client by closing the underlying connection. + + Args: + stop_signals (``tuple``, *optional*): + Iterable containing signals the signal handler will listen to. + Defaults to (SIGINT, SIGTERM, SIGABRT). + """ + + def signal_handler(*args): + self.is_idle = False + + for s in stop_signals: + signal(s, signal_handler) + + self.is_idle = True + + while self.is_idle: + time.sleep(1) + + self.stop() + + def run(self): + """Use this method to automatically start and idle a Client. + Requires no parameters. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + self.start() + self.idle() + + def add_handler(self, handler: Handler, group: int = 0): + """Use this method to register an update handler. + + You can register multiple handlers, but at most one handler within a group + will be used for a single update. To handle the same update more than once, register + your handler using a different group id (lower group id == higher priority). + + Args: + handler (``Handler``): + The handler to be registered. + + group (``int``, *optional*): + The group identifier, defaults to 0. + + Returns: + A tuple of (handler, group) + """ + if isinstance(handler, DisconnectHandler): + self.disconnect_handler = handler.callback + else: + self.dispatcher.add_handler(handler, group) + + return handler, group + + def remove_handler(self, handler: Handler, group: int = 0): + """Removes a previously-added update handler. + + Make sure to provide the right group that the handler was added in. You can use + the return value of the :meth:`add_handler` method, a tuple of (handler, group), and + pass it directly. + + Args: + handler (``Handler``): + The handler to be removed. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + if isinstance(handler, DisconnectHandler): + self.disconnect_handler = None + else: + self.dispatcher.remove_handler(handler, group) + + def stop_transmission(self): + """Use this method to stop downloading or uploading a file. + Must be called inside a progress callback function. + """ + raise Client.StopTransmission + + def authorize_bot(self): + try: + r = self.send( + functions.auth.ImportBotAuthorization( + flags=0, + api_id=self.api_id, + api_hash=self.api_hash, + bot_auth_token=self.bot_token + ) + ) + except UserMigrate as e: + self.session.stop() + + self.dc_id = e.x + self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() + + self.session = Session( + self, + self.dc_id, + self.auth_key + ) + + self.session.start() + self.authorize_bot() + else: + self.user_id = r.user.id + + print("Logged in successfully as @{}".format(r.user.username)) + + def authorize_user(self): + phone_number_invalid_raises = self.phone_number is not None + phone_code_invalid_raises = self.phone_code is not None + password_invalid_raises = self.password is not None + first_name_invalid_raises = self.first_name is not None + + def default_phone_number_callback(): + while True: + phone_number = input("Enter phone number: ") + confirm = input("Is \"{}\" correct? (y/n): ".format(phone_number)) + + if confirm in ("y", "1"): + return phone_number + elif confirm in ("n", "2"): + continue + + while True: + self.phone_number = ( + default_phone_number_callback() if self.phone_number is None + else str(self.phone_number()) if callable(self.phone_number) + else str(self.phone_number) + ) + + self.phone_number = self.phone_number.strip("+") + + try: + r = self.send( + functions.auth.SendCode( + self.phone_number, + self.api_id, + self.api_hash + ) + ) + except (PhoneMigrate, NetworkMigrate) as e: + self.session.stop() + + self.dc_id = e.x + + self.auth_key = Auth( + self.dc_id, + self.test_mode, + self.ipv6, + self._proxy + ).create() + + self.session = Session( + self, + self.dc_id, + self.auth_key + ) + + self.session.start() + except (PhoneNumberInvalid, PhoneNumberBanned) as e: + if phone_number_invalid_raises: + raise + else: + print(e.MESSAGE) + self.phone_number = None + except FloodWait as e: + if phone_number_invalid_raises: + raise + else: + print(e.MESSAGE.format(x=e.x)) + time.sleep(e.x) + except Exception as e: + log.error(e, exc_info=True) + raise + else: + break + + phone_registered = r.phone_registered + phone_code_hash = r.phone_code_hash + terms_of_service = r.terms_of_service + + if terms_of_service: + print("\n" + terms_of_service.text + "\n") + + if self.force_sms: + self.send( + functions.auth.ResendCode( + phone_number=self.phone_number, + phone_code_hash=phone_code_hash + ) + ) + + while True: + if not phone_registered: + self.first_name = ( + input("First name: ") if self.first_name is None + else str(self.first_name()) if callable(self.first_name) + else str(self.first_name) + ) + + self.last_name = ( + input("Last name: ") if self.last_name is None + else str(self.last_name()) if callable(self.last_name) + else str(self.last_name) + ) + + self.phone_code = ( + input("Enter phone code: ") if self.phone_code is None + else str(self.phone_code(self.phone_number)) if callable(self.phone_code) + else str(self.phone_code) + ) + + try: + if phone_registered: + try: + r = self.send( + functions.auth.SignIn( + self.phone_number, + phone_code_hash, + self.phone_code + ) + ) + except PhoneNumberUnoccupied: + log.warning("Phone number unregistered") + phone_registered = False + continue + else: + try: + r = self.send( + functions.auth.SignUp( + self.phone_number, + phone_code_hash, + self.phone_code, + self.first_name, + self.last_name + ) + ) + except PhoneNumberOccupied: + log.warning("Phone number already registered") + phone_registered = True + continue + except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e: + if phone_code_invalid_raises: + raise + else: + print(e.MESSAGE) + self.phone_code = None + except FirstnameInvalid as e: + if first_name_invalid_raises: + raise + else: + print(e.MESSAGE) + self.first_name = None + except SessionPasswordNeeded as e: + print(e.MESSAGE) + + def default_password_callback(password_hint: str) -> str: + print("Hint: {}".format(password_hint)) + return input("Enter password (empty to recover): ") + + def default_recovery_callback(email_pattern: str) -> str: + print("An e-mail containing the recovery code has been sent to {}".format(email_pattern)) + return input("Enter password recovery code: ") + + while True: + try: + r = self.send(functions.account.GetPassword()) + + self.password = ( + default_password_callback(r.hint) if self.password is None + else str(self.password(r.hint) or "") if callable(self.password) + else str(self.password) + ) + + if self.password == "": + r = self.send(functions.auth.RequestPasswordRecovery()) + + self.recovery_code = ( + default_recovery_callback(r.email_pattern) if self.recovery_code is None + else str(self.recovery_code(r.email_pattern)) if callable(self.recovery_code) + else str(self.recovery_code) + ) + + r = self.send( + functions.auth.RecoverPassword( + code=self.recovery_code + ) + ) + else: + r = self.send( + functions.auth.CheckPassword( + password=compute_check(r, self.password) + ) + ) + except (PasswordEmpty, PasswordRecoveryNa, PasswordHashInvalid) as e: + if password_invalid_raises: + raise + else: + print(e.MESSAGE) + self.password = None + self.recovery_code = None + except FloodWait as e: + if password_invalid_raises: + raise + else: + print(e.MESSAGE.format(x=e.x)) + time.sleep(e.x) + self.password = None + self.recovery_code = None + except Exception as e: + log.error(e, exc_info=True) + raise + else: + break + break + except FloodWait as e: + if phone_code_invalid_raises or first_name_invalid_raises: + raise + else: + print(e.MESSAGE.format(x=e.x)) + time.sleep(e.x) + except Exception as e: + log.error(e, exc_info=True) + raise + else: + break + + if terms_of_service: + assert self.send(functions.help.AcceptTermsOfService(terms_of_service.id)) + + self.password = None + self.user_id = r.user.id + + print("Logged in successfully as {}".format(r.user.first_name)) + + def fetch_peers(self, entities: List[Union[types.User, + types.Chat, types.ChatForbidden, + types.Channel, types.ChannelForbidden]]): + for entity in entities: + if isinstance(entity, types.User): + user_id = entity.id + + access_hash = entity.access_hash + + if access_hash is None: + continue + + username = entity.username + phone = entity.phone + + input_peer = types.InputPeerUser( + user_id=user_id, + access_hash=access_hash + ) + + self.peers_by_id[user_id] = input_peer + + if username is not None: + self.peers_by_username[username.lower()] = input_peer + + if phone is not None: + self.peers_by_phone[phone] = input_peer + + if isinstance(entity, (types.Chat, types.ChatForbidden)): + chat_id = entity.id + peer_id = -chat_id + + input_peer = types.InputPeerChat( + chat_id=chat_id + ) + + self.peers_by_id[peer_id] = input_peer + + if isinstance(entity, (types.Channel, types.ChannelForbidden)): + channel_id = entity.id + peer_id = int("-100" + str(channel_id)) + + access_hash = entity.access_hash + + if access_hash is None: + continue + + username = getattr(entity, "username", None) + + input_peer = types.InputPeerChannel( + channel_id=channel_id, + access_hash=access_hash + ) + + self.peers_by_id[peer_id] = input_peer + + if username is not None: + self.peers_by_username[username.lower()] = input_peer + + def download_worker(self): + name = threading.current_thread().name + log.debug("{} started".format(name)) + + while True: + media = self.download_queue.get() + + if media is None: + break + + temp_file_path = "" + final_file_path = "" + + try: + media, file_name, done, progress, progress_args, path = media + + file_id = media.file_id + size = media.file_size + + directory, file_name = os.path.split(file_name) + directory = directory or "downloads" + + try: + decoded = utils.decode(file_id) + fmt = " 24 else " 24: + volume_id = unpacked[4] + secret = unpacked[5] + local_id = unpacked[6] + + media_type_str = Client.MEDIA_TYPE_ID.get(media_type, None) + + if media_type_str is None: + raise FileIdInvalid("Unknown media type: {}".format(unpacked[0])) + + file_name = file_name or getattr(media, "file_name", None) + + if not file_name: + if media_type == 3: + extension = ".ogg" + elif media_type in (4, 10, 13): + extension = mimetypes.guess_extension(media.mime_type) or ".mp4" + elif media_type == 5: + extension = mimetypes.guess_extension(media.mime_type) or ".unknown" + elif media_type == 8: + extension = ".webp" + elif media_type == 9: + extension = mimetypes.guess_extension(media.mime_type) or ".mp3" + elif media_type in (0, 1, 2): + extension = ".jpg" + else: + continue + + file_name = "{}_{}_{}{}".format( + media_type_str, + datetime.fromtimestamp( + getattr(media, "date", None) or time.time() + ).strftime("%Y-%m-%d_%H-%M-%S"), + self.rnd_id(), + extension + ) + + temp_file_path = self.get_file( + dc_id=dc_id, + id=id, + access_hash=access_hash, + volume_id=volume_id, + local_id=local_id, + secret=secret, + size=size, + progress=progress, + progress_args=progress_args + ) + + if temp_file_path: + final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + os.makedirs(directory, exist_ok=True) + shutil.move(temp_file_path, final_file_path) + except Exception as e: + log.error(e, exc_info=True) + + try: + os.remove(temp_file_path) + except OSError: + pass + else: + # TODO: "" or None for faulty download, which is better? + # os.path methods return "" in case something does not exist, I prefer this. + # For now let's keep None + path[0] = final_file_path or None + finally: + done.set() + + log.debug("{} stopped".format(name)) + + def updates_worker(self): + name = threading.current_thread().name + log.debug("{} started".format(name)) + + while True: + updates = self.updates_queue.get() + + if updates is None: + break + + try: + if isinstance(updates, (types.Update, types.UpdatesCombined)): + self.fetch_peers(updates.users) + self.fetch_peers(updates.chats) + + for update in updates.updates: + channel_id = getattr( + getattr( + getattr( + update, "message", None + ), "to_id", None + ), "channel_id", None + ) or getattr(update, "channel_id", None) + + pts = getattr(update, "pts", None) + pts_count = getattr(update, "pts_count", None) + + if isinstance(update, types.UpdateChannelTooLong): + log.warning(update) + + if isinstance(update, types.UpdateNewChannelMessage): + message = update.message + + if not isinstance(message, types.MessageEmpty): + try: + diff = self.send( + functions.updates.GetChannelDifference( + channel=self.resolve_peer(int("-100" + str(channel_id))), + filter=types.ChannelMessagesFilter( + ranges=[types.MessageRange( + min_id=update.message.id, + max_id=update.message.id + )] + ), + pts=pts - pts_count, + limit=pts + ) + ) + except ChannelPrivate: + pass + else: + if not isinstance(diff, types.updates.ChannelDifferenceEmpty): + updates.users += diff.users + updates.chats += diff.chats + + if channel_id and pts: + if channel_id not in self.channels_pts: + self.channels_pts[channel_id] = [] + + if pts in self.channels_pts[channel_id]: + continue + + self.channels_pts[channel_id].append(pts) + + if len(self.channels_pts[channel_id]) > 50: + self.channels_pts[channel_id] = self.channels_pts[channel_id][25:] + + self.dispatcher.updates_queue.put((update, updates.users, updates.chats)) + elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)): + diff = self.send( + functions.updates.GetDifference( + pts=updates.pts - updates.pts_count, + date=updates.date, + qts=-1 + ) + ) + + if diff.new_messages: + self.dispatcher.updates_queue.put(( + types.UpdateNewMessage( + message=diff.new_messages[0], + pts=updates.pts, + pts_count=updates.pts_count + ), + diff.users, + diff.chats + )) + else: + self.dispatcher.updates_queue.put((diff.other_updates[0], [], [])) + elif isinstance(updates, types.UpdateShort): + self.dispatcher.updates_queue.put((updates.update, [], [])) + elif isinstance(updates, types.UpdatesTooLong): + log.warning(updates) + except Exception as e: + log.error(e, exc_info=True) + + log.debug("{} stopped".format(name)) + + def send(self, + data: Object, + retries: int = Session.MAX_RETRIES, + timeout: float = Session.WAIT_TIMEOUT): + """Use this method to send Raw Function queries. + + This method makes possible to manually call every single Telegram API method in a low-level manner. + Available functions are listed in the :obj:`functions ` package and may accept compound + data types from :obj:`types ` as well as bare types such as ``int``, ``str``, etc... + + Args: + data (``Object``): + The API Schema function filled with proper arguments. + + retries (``int``): + Number of retries. + + timeout (``float``): + Timeout in seconds. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if not self.is_started: + raise ConnectionError("Client has not been started") + + if self.no_updates: + data = functions.InvokeWithoutUpdates(data) + + if self.takeout_id: + data = functions.InvokeWithTakeout(self.takeout_id, data) + + r = self.session.send(data, retries, timeout) + + self.fetch_peers(getattr(r, "users", [])) + self.fetch_peers(getattr(r, "chats", [])) + + return r + + def load_config(self): + parser = ConfigParser() + parser.read(self.config_file) + + if self.api_id and self.api_hash: + pass + else: + if parser.has_section("pyrogram"): + self.api_id = parser.getint("pyrogram", "api_id") + self.api_hash = parser.get("pyrogram", "api_hash") + else: + raise AttributeError( + "No API Key found. " + "More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration" + ) + + for option in ["app_version", "device_model", "system_version", "lang_code"]: + if getattr(self, option): + pass + else: + if parser.has_section("pyrogram"): + setattr(self, option, parser.get( + "pyrogram", + option, + fallback=getattr(Client, option.upper()) + )) + else: + setattr(self, option, getattr(Client, option.upper())) + + if self._proxy: + self._proxy["enabled"] = bool(self._proxy.get("enabled", True)) + else: + self._proxy = {} + + if parser.has_section("proxy"): + self._proxy["enabled"] = parser.getboolean("proxy", "enabled", fallback=True) + self._proxy["hostname"] = parser.get("proxy", "hostname") + self._proxy["port"] = parser.getint("proxy", "port") + self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None + self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None + + def load_session(self): + try: + with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f: + s = json.load(f) + except FileNotFoundError: + self.dc_id = 1 + self.date = 0 + self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() + else: + self.dc_id = s["dc_id"] + self.test_mode = s["test_mode"] + self.auth_key = base64.b64decode("".join(s["auth_key"])) + self.user_id = s["user_id"] + self.date = s.get("date", 0) + + for k, v in s.get("peers_by_id", {}).items(): + self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) + + for k, v in s.get("peers_by_username", {}).items(): + peer = self.peers_by_id.get(v, None) + + if peer: + self.peers_by_username[k] = peer + + for k, v in s.get("peers_by_phone", {}).items(): + peer = self.peers_by_id.get(v, None) + + if peer: + self.peers_by_phone[k] = peer + + def load_plugins(self): + if self.plugins_dir is not None: + plugins_count = 0 + + for path in Path(self.plugins_dir).rglob("*.py"): + file_path = os.path.splitext(str(path))[0] + import_path = [] + + while file_path: + file_path, tail = os.path.split(file_path) + import_path.insert(0, tail) + + import_path = ".".join(import_path) + module = import_module(import_path) + + for name in dir(module): + # noinspection PyBroadException + try: + handler, group = getattr(module, name) + + if isinstance(handler, Handler) and isinstance(group, int): + self.add_handler(handler, group) + + log.info('{}("{}") from "{}" loaded in group {}'.format( + type(handler).__name__, name, import_path, group)) + + plugins_count += 1 + except Exception: + pass + + if plugins_count > 0: + log.warning('Successfully loaded {} plugin{} from "{}"'.format( + plugins_count, + "s" if plugins_count > 1 else "", + self.plugins_dir + )) + else: + log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(self.plugins_dir)) + + def save_session(self): + auth_key = base64.b64encode(self.auth_key).decode() + auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] + + os.makedirs(self.workdir, exist_ok=True) + + with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), "w", encoding="utf-8") as f: + json.dump( + dict( + dc_id=self.dc_id, + test_mode=self.test_mode, + auth_key=auth_key, + user_id=self.user_id, + date=self.date + ), + f, + indent=4 + ) + + def get_initial_dialogs_chunk(self, + offset_date: int = 0): + while True: + try: + r = self.send( + functions.messages.GetDialogs( + offset_date=offset_date, + offset_id=0, + offset_peer=types.InputPeerEmpty(), + limit=self.DIALOGS_AT_ONCE, + hash=0, + exclude_pinned=True + ) + ) + except FloodWait as e: + log.warning("get_dialogs flood: waiting {} seconds".format(e.x)) + time.sleep(e.x) + else: + log.info("Total peers: {}".format(len(self.peers_by_id))) + return r + + def get_initial_dialogs(self): + self.send(functions.messages.GetPinnedDialogs()) + + dialogs = self.get_initial_dialogs_chunk() + offset_date = utils.get_offset_date(dialogs) + + while len(dialogs.dialogs) == self.DIALOGS_AT_ONCE: + dialogs = self.get_initial_dialogs_chunk(offset_date) + offset_date = utils.get_offset_date(dialogs) + + self.get_initial_dialogs_chunk() + + def resolve_peer(self, + peer_id: Union[int, str]): + """Use this method to get the InputPeer of a known peer_id. + + This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API + method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an + InputPeer type is required. + + Args: + peer_id (``int`` | ``str``): + The peer id you want to extract the InputPeer from. + Can be a direct id (int), a username (str) or a phone number (str). + + Returns: + On success, the resolved peer id is returned in form of an InputPeer object. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + ``KeyError`` in case the peer doesn't exist in the internal database. + """ + try: + return self.peers_by_id[peer_id] + except KeyError: + if type(peer_id) is str: + if peer_id in ("self", "me"): + return types.InputPeerSelf() + + peer_id = re.sub(r"[@+\s]", "", peer_id.lower()) + + try: + int(peer_id) + except ValueError: + if peer_id not in self.peers_by_username: + self.send( + functions.contacts.ResolveUsername( + username=peer_id + ) + ) + + return self.peers_by_username[peer_id] + else: + try: + return self.peers_by_phone[peer_id] + except KeyError: + raise PeerIdInvalid + + if peer_id > 0: + self.fetch_peers( + self.send( + functions.users.GetUsers( + id=[types.InputUser(peer_id, 0)] + ) + ) + ) + else: + if str(peer_id).startswith("-100"): + self.send( + functions.channels.GetChannels( + id=[types.InputChannel(int(str(peer_id)[4:]), 0)] + ) + ) + else: + self.send( + functions.messages.GetChats( + id=[-peer_id] + ) + ) + + try: + return self.peers_by_id[peer_id] + except KeyError: + raise PeerIdInvalid + + def save_file(self, + path: str, + file_id: int = None, + file_part: int = 0, + progress: callable = None, + progress_args: tuple = ()): + """Use this method to upload a file onto Telegram servers, without actually sending the message to anyone. + + This is a utility method intended to be used **only** when working with Raw Functions (i.e: a Telegram API + method you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an + InputFile type is required. + + Args: + path (``str``): + The path of the file you want to upload that exists on your local machine. + + file_id (``int``, *optional*): + In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. + + file_part (``int``, *optional*): + In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the uploaded file is returned in form of an InputFile object. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + part_size = 512 * 1024 + file_size = os.path.getsize(path) + + if file_size == 0: + raise ValueError("File size equals to 0 B") + + if file_size > 1500 * 1024 * 1024: + raise ValueError("Telegram doesn't support uploading files bigger than 1500 MiB") + + file_total_parts = int(math.ceil(file_size / part_size)) + is_big = True if file_size > 10 * 1024 * 1024 else False + is_missing_part = True if file_id is not None else False + file_id = file_id or self.rnd_id() + md5_sum = md5() if not is_big and not is_missing_part else None + + session = Session(self, self.dc_id, self.auth_key, is_media=True) + session.start() + + try: + with open(path, "rb") as f: + f.seek(part_size * file_part) + + while True: + chunk = f.read(part_size) + + if not chunk: + if not is_big: + md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) + break + + if is_big: + rpc = functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk + ) + else: + rpc = functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk + ) + + assert session.send(rpc), "Couldn't upload file" + + if is_missing_part: + return + + if not is_big: + md5_sum.update(chunk) + + file_part += 1 + + if progress: + progress(self, min(file_part * part_size, file_size), file_size, *progress_args) + except Client.StopTransmission: + raise + except Exception as e: + log.error(e, exc_info=True) + else: + if is_big: + return types.InputFileBig( + id=file_id, + parts=file_total_parts, + name=os.path.basename(path), + + ) + else: + return types.InputFile( + id=file_id, + parts=file_total_parts, + name=os.path.basename(path), + md5_checksum=md5_sum + ) + finally: + session.stop() + + def get_file(self, + dc_id: int, + id: int = None, + access_hash: int = None, + volume_id: int = None, + local_id: int = None, + secret: int = None, + size: int = None, + progress: callable = None, + progress_args: tuple = ()) -> str: + with self.media_sessions_lock: + session = self.media_sessions.get(dc_id, None) + + if session is None: + if dc_id != self.dc_id: + exported_auth = self.send( + functions.auth.ExportAuthorization( + dc_id=dc_id + ) + ) + + session = Session( + self, + dc_id, + Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(), + is_media=True + ) + + session.start() + + self.media_sessions[dc_id] = session + + session.send( + functions.auth.ImportAuthorization( + id=exported_auth.id, + bytes=exported_auth.bytes + ) + ) + else: + session = Session( + self, + dc_id, + self.auth_key, + is_media=True + ) + + session.start() + + self.media_sessions[dc_id] = session + + if volume_id: # Photos are accessed by volume_id, local_id, secret + location = types.InputFileLocation( + volume_id=volume_id, + local_id=local_id, + secret=secret, + file_reference=b"" + ) + else: # Any other file can be more easily accessed by id and access_hash + location = types.InputDocumentFileLocation( + id=id, + access_hash=access_hash, + file_reference=b"" + ) + + limit = 1024 * 1024 + offset = 0 + file_name = "" + + try: + r = session.send( + functions.upload.GetFile( + location=location, + offset=offset, + limit=limit + ) + ) + + if isinstance(r, types.upload.File): + with tempfile.NamedTemporaryFile("wb", delete=False) as f: + file_name = f.name + + while True: + chunk = r.bytes + + if not chunk: + break + + f.write(chunk) + + offset += limit + + if progress: + progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) + + r = session.send( + functions.upload.GetFile( + location=location, + offset=offset, + limit=limit + ) + ) + + elif isinstance(r, types.upload.FileCdnRedirect): + with self.media_sessions_lock: + cdn_session = self.media_sessions.get(r.dc_id, None) + + if cdn_session is None: + cdn_session = Session( + self, + r.dc_id, + Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(), + is_media=True, + is_cdn=True + ) + + cdn_session.start() + + self.media_sessions[r.dc_id] = cdn_session + + try: + with tempfile.NamedTemporaryFile("wb", delete=False) as f: + file_name = f.name + + while True: + r2 = cdn_session.send( + functions.upload.GetCdnFile( + file_token=r.file_token, + offset=offset, + limit=limit + ) + ) + + if isinstance(r2, types.upload.CdnFileReuploadNeeded): + try: + session.send( + functions.upload.ReuploadCdnFile( + file_token=r.file_token, + request_token=r2.request_token + ) + ) + except VolumeLocNotFound: + break + else: + continue + + chunk = r2.bytes + + # https://core.telegram.org/cdn#decrypting-files + decrypted_chunk = AES.ctr256_decrypt( + chunk, + r.encryption_key, + bytearray( + r.encryption_iv[:-4] + + (offset // 16).to_bytes(4, "big") + ) + ) + + hashes = session.send( + functions.upload.GetCdnFileHashes( + r.file_token, + offset + ) + ) + + # https://core.telegram.org/cdn#verifying-files + for i, h in enumerate(hashes): + cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] + assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i) + + f.write(decrypted_chunk) + + offset += limit + + if progress: + progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) + + if len(chunk) < limit: + break + except Exception as e: + raise e + except Exception as e: + if not isinstance(e, Client.StopTransmission): + log.error(e, exc_info=True) + + try: + os.remove(file_name) + except OSError: + pass + + return "" + else: + return file_name From 6ec3b12aebf3e486f73967bb91f807e3a15f6061 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 16 Jan 2019 15:40:02 +0100 Subject: [PATCH 228/326] Smart plugins enhancements --- pyrogram/client/client.py | 153 +++++++++++++++++++++++++++++++------- 1 file changed, 126 insertions(+), 27 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 576dea9a..fe4de3ff 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -157,10 +157,8 @@ class Client(Methods, BaseClient): config_file (``str``, *optional*): Path of the configuration file. Defaults to ./config.ini - plugins_dir (``str``, *optional*): - Define a custom directory for your plugins. The plugins directory is the location in your - filesystem where Pyrogram will automatically load your update handlers. - Defaults to None (plugins disabled). + plugins (``dict``, *optional*): + TODO: doctrings no_updates (``bool``, *optional*): Pass True to completely disable incoming updates for the current session. @@ -197,7 +195,7 @@ class Client(Methods, BaseClient): workers: int = BaseClient.WORKERS, workdir: str = BaseClient.WORKDIR, config_file: str = BaseClient.CONFIG_FILE, - plugins_dir: str = None, + plugins: dict = None, no_updates: bool = None, takeout: bool = None): super().__init__() @@ -223,7 +221,7 @@ class Client(Methods, BaseClient): self.workers = workers self.workdir = workdir self.config_file = config_file - self.plugins_dir = plugins_dir + self.plugins = plugins self.no_updates = no_updates self.takeout = takeout @@ -1074,6 +1072,38 @@ class Client(Methods, BaseClient): self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None + if self.plugins: + self.plugins["enabled"] = bool(self.plugins.get("enabled", True)) + else: + self.plugins = {} + + try: + section = parser["plugins"] + + include = section.get("include") or None + exclude = section.get("exclude") or None + + if include is not None: + include = [ + (i.split()[0], i.split()[1:] or None) + for i in include.strip().split("\n") + ] + + if exclude is not None: + exclude = [ + (i.split()[0], i.split()[1:] or None) + for i in exclude.strip().split("\n") + ] + + self.plugins["enabled"] = section.getboolean("enabled", True) + self.plugins["root"] = section.get("root") + self.plugins["include"] = include + self.plugins["exclude"] = exclude + except KeyError: + pass + else: + print(self.plugins) + def load_session(self): try: with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f: @@ -1105,43 +1135,112 @@ class Client(Methods, BaseClient): self.peers_by_phone[k] = peer def load_plugins(self): - if self.plugins_dir is not None: + if self.plugins.get("enabled", False): + root = self.plugins["root"] + include = self.plugins["include"] + exclude = self.plugins["exclude"] + plugins_count = 0 - for path in Path(self.plugins_dir).rglob("*.py"): - file_path = os.path.splitext(str(path))[0] - import_path = [] + if include is None: + for path in sorted(Path(root).rglob("*.py")): + module_path = os.path.splitext(str(path))[0].replace("/", ".") + module = import_module(module_path) - while file_path: - file_path, tail = os.path.split(file_path) - import_path.insert(0, tail) + for name in vars(module).keys(): + # noinspection PyBroadException + try: + handler, group = getattr(module, name) - import_path = ".".join(import_path) - module = import_module(import_path) + if isinstance(handler, Handler) and isinstance(group, int): + self.add_handler(handler, group) + + log.info('[LOAD] {}("{}") in group {} from "{}"'.format( + type(handler).__name__, name, group, module_path)) + + plugins_count += 1 + except Exception: + pass + else: + for path, handlers in include: + module_path = root + "." + path + warn_non_existent_functions = True - for name in dir(module): - # noinspection PyBroadException try: - handler, group = getattr(module, name) + module = import_module(module_path) + except ModuleNotFoundError: + log.warning('[LOAD] Ignoring non-existent module "{}"'.format(module_path)) + continue - if isinstance(handler, Handler) and isinstance(group, int): - self.add_handler(handler, group) + if "__path__" in dir(module): + log.warning('[LOAD] Ignoring namespace "{}"'.format(module_path)) + continue - log.info('{}("{}") from "{}" loaded in group {}'.format( - type(handler).__name__, name, import_path, group)) + if handlers is None: + handlers = vars(module).keys() + warn_non_existent_functions = False - plugins_count += 1 - except Exception: - pass + for name in handlers: + # noinspection PyBroadException + try: + handler, group = getattr(module, name) + + if isinstance(handler, Handler) and isinstance(group, int): + self.add_handler(handler, group) + + log.info('[LOAD] {}("{}") in group {} from "{}"'.format( + type(handler).__name__, name, group, module_path)) + + plugins_count += 1 + except Exception: + if warn_non_existent_functions: + log.warning('[LOAD] Ignoring non-existent function "{}" from "{}"'.format( + name, module_path)) + + if exclude is not None: + for path, handlers in exclude: + module_path = root + "." + path + warn_non_existent_functions = True + + try: + module = import_module(module_path) + except ModuleNotFoundError: + log.warning('[UNLOAD] Ignoring non-existent module "{}"'.format(module_path)) + continue + + if "__path__" in dir(module): + log.warning('[UNLOAD] Ignoring namespace "{}"'.format(module_path)) + continue + + if handlers is None: + handlers = vars(module).keys() + warn_non_existent_functions = False + + for name in handlers: + # noinspection PyBroadException + try: + handler, group = getattr(module, name) + + if isinstance(handler, Handler) and isinstance(group, int): + self.remove_handler(handler, group) + + log.info('[UNLOAD] {}("{}") from group {} in "{}"'.format( + type(handler).__name__, name, group, module_path)) + + plugins_count -= 1 + except Exception: + if warn_non_existent_functions: + log.warning('[UNLOAD] Ignoring non-existent function "{}" from "{}"'.format( + name, module_path)) if plugins_count > 0: log.warning('Successfully loaded {} plugin{} from "{}"'.format( plugins_count, "s" if plugins_count > 1 else "", - self.plugins_dir + root )) else: - log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(self.plugins_dir)) + log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(root)) def save_session(self): auth_key = base64.b64encode(self.auth_key).decode() From be013de4d4b0534d8428a4a39dadc738294a659e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 16 Jan 2019 20:25:48 +0100 Subject: [PATCH 229/326] Fix plugins load via Client parameter --- pyrogram/client/client.py | 56 +++++++++++++++------------------------ 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index fe4de3ff..eb205bc2 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1074,35 +1074,27 @@ class Client(Methods, BaseClient): if self.plugins: self.plugins["enabled"] = bool(self.plugins.get("enabled", True)) + self.plugins["include"] = "\n".join(self.plugins.get("include", [])) or None + self.plugins["exclude"] = "\n".join(self.plugins.get("exclude", [])) or None else: - self.plugins = {} - try: section = parser["plugins"] - include = section.get("include") or None - exclude = section.get("exclude") or None - - if include is not None: - include = [ - (i.split()[0], i.split()[1:] or None) - for i in include.strip().split("\n") - ] - - if exclude is not None: - exclude = [ - (i.split()[0], i.split()[1:] or None) - for i in exclude.strip().split("\n") - ] - - self.plugins["enabled"] = section.getboolean("enabled", True) - self.plugins["root"] = section.get("root") - self.plugins["include"] = include - self.plugins["exclude"] = exclude + self.plugins = { + "enabled": section.getboolean("enabled", True), + "root": section.get("root"), + "include": section.get("include") or None, + "exclude": section.get("exclude") or None + } except KeyError: pass - else: - print(self.plugins) + + for option in ["include", "exclude"]: + if self.plugins[option] is not None: + self.plugins[option] = [ + (i.split()[0], i.split()[1:] or None) + for i in self.plugins[option].strip().split("\n") + ] def load_session(self): try: @@ -1140,7 +1132,7 @@ class Client(Methods, BaseClient): include = self.plugins["include"] exclude = self.plugins["exclude"] - plugins_count = 0 + count = 0 if include is None: for path in sorted(Path(root).rglob("*.py")): @@ -1158,7 +1150,7 @@ class Client(Methods, BaseClient): log.info('[LOAD] {}("{}") in group {} from "{}"'.format( type(handler).__name__, name, group, module_path)) - plugins_count += 1 + count += 1 except Exception: pass else: @@ -1191,7 +1183,7 @@ class Client(Methods, BaseClient): log.info('[LOAD] {}("{}") in group {} from "{}"'.format( type(handler).__name__, name, group, module_path)) - plugins_count += 1 + count += 1 except Exception: if warn_non_existent_functions: log.warning('[LOAD] Ignoring non-existent function "{}" from "{}"'.format( @@ -1227,20 +1219,16 @@ class Client(Methods, BaseClient): log.info('[UNLOAD] {}("{}") from group {} in "{}"'.format( type(handler).__name__, name, group, module_path)) - plugins_count -= 1 + count -= 1 except Exception: if warn_non_existent_functions: log.warning('[UNLOAD] Ignoring non-existent function "{}" from "{}"'.format( name, module_path)) - if plugins_count > 0: - log.warning('Successfully loaded {} plugin{} from "{}"'.format( - plugins_count, - "s" if plugins_count > 1 else "", - root - )) + if count > 0: + log.warning('Successfully loaded {} plugin{} from "{}"'.format(count, "s" if count > 1 else "", root)) else: - log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(root)) + log.warning('No plugin loaded from "{}"'.format(root)) def save_session(self): auth_key = base64.b64encode(self.auth_key).decode() From 919894cd3f9d2f9911328ec8fe9ec72779f19d82 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 17 Jan 2019 15:21:50 +0100 Subject: [PATCH 230/326] Update Smart Plugins docs --- docs/source/resources/SmartPlugins.rst | 195 +++++++++++++++++++++++-- 1 file changed, 181 insertions(+), 14 deletions(-) diff --git a/docs/source/resources/SmartPlugins.rst b/docs/source/resources/SmartPlugins.rst index 46c4e17a..3bff9632 100644 --- a/docs/source/resources/SmartPlugins.rst +++ b/docs/source/resources/SmartPlugins.rst @@ -1,9 +1,9 @@ Smart Plugins ============= -Pyrogram embeds a **smart** (automatic) and lightweight plugin system that is meant to further simplify the organization -of large projects and to provide a way for creating pluggable components that can be **easily shared** across different -Pyrogram applications with **minimal boilerplate code**. +Pyrogram embeds a **smart**, lightweight yet powerful plugin system that is meant to further simplify the organization +of large projects and to provide a way for creating pluggable (modular) components that can be **easily shared** across +different Pyrogram applications with **minimal boilerplate code**. .. tip:: @@ -13,7 +13,7 @@ Introduction ------------ Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize -your applications, you had to do something like this... +your applications, you had to do something like this: .. note:: @@ -63,7 +63,7 @@ your applications, you had to do something like this... app.run() -...which is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to +This is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to manually ``import``, manually :meth:`add_handler ` and manually instantiate each :obj:`MessageHandler ` object because **you can't use those cool decorators** for your functions. So... What if you could? @@ -74,8 +74,8 @@ Using Smart Plugins Setting up your Pyrogram project to accommodate Smart Plugins is pretty straightforward: #. Create a new folder to store all the plugins (e.g.: "plugins"). -#. Put your files full of plugins inside. -#. Enable plugins in your Client. +#. Put your files full of plugins inside. Organize them as you wish. +#. Enable plugins in your Client or via the *config.ini* file. .. note:: @@ -107,20 +107,187 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight def echo_reversed(client, message): message.reply(message.text[::-1]) +- ``config.ini`` + + .. code-block:: ini + + [plugins] + root = plugins + - ``main.py`` .. code-block:: python from pyrogram import Client - Client("my_account", plugins_dir="plugins").run() + Client("my_account").run() -The first important thing to note is the new ``plugins`` folder, whose name is passed to the the ``plugins_dir`` -parameter when creating a :obj:`Client ` in the ``main.py`` file — you can put *any python file* in -there and each file can contain *any decorated function* (handlers) with only one limitation: within a single plugin -file you must use different names for each decorated function. Your Pyrogram Client instance will **automatically** -scan the folder upon creation to search for valid handlers and register them for you. + Alternatively, without using the *config.ini* file: + + .. code-block:: python + + from pyrogram import Client + + plugins = dict( + root="plugins" + ) + + Client("my_account", plugins=plugins).run() + +The first important thing to note is the new ``plugins`` folder. You can put *any python file* in *any subfolder* and +each file can contain *any decorated function* (handlers) with one limitation: within a single module (file) you must +use different names for each decorated function. + +The second thing is telling Pyrogram where to look for your plugins: you can either use the *config.ini* file or +the Client parameter "plugins"; the *root* value must match the name of your plugins folder. Your Pyrogram Client +instance will **automatically** scan the folder upon starting to search for valid handlers and register them for you. Then you'll notice you can now use decorators. That's right, you can apply the usual decorators to your callback functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class) -instead of the usual ``@app`` (Client instance) namespace and things will work just the same. +instead of the usual ``@app`` (Client instance) and things will work just the same. + +Specifying the Plugins to include +--------------------------------- + +By default, if you don't explicitly supply a list of plugins, every valid one found inside your plugins root folder will +be included by following the alphabetical order of the directory structure (files and subfolders); the single handlers +found inside each module will be, instead, loaded in the order they are defined, from top to bottom. + +.. note:: + + Remember: there can be at most one handler, within a group, dealing with a specific update. Plugins with overlapping + filters included a second time will not work. Learn more at `More on Updates `_. + +This default loading behaviour is usually enough, but sometimes you want to have more control on what to include (or +exclude) and in which exact order to load plugins. The way to do this is to make use of ``include`` and ``exclude`` +keys, either in the *config.ini* or in the dictionary passed as Client argument. Here's how they work: + +- If both ``include`` and ``exclude`` are omitted, all plugins are loaded as described above. +- If ``include`` is given, only the specified plugins will be loaded, in the order they are passed. +- If ``exclude`` is given, the plugins specified here will be unloaded. + +The ``include`` and ``exclude`` value is a **list of strings**. Each string containing the path of the module relative +to the plugins root folder, in Python notation (dots instead of slashes). + + E.g.: ``subfolder.module`` refers to ``plugins/subfolder/module.py`` (root="plugins"). + +You can also choose the order in which the single handlers inside a module are loaded, thus overriding the default +top-to-bottom loading policy. You can do this by appending the name of the functions to the module path, each one +separated by a blank space. + + E.g.: ``subfolder.module fn2 fn1 fn3`` will load *fn2*, *fn1* and *fn3* from *subfolder.module*, in this order. + +Examples +^^^^^^^^ + +Given this plugins folder structure with three modules, each containing their own handlers (fn1, fn2, etc...), which are +also organized in subfolders: + +.. code-block:: text + + myproject/ + plugins/ + subfolder1/ + plugins1.py + - fn1 + - fn2 + - fn3 + subfolder2/ + plugins2.py + ... + plugins0.py + ... + ... + +- Load every handler from every module, namely *plugins0.py*, *plugins1.py* and *plugins2.py* in alphabetical order + (files) and definition order (handlers inside files): + + Using *config.ini* file: + + .. code-block:: ini + + [plugins] + root = plugins + + Using *Client*'s parameter: + + .. code-block:: python + + plugins = dict( + root="plugins" + ) + + Client("my_account", plugins=plugins).run() + +- Load only handlers defined inside *plugins2.py* and *plugins0.py*, in this order: + + Using *config.ini* file: + + .. code-block:: ini + + [plugins] + root = plugins + include = + subfolder2.plugins2 + plugins0 + + Using *Client*'s parameter: + + .. code-block:: python + + plugins = dict( + root="plugins", + include=[ + "subfolder2.plugins2", + "plugins0" + ] + ) + + Client("my_account", plugins=plugins).run() + +- Load everything except the handlers inside *plugins2.py*: + + Using *config.ini* file: + + .. code-block:: ini + + [plugins] + root = plugins + exclude = subfolder2.plugins2 + + Using *Client*'s parameter: + + .. code-block:: python + + plugins = dict( + root="plugins", + exclude=["subfolder2.plugins2"] + ) + + Client("my_account", plugins=plugins).run() + +- Load only *fn3*, *fn1* and *fn2* (in this order) from *plugins1.py*: + + Using *config.ini* file: + + .. code-block:: ini + + [plugins] + root = plugins + include = subfolder1.plugins1 fn3 fn1 fn2 + + Using *Client*'s parameter: + + .. code-block:: python + + plugins = dict( + root="plugins", + include=["subfolder1.plugins1 fn3 fn1 fn2"] + ) + + Client("my_account", plugins=plugins).run() + +Load/Unload Plugins at Runtime +------------------------------ + +TODO \ No newline at end of file From 8cfb6614d4fd8ee4c58bfc0d4018888388f26abc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 17 Jan 2019 15:23:46 +0100 Subject: [PATCH 231/326] Rename TgCrypto title to Fast Crypto More generic name. Keep TgCrypto.rst in order not to break links --- docs/source/resources/TgCrypto.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/resources/TgCrypto.rst b/docs/source/resources/TgCrypto.rst index 734c48e4..2af09a06 100644 --- a/docs/source/resources/TgCrypto.rst +++ b/docs/source/resources/TgCrypto.rst @@ -1,5 +1,5 @@ -TgCrypto -======== +Fast Crypto +=========== Pyrogram's speed can be *dramatically* boosted up by TgCrypto_, a high-performance, easy-to-install Telegram Crypto Library specifically written in C for Pyrogram [#f1]_ as a Python extension. From 76d4e4f60e721b7aab3586745fdf6131bff00b97 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 21 Jan 2019 15:36:54 +0100 Subject: [PATCH 232/326] Fix "left" status not being parsed in ChatMember (#204) --- pyrogram/client/types/user_and_chats/chat_member.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index e901e0e1..3a4a93f7 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -155,7 +155,11 @@ class ChatMember(PyrogramType): chat_member = ChatMember( user=user, - status="kicked" if rights.view_messages else "restricted", + status=( + "left" if member.left + else "kicked" if rights.view_messages + else "restricted" + ), until_date=0 if rights.until_date == (1 << 31) - 1 else rights.until_date, client=client ) From d6a1503344d73def675205cc0563aa3cb43ef480 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 21 Jan 2019 15:38:36 +0100 Subject: [PATCH 233/326] Add "date" attribute to ChatMember (#204) --- pyrogram/client/types/user_and_chats/chat_member.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 3a4a93f7..6b024737 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -33,6 +33,9 @@ class ChatMember(PyrogramType): The member's status in the chat. Can be "creator", "administrator", "member", "restricted", "left" or "kicked". + date (``int``, *optional*): + Date when the user joined, unix time. Not available for creator. + until_date (``int``, *optional*): Restricted and kicked only. Date when restrictions will be lifted for this user, unix time. @@ -86,6 +89,7 @@ class ChatMember(PyrogramType): client: "pyrogram.client.ext.BaseClient", user: "pyrogram.User", status: str, + date: int = None, until_date: int = None, can_be_edited: bool = None, can_change_info: bool = None, @@ -104,6 +108,7 @@ class ChatMember(PyrogramType): self.user = user self.status = status + self.date = date self.until_date = until_date self.can_be_edited = can_be_edited self.can_change_info = can_change_info @@ -124,13 +129,13 @@ class ChatMember(PyrogramType): user = pyrogram.User._parse(client, user) if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)): - return ChatMember(user=user, status="member", client=client) + return ChatMember(user=user, status="member", date=member.date, client=client) if isinstance(member, (types.ChannelParticipantCreator, types.ChatParticipantCreator)): return ChatMember(user=user, status="creator", client=client) if isinstance(member, types.ChatParticipantAdmin): - return ChatMember(user=user, status="administrator", client=client) + return ChatMember(user=user, status="administrator", date=member.date, client=client) if isinstance(member, types.ChannelParticipantAdmin): rights = member.admin_rights @@ -138,6 +143,7 @@ class ChatMember(PyrogramType): return ChatMember( user=user, status="administrator", + date=member.date, can_be_edited=member.can_edit, can_change_info=rights.change_info, can_post_messages=rights.post_messages, @@ -160,6 +166,7 @@ class ChatMember(PyrogramType): else "kicked" if rights.view_messages else "restricted" ), + date=member.date, until_date=0 if rights.until_date == (1 << 31) - 1 else rights.until_date, client=client ) From a57ee7b33392655db84b638fc3605508881cdfb8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 21 Jan 2019 16:33:33 +0100 Subject: [PATCH 234/326] Accommodate parsing of invited_by attribute of ChatMember (#204) --- pyrogram/client/methods/chats/get_chat_member.py | 4 +++- pyrogram/client/types/user_and_chats/chat_members.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index 4b67dd5e..faf0c33b 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -67,6 +67,8 @@ class GetChatMember(BaseClient): ) ) - return pyrogram.ChatMember._parse(self, r.participant, r.users[0]) + users = {i.id: i for i in r.users} + + return pyrogram.ChatMember._parse(self, r.participant, users) else: raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index 88219514..39d69089 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -59,7 +59,7 @@ class ChatMembers(PyrogramType): total_count = len(members) for member in members: - chat_members.append(ChatMember._parse(client, member, users[member.user_id])) + chat_members.append(ChatMember._parse(client, member, users)) return ChatMembers( total_count=total_count, From 16b7203ee9593f546c61172a52b9bad3a6cb57ed Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 21 Jan 2019 16:34:46 +0100 Subject: [PATCH 235/326] Add invite_by attribute in ChatMember (#204) --- .../client/types/user_and_chats/chat_member.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 6b024737..38e90b8d 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -36,6 +36,10 @@ class ChatMember(PyrogramType): date (``int``, *optional*): Date when the user joined, unix time. Not available for creator. + invited_by (:obj:`User `, *optional*): + Information about the user who invited this member. + In case the user joined by himself this will be the same as "user". + until_date (``int``, *optional*): Restricted and kicked only. Date when restrictions will be lifted for this user, unix time. @@ -90,6 +94,7 @@ class ChatMember(PyrogramType): user: "pyrogram.User", status: str, date: int = None, + invited_by: "pyrogram.User" = None, until_date: int = None, can_be_edited: bool = None, can_change_info: bool = None, @@ -109,6 +114,7 @@ class ChatMember(PyrogramType): self.user = user self.status = status self.date = date + self.invited_by = invited_by self.until_date = until_date self.can_be_edited = can_be_edited self.can_change_info = can_change_info @@ -125,17 +131,18 @@ class ChatMember(PyrogramType): self.can_add_web_page_previews = can_add_web_page_previews @staticmethod - def _parse(client, member, user) -> "ChatMember": - user = pyrogram.User._parse(client, user) + def _parse(client, member, users) -> "ChatMember": + user = pyrogram.User._parse(client, users[member.user_id]) + invited_by = pyrogram.User._parse(client, users[member.inviter_id]) if hasattr(member, "inviter_id") else None if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)): - return ChatMember(user=user, status="member", date=member.date, client=client) + return ChatMember(user=user, status="member", date=member.date, invited_by=invited_by, client=client) if isinstance(member, (types.ChannelParticipantCreator, types.ChatParticipantCreator)): return ChatMember(user=user, status="creator", client=client) if isinstance(member, types.ChatParticipantAdmin): - return ChatMember(user=user, status="administrator", date=member.date, client=client) + return ChatMember(user=user, status="administrator", date=member.date, invited_by=invited_by, client=client) if isinstance(member, types.ChannelParticipantAdmin): rights = member.admin_rights @@ -144,6 +151,7 @@ class ChatMember(PyrogramType): user=user, status="administrator", date=member.date, + invited_by=invited_by, can_be_edited=member.can_edit, can_change_info=rights.change_info, can_post_messages=rights.post_messages, From f0c8f65e9dc47447ed37a8216d03e63f0018b6bd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 21 Jan 2019 16:41:56 +0100 Subject: [PATCH 236/326] Add promoted_by attribute in ChatMember (#204) --- pyrogram/client/types/user_and_chats/chat_member.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 38e90b8d..c2dbccbd 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -37,9 +37,12 @@ class ChatMember(PyrogramType): Date when the user joined, unix time. Not available for creator. invited_by (:obj:`User `, *optional*): - Information about the user who invited this member. + Administrators and self member only. Information about the user who invited this member. In case the user joined by himself this will be the same as "user". + promoted_by (:obj:`User `, *optional*): + Administrators only. Information about the user who promoted this member as administrator. + until_date (``int``, *optional*): Restricted and kicked only. Date when restrictions will be lifted for this user, unix time. @@ -95,6 +98,7 @@ class ChatMember(PyrogramType): status: str, date: int = None, invited_by: "pyrogram.User" = None, + promoted_by: "pyrogram.User" = None, until_date: int = None, can_be_edited: bool = None, can_change_info: bool = None, @@ -115,6 +119,7 @@ class ChatMember(PyrogramType): self.status = status self.date = date self.invited_by = invited_by + self.promoted_by = promoted_by self.until_date = until_date self.can_be_edited = can_be_edited self.can_change_info = can_change_info @@ -152,6 +157,7 @@ class ChatMember(PyrogramType): status="administrator", date=member.date, invited_by=invited_by, + promoted_by=pyrogram.User._parse(client, users[member.promoted_by]), can_be_edited=member.can_edit, can_change_info=rights.change_info, can_post_messages=rights.post_messages, From b919ed82420c9358dcb5c73858d5fc4fd0a55193 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 21 Jan 2019 16:53:54 +0100 Subject: [PATCH 237/326] Add restricted_by attribute in ChatMember (#204) --- pyrogram/client/types/user_and_chats/chat_member.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index c2dbccbd..590a67e5 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -43,6 +43,9 @@ class ChatMember(PyrogramType): promoted_by (:obj:`User `, *optional*): Administrators only. Information about the user who promoted this member as administrator. + restricted_by (:obj:`User `, *optional*): + Restricted and kicked only. Information about the user who restricted or kicked this member. + until_date (``int``, *optional*): Restricted and kicked only. Date when restrictions will be lifted for this user, unix time. @@ -99,6 +102,7 @@ class ChatMember(PyrogramType): date: int = None, invited_by: "pyrogram.User" = None, promoted_by: "pyrogram.User" = None, + restricted_by: "pyrogram.User" = None, until_date: int = None, can_be_edited: bool = None, can_change_info: bool = None, @@ -120,6 +124,7 @@ class ChatMember(PyrogramType): self.date = date self.invited_by = invited_by self.promoted_by = promoted_by + self.restricted_by = restricted_by self.until_date = until_date self.can_be_edited = can_be_edited self.can_change_info = can_change_info @@ -181,6 +186,7 @@ class ChatMember(PyrogramType): else "restricted" ), date=member.date, + restricted_by=pyrogram.User._parse(client, users[member.kicked_by]), until_date=0 if rights.until_date == (1 << 31) - 1 else rights.until_date, client=client ) From c0a5b0a2c3263a26376beca3cc78b69df6cce59d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 21 Jan 2019 16:56:22 +0100 Subject: [PATCH 238/326] Fix kicked members reporting "left" as status --- pyrogram/client/types/user_and_chats/chat_member.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 590a67e5..70f32540 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -181,8 +181,8 @@ class ChatMember(PyrogramType): chat_member = ChatMember( user=user, status=( - "left" if member.left - else "kicked" if rights.view_messages + "kicked" if rights.view_messages + else "left" if member.left else "restricted" ), date=member.date, From f4b4496995808891bff34c8c0e07403ce764a6ec Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 21 Jan 2019 18:34:28 +0100 Subject: [PATCH 239/326] Update API schema to Layer 93 --- compiler/api/source/main_api.tl | 57 ++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 61bd1a41..7bbf15e7 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -96,12 +96,12 @@ userStatusLastWeek#7bf09fc = UserStatus; userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#9ba2d800 id:int = Chat; -chat#d91cdd54 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true admins_enabled:flags.3?true admin:flags.4?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel = Chat; +chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#7328bdb id:int title:string = Chat; -channel#c88974ac flags:# creator:flags.0?true left:flags.2?true editor:flags.3?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true democracy:flags.10?true signatures:flags.11?true min:flags.12?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChannelAdminRights banned_rights:flags.15?ChannelBannedRights participants_count:flags.17?int = Chat; +channel#4df30834 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; 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#edd2a791 flags:# id:int participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int = ChatFull; +chatFull#22a235da flags:# can_set_username:flags.7?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 = ChatFull; channelFull#1c87a71a 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 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 = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; @@ -163,6 +163,7 @@ photo#9c477dd8 flags:# has_stickers:flags.0?true id:long access_hash:long file_r photoSizeEmpty#e17e23c type:string = PhotoSize; photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; +photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize; geoPointEmpty#1117dd5f = GeoPoint; geoPoint#296f104 long:double lat:double access_hash:long = GeoPoint; @@ -186,8 +187,7 @@ peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bo peerSettings#818426cd flags:# report_spam:flags.0?true = PeerSettings; -wallPaper#ccb03657 id:int title:string sizes:Vector color:int = WallPaper; -wallPaperSolid#63117f24 id:int title:string bg_color:int color:int = WallPaper; +wallPaper#f04f91ec id:long flags:# creator:flags.0?true default:flags.1?true access_hash:long slug:string document:Document = WallPaper; inputReportReasonSpam#58dbcab8 = ReportReason; inputReportReasonViolence#1e22c78d = ReportReason; @@ -221,7 +221,7 @@ messages.dialogsSlice#71e094f3 count:int dialogs:Vector messages:Vector< messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs; messages.messages#8c718e87 messages:Vector chats:Vector users:Vector = messages.Messages; -messages.messagesSlice#b446ae3 count:int messages:Vector chats:Vector users:Vector = messages.Messages; +messages.messagesSlice#a6c47aaa flags:# inexact:flags.1?true count:int messages:Vector chats:Vector users:Vector = messages.Messages; messages.channelMessages#99262e37 flags:# inexact:flags.1?true pts:int count:int messages:Vector chats:Vector users:Vector = messages.Messages; messages.messagesNotModified#74535f21 count:int = messages.Messages; @@ -281,7 +281,6 @@ updateNewChannelMessage#62ba04d9 message:Message pts:int pts_count:int = Update; updateReadChannelInbox#4214f37f channel_id:int max_id:int = Update; updateDeleteChannelMessages#c37521c9 channel_id:int messages:Vector pts:int pts_count:int = Update; updateChannelMessageViews#98a12b4b channel_id:int id:int views:int = Update; -updateChatAdmins#6e947941 chat_id:int enabled:Bool version:int = Update; updateChatParticipantAdmin#b6901959 chat_id:int user_id:int is_admin:Bool version:int = Update; updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update; updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true order:Vector = Update; @@ -318,6 +317,7 @@ updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Up updateUserPinnedMessage#4c43da18 user_id:int id:int = Update; updateChatPinnedMessage#22893b26 chat_id:int id:int = Update; updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update; +updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -382,7 +382,7 @@ inputDocumentEmpty#72f0eaae = InputDocument; inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument; documentEmpty#36f8c871 id:long = Document; -document#59534e4c id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumb:PhotoSize dc_id:int attributes:Vector = 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; help.support#17c6b5f6 phone_number:string user:User = help.Support; @@ -544,8 +544,8 @@ channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges: channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant; channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant; channelParticipantCreator#e3e2e1f9 user_id:int = ChannelParticipant; -channelParticipantAdmin#a82fa898 flags:# can_edit:flags.0?true user_id:int inviter_id:int promoted_by:int date:int admin_rights:ChannelAdminRights = ChannelParticipant; -channelParticipantBanned#222c1886 flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChannelBannedRights = ChannelParticipant; +channelParticipantAdmin#5daa6e23 flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights = ChannelParticipant; +channelParticipantBanned#1c0facaf flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant; channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter; channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter; @@ -553,6 +553,7 @@ channelParticipantsKicked#a3b54985 q:string = ChannelParticipantsFilter; channelParticipantsBots#b0d1865b = ChannelParticipantsFilter; channelParticipantsBanned#1427a5e1 q:string = ChannelParticipantsFilter; channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter; +channelParticipantsContacts#bb6ae88d q:string = ChannelParticipantsFilter; channels.channelParticipants#f56ee2a8 count:int participants:Vector users:Vector = channels.ChannelParticipants; channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants; @@ -789,10 +790,6 @@ langPackDifference#f385c1f6 lang_code:string from_version:int version:int string langPackLanguage#eeca5ce3 flags:# official:flags.0?true rtl:flags.2?true beta:flags.3?true name:string native_name:string lang_code:string base_lang_code:flags.1?string plural_code:string strings_count:int translated_count:int translations_url:string = LangPackLanguage; -channelAdminRights#5d7ceba5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true invite_link:flags.6?true pin_messages:flags.7?true add_admins:flags.9?true manage_call:flags.10?true = ChannelAdminRights; - -channelBannedRights#58cf4249 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true until_date:int = ChannelBannedRights; - channelAdminLogEventActionChangeTitle#e6dfb825 prev_value:string new_value:string = ChannelAdminLogEventAction; channelAdminLogEventActionChangeAbout#55188a2e prev_value:string new_value:string = ChannelAdminLogEventAction; channelAdminLogEventActionChangeUsername#6a4afc38 prev_value:string new_value:string = ChannelAdminLogEventAction; @@ -809,6 +806,8 @@ channelAdminLogEventActionParticipantToggleBan#e6d83d7e prev_participant:Channel channelAdminLogEventActionParticipantToggleAdmin#d5676710 prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionChangeStickerSet#b1c3caa7 prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction; channelAdminLogEventActionTogglePreHistoryHidden#5f5c95f1 new_value:Bool = ChannelAdminLogEventAction; +channelAdminLogEventActionDefaultBannedRights#2df5fc0a prev_banned_rights:ChatBannedRights new_banned_rights:ChatBannedRights = ChannelAdminLogEventAction; +channelAdminLogEventActionStopPoll#8f079643 message:Message = ChannelAdminLogEventAction; channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -972,6 +971,16 @@ chatOnlines#f041e250 onlines:int = ChatOnlines; statsURL#47a971e0 url:string = StatsURL; +chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true = ChatAdminRights; + +chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true until_date:int = ChatBannedRights; + +inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper; +inputWallPaperSlug#72091c80 slug:string = InputWallPaper; + +account.wallPapersNotModified#1c199183 = account.WallPapers; +account.wallPapers#702b65a9 hash:int wallpapers:Vector = account.WallPapers; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1005,7 +1014,7 @@ account.getNotifySettings#12b3ad31 peer:InputNotifyPeer = PeerNotifySettings; account.resetNotifySettings#db7e1747 = Bool; account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User; account.updateStatus#6628562c offline:Bool = Bool; -account.getWallPapers#c04cfac2 = Vector; +account.getWallPapers#aabb1763 hash:int = account.WallPapers; account.reportPeer#ae189d5f peer:InputPeer reason:ReportReason = Bool; account.checkUsername#2714d86c username:string = Bool; account.updateUsername#3e0bdd7c username:string = User; @@ -1046,6 +1055,10 @@ account.cancelPasswordEmail#c1cbd5b6 = Bool; account.getContactSignUpNotification#9f07c728 = Bool; account.setContactSignUpNotification#cff43f61 silent:Bool = Bool; account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates; +account.uploadWallPaper#c7ba9b4d file:InputFile mime_type:string = WallPaper; +account.getWallPaper#fc8ddbea wallpaper:InputWallPaper = WallPaper; +account.saveWallPaper#189581b3 wallpaper:InputWallPaper unsave:Bool = Bool; +account.installWallPaper#4a0378ce wallpaper:InputWallPaper = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; @@ -1107,7 +1120,7 @@ messages.readMessageContents#36a73f77 id:Vector = messages.AffectedMessages messages.getStickers#43d4f2c emoticon:string hash:int = messages.Stickers; messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers; messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector = MessageMedia; -messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite; +messages.exportChatInvite#df7534c peer:InputPeer = ExportedChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; messages.importChatInvite#6c50051c hash:string = Updates; messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet; @@ -1115,7 +1128,6 @@ messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = m messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates; messages.getMessagesViews#c4c8a55d peer:InputPeer id:Vector increment:Bool = Vector; -messages.toggleChatAdmins#ec8bd9e1 chat_id:int enabled:Bool = Updates; messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool; messages.migrateChat#15a3b8e3 chat_id:int = Updates; messages.searchGlobal#9e3cacb0 q:string offset_date:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; @@ -1174,6 +1186,8 @@ messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector = Upd messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates; messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines; messages.getStatsURL#83f6c0cd peer:InputPeer = StatsURL; +messages.editChatAbout#def60797 peer:InputPeer about:string = Bool; +messages.editChatDefaultBannedRights#a5866b41 peer:InputPeer banned_rights:ChatBannedRights = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1223,8 +1237,7 @@ channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channe channels.getChannels#a7f6bbb id:Vector = messages.Chats; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull; channels.createChannel#f4893d7f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string = Updates; -channels.editAbout#13e27f1e channel:InputChannel about:string = Bool; -channels.editAdmin#20b88214 channel:InputChannel user_id:InputUser admin_rights:ChannelAdminRights = Updates; +channels.editAdmin#70f893ba channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights = Updates; channels.editTitle#566decd0 channel:InputChannel title:string = Updates; channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates; channels.checkUsername#10e6bd2c channel:InputChannel username:string = Bool; @@ -1232,13 +1245,11 @@ channels.updateUsername#3514b3de channel:InputChannel username:string = Bool; channels.joinChannel#24b524c5 channel:InputChannel = Updates; channels.leaveChannel#f836aa95 channel:InputChannel = Updates; channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector = Updates; -channels.exportInvite#c7560885 channel:InputChannel = ExportedChatInvite; channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; -channels.toggleInvites#49609307 channel:InputChannel enabled:Bool = Updates; channels.exportMessageLink#ceb77163 channel:InputChannel id:int grouped:Bool = ExportedMessageLink; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats; -channels.editBanned#bfd915cd channel:InputChannel user_id:InputUser banned_rights:ChannelBannedRights = Updates; +channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates; channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector max_id:long min_id:long limit:int = channels.AdminLogResults; channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool; channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector = Bool; @@ -1276,4 +1287,4 @@ langpack.getDifference#9d51e814 lang_code:string from_version:int = LangPackDiff langpack.getLanguages#42c6978f lang_pack:string = Vector; langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLanguage; -// LAYER 91 +// LAYER 93 From cb0b8ebeae08f32e21958a2b1bfa4b944a092536 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 21 Jan 2019 18:35:11 +0100 Subject: [PATCH 240/326] Fix Chat sticker_set_name --- pyrogram/client/types/user_and_chats/chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index ec30b866..75313651 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -197,7 +197,7 @@ class Chat(PyrogramType): parsed_chat.description = full_chat.about or None # TODO: Add StickerSet type parsed_chat.can_set_sticker_set = full_chat.can_set_stickers - parsed_chat.sticker_set_name = full_chat.stickerset + parsed_chat.sticker_set_name = getattr(full_chat.stickerset, "short_name", None) if full_chat.pinned_msg_id: parsed_chat.pinned_message = client.get_messages( From e99f86b69fc0d13f723703f08a53d4d4640ae273 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 21 Jan 2019 18:45:52 +0100 Subject: [PATCH 241/326] Update media thumbs parsing for L93 --- .../types/messages_and_media/animation.py | 2 +- .../client/types/messages_and_media/audio.py | 2 +- .../types/messages_and_media/document.py | 2 +- .../client/types/messages_and_media/photo.py | 1 - .../types/messages_and_media/photo_size.py | 44 ++++++++++--------- .../types/messages_and_media/sticker.py | 2 +- .../client/types/messages_and_media/video.py | 2 +- .../types/messages_and_media/video_note.py | 2 +- 8 files changed, 30 insertions(+), 27 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index d5661ea8..9f3ba886 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -97,7 +97,7 @@ class Animation(PyrogramType): width=getattr(video_attributes, "w", 0), height=getattr(video_attributes, "h", 0), duration=getattr(video_attributes, "duration", 0), - thumb=PhotoSize._parse(client, animation.thumb), + thumb=PhotoSize._parse(client, animation.thumbs), mime_type=animation.mime_type, file_size=animation.size, file_name=file_name, diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index cfecceae..9c5698f1 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -99,7 +99,7 @@ class Audio(PyrogramType): title=audio_attributes.title, mime_type=audio.mime_type, file_size=audio.size, - thumb=PhotoSize._parse(client, audio.thumb), + thumb=PhotoSize._parse(client, audio.thumbs), file_name=file_name, date=audio.date, client=client diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index e84b5149..5cd01a92 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -78,7 +78,7 @@ class Document(PyrogramType): document.access_hash ) ), - thumb=PhotoSize._parse(client, document.thumb), + thumb=PhotoSize._parse(client, document.thumbs), file_name=file_name, mime_type=document.mime_type, file_size=document.size, diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 72187761..aa2b3a26 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -61,7 +61,6 @@ class Photo(PyrogramType): for raw_size in raw_sizes: if isinstance(raw_size, (types.PhotoSize, types.PhotoCachedSize)): - if isinstance(raw_size, types.PhotoSize): file_size = raw_size.size elif isinstance(raw_size, types.PhotoCachedSize): diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index 05f00455..e1be74df 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from struct import pack +from typing import List, Union import pyrogram from pyrogram.api import types @@ -56,27 +57,30 @@ class PhotoSize(PyrogramType): self.file_size = file_size @staticmethod - def _parse(client, photo_size: types.PhotoSize or types.PhotoCachedSize): - if isinstance(photo_size, (types.PhotoSize, types.PhotoCachedSize)): + def _parse(client, thumbs: List) -> Union["PhotoSize", None]: + if not thumbs: + return None - if isinstance(photo_size, types.PhotoSize): - file_size = photo_size.size - elif isinstance(photo_size, types.PhotoCachedSize): - file_size = len(photo_size.bytes) - else: - file_size = 0 + photo_size = thumbs[-1] - loc = photo_size.location + if not isinstance(photo_size, (types.PhotoSize, types.PhotoCachedSize, types.PhotoStrippedSize)): + return None - if isinstance(loc, types.FileLocation): - return PhotoSize( - file_id=encode( - pack( - " Date: Thu, 24 Jan 2019 17:03:51 +0100 Subject: [PATCH 242/326] Add ChatPermissions type --- docs/source/pyrogram/Types.rst | 1 + pyrogram/__init__.py | 3 +- pyrogram/client/types/__init__.py | 2 +- .../client/types/user_and_chats/__init__.py | 1 + .../types/user_and_chats/chat_permissions.py | 173 ++++++++++++++++++ 5 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 pyrogram/client/types/user_and_chats/chat_permissions.py diff --git a/docs/source/pyrogram/Types.rst b/docs/source/pyrogram/Types.rst index 6e0a14db..14fb9147 100644 --- a/docs/source/pyrogram/Types.rst +++ b/docs/source/pyrogram/Types.rst @@ -16,6 +16,7 @@ Users & Chats ChatPhoto ChatMember ChatMembers + ChatPermissions Dialog Dialogs diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index db3d8674..7f58a6f6 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -32,7 +32,8 @@ from .client.types import ( Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, - Poll, PollOption, ChatPreview, StopPropagation, Game, CallbackGame, GameHighScore, GameHighScores + Poll, PollOption, ChatPreview, StopPropagation, Game, CallbackGame, GameHighScore, GameHighScores, + ChatPermissions ) from .client import ( Client, ChatAction, ParseMode, Emoji, diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index ca332a22..f29da7bb 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -33,5 +33,5 @@ from .messages_and_media import ( from .update import StopPropagation from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, - Dialog, Dialogs, User, UserStatus, ChatPreview + Dialog, Dialogs, User, UserStatus, ChatPreview, ChatPermissions ) diff --git a/pyrogram/client/types/user_and_chats/__init__.py b/pyrogram/client/types/user_and_chats/__init__.py index 24a9a51b..9c9c0beb 100644 --- a/pyrogram/client/types/user_and_chats/__init__.py +++ b/pyrogram/client/types/user_and_chats/__init__.py @@ -19,6 +19,7 @@ from .chat import Chat from .chat_member import ChatMember from .chat_members import ChatMembers +from .chat_permissions import ChatPermissions from .chat_photo import ChatPhoto from .chat_preview import ChatPreview from .dialog import Dialog diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/client/types/user_and_chats/chat_permissions.py new file mode 100644 index 00000000..68b4567e --- /dev/null +++ b/pyrogram/client/types/user_and_chats/chat_permissions.py @@ -0,0 +1,173 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +from pyrogram.api import types +from ..pyrogram_type import PyrogramType + + +class ChatPermissions(PyrogramType): + """This object represents both a chat default permissions and a single member permissions within a chat. + + Some permissions make sense depending on the context: default chat permissions, restricted/kicked member or + administrators in groups or channels. + + Args: + until_date (``int``, *optional*): + Applicable to restricted and kicked members only. + Date when user restrictions will be lifted, unix time. + 0 means the restrictions will never be lifted (user restricted forever). + + can_be_edited (``bool``, *optional*): + Applicable to administrators only. + True, if you are allowed to edit administrator privileges of the user. + + can_change_info (``bool``, *optional*): + Applicable to default chat permissions in private groups and administrators in public groups only. + True, if the chat title, photo and other settings can be changed. + + can_post_messages (``bool``, *optional*): + Applicable to channel administrators only. + True, if the administrator can post messages in the channel, channels only. + + can_edit_messages (``bool``, *optional*): + Applicable to channel administrators only. + True, if the administrator can edit messages of other users and can pin messages, channels only. + + can_delete_messages (``bool``, *optional*): + Applicable to administrators only. + True, if the administrator can delete messages of other users. + + can_restrict_members (``bool``, *optional*): + Applicable to administrators only. + True, if the administrator can restrict, ban or unban chat members. + + can_invite_users (``bool``, *optional*): + Applicable to default chat permissions and administrators only. + True, if new users can be invited to the chat. + + can_pin_messages (``bool``, *optional*): + Applicable to default chat permissions in private groups and administrators in public groups only. + True, if messages can be pinned, supergroups only. + + can_promote_members (``bool``, *optional*): + Applicable to administrators only. + True, if the administrator can add new administrators with a subset of his own privileges or demote + administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed + by the user). + + can_send_messages (``bool``, *optional*): + Applicable to default chat permissions and restricted members only. + True, if text messages, contacts, locations and venues can be sent. + + can_send_media_messages (``bool``, *optional*): + Applicable to default chat permissions and restricted members only. + True, if audios, documents, photos, videos, video notes and voice notes can be sent, implies + can_send_messages. + + can_send_other_messages (``bool``, *optional*): + Applicable to default chat permissions and restricted members only. + True, if animations, games, stickers and inline bot results can be sent, implies can_send_media_messages. + + can_add_web_page_previews (``bool``, *optional*): + Applicable to default chat permissions and restricted members only. + True, if web page previews can be attached to text messages, implies can_send_media_messages. + + can_send_polls (``bool``, *optional*): + Applicable to default chat permissions and restricted members only. + True, if polls can be sent, implies can_send_media_messages. + """ + + def __init__( + self, + *, + until_date: int = None, + + # Admin permissions + can_be_edited: bool = None, + can_change_info: bool = None, + can_post_messages: bool = None, # Channels only + can_edit_messages: bool = None, # Channels only + can_delete_messages: bool = None, + can_restrict_members: bool = None, + can_invite_users: bool = None, + can_pin_messages: bool = None, # Supergroups only + can_promote_members: bool = None, + + # Restricted user permissions + can_send_messages: bool = None, # Text, contacts, locations and venues + can_send_media_messages: bool = None, # Audios, documents, photos, videos, video notes and voice notes + can_send_other_messages: bool = None, # Animations (GIFs), games, stickers, inline bot results + can_add_web_page_previews: bool = None, + can_send_polls: bool = None + ): + super().__init__(None) + + self.until_date = until_date + self.can_be_edited = can_be_edited + + self.can_change_info = can_change_info + self.can_post_messages = can_post_messages + self.can_edit_messages = can_edit_messages + self.can_delete_messages = can_delete_messages + self.can_restrict_members = can_restrict_members + self.can_invite_users = can_invite_users + self.can_pin_messages = can_pin_messages + self.can_promote_members = can_promote_members + + self.can_send_messages = can_send_messages + self.can_send_media_messages = can_send_media_messages + self.can_send_other_messages = can_send_other_messages + self.can_add_web_page_previews = can_add_web_page_previews + self.can_send_polls = can_send_polls + + @staticmethod + def _parse(member: Union[types.ChannelParticipantAdmin, types.ChannelParticipantBanned]) -> "ChatPermissions": + if isinstance(member, types.ChannelParticipantAdmin): + permissions = member.admin_rights + + return ChatPermissions( + can_be_edited=member.can_edit, + can_change_info=permissions.change_info, + can_post_messages=permissions.post_messages, + can_edit_messages=permissions.edit_messages, + can_delete_messages=permissions.delete_messages, + can_restrict_members=permissions.ban_users, + can_invite_users=permissions.invite_users, + can_pin_messages=permissions.pin_messages, + can_promote_members=permissions.add_admins + ) + + if isinstance(member, types.ChannelParticipantBanned): + denied_permissions = member.banned_rights # type: types.ChatBannedRights + + return ChatPermissions( + until_date=0 if denied_permissions.until_date == (1 << 31) - 1 else denied_permissions.until_date, + can_send_messages=not denied_permissions.send_messages, + can_send_media_messages=not denied_permissions.send_media, + can_send_other_messages=( + not denied_permissions.send_stickers or not denied_permissions.send_gifs or + not denied_permissions.send_games or not denied_permissions.send_inline + ), + can_add_web_page_previews=not denied_permissions.embed_links, + can_send_polls=not denied_permissions.send_polls, + can_change_info=not denied_permissions.change_info, + can_invite_users=not denied_permissions.invite_users, + can_pin_messages=not denied_permissions.pin_messages + ) From e9b17303184aec9ff051b2e7a20f2f676bb38c52 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 24 Jan 2019 17:05:15 +0100 Subject: [PATCH 243/326] Update ChatMember to use the new ChatPermissions type --- .../types/user_and_chats/chat_member.py | 142 +++++------------- 1 file changed, 35 insertions(+), 107 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 70f32540..5769b3f8 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -30,8 +30,8 @@ class ChatMember(PyrogramType): Information about the user. status (``str``): - The member's status in the chat. Can be "creator", "administrator", "member", "restricted", - "left" or "kicked". + The member's status in the chat. + Can be "creator", "administrator", "member", "restricted", "left" or "kicked". date (``int``, *optional*): Date when the user joined, unix time. Not available for creator. @@ -46,52 +46,9 @@ class ChatMember(PyrogramType): restricted_by (:obj:`User `, *optional*): Restricted and kicked only. Information about the user who restricted or kicked this member. - until_date (``int``, *optional*): - Restricted and kicked only. Date when restrictions will be lifted for this user, unix time. - - can_be_edited (``bool``, *optional*): - Administrators only. True, if the bot is allowed to edit administrator privileges of that user. - - can_change_info (``bool``, *optional*): - Administrators only. True, if the administrator can change the chat title, photo and other settings. - - can_post_messages (``bool``, *optional*): - Administrators only. True, if the administrator can post in the channel, channels only. - - can_edit_messages (``bool``, *optional*): - Administrators only. True, if the administrator can edit messages of other users and can pin messages, - channels only. - - can_delete_messages (``bool``, *optional*): - Administrators only. True, if the administrator can delete messages of other users. - - can_invite_users (``bool``, *optional*): - Administrators only. True, if the administrator can invite new users to the chat. - - can_restrict_members (``bool``, *optional*): - Administrators only. True, if the administrator can restrict, ban or unban chat members. - - can_pin_messages (``bool``, *optional*): - Administrators only. True, if the administrator can pin messages, supergroups only. - - can_promote_members (``bool``, *optional*): - Administrators only. True, if the administrator can add new administrators with a subset of his - own privileges or demote administrators that he has promoted, directly or indirectly (promoted by - administrators that were appointed by the user). - - can_send_messages (``bool``, *optional*): - Restricted only. True, if the user can send text messages, contacts, locations and venues. - - can_send_media_messages (``bool``, *optional*): - Restricted only. True, if the user can send audios, documents, photos, videos, video notes and voice notes, - implies can_send_messages. - - can_send_other_messages (``bool``, *optional*): - Restricted only. True, if the user can send animations, games, stickers and use inline bots, implies - can_send_media_messages. - - can_add_web_page_previews (``bool``, *optional*): - Restricted only. True, if user may add web page previews to his messages, implies can_send_media_messages. + permissions (:obj:`ChatPermissions ` *optional*): + Administrators, restricted and kicked members only. + Information about the member permissions. """ def __init__(self, @@ -103,20 +60,7 @@ class ChatMember(PyrogramType): invited_by: "pyrogram.User" = None, promoted_by: "pyrogram.User" = None, restricted_by: "pyrogram.User" = None, - until_date: int = None, - can_be_edited: bool = None, - can_change_info: bool = None, - can_post_messages: bool = None, - can_edit_messages: bool = None, - can_delete_messages: bool = None, - can_invite_users: bool = None, - can_restrict_members: bool = None, - can_pin_messages: bool = None, - can_promote_members: bool = None, - can_send_messages: bool = None, - can_send_media_messages: bool = None, - can_send_other_messages: bool = None, - can_add_web_page_previews: bool = None): + permissions: "pyrogram.ChatPermissions" = None): super().__init__(client) self.user = user @@ -125,79 +69,63 @@ class ChatMember(PyrogramType): self.invited_by = invited_by self.promoted_by = promoted_by self.restricted_by = restricted_by - self.until_date = until_date - self.can_be_edited = can_be_edited - self.can_change_info = can_change_info - self.can_post_messages = can_post_messages - self.can_edit_messages = can_edit_messages - self.can_delete_messages = can_delete_messages - self.can_invite_users = can_invite_users - self.can_restrict_members = can_restrict_members - self.can_pin_messages = can_pin_messages - self.can_promote_members = can_promote_members - self.can_send_messages = can_send_messages - self.can_send_media_messages = can_send_media_messages - self.can_send_other_messages = can_send_other_messages - self.can_add_web_page_previews = can_add_web_page_previews + self.permissions = permissions @staticmethod def _parse(client, member, users) -> "ChatMember": user = pyrogram.User._parse(client, users[member.user_id]) - invited_by = pyrogram.User._parse(client, users[member.inviter_id]) if hasattr(member, "inviter_id") else None + + invited_by = ( + pyrogram.User._parse(client, users[member.inviter_id]) + if getattr(member, "inviter_id", None) else None + ) if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)): - return ChatMember(user=user, status="member", date=member.date, invited_by=invited_by, client=client) + return ChatMember( + user=user, + status="member", + date=member.date, + invited_by=invited_by, + client=client + ) if isinstance(member, (types.ChannelParticipantCreator, types.ChatParticipantCreator)): - return ChatMember(user=user, status="creator", client=client) + return ChatMember( + user=user, + status="creator", + client=client + ) if isinstance(member, types.ChatParticipantAdmin): - return ChatMember(user=user, status="administrator", date=member.date, invited_by=invited_by, client=client) + return ChatMember( + user=user, + status="administrator", + date=member.date, + invited_by=invited_by, + client=client + ) if isinstance(member, types.ChannelParticipantAdmin): - rights = member.admin_rights - return ChatMember( user=user, status="administrator", date=member.date, invited_by=invited_by, promoted_by=pyrogram.User._parse(client, users[member.promoted_by]), - can_be_edited=member.can_edit, - can_change_info=rights.change_info, - can_post_messages=rights.post_messages, - can_edit_messages=rights.edit_messages, - can_delete_messages=rights.delete_messages, - can_invite_users=rights.invite_users or rights.invite_link, - can_restrict_members=rights.ban_users, - can_pin_messages=rights.pin_messages, - can_promote_members=rights.add_admins, + permissions=pyrogram.ChatPermissions._parse(member), client=client ) if isinstance(member, types.ChannelParticipantBanned): - rights = member.banned_rights - - chat_member = ChatMember( + return ChatMember( user=user, status=( - "kicked" if rights.view_messages + "kicked" if member.banned_rights.view_messages else "left" if member.left else "restricted" ), date=member.date, restricted_by=pyrogram.User._parse(client, users[member.kicked_by]), - until_date=0 if rights.until_date == (1 << 31) - 1 else rights.until_date, + permissions=pyrogram.ChatPermissions._parse(member), client=client ) - - if chat_member.status == "restricted": - chat_member.can_send_messages = not rights.send_messages - chat_member.can_send_media_messages = not rights.send_media - chat_member.can_send_other_messages = ( - not rights.send_stickers or not rights.send_gifs or - not rights.send_games or not rights.send_inline - ) - chat_member.can_add_web_page_previews = not rights.embed_links - - return chat_member From 56e7bc996284cf5c46fef43dc7010d6b129700ef Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 24 Jan 2019 17:20:29 +0100 Subject: [PATCH 244/326] Update kick*, promote*, restrict* and unban_chat_member methods For Layer 93 --- pyrogram/client/methods/chats/kick_chat_member.py | 2 +- .../client/methods/chats/promote_chat_member.py | 13 ++++++------- .../client/methods/chats/restrict_chat_member.py | 2 +- pyrogram/client/methods/chats/unban_chat_member.py | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index 4cd66ec4..49b3dcfd 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -65,7 +65,7 @@ class KickChatMember(BaseClient): functions.channels.EditBanned( channel=chat_peer, user_id=user_peer, - banned_rights=types.ChannelBannedRights( + banned_rights=types.ChatBannedRights( until_date=until_date, view_messages=True, send_messages=True, diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py index 9f2260f0..d1f942c2 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/client/methods/chats/promote_chat_member.py @@ -30,11 +30,12 @@ class PromoteChatMember(BaseClient): can_post_messages: bool = False, can_edit_messages: bool = False, can_delete_messages: bool = True, - can_invite_users: bool = True, can_restrict_members: bool = True, + can_invite_users: bool = True, can_pin_messages: bool = False, can_promote_members: bool = False) -> bool: """Use this method to promote or demote a user in a supergroup or a channel. + You must be an administrator in the chat for this to work and must have the appropriate admin rights. Pass False for all boolean parameters to demote a user. @@ -58,12 +59,12 @@ class PromoteChatMember(BaseClient): can_delete_messages (``bool``, *optional*): Pass True, if the administrator can delete messages of other users. - can_invite_users (``bool``, *optional*): - Pass True, if the administrator can invite new users to the chat. - can_restrict_members (``bool``, *optional*): Pass True, if the administrator can restrict, ban or unban chat members. + can_invite_users (``bool``, *optional*): + Pass True, if the administrator can invite new users to the chat. + can_pin_messages (``bool``, *optional*): Pass True, if the administrator can pin messages, supergroups only. @@ -82,17 +83,15 @@ class PromoteChatMember(BaseClient): functions.channels.EditAdmin( channel=self.resolve_peer(chat_id), user_id=self.resolve_peer(user_id), - admin_rights=types.ChannelAdminRights( + admin_rights=types.ChatAdminRights( change_info=can_change_info or None, post_messages=can_post_messages or None, edit_messages=can_edit_messages or None, delete_messages=can_delete_messages or None, ban_users=can_restrict_members or None, invite_users=can_invite_users or None, - invite_link=can_invite_users or None, pin_messages=can_pin_messages or None, add_admins=can_promote_members or None, - manage_call=None ) ) ) diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index dbe0054b..f3e43df3 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -98,7 +98,7 @@ class RestrictChatMember(BaseClient): functions.channels.EditBanned( channel=self.resolve_peer(chat_id), user_id=self.resolve_peer(user_id), - banned_rights=types.ChannelBannedRights( + banned_rights=types.ChatBannedRights( until_date=until_date, send_messages=send_messages, send_media=send_media, diff --git a/pyrogram/client/methods/chats/unban_chat_member.py b/pyrogram/client/methods/chats/unban_chat_member.py index da706a1f..4c68b14f 100644 --- a/pyrogram/client/methods/chats/unban_chat_member.py +++ b/pyrogram/client/methods/chats/unban_chat_member.py @@ -48,7 +48,7 @@ class UnbanChatMember(BaseClient): functions.channels.EditBanned( channel=self.resolve_peer(chat_id), user_id=self.resolve_peer(user_id), - banned_rights=types.ChannelBannedRights( + banned_rights=types.ChatBannedRights( until_date=0 ) ) From 44deabf3999da863d41e631619b3b2abedde89bc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 24 Jan 2019 17:21:41 +0100 Subject: [PATCH 245/326] Update iter_chat_members efficiency --- pyrogram/client/ext/base_client.py | 3 +++ pyrogram/client/methods/chats/iter_chat_members.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 7b94ae6e..d2c348a8 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -126,3 +126,6 @@ class BaseClient: def get_chat_members(self, *args, **kwargs): pass + + def get_chat_members_count(self, *args, **kwargs): + pass diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index 963081f8..5d0fa911 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -81,9 +81,14 @@ class IterChatMembers(BaseClient): yielded = set() queries = [query] if query else QUERIES total = limit or (1 << 31) - 1 - filter = Filters.RECENT if total <= 10000 and filter == Filters.ALL else filter limit = min(200, total) + filter = ( + Filters.RECENT + if self.get_chat_members_count(chat_id) <= 10000 and filter == Filters.ALL + else filter + ) + if filter not in QUERYABLE_FILTERS: queries = [""] From 8a41075dc7f30d1f8a9610f2eabfe14e2f606938 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 24 Jan 2019 20:00:59 +0100 Subject: [PATCH 246/326] Update README.rst --- README.rst | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/README.rst b/README.rst index f7d2b44e..fcc6407a 100644 --- a/README.rst +++ b/README.rst @@ -3,6 +3,8 @@ Pyrogram ======== + `A fully asynchronous variant is also available! `_ + .. code-block:: python from pyrogram import Client, Filters @@ -17,18 +19,20 @@ Pyrogram app.run() -**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for -building custom Telegram applications that interact with the MTProto API as both User and Bot. +**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C. +It enables you to easily build custom Telegram applications that interact with the MTProto API as both user and bot. Features -------- -- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away. -- **High-level**: The low-level details of MTProto are abstracted and automatically handled. +- **Easy**: You can install Pyrogram with pip and start building your app right away. +- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way. - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. -- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0. -- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API. -- **Full API**, allowing to execute any advanced action an official client is able to do, and more. +- **Documented**: Pyrogram API methods, types and public interfaces are well documented. +- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted. +- **Updated**, to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0. +- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code. +- **Comprehensive**: Execute any advanced action an official client is able to do, and even more. Requirements ------------ @@ -47,7 +51,7 @@ Getting Started --------------- - The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.ml. -- Reading Examples_ in this repository is also a good way for learning how things work. +- Reading `Examples in this repository`_ is also a good way for learning how Pyrogram works. - Seeking extra help? Don't be shy, come join and ask our Community_! - For other requests you can send an Email_ or a Message_. @@ -67,7 +71,7 @@ Copyright & License .. _`Telegram`: https://telegram.org/ .. _`Telegram API key`: https://docs.pyrogram.ml/start/ProjectSetup#api-keys .. _`Community`: https://t.me/PyrogramChat -.. _`Examples`: https://github.com/pyrogram/pyrogram/tree/master/examples +.. _`Examples in this repository`: https://github.com/pyrogram/pyrogram/tree/master/examples .. _`GitHub`: https://github.com/pyrogram/pyrogram/issues .. _`Email`: admin@pyrogram.ml .. _`Message`: https://t.me/haskell @@ -83,17 +87,17 @@ Copyright & License

- Telegram MTProto API Client Library for Python + Telegram MTProto API Framework for Python
-
- Download - - • Documentation • + + Changelog + + • Community @@ -104,7 +108,7 @@ Copyright & License TgCrypto + alt="TgCrypto Version">

@@ -112,12 +116,12 @@ Copyright & License :target: https://pyrogram.ml :alt: Pyrogram -.. |description| replace:: **Telegram MTProto API Client Library for Python** +.. |description| replace:: **Telegram MTProto API Framework for Python** -.. |scheme| image:: "https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30" +.. |schema| image:: "https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30" :target: compiler/api/source/main_api.tl - :alt: Scheme Layer + :alt: Schema Layer .. |tgcrypto| image:: "https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30" :target: https://github.com/pyrogram/tgcrypto - :alt: TgCrypto + :alt: TgCrypto Version From d23e4079e4d29b72cd029d0f81c70e2a69de56dc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 24 Jan 2019 20:03:14 +0100 Subject: [PATCH 247/326] Update docs index page --- docs/source/index.rst | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index ca9a38a3..67a3b03f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -10,27 +10,28 @@ Welcome to Pyrogram

- Telegram MTProto API Client Library for Python + Telegram MTProto API Framework for Python +
- - Download + + Documentation • - - Source code + + Changelog Community
- + Scheme Layer + alt="Schema Layer"> TgCrypto + alt="TgCrypto Version">

@@ -55,18 +56,20 @@ using the Next button at the end of each page. But first, here's a brief overvie About ----- -**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for -building custom Telegram applications that interact with the MTProto API as both User and Bot. +**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C. +It enables you to easily build custom Telegram applications that interact with the MTProto API as both user and bot. Features -------- -- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away. -- **High-level**: The low-level details of MTProto are abstracted and automatically handled. +- **Easy**: You can install Pyrogram with pip and start building your app right away. +- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way. - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. -- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0. -- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API. -- **Full API**, allowing to execute any advanced action an official client is able to do, and more. +- **Documented**: Pyrogram API methods, types and public interfaces are well documented. +- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted. +- **Updated**, to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0. +- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code. +- **Comprehensive**: Execute any advanced action an official client is able to do, and even more. To get started, press the Next button. From c8e8fe0cd7c9bf050eefe2ae9abee7ac650238e5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Jan 2019 00:38:43 +0100 Subject: [PATCH 248/326] Add Load/Unload Plugins at Runtime section in SmartPlugins.rst --- docs/source/resources/SmartPlugins.rst | 82 +++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 8 deletions(-) diff --git a/docs/source/resources/SmartPlugins.rst b/docs/source/resources/SmartPlugins.rst index 3bff9632..a609c276 100644 --- a/docs/source/resources/SmartPlugins.rst +++ b/docs/source/resources/SmartPlugins.rst @@ -13,7 +13,8 @@ Introduction ------------ Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize -your applications, you had to do something like this: +your applications, you had to put your function definitions in separate files and register them inside your main script, +like this: .. note:: @@ -66,15 +67,15 @@ your applications, you had to do something like this: This is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to manually ``import``, manually :meth:`add_handler ` and manually instantiate each :obj:`MessageHandler ` object because **you can't use those cool decorators** for your -functions. So... What if you could? +functions. So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically. Using Smart Plugins ------------------- -Setting up your Pyrogram project to accommodate Smart Plugins is pretty straightforward: +Setting up your Pyrogram project to accommodate Smart Plugins is straightforward: -#. Create a new folder to store all the plugins (e.g.: "plugins"). -#. Put your files full of plugins inside. Organize them as you wish. +#. Create a new folder to store all the plugins (e.g.: "plugins", "handlers", ...). +#. Put your python files full of plugins inside. Organize them as you wish. #. Enable plugins in your Client or via the *config.ini* file. .. note:: @@ -160,7 +161,7 @@ found inside each module will be, instead, loaded in the order they are defined, This default loading behaviour is usually enough, but sometimes you want to have more control on what to include (or exclude) and in which exact order to load plugins. The way to do this is to make use of ``include`` and ``exclude`` -keys, either in the *config.ini* or in the dictionary passed as Client argument. Here's how they work: +keys, either in the *config.ini* file or in the dictionary passed as Client argument. Here's how they work: - If both ``include`` and ``exclude`` are omitted, all plugins are loaded as described above. - If ``include`` is given, only the specified plugins will be loaded, in the order they are passed. @@ -169,7 +170,7 @@ keys, either in the *config.ini* or in the dictionary passed as Client argument. The ``include`` and ``exclude`` value is a **list of strings**. Each string containing the path of the module relative to the plugins root folder, in Python notation (dots instead of slashes). - E.g.: ``subfolder.module`` refers to ``plugins/subfolder/module.py`` (root="plugins"). + E.g.: ``subfolder.module`` refers to ``plugins/subfolder/module.py``, with ``root="plugins"`. You can also choose the order in which the single handlers inside a module are loaded, thus overriding the default top-to-bottom loading policy. You can do this by appending the name of the functions to the module path, each one @@ -290,4 +291,69 @@ also organized in subfolders: Load/Unload Plugins at Runtime ------------------------------ -TODO \ No newline at end of file +In the `previous section <#specifying-the-plugins-to-include>`_ we've explained how to specify which plugins to load and +which to ignore before your Client starts. Here we'll show, instead, how to unload and load again a previously +registered plugins at runtime. + +Each function decorated with the usual ``on_message`` decorator (or any other decorator that deals with Telegram updates +) will be modified in such a way that, when you reference them later on, they will be actually pointing to a tuple of +*(handler: Handler, group: int)*. The actual callback function is therefore stored inside the handler's *callback* +attribute. Here's an example: + +- ``plugins/handlers.py`` + + .. code-block:: python + :emphasize-lines: 5, 6 + + @Client.on_message(Filters.text & Filters.private) + def echo(client, message): + message.reply(message.text) + + print(echo) + print(echo[0].callback) + +- Printing ``echo`` will show something like ``(, 0)``. + +- Printing ``echo[0].callback``, that is, the *callback* attribute of the first eleent of the tuple, which is an + Handler, will reveal the actual callback ````. + +Unloading +^^^^^^^^^ + +In order to unload a plugin, or any other handler, all you need to do is obtain a reference to it (by importing the +relevant module) and call :meth:`remove_handler ` Client's method with your function +name preceded by the star ``*`` operator as argument. Example: + +- ``main.py`` + + .. code-block:: python + + from plugins.handlers import echo + + ... + + app.remove_handler(*echo) + +The star ``*`` operator is used to unpack the tuple into positional arguments so that *remove_handler* will receive +exactly what is needed. The same could have been achieved with: + +.. code-block:: python + + handler, group = echo + app.remove_handler(handler, group) + +Loading +^^^^^^^ + +Similarly to the unloading process, in order to load again a previously unloaded plugin you do the same, but this time +using :meth:`add_handler ` instead. Example: + +- ``main.py`` + + .. code-block:: python + + from plugins.handlers import echo + + ... + + app.add_handler(*echo) \ No newline at end of file From 6d7d7f6c5eb8948bc36b285817f27d217dcc96ad Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Jan 2019 09:21:21 +0100 Subject: [PATCH 249/326] Add ConfigurationFile.rst --- docs/source/index.rst | 1 + docs/source/resources/ConfigurationFile.rst | 73 +++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 docs/source/resources/ConfigurationFile.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 67a3b03f..f4abd5f8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -88,6 +88,7 @@ To get started, press the Next button. resources/UpdateHandling resources/UsingFilters resources/MoreOnUpdates + resources/ConfigurationFile resources/SmartPlugins resources/AutoAuthorization resources/CustomizeSessions diff --git a/docs/source/resources/ConfigurationFile.rst b/docs/source/resources/ConfigurationFile.rst new file mode 100644 index 00000000..1f9966a2 --- /dev/null +++ b/docs/source/resources/ConfigurationFile.rst @@ -0,0 +1,73 @@ +Configuration File +================== + +As already mentioned in previous sections, Pyrogram can also be configured by the use of an INI file. +This page explains how this file is structured in Pyrogram, how to use it and why. + +Introduction +------------ + +The idea behind using a configuration file is to help keeping your code free of settings (private) information such as +the API Key and Proxy without having you to even deal with how to load such settings. The configuration file, usually +referred as ``config.ini` file, is automatically loaded from the root of your working directory; all you need to do is +fill in the necessary parts. + +.. note:: + + The configuration file is optional, but recommended. If, for any reason, you prefer not to use it, there's always an + alternative way to configure Pyrogram via Client's parameters. Doing so, you can have full control on how to store + and load your settings (e.g.: from environment variables). + + Settings specified via Client's parameter have higher priority and will override any setting stored in the + configuration file. + + +The config.ini File +------------------- + +By default, Pyrogram will look for a file named ``config.ini`` placed at the root of your working directory, that is, in +the same folder of your running script. You can change the name or location of your configuration file by specifying +that in your Client's parameter *config_file*. + +- Replace the default *config.ini* file with *my_configuration.ini*: + + .. code-block:: python + + from pyrogram import Client + + app = Client("my_account", config_file="my_configuration.ini") + + +Configuration Sections +---------------------- + +There are all the sections Pyrogram uses in its configuration file: + +Pyrogram +^^^^^^^^ + +The ``pyrogram`` section is used to store Telegram credentials, namely the API Key, which consists of two parts: +*api_id* and *api_hash*. + +.. code-block:: ini + + [pyrogram] + api_id = 12345 + api_hash = 0123456789abcdef0123456789abcdef + +`More info <../start/Setup.html#configuration>`_ + +Proxy +^^^^^ + +The ``proxy`` section contains settings about your SOCKS5 proxy. + +.. code-block:: ini + + [proxy] + enabled = True + hostname = 11.22.33.44 + port = 1080 + username = + password = + From c17bf3c64c5519b406cbd25f4e22c13bcd056bfb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Jan 2019 09:40:37 +0100 Subject: [PATCH 250/326] Fix sphinx warnings --- docs/source/resources/SmartPlugins.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/resources/SmartPlugins.rst b/docs/source/resources/SmartPlugins.rst index a609c276..972efdd8 100644 --- a/docs/source/resources/SmartPlugins.rst +++ b/docs/source/resources/SmartPlugins.rst @@ -170,7 +170,7 @@ keys, either in the *config.ini* file or in the dictionary passed as Client argu The ``include`` and ``exclude`` value is a **list of strings**. Each string containing the path of the module relative to the plugins root folder, in Python notation (dots instead of slashes). - E.g.: ``subfolder.module`` refers to ``plugins/subfolder/module.py``, with ``root="plugins"`. + E.g.: ``subfolder.module`` refers to ``plugins/subfolder/module.py``, with ``root="plugins"``. You can also choose the order in which the single handlers inside a module are loaded, thus overriding the default top-to-bottom loading policy. You can do this by appending the name of the functions to the module path, each one From e4c8592616436329b24f5225ede57a7003c1dd09 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Jan 2019 09:40:55 +0100 Subject: [PATCH 251/326] Update ConfigurationFile.rst --- docs/source/resources/ConfigurationFile.rst | 35 +++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/docs/source/resources/ConfigurationFile.rst b/docs/source/resources/ConfigurationFile.rst index 1f9966a2..759bfd9f 100644 --- a/docs/source/resources/ConfigurationFile.rst +++ b/docs/source/resources/ConfigurationFile.rst @@ -9,7 +9,7 @@ Introduction The idea behind using a configuration file is to help keeping your code free of settings (private) information such as the API Key and Proxy without having you to even deal with how to load such settings. The configuration file, usually -referred as ``config.ini` file, is automatically loaded from the root of your working directory; all you need to do is +referred as ``config.ini`` file, is automatically loaded from the root of your working directory; all you need to do is fill in the necessary parts. .. note:: @@ -25,9 +25,9 @@ fill in the necessary parts. The config.ini File ------------------- -By default, Pyrogram will look for a file named ``config.ini`` placed at the root of your working directory, that is, in -the same folder of your running script. You can change the name or location of your configuration file by specifying -that in your Client's parameter *config_file*. +By default, Pyrogram will look for a file named ``config.ini`` placed at the root of your working directory, that is, +the same folder of your running script. You can change the name or location of your configuration file by specifying it +in your Client's parameter *config_file*. - Replace the default *config.ini* file with *my_configuration.ini*: @@ -41,13 +41,12 @@ that in your Client's parameter *config_file*. Configuration Sections ---------------------- -There are all the sections Pyrogram uses in its configuration file: +These are all the sections Pyrogram uses in its configuration file: Pyrogram ^^^^^^^^ -The ``pyrogram`` section is used to store Telegram credentials, namely the API Key, which consists of two parts: -*api_id* and *api_hash*. +The ``[pyrogram]`` section contains your Telegram API credentials *api_id* and *api_hash*. .. code-block:: ini @@ -55,12 +54,12 @@ The ``pyrogram`` section is used to store Telegram credentials, namely the API K api_id = 12345 api_hash = 0123456789abcdef0123456789abcdef -`More info <../start/Setup.html#configuration>`_ +`More info about API Key. <../start/Setup.html#configuration>`_ Proxy ^^^^^ -The ``proxy`` section contains settings about your SOCKS5 proxy. +The ``[proxy]`` section contains settings about your SOCKS5 proxy. .. code-block:: ini @@ -71,3 +70,21 @@ The ``proxy`` section contains settings about your SOCKS5 proxy. username = password = +`More info about SOCKS5 Proxy. `_ + +Plugins +^^^^^^^ + +The ``[plugins]`` section contains settings about Smart Plugins. + +.. code-block:: ini + + [plugins] + root = plugins + include = + module + folder.module + exclude = + module fn2 + +`More info about Smart Plugins. `_ From 5e97cb34208dc7bacdf60880b094960cb01fa501 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Jan 2019 09:43:56 +0100 Subject: [PATCH 252/326] Update documentation index page --- docs/source/index.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index f4abd5f8..067e6fbf 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -49,15 +49,15 @@ Welcome to Pyrogram app.run() -Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library. +Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the framework. Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order using the Next button at the end of each page. But first, here's a brief overview of what is this all about. About ----- -**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C. -It enables you to easily build custom Telegram applications that interact with the MTProto API as both user and bot. +**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and +C. It enables you to easily build custom Telegram applications that interact with the MTProto API as both user and bot. Features -------- From a8a6f53e2da871ad8d461661210f1922fcd9b450 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 26 Jan 2019 13:01:44 +0100 Subject: [PATCH 253/326] Fix the configuration load process breaking in case of no plugins --- pyrogram/client/client.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index eb205bc2..32612821 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1087,14 +1087,15 @@ class Client(Methods, BaseClient): "exclude": section.get("exclude") or None } except KeyError: - pass + self.plugins = {} - for option in ["include", "exclude"]: - if self.plugins[option] is not None: - self.plugins[option] = [ - (i.split()[0], i.split()[1:] or None) - for i in self.plugins[option].strip().split("\n") - ] + if self.plugins: + for option in ["include", "exclude"]: + if self.plugins[option] is not None: + self.plugins[option] = [ + (i.split()[0], i.split()[1:] or None) + for i in self.plugins[option].strip().split("\n") + ] def load_session(self): try: From f376efd496997c697a936b7a1be081a6f4f02676 Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Sun, 27 Jan 2019 12:07:19 +0300 Subject: [PATCH 254/326] Run generate for editable installs --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cc2a3880..6062c987 100644 --- a/setup.py +++ b/setup.py @@ -127,7 +127,7 @@ class Generate(Command): docs_compiler.start() -if len(argv) > 1 and argv[1] in ["bdist_wheel", "install"]: +if len(argv) > 1 and argv[1] in ["bdist_wheel", "install", "develop"]: error_compiler.start() api_compiler.start() docs_compiler.start() From 52f2a2b17d96a9fc8b9d143330ff9fdf6151d06c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 27 Jan 2019 10:41:14 +0100 Subject: [PATCH 255/326] Add TARGET to hold the enabled chats and more comments --- examples/welcome.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/welcome.py b/examples/welcome.py index 06a38cb7..ab252672 100644 --- a/examples/welcome.py +++ b/examples/welcome.py @@ -6,13 +6,15 @@ to make it only work for specific messages in a specific chat. from pyrogram import Client, Emoji, Filters -MENTION = "[{}](tg://user?id={})" -MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!" +TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames +MENTION = "[{}](tg://user?id={})" # User mention markup +MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!" # Welcome message app = Client("my_account") -@app.on_message(Filters.chat("PyrogramChat") & Filters.new_chat_members) +# Filter in only new_chat_members updates generated in TARGET chat +@app.on_message(Filters.chat(TARGET) & Filters.new_chat_members) def welcome(client, message): # Build the new members list (with mentions) by using their first_name new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members] From 67a35f8c7e9fe6bbb81078d4a5fa492723d2a185 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 27 Jan 2019 11:13:10 +0100 Subject: [PATCH 256/326] Handle get_history flood waits It's likely to get triggered when using iter_history (every ~3k msgs) --- .../client/methods/messages/get_history.py | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index d45623f4..73923b44 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -16,12 +16,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import logging +import time from typing import Union import pyrogram from pyrogram.api import functions +from pyrogram.api.errors import FloodWait from ...ext import BaseClient +log = logging.getLogger(__name__) + class GetHistory(BaseClient): def get_history(self, @@ -66,21 +71,28 @@ class GetHistory(BaseClient): :class:`Error ` in case of a Telegram RPC error. """ - messages = pyrogram.Messages._parse( - self, - self.send( - functions.messages.GetHistory( - peer=self.resolve_peer(chat_id), - offset_id=offset_id, - offset_date=offset_date, - add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0), - limit=limit, - max_id=0, - min_id=0, - hash=0 + while True: + try: + messages = pyrogram.Messages._parse( + self, + self.send( + functions.messages.GetHistory( + peer=self.resolve_peer(chat_id), + offset_id=offset_id, + offset_date=offset_date, + add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0), + limit=limit, + max_id=0, + min_id=0, + hash=0 + ) + ) ) - ) - ) + except FloodWait as e: + log.warning("Sleeping for {}s".format(e.x)) + time.sleep(e.x) + else: + break if reverse: messages.messages.reverse() From 628ddd4a25c67e22dc3698820bb8364f29ec2c85 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 27 Jan 2019 11:24:23 +0100 Subject: [PATCH 257/326] Update Client's docstrings --- pyrogram/client/client.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 32612821..1bbc2267 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -139,8 +139,9 @@ class Client(Methods, BaseClient): Only applicable for new sessions. first_name (``str``, *optional*): - Pass a First Name to avoid entering it manually. It will be used to automatically - create a new Telegram account in case the phone number you passed is not registered yet. + Pass a First Name as string to avoid entering it manually. Or pass a callback function which accepts no + arguments and must return the correct name as string (e.g., "Dan"). It will be used to automatically create + a new Telegram account in case the phone number you passed is not registered yet. Only applicable for new sessions. last_name (``str``, *optional*): @@ -158,7 +159,8 @@ class Client(Methods, BaseClient): Path of the configuration file. Defaults to ./config.ini plugins (``dict``, *optional*): - TODO: doctrings + Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. + This is an alternative way to setup plugins if you don't want to use the *config.ini* file. no_updates (``bool``, *optional*): Pass True to completely disable incoming updates for the current session. From adfba5ffdf40163cbb1eae447bc35968845bf083 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 27 Jan 2019 11:41:46 +0100 Subject: [PATCH 258/326] Add IMAGE_PROCESS_FAILED error --- compiler/error/source/400_BAD_REQUEST.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 82474096..9250c566 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -87,3 +87,4 @@ MESSAGE_POLL_CLOSED You can't interact with a closed poll MEDIA_INVALID The media is invalid BOT_SCORE_NOT_MODIFIED The bot score was not modified USER_BOT_REQUIRED The method can be used by bots only +IMAGE_PROCESS_FAILED The server failed to process your image \ No newline at end of file From 0f0e19eb1a866ff9c0afbcd9c27a913edb0ad032 Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Mon, 28 Jan 2019 01:45:36 +0300 Subject: [PATCH 259/326] Add TAKEOUT_INIT_DELAY_X to error sources --- compiler/error/source/420_FLOOD.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/error/source/420_FLOOD.tsv b/compiler/error/source/420_FLOOD.tsv index bf404156..fd4d09e6 100644 --- a/compiler/error/source/420_FLOOD.tsv +++ b/compiler/error/source/420_FLOOD.tsv @@ -1,2 +1,3 @@ id message FLOOD_WAIT_X A wait of {x} seconds is required +TAKEOUT_INIT_DELAY_X Either confirm data export request on another device or wait {x} seconds From 9079fbc9326493a377481d703b8ef3d07d928af2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 30 Jan 2019 15:52:29 +0100 Subject: [PATCH 260/326] Slightly reword TAKEOUT_INIT_DELAY_X error message --- compiler/error/source/420_FLOOD.tsv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/error/source/420_FLOOD.tsv b/compiler/error/source/420_FLOOD.tsv index fd4d09e6..3d5ceabd 100644 --- a/compiler/error/source/420_FLOOD.tsv +++ b/compiler/error/source/420_FLOOD.tsv @@ -1,3 +1,3 @@ id message FLOOD_WAIT_X A wait of {x} seconds is required -TAKEOUT_INIT_DELAY_X Either confirm data export request on another device or wait {x} seconds +TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {x} seconds From f0d059da07b0261357106a1260053ad3ce9c7a25 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 30 Jan 2019 17:16:50 +0100 Subject: [PATCH 261/326] First working (and ugly) way for fixing raw updates being swallowed --- pyrogram/client/dispatcher/dispatcher.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 47999bc6..b9acf519 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -128,10 +128,11 @@ class Dispatcher: parser = self.update_parsers.get(type(update), None) - if parser is None: - continue - - parsed_update, handler_type = parser(update, users, chats) + parsed_update, handler_type = ( + parser(update, users, chats) + if parser + else (None, type) + ) for group in self.groups.values(): try: From c40f061d9acfb3c598b4a4c1712844dcd3f4cdaf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 Feb 2019 19:01:35 +0100 Subject: [PATCH 262/326] Fix CallbackQuery docstrings --- pyrogram/client/types/bots/callback_query.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index 62f651d0..c2558844 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -40,15 +40,15 @@ class CallbackQuery(PyrogramType, Update): Sender. chat_instance (``str``, *optional*): + Global identifier, uniquely corresponding to the chat to which the message with the callback button was + sent. Useful for high scores in games. + + message (:obj:`Message `, *optional*): Message with the callback button that originated the query. Note that message content and message date will not be available if the message is too old. - message (:obj:`Message `, *optional*): - Identifier of the message sent via the bot in inline mode, that originated the query. - inline_message_id (``str``): - Global identifier, uniquely corresponding to the chat to which the message with the callback button was - sent. Useful for high scores in games. + Identifier of the message sent via the bot in inline mode, that originated the query. data (``bytes``, *optional*): Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field. @@ -72,9 +72,9 @@ class CallbackQuery(PyrogramType, Update): self.id = id self.from_user = from_user + self.chat_instance = chat_instance self.message = message self.inline_message_id = inline_message_id - self.chat_instance = chat_instance self.data = data self.game_short_name = game_short_name From 249e4053395765fa0390061aff95be03e7963e84 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 3 Feb 2019 11:03:11 +0100 Subject: [PATCH 263/326] Update API schema to Layer 95 --- compiler/api/source/main_api.tl | 23 ++++++++++++++--------- pyrogram/client/client.py | 7 ++++--- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 7bbf15e7..8ad94a4e 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -187,7 +187,7 @@ peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bo peerSettings#818426cd flags:# report_spam:flags.0?true = PeerSettings; -wallPaper#f04f91ec id:long flags:# creator:flags.0?true default:flags.1?true access_hash:long slug:string document:Document = WallPaper; +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; inputReportReasonSpam#58dbcab8 = ReportReason; inputReportReasonViolence#1e22c78d = ReportReason; @@ -981,6 +981,10 @@ inputWallPaperSlug#72091c80 slug:string = InputWallPaper; account.wallPapersNotModified#1c199183 = account.WallPapers; account.wallPapers#702b65a9 hash:int wallpapers:Vector = account.WallPapers; +codeSettings#302f59f3 flags:# allow_flashcall:flags.0?true current_number:flags.1?true app_hash_persistent:flags.2?true app_hash:flags.3?string = CodeSettings; + +wallPaperSettings#a12f40b8 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int intensity:flags.3?int = WallPaperSettings; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -991,7 +995,7 @@ invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X; invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X; invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; -auth.sendCode#86aef0ec flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool api_id:int api_hash:string = auth.SentCode; +auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode; auth.signUp#1b067634 phone_number:string phone_code_hash:string phone_code:string first_name:string last_name:string = auth.Authorization; auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization; auth.logOut#5717da40 = Bool; @@ -1023,7 +1027,7 @@ account.setPrivacy#c9f81ce8 key:InputPrivacyKey rules:Vector = account.deleteAccount#418d4e0b reason:string = Bool; account.getAccountTTL#8fc711d = AccountDaysTTL; account.setAccountTTL#2442485e ttl:AccountDaysTTL = Bool; -account.sendChangePhoneCode#8e57deb flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool = auth.SentCode; +account.sendChangePhoneCode#82574ae5 phone_number:string settings:CodeSettings = auth.SentCode; account.changePhone#70c32edb phone_number:string phone_code_hash:string phone_code:string = User; account.updateDeviceLocked#38df3532 period:int = Bool; account.getAuthorizations#e320c158 = account.Authorizations; @@ -1031,7 +1035,7 @@ account.resetAuthorization#df77f3bc hash:long = Bool; account.getPassword#548a30f5 = account.Password; account.getPasswordSettings#9cd4eaf9 password:InputCheckPasswordSRP = account.PasswordSettings; account.updatePasswordSettings#a59b102f password:InputCheckPasswordSRP new_settings:account.PasswordInputSettings = Bool; -account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode; +account.sendConfirmPhoneCode#1b3faa88 hash:string settings:CodeSettings = auth.SentCode; account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool; account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword; account.getWebAuthorizations#182e6d6f = account.WebAuthorizations; @@ -1043,7 +1047,7 @@ account.saveSecureValue#899fe31d value:InputSecureValue secure_secret_id:long = account.deleteSecureValue#b880bc4b types:Vector = Bool; account.getAuthorizationForm#b86ba8e1 bot_id:int scope:string public_key:string = account.AuthorizationForm; account.acceptAuthorization#e7027c94 bot_id:int scope:string public_key:string value_hashes:Vector credentials:SecureCredentialsEncrypted = Bool; -account.sendVerifyPhoneCode#823380b4 flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool = auth.SentCode; +account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode; account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool; account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode; account.verifyEmail#ecba39db email:string code:string = Bool; @@ -1055,10 +1059,11 @@ account.cancelPasswordEmail#c1cbd5b6 = Bool; account.getContactSignUpNotification#9f07c728 = Bool; account.setContactSignUpNotification#cff43f61 silent:Bool = Bool; account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates; -account.uploadWallPaper#c7ba9b4d file:InputFile mime_type:string = WallPaper; account.getWallPaper#fc8ddbea wallpaper:InputWallPaper = WallPaper; -account.saveWallPaper#189581b3 wallpaper:InputWallPaper unsave:Bool = Bool; -account.installWallPaper#4a0378ce wallpaper:InputWallPaper = Bool; +account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPaperSettings = WallPaper; +account.saveWallPaper#6c5a5b37 wallpaper:InputWallPaper unsave:Bool settings:WallPaperSettings = Bool; +account.installWallPaper#feed5769 wallpaper:InputWallPaper settings:WallPaperSettings = Bool; +account.resetWallPapers#bb3b9804 = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; @@ -1287,4 +1292,4 @@ langpack.getDifference#9d51e814 lang_code:string from_version:int = LangPackDiff langpack.getLanguages#42c6978f lang_pack:string = Vector; langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLanguage; -// LAYER 93 +// LAYER 95 diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 576dea9a..eba52b44 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -530,9 +530,10 @@ class Client(Methods, BaseClient): try: r = self.send( functions.auth.SendCode( - self.phone_number, - self.api_id, - self.api_hash + phone_number=self.phone_number, + api_id=self.api_id, + api_hash=self.api_hash, + settings=types.CodeSettings() ) ) except (PhoneMigrate, NetworkMigrate) as e: From 429cfd0882edca4bded39ba5b615b5f469310e06 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Feb 2019 10:35:00 +0100 Subject: [PATCH 264/326] Move the check method into Handler superclass --- pyrogram/client/handlers/callback_query_handler.py | 7 ------- pyrogram/client/handlers/deleted_messages_handler.py | 6 +----- pyrogram/client/handlers/handler.py | 7 +++++++ pyrogram/client/handlers/message_handler.py | 7 ------- pyrogram/client/handlers/user_status_handler.py | 7 ------- 5 files changed, 8 insertions(+), 26 deletions(-) diff --git a/pyrogram/client/handlers/callback_query_handler.py b/pyrogram/client/handlers/callback_query_handler.py index e991c019..88ddd5a0 100644 --- a/pyrogram/client/handlers/callback_query_handler.py +++ b/pyrogram/client/handlers/callback_query_handler.py @@ -45,10 +45,3 @@ class CallbackQueryHandler(Handler): def __init__(self, callback: callable, filters=None): super().__init__(callback, filters) - - def check(self, callback_query): - return ( - self.filters(callback_query) - if callable(self.filters) - else True - ) diff --git a/pyrogram/client/handlers/deleted_messages_handler.py b/pyrogram/client/handlers/deleted_messages_handler.py index c084f353..52177dcc 100644 --- a/pyrogram/client/handlers/deleted_messages_handler.py +++ b/pyrogram/client/handlers/deleted_messages_handler.py @@ -48,8 +48,4 @@ class DeletedMessagesHandler(Handler): super().__init__(callback, filters) def check(self, messages): - return ( - self.filters(messages.messages[0]) - if callable(self.filters) - else True - ) + return super().check(messages.messages[0]) diff --git a/pyrogram/client/handlers/handler.py b/pyrogram/client/handlers/handler.py index 9fd0e206..36963280 100644 --- a/pyrogram/client/handlers/handler.py +++ b/pyrogram/client/handlers/handler.py @@ -21,3 +21,10 @@ class Handler: def __init__(self, callback: callable, filters=None): self.callback = callback self.filters = filters + + def check(self, update): + return ( + self.filters(update) + if callable(self.filters) + else True + ) diff --git a/pyrogram/client/handlers/message_handler.py b/pyrogram/client/handlers/message_handler.py index 8a52a0b5..67b4587e 100644 --- a/pyrogram/client/handlers/message_handler.py +++ b/pyrogram/client/handlers/message_handler.py @@ -46,10 +46,3 @@ class MessageHandler(Handler): def __init__(self, callback: callable, filters=None): super().__init__(callback, filters) - - def check(self, message): - return ( - self.filters(message) - if callable(self.filters) - else True - ) diff --git a/pyrogram/client/handlers/user_status_handler.py b/pyrogram/client/handlers/user_status_handler.py index 643a064d..856ef81d 100644 --- a/pyrogram/client/handlers/user_status_handler.py +++ b/pyrogram/client/handlers/user_status_handler.py @@ -45,10 +45,3 @@ class UserStatusHandler(Handler): def __init__(self, callback: callable, filters=None): super().__init__(callback, filters) - - def check(self, user_status): - return ( - self.filters(user_status) - if callable(self.filters) - else True - ) From 392fea6e325d2dd956c7e8214971b3e978f563f7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Feb 2019 11:46:57 +0100 Subject: [PATCH 265/326] Refactor Dispatcher's worker Closes #211 --- pyrogram/client/dispatcher/dispatcher.py | 39 ++++++++++++------------ 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index b9acf519..cb22a48c 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -130,34 +130,33 @@ class Dispatcher: parsed_update, handler_type = ( parser(update, users, chats) - if parser - else (None, type) + if parser is not None + else (None, type(None)) ) for group in self.groups.values(): - try: - for handler in group: - args = None + for handler in group: + args = None - if isinstance(handler, RawUpdateHandler): - args = (update, users, chats) - elif isinstance(handler, handler_type): - if handler.check(parsed_update): - args = (parsed_update,) + if isinstance(handler, handler_type): + if handler.check(parsed_update): + args = (parsed_update,) + elif isinstance(handler, RawUpdateHandler): + args = (update, users, chats) - if args is None: - continue + if args is None: + continue - try: - handler.callback(self.client, *args) - except StopIteration: - raise - except Exception as e: - log.error(e, exc_info=True) + try: + handler.callback(self.client, *args) + except pyrogram.StopPropagation: + raise + except Exception as e: + log.error(e, exc_info=True) - break - except StopIteration: break + except pyrogram.StopPropagation: + pass except Exception as e: log.error(e, exc_info=True) From a6dbed6dfb07989f064bae5938fb3b67eb144a69 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Feb 2019 12:33:54 +0100 Subject: [PATCH 266/326] Add a way to continue the update propagation within a group Add continue_propagation() method and ContinuePropagation exception Closes #212 --- docs/source/resources/MoreOnUpdates.rst | 85 ++++++++++++++++++++++-- pyrogram/__init__.py | 3 +- pyrogram/client/dispatcher/dispatcher.py | 2 + pyrogram/client/types/__init__.py | 2 +- pyrogram/client/types/update.py | 7 ++ 5 files changed, 91 insertions(+), 8 deletions(-) diff --git a/docs/source/resources/MoreOnUpdates.rst b/docs/source/resources/MoreOnUpdates.rst index 44295f35..9712a5d2 100644 --- a/docs/source/resources/MoreOnUpdates.rst +++ b/docs/source/resources/MoreOnUpdates.rst @@ -91,13 +91,14 @@ Stop Propagation In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following: - Call the update's bound-method ``.stop_propagation()`` (preferred way). -- Manually ``raise StopPropagation`` error (more suitable for raw updates only). +- Manually ``raise StopPropagation`` exception (more suitable for raw updates only). .. note:: - Note that ``.stop_propagation()`` is just an elegant and intuitive way to raise a ``StopPropagation`` error; - this means that any code coming *after* calling it won't be executed as your function just raised a custom exception - to signal the dispatcher not to propagate the update anymore. + Internally, the propagation is stopped by handling a custom exception. ``.stop_propagation()`` is just an elegant + and intuitive way to ``raise StopPropagation``; this also means that any code coming *after* calling the method + won't be executed as your function just raised an exception to signal the dispatcher not to propagate the + update anymore. Example with ``stop_propagation()``: @@ -139,10 +140,82 @@ Example with ``raise StopPropagation``: def _(client, message): print(2) -The handler in group number 2 will never be executed because the propagation was stopped before. The output of both -examples will be: +Each handler is registered in a different group, but the handler in group number 2 will never be executed because the +propagation was stopped earlier. The output of both (equivalent) examples will be: .. code-block:: text 0 1 + +Continue Propagation +^^^^^^^^^^^^^^^^^^^^ + +As opposed to `stopping the update propagation <#stop-propagation>`_ and also as an alternative to the +`handler groups <#handler-groups>`_, you can signal the internal dispatcher to continue the update propagation within +the group regardless of the next handler's filters. This allows you to register multiple handlers with overlapping +filters in the same group; to let the dispatcher process the next handler you can do *one* of the following in each +handler you want to grant permission to continue: + +- Call the update's bound-method ``.continue_propagation()`` (preferred way). +- Manually ``raise ContinuePropagation`` exception (more suitable for raw updates only). + +.. note:: + + Internally, the propagation is continued by handling a custom exception. ``.continue_propagation()`` is just an + elegant and intuitive way to ``raise ContinuePropagation``; this also means that any code coming *after* calling the + method won't be executed as your function just raised an exception to signal the dispatcher to continue with the + next available handler. + + +Example with ``continue_propagation()``: + +.. code-block:: python + + @app.on_message(Filters.private) + def _(client, message): + print(0) + message.continue_propagation() + + + @app.on_message(Filters.private) + def _(client, message): + print(1) + message.continue_propagation() + + + @app.on_message(Filters.private) + def _(client, message): + print(2) + +Example with ``raise ContinuePropagation``: + +.. code-block:: python + + from pyrogram import ContinuePropagation + + @app.on_message(Filters.private) + def _(client, message): + print(0) + raise ContinuePropagation + + + @app.on_message(Filters.private) + def _(client, message): + print(1) + raise ContinuePropagation + + + @app.on_message(Filters.private) + def _(client, message): + print(2) + +Three handlers are registered in the same group, and all of them will be executed because the propagation was continued +in each handler (except in the last one, where is useless to do so since there is no more handlers after). +The output of both (equivalent) examples will be: + +.. code-block:: text + + 0 + 1 + 2 \ No newline at end of file diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index db3d8674..62f74ddf 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -32,7 +32,8 @@ from .client.types import ( Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, - Poll, PollOption, ChatPreview, StopPropagation, Game, CallbackGame, GameHighScore, GameHighScores + Poll, PollOption, ChatPreview, StopPropagation, ContinuePropagation, Game, CallbackGame, GameHighScore, + GameHighScores ) from .client import ( Client, ChatAction, ParseMode, Emoji, diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index cb22a48c..5a463077 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -151,6 +151,8 @@ class Dispatcher: handler.callback(self.client, *args) except pyrogram.StopPropagation: raise + except pyrogram.ContinuePropagation: + continue except Exception as e: log.error(e, exc_info=True) diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index ca332a22..a29b0816 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -30,7 +30,7 @@ from .messages_and_media import ( Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos, Message, Messages, MessageEntity, Poll, PollOption, Game ) -from .update import StopPropagation +from .update import StopPropagation, ContinuePropagation from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, Dialog, Dialogs, User, UserStatus, ChatPreview diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py index 80c233c0..2ec22f5a 100644 --- a/pyrogram/client/types/update.py +++ b/pyrogram/client/types/update.py @@ -21,6 +21,13 @@ class StopPropagation(StopIteration): pass +class ContinuePropagation(StopIteration): + pass + + class Update: def stop_propagation(self): raise StopPropagation + + def continue_propagation(self): + raise ContinuePropagation From f1a4e9f64b5b4a709d3871b500aeffda7a405275 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Feb 2019 12:53:33 +0100 Subject: [PATCH 267/326] Add VoiceCalls.rst --- docs/source/index.rst | 1 + docs/source/resources/VoiceCalls.rst | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 docs/source/resources/VoiceCalls.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 067e6fbf..1d77c02f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -99,6 +99,7 @@ To get started, press the Next button. resources/ErrorHandling resources/TestServers resources/AdvancedUsage + resources/VoiceCalls resources/Changelog .. toctree:: diff --git a/docs/source/resources/VoiceCalls.rst b/docs/source/resources/VoiceCalls.rst new file mode 100644 index 00000000..c1a8cc53 --- /dev/null +++ b/docs/source/resources/VoiceCalls.rst @@ -0,0 +1,10 @@ +Voice Calls +=========== + +A working proof-of-concept of Telegram voice calls using Pyrogram can be found here: +https://github.com/bakatrouble/pylibtgvoip. Thanks to `@bakatrouble `_. + +.. note:: + + This page will be updated with more information once voice calls become eventually more usable and more integrated + in Pyrogram itself. From c213118a746c036f678f6e4007506e31d2d52562 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Feb 2019 13:00:09 +0100 Subject: [PATCH 268/326] Update develop version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 62f74ddf..152c95af 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2019 Dan Tès Date: Mon, 4 Feb 2019 13:04:57 +0100 Subject: [PATCH 269/326] Update README.rst --- README.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index fcc6407a..8b39f4b6 100644 --- a/README.rst +++ b/README.rst @@ -3,8 +3,6 @@ Pyrogram ======== - `A fully asynchronous variant is also available! `_ - .. code-block:: python from pyrogram import Client, Filters @@ -20,17 +18,19 @@ Pyrogram app.run() **Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C. -It enables you to easily build custom Telegram applications that interact with the MTProto API as both user and bot. +It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the `MTProto API`_. + + `A fully-asynchronous variant is also available » `_ Features -------- -- **Easy**: You can install Pyrogram with pip and start building your app right away. +- **Easy**: You can install Pyrogram with pip and start building your applications right away. - **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way. - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. - **Documented**: Pyrogram API methods, types and public interfaces are well documented. - **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted. -- **Updated**, to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0. +- **Updated**, to the latest Telegram API version, currently Layer 91 on top of `MTProto 2.0`_. - **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code. - **Comprehensive**: Execute any advanced action an official client is able to do, and even more. @@ -47,8 +47,8 @@ Installing pip3 install pyrogram -Getting Started ---------------- +Resources +--------- - The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.ml. - Reading `Examples in this repository`_ is also a good way for learning how Pyrogram works. @@ -69,6 +69,7 @@ Copyright & License - Licensed under the terms of the `GNU Lesser General Public License v3 or later (LGPLv3+)`_ .. _`Telegram`: https://telegram.org/ +.. _`MTProto API`: https://core.telegram.org/api#telegram-api .. _`Telegram API key`: https://docs.pyrogram.ml/start/ProjectSetup#api-keys .. _`Community`: https://t.me/PyrogramChat .. _`Examples in this repository`: https://github.com/pyrogram/pyrogram/tree/master/examples @@ -76,6 +77,7 @@ Copyright & License .. _`Email`: admin@pyrogram.ml .. _`Message`: https://t.me/haskell .. _TgCrypto: https://github.com/pyrogram/tgcrypto +.. _`MTProto 2.0`: https://core.telegram.org/mtproto .. _`GNU Lesser General Public License v3 or later (LGPLv3+)`: COPYING.lesser .. |header| raw:: html From 6f44cb52143add1e1ce2b8b8cb8d95ff487bcc99 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Feb 2019 13:06:23 +0100 Subject: [PATCH 270/326] Update docs index page --- docs/source/index.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 1d77c02f..4d913d23 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -56,18 +56,18 @@ using the Next button at the end of each page. But first, here's a brief overvie About ----- -**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and -C. It enables you to easily build custom Telegram applications that interact with the MTProto API as both user and bot. +**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C. +It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the `MTProto API`_. Features -------- -- **Easy**: You can install Pyrogram with pip and start building your app right away. +- **Easy**: You can install Pyrogram with pip and start building your applications right away. - **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way. - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. - **Documented**: Pyrogram API methods, types and public interfaces are well documented. - **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted. -- **Updated**, to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0. +- **Updated**, to the latest Telegram API version, currently Layer 91 on top of `MTProto 2.0`_. - **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code. - **Comprehensive**: Execute any advanced action an official client is able to do, and even more. @@ -116,4 +116,6 @@ To get started, press the Next button. types/index .. _`Telegram`: https://telegram.org/ -.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto/ \ No newline at end of file +.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto/ +.. _`MTProto API`: https://core.telegram.org/api#telegram-api +.. _`MTProto 2.0`: https://core.telegram.org/mtproto \ No newline at end of file From 9f7884b0ed956634853f82d706e38f97d8279d5d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Feb 2019 16:29:13 +0100 Subject: [PATCH 271/326] Fix PyPI readme render --- README.rst | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 8b39f4b6..baf64845 100644 --- a/README.rst +++ b/README.rst @@ -120,10 +120,10 @@ Copyright & License .. |description| replace:: **Telegram MTProto API Framework for Python** -.. |schema| image:: "https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30" +.. |schema| image:: https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30 :target: compiler/api/source/main_api.tl :alt: Schema Layer -.. |tgcrypto| image:: "https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30" +.. |tgcrypto| image:: https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30 :target: https://github.com/pyrogram/tgcrypto :alt: TgCrypto Version diff --git a/setup.py b/setup.py index 6062c987..cba41b78 100644 --- a/setup.py +++ b/setup.py @@ -39,10 +39,10 @@ def get_version(): def get_readme(): - # PyPI doesn"t like raw html + # PyPI doesn't like raw html with open("README.rst", encoding="utf-8") as f: readme = re.sub(r"\.\. \|.+\| raw:: html(?:\s{4}.+)+\n\n", "", f.read()) - return re.sub(r"\|header\|", "|logo|\n\n|description|\n\n|scheme| |tgcrypto|", readme) + return re.sub(r"\|header\|", "|logo|\n\n|description|\n\n|schema| |tgcrypto|", readme) class Clean(Command): From 5de2b67df5bb660ba4370fd27f0324318532d025 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Feb 2019 16:40:24 +0100 Subject: [PATCH 272/326] Update Pyrogram to v0.11.0 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 152c95af..2bc1fed3 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2019 Dan Tès Date: Mon, 4 Feb 2019 17:02:48 +0100 Subject: [PATCH 273/326] Update reported version in docs --- docs/source/start/Installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/start/Installation.rst b/docs/source/start/Installation.rst index 78adf6ae..c5c6cb31 100644 --- a/docs/source/start/Installation.rst +++ b/docs/source/start/Installation.rst @@ -82,7 +82,7 @@ If no error shows up you are good to go. >>> import pyrogram >>> pyrogram.__version__ - '0.10.3' + '0.11.0' .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto .. _develop: http://github.com/pyrogram/pyrogram From 6109129f73976ad0f5b6ac1a7662bf966a387e16 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Feb 2019 18:23:40 +0100 Subject: [PATCH 274/326] Reword some filter docstrings to better explain their usages --- pyrogram/client/filters/filters.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 57a21045..f5bcd5b5 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -62,16 +62,16 @@ class Filters: create = create me = create("Me", lambda _, m: bool(m.from_user and m.from_user.is_self)) - """Filter messages coming from you yourself""" + """Filter messages generated by you yourself.""" bot = create("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot)) - """Filter messages coming from bots""" + """Filter messages coming from bots.""" incoming = create("Incoming", lambda _, m: not m.outgoing) - """Filter incoming messages.""" + """Filter incoming messages. Messages sent to your own chat (Saved Messages) are also recognised as incoming.""" outgoing = create("Outgoing", lambda _, m: m.outgoing) - """Filter outgoing messages.""" + """Filter outgoing messages. Messages sent to your own chat (Saved Messages) are not recognized as outgoing.""" text = create("Text", lambda _, m: bool(m.text)) """Filter text messages.""" From a2263ad8ce2b9b47ad7933d59d247ad6a2d5607d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 5 Feb 2019 11:50:32 +0100 Subject: [PATCH 275/326] Make send_media_group return the new Messages object --- .../methods/messages/send_media_group.py | 110 ++++++++++++------ 1 file changed, 76 insertions(+), 34 deletions(-) diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index e1731e0b..6eb0f415 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -17,20 +17,23 @@ # along with Pyrogram. If not, see . import binascii +import logging import mimetypes import os import struct +import time from typing import Union, List import pyrogram from pyrogram.api import functions, types -from pyrogram.api.errors import FileIdInvalid +from pyrogram.api.errors import FileIdInvalid, FloodWait from pyrogram.client.ext import BaseClient, utils +log = logging.getLogger(__name__) + class SendMediaGroup(BaseClient): # TODO: Add progress parameter - # TODO: Return new Message object # TODO: Figure out how to send albums using URLs def send_media_group(self, chat_id: Union[int, str], @@ -38,7 +41,6 @@ class SendMediaGroup(BaseClient): disable_notification: bool = None, reply_to_message_id: int = None): """Use this method to send a group of photos or videos as an album. - On success, an Update containing the sent Messages is returned. Args: chat_id (``int`` | ``str``): @@ -57,6 +59,13 @@ class SendMediaGroup(BaseClient): reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. + + Returns: + On success, a :obj:`Messages ` object is returned containing all the + single messages sent. + + Raises: + :class:`Error ` in case of a Telegram RPC error. """ multi_media = [] @@ -65,14 +74,21 @@ class SendMediaGroup(BaseClient): if isinstance(i, pyrogram.InputMediaPhoto): if os.path.exists(i.media): - media = self.send( - functions.messages.UploadMedia( - peer=self.resolve_peer(chat_id), - media=types.InputMediaUploadedPhoto( - file=self.save_file(i.media) + while True: + try: + media = self.send( + functions.messages.UploadMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaUploadedPhoto( + file=self.save_file(i.media) + ) + ) ) - ) - ) + except FloodWait as e: + log.warning("Sleeping for {}s".format(e.x)) + time.sleep(e.x) + else: + break media = types.InputMediaPhoto( id=types.InputPhoto( @@ -106,25 +122,32 @@ class SendMediaGroup(BaseClient): ) elif isinstance(i, pyrogram.InputMediaVideo): if os.path.exists(i.media): - media = self.send( - functions.messages.UploadMedia( - peer=self.resolve_peer(chat_id), - media=types.InputMediaUploadedDocument( - file=self.save_file(i.media), - thumb=None if i.thumb is None else self.save_file(i.thumb), - mime_type=mimetypes.types_map[".mp4"], - attributes=[ - types.DocumentAttributeVideo( - supports_streaming=i.supports_streaming or None, - duration=i.duration, - w=i.width, - h=i.height - ), - types.DocumentAttributeFilename(os.path.basename(i.media)) - ] + while True: + try: + media = self.send( + functions.messages.UploadMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaUploadedDocument( + file=self.save_file(i.media), + thumb=None if i.thumb is None else self.save_file(i.thumb), + mime_type=mimetypes.types_map[".mp4"], + attributes=[ + types.DocumentAttributeVideo( + supports_streaming=i.supports_streaming or None, + duration=i.duration, + w=i.width, + h=i.height + ), + types.DocumentAttributeFilename(os.path.basename(i.media)) + ] + ) + ) ) - ) - ) + except FloodWait as e: + log.warning("Sleeping for {}s".format(e.x)) + time.sleep(e.x) + else: + break media = types.InputMediaDocument( id=types.InputDocument( @@ -165,11 +188,30 @@ class SendMediaGroup(BaseClient): ) ) - return self.send( - functions.messages.SendMultiMedia( - peer=self.resolve_peer(chat_id), - multi_media=multi_media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id + while True: + try: + r = self.send( + functions.messages.SendMultiMedia( + peer=self.resolve_peer(chat_id), + multi_media=multi_media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id + ) + ) + except FloodWait as e: + log.warning("Sleeping for {}s".format(e.x)) + time.sleep(e.x) + else: + break + + return pyrogram.Messages._parse( + self, + types.messages.Messages( + messages=[m.message for m in filter( + lambda u: isinstance(u, (types.UpdateNewMessage, types.UpdateNewChannelMessage)), + r.updates + )], + users=r.users, + chats=r.chats ) ) From 712b390f771023c1dee0185ce5e3eca4241c2458 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 5 Feb 2019 17:10:00 +0100 Subject: [PATCH 276/326] Add a retry mechanism when uploading chunks --- pyrogram/client/client.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 1bbc2267..f62c046c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1439,21 +1439,25 @@ class Client(Methods, BaseClient): md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) break - if is_big: - rpc = functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk - ) - else: - rpc = functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk - ) + for _ in range(3): + if is_big: + rpc = functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk + ) + else: + rpc = functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk + ) - assert session.send(rpc), "Couldn't upload file" + if session.send(rpc): + break + else: + raise AssertionError("Telegram didn't accept chunk #{} of {}".format(file_part, path)) if is_missing_part: return From 23a40a45caa3467c05388e130c6a363a578fbffe Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 6 Feb 2019 11:28:57 +0100 Subject: [PATCH 277/326] Make get_contacts return a list of User objects --- pyrogram/client/methods/contacts/get_contacts.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py index 7e90476c..29b7e176 100644 --- a/pyrogram/client/methods/contacts/get_contacts.py +++ b/pyrogram/client/methods/contacts/get_contacts.py @@ -19,7 +19,8 @@ import logging import time -from pyrogram.api import functions, types +import pyrogram +from pyrogram.api import functions from pyrogram.api.errors import FloodWait from ...ext import BaseClient @@ -28,12 +29,10 @@ log = logging.getLogger(__name__) class GetContacts(BaseClient): def get_contacts(self): - """Use this method to get contacts from your Telegram address book - - Requires no parameters. + """Use this method to get contacts from your Telegram address book. Returns: - On success, the user's contacts are returned + On success, a list of :obj:`User` objects is returned. Raises: :class:`Error ` in case of a Telegram RPC error. @@ -44,9 +43,6 @@ class GetContacts(BaseClient): except FloodWait as e: log.warning("get_contacts flood: waiting {} seconds".format(e.x)) time.sleep(e.x) - continue else: - if isinstance(contacts, types.contacts.Contacts): - log.info("Total contacts: {}".format(len(self.peers_by_phone))) - - return contacts + log.info("Total contacts: {}".format(len(self.peers_by_phone))) + return [pyrogram.User._parse(self, user) for user in contacts.users] From f910dbed1bd1cc85b5ac11de992a1404b578f61b Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Wed, 6 Feb 2019 21:34:31 +0300 Subject: [PATCH 278/326] Define call errors --- compiler/error/source/400_BAD_REQUEST.tsv | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 9250c566..b7a629be 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -87,4 +87,6 @@ MESSAGE_POLL_CLOSED You can't interact with a closed poll MEDIA_INVALID The media is invalid BOT_SCORE_NOT_MODIFIED The bot score was not modified USER_BOT_REQUIRED The method can be used by bots only -IMAGE_PROCESS_FAILED The server failed to process your image \ No newline at end of file +IMAGE_PROCESS_FAILED The server failed to process your image +CALL_ALREADY_ACCEPTED The call is already accepted +CALL_ALREADY_DECLINED The call is already declined From 0216df7fc3a78f3df33f19923447d8b0871612c7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 6 Feb 2019 19:44:23 +0100 Subject: [PATCH 279/326] Update develop version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 2bc1fed3..2306c4a0 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2019 Dan Tès Date: Thu, 7 Feb 2019 13:20:44 +0100 Subject: [PATCH 280/326] Vendor an updated (and working) typing module to fix <3.5.3 errors Monkey patch from https://github.com/python/typing --- pyrogram/__init__.py | 5 + pyrogram/vendor/__init__.py | 19 + pyrogram/vendor/typing/__init__.py | 17 + pyrogram/vendor/typing/typing.py | 2413 ++++++++++++++++++++++++++++ 4 files changed, 2454 insertions(+) create mode 100644 pyrogram/vendor/__init__.py create mode 100644 pyrogram/vendor/typing/__init__.py create mode 100644 pyrogram/vendor/typing/typing.py diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 2bc1fed3..2db6ac57 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -18,6 +18,11 @@ import sys +from .vendor import typing + +# Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one. +sys.modules["typing"] = typing + __copyright__ = "Copyright (C) 2017-2019 Dan Tès ".replace( "\xe8", "e" if sys.getfilesystemencoding() != "utf-8" else "\xe8" diff --git a/pyrogram/vendor/__init__.py b/pyrogram/vendor/__init__.py new file mode 100644 index 00000000..208b84f9 --- /dev/null +++ b/pyrogram/vendor/__init__.py @@ -0,0 +1,19 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from .typing import typing diff --git a/pyrogram/vendor/typing/__init__.py b/pyrogram/vendor/typing/__init__.py new file mode 100644 index 00000000..f3769dd4 --- /dev/null +++ b/pyrogram/vendor/typing/__init__.py @@ -0,0 +1,17 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . diff --git a/pyrogram/vendor/typing/typing.py b/pyrogram/vendor/typing/typing.py new file mode 100644 index 00000000..2189cd48 --- /dev/null +++ b/pyrogram/vendor/typing/typing.py @@ -0,0 +1,2413 @@ +import abc +from abc import abstractmethod, abstractproperty +import collections +import contextlib +import functools +import re as stdlib_re # Avoid confusion with the re we export. +import sys +import types +try: + import collections.abc as collections_abc +except ImportError: + import collections as collections_abc # Fallback for PY3.2. +if sys.version_info[:2] >= (3, 6): + import _collections_abc # Needed for private function _check_methods # noqa +try: + from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType +except ImportError: + WrapperDescriptorType = type(object.__init__) + MethodWrapperType = type(object().__str__) + MethodDescriptorType = type(str.join) + + +# Please keep __all__ alphabetized within each category. +__all__ = [ + # Super-special typing primitives. + 'Any', + 'Callable', + 'ClassVar', + 'Generic', + 'Optional', + 'Tuple', + 'Type', + 'TypeVar', + 'Union', + + # ABCs (from collections.abc). + 'AbstractSet', # collections.abc.Set. + 'GenericMeta', # subclass of abc.ABCMeta and a metaclass + # for 'Generic' and ABCs below. + 'ByteString', + 'Container', + 'ContextManager', + 'Hashable', + 'ItemsView', + 'Iterable', + 'Iterator', + 'KeysView', + 'Mapping', + 'MappingView', + 'MutableMapping', + 'MutableSequence', + 'MutableSet', + 'Sequence', + 'Sized', + 'ValuesView', + # The following are added depending on presence + # of their non-generic counterparts in stdlib: + # Awaitable, + # AsyncIterator, + # AsyncIterable, + # Coroutine, + # Collection, + # AsyncGenerator, + # AsyncContextManager + + # Structural checks, a.k.a. protocols. + 'Reversible', + 'SupportsAbs', + 'SupportsBytes', + 'SupportsComplex', + 'SupportsFloat', + 'SupportsInt', + 'SupportsRound', + + # Concrete collection types. + 'Counter', + 'Deque', + 'Dict', + 'DefaultDict', + 'List', + 'Set', + 'FrozenSet', + 'NamedTuple', # Not really a type. + 'Generator', + + # One-off things. + 'AnyStr', + 'cast', + 'get_type_hints', + 'NewType', + 'no_type_check', + 'no_type_check_decorator', + 'NoReturn', + 'overload', + 'Text', + 'TYPE_CHECKING', +] + +# The pseudo-submodules 're' and 'io' are part of the public +# namespace, but excluded from __all__ because they might stomp on +# legitimate imports of those modules. + + +def _qualname(x): + if sys.version_info[:2] >= (3, 3): + return x.__qualname__ + else: + # Fall back to just name. + return x.__name__ + + +def _trim_name(nm): + whitelist = ('_TypeAlias', '_ForwardRef', '_TypingBase', '_FinalTypingBase') + if nm.startswith('_') and nm not in whitelist: + nm = nm[1:] + return nm + + +class TypingMeta(type): + """Metaclass for most types defined in typing module + (not a part of public API). + + This overrides __new__() to require an extra keyword parameter + '_root', which serves as a guard against naive subclassing of the + typing classes. Any legitimate class defined using a metaclass + derived from TypingMeta must pass _root=True. + + This also defines a dummy constructor (all the work for most typing + constructs is done in __new__) and a nicer repr(). + """ + + _is_protocol = False + + def __new__(cls, name, bases, namespace, *, _root=False): + if not _root: + raise TypeError("Cannot subclass %s" % + (', '.join(map(_type_repr, bases)) or '()')) + return super().__new__(cls, name, bases, namespace) + + def __init__(self, *args, **kwds): + pass + + def _eval_type(self, globalns, localns): + """Override this in subclasses to interpret forward references. + + For example, List['C'] is internally stored as + List[_ForwardRef('C')], which should evaluate to List[C], + where C is an object found in globalns or localns (searching + localns first, of course). + """ + return self + + def _get_type_vars(self, tvars): + pass + + def __repr__(self): + qname = _trim_name(_qualname(self)) + return '%s.%s' % (self.__module__, qname) + + +class _TypingBase(metaclass=TypingMeta, _root=True): + """Internal indicator of special typing constructs.""" + + __slots__ = ('__weakref__',) + + def __init__(self, *args, **kwds): + pass + + def __new__(cls, *args, **kwds): + """Constructor. + + This only exists to give a better error message in case + someone tries to subclass a special typing object (not a good idea). + """ + if (len(args) == 3 and + isinstance(args[0], str) and + isinstance(args[1], tuple)): + # Close enough. + raise TypeError("Cannot subclass %r" % cls) + return super().__new__(cls) + + # Things that are not classes also need these. + def _eval_type(self, globalns, localns): + return self + + def _get_type_vars(self, tvars): + pass + + def __repr__(self): + cls = type(self) + qname = _trim_name(_qualname(cls)) + return '%s.%s' % (cls.__module__, qname) + + def __call__(self, *args, **kwds): + raise TypeError("Cannot instantiate %r" % type(self)) + + +class _FinalTypingBase(_TypingBase, _root=True): + """Internal mix-in class to prevent instantiation. + + Prevents instantiation unless _root=True is given in class call. + It is used to create pseudo-singleton instances Any, Union, Optional, etc. + """ + + __slots__ = () + + def __new__(cls, *args, _root=False, **kwds): + self = super().__new__(cls, *args, **kwds) + if _root is True: + return self + raise TypeError("Cannot instantiate %r" % cls) + + def __reduce__(self): + return _trim_name(type(self).__name__) + + +class _ForwardRef(_TypingBase, _root=True): + """Internal wrapper to hold a forward reference.""" + + __slots__ = ('__forward_arg__', '__forward_code__', + '__forward_evaluated__', '__forward_value__') + + def __init__(self, arg): + super().__init__(arg) + if not isinstance(arg, str): + raise TypeError('Forward reference must be a string -- got %r' % (arg,)) + try: + code = compile(arg, '', 'eval') + except SyntaxError: + raise SyntaxError('Forward reference must be an expression -- got %r' % + (arg,)) + self.__forward_arg__ = arg + self.__forward_code__ = code + self.__forward_evaluated__ = False + self.__forward_value__ = None + + def _eval_type(self, globalns, localns): + if not self.__forward_evaluated__ or localns is not globalns: + if globalns is None and localns is None: + globalns = localns = {} + elif globalns is None: + globalns = localns + elif localns is None: + localns = globalns + self.__forward_value__ = _type_check( + eval(self.__forward_code__, globalns, localns), + "Forward references must evaluate to types.") + self.__forward_evaluated__ = True + return self.__forward_value__ + + def __eq__(self, other): + if not isinstance(other, _ForwardRef): + return NotImplemented + return (self.__forward_arg__ == other.__forward_arg__ and + self.__forward_value__ == other.__forward_value__) + + def __hash__(self): + return hash((self.__forward_arg__, self.__forward_value__)) + + def __instancecheck__(self, obj): + raise TypeError("Forward references cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Forward references cannot be used with issubclass().") + + def __repr__(self): + return '_ForwardRef(%r)' % (self.__forward_arg__,) + + +class _TypeAlias(_TypingBase, _root=True): + """Internal helper class for defining generic variants of concrete types. + + Note that this is not a type; let's call it a pseudo-type. It cannot + be used in instance and subclass checks in parameterized form, i.e. + ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning + ``False``. + """ + + __slots__ = ('name', 'type_var', 'impl_type', 'type_checker') + + def __init__(self, name, type_var, impl_type, type_checker): + """Initializer. + + Args: + name: The name, e.g. 'Pattern'. + type_var: The type parameter, e.g. AnyStr, or the + specific type, e.g. str. + impl_type: The implementation type. + type_checker: Function that takes an impl_type instance. + and returns a value that should be a type_var instance. + """ + assert isinstance(name, str), repr(name) + assert isinstance(impl_type, type), repr(impl_type) + assert not isinstance(impl_type, TypingMeta), repr(impl_type) + assert isinstance(type_var, (type, _TypingBase)), repr(type_var) + self.name = name + self.type_var = type_var + self.impl_type = impl_type + self.type_checker = type_checker + + def __repr__(self): + return "%s[%s]" % (self.name, _type_repr(self.type_var)) + + def __getitem__(self, parameter): + if not isinstance(self.type_var, TypeVar): + raise TypeError("%s cannot be further parameterized." % self) + if self.type_var.__constraints__ and isinstance(parameter, type): + if not issubclass(parameter, self.type_var.__constraints__): + raise TypeError("%s is not a valid substitution for %s." % + (parameter, self.type_var)) + if isinstance(parameter, TypeVar) and parameter is not self.type_var: + raise TypeError("%s cannot be re-parameterized." % self) + return self.__class__(self.name, parameter, + self.impl_type, self.type_checker) + + def __eq__(self, other): + if not isinstance(other, _TypeAlias): + return NotImplemented + return self.name == other.name and self.type_var == other.type_var + + def __hash__(self): + return hash((self.name, self.type_var)) + + def __instancecheck__(self, obj): + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with isinstance().") + return isinstance(obj, self.impl_type) + + def __subclasscheck__(self, cls): + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with issubclass().") + return issubclass(cls, self.impl_type) + + +def _get_type_vars(types, tvars): + for t in types: + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): + t._get_type_vars(tvars) + + +def _type_vars(types): + tvars = [] + _get_type_vars(types, tvars) + return tuple(tvars) + + +def _eval_type(t, globalns, localns): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): + return t._eval_type(globalns, localns) + return t + + +def _type_check(arg, msg): + """Check that the argument is a type, and return it (internal helper). + + As a special case, accept None and return type(None) instead. + Also, _TypeAlias instances (e.g. Match, Pattern) are acceptable. + + The msg argument is a human-readable error message, e.g. + + "Union[arg, ...]: arg should be a type." + + We append the repr() of the actual value (truncated to 100 chars). + """ + if arg is None: + return type(None) + if isinstance(arg, str): + arg = _ForwardRef(arg) + if ( + isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or + not isinstance(arg, (type, _TypingBase)) and not callable(arg) + ): + raise TypeError(msg + " Got %.100r." % (arg,)) + # Bare Union etc. are not valid as type arguments + if ( + type(arg).__name__ in ('_Union', '_Optional') and + not getattr(arg, '__origin__', None) or + isinstance(arg, TypingMeta) and arg._gorg in (Generic, _Protocol) + ): + raise TypeError("Plain %s is not valid as type argument" % arg) + return arg + + +def _type_repr(obj): + """Return the repr() of an object, special-casing types (internal helper). + + If obj is a type, we return a shorter version than the default + type.__repr__, based on the module and qualified name, which is + typically enough to uniquely identify a type. For everything + else, we fall back on repr(obj). + """ + if isinstance(obj, type) and not isinstance(obj, TypingMeta): + if obj.__module__ == 'builtins': + return _qualname(obj) + return '%s.%s' % (obj.__module__, _qualname(obj)) + if obj is ...: + return('...') + if isinstance(obj, types.FunctionType): + return obj.__name__ + return repr(obj) + + +class _Any(_FinalTypingBase, _root=True): + """Special type indicating an unconstrained type. + + - Any is compatible with every type. + - Any assumed to have all methods. + - All values assumed to be instances of Any. + + Note that all the above statements are true from the point of view of + static type checkers. At runtime, Any should not be used with instance + or class checks. + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("Any cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Any cannot be used with issubclass().") + + +Any = _Any(_root=True) + + +class _NoReturn(_FinalTypingBase, _root=True): + """Special type indicating functions that never return. + Example:: + + from typing import NoReturn + + def stop() -> NoReturn: + raise Exception('no way') + + This type is invalid in other positions, e.g., ``List[NoReturn]`` + will fail in static type checkers. + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("NoReturn cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("NoReturn cannot be used with issubclass().") + + +NoReturn = _NoReturn(_root=True) + + +class TypeVar(_TypingBase, _root=True): + """Type variable. + + Usage:: + + T = TypeVar('T') # Can be anything + A = TypeVar('A', str, bytes) # Must be str or bytes + + Type variables exist primarily for the benefit of static type + checkers. They serve as the parameters for generic types as well + as for generic function definitions. See class Generic for more + information on generic types. Generic functions work as follows: + + def repeat(x: T, n: int) -> List[T]: + '''Return a list containing n references to x.''' + return [x]*n + + def longest(x: A, y: A) -> A: + '''Return the longest of two strings.''' + return x if len(x) >= len(y) else y + + The latter example's signature is essentially the overloading + of (str, str) -> str and (bytes, bytes) -> bytes. Also note + that if the arguments are instances of some subclass of str, + the return type is still plain str. + + At runtime, isinstance(x, T) and issubclass(C, T) will raise TypeError. + + Type variables defined with covariant=True or contravariant=True + can be used do declare covariant or contravariant generic types. + See PEP 484 for more details. By default generic types are invariant + in all type variables. + + Type variables can be introspected. e.g.: + + T.__name__ == 'T' + T.__constraints__ == () + T.__covariant__ == False + T.__contravariant__ = False + A.__constraints__ == (str, bytes) + """ + + __slots__ = ('__name__', '__bound__', '__constraints__', + '__covariant__', '__contravariant__') + + def __init__(self, name, *constraints, bound=None, + covariant=False, contravariant=False): + super().__init__(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant) + self.__name__ = name + if covariant and contravariant: + raise ValueError("Bivariant types are not supported.") + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + if constraints and bound is not None: + raise TypeError("Constraints cannot be combined with bound=...") + if constraints and len(constraints) == 1: + raise TypeError("A single constraint is not allowed") + msg = "TypeVar(name, constraint, ...): constraints must be types." + self.__constraints__ = tuple(_type_check(t, msg) for t in constraints) + if bound: + self.__bound__ = _type_check(bound, "Bound must be a type.") + else: + self.__bound__ = None + + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) + + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __instancecheck__(self, instance): + raise TypeError("Type variables cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Type variables cannot be used with issubclass().") + + +# Some unconstrained type variables. These are used by the container types. +# (These are not for export.) +T = TypeVar('T') # Any type. +KT = TypeVar('KT') # Key type. +VT = TypeVar('VT') # Value type. +T_co = TypeVar('T_co', covariant=True) # Any type covariant containers. +V_co = TypeVar('V_co', covariant=True) # Any type covariant containers. +VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers. +T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant. + +# A useful type variable with constraints. This represents string types. +# (This one *is* for export!) +AnyStr = TypeVar('AnyStr', bytes, str) + + +def _replace_arg(arg, tvars, args): + """An internal helper function: replace arg if it is a type variable + found in tvars with corresponding substitution from args or + with corresponding substitution sub-tree if arg is a generic type. + """ + + if tvars is None: + tvars = [] + if hasattr(arg, '_subs_tree') and isinstance(arg, (GenericMeta, _TypingBase)): + return arg._subs_tree(tvars, args) + if isinstance(arg, TypeVar): + for i, tvar in enumerate(tvars): + if arg == tvar: + return args[i] + return arg + + +# Special typing constructs Union, Optional, Generic, Callable and Tuple +# use three special attributes for internal bookkeeping of generic types: +# * __parameters__ is a tuple of unique free type parameters of a generic +# type, for example, Dict[T, T].__parameters__ == (T,); +# * __origin__ keeps a reference to a type that was subscripted, +# e.g., Union[T, int].__origin__ == Union; +# * __args__ is a tuple of all arguments used in subscripting, +# e.g., Dict[T, int].__args__ == (T, int). + + +def _subs_tree(cls, tvars=None, args=None): + """An internal helper function: calculate substitution tree + for generic cls after replacing its type parameters with + substitutions in tvars -> args (if any). + Repeat the same following __origin__'s. + + Return a list of arguments with all possible substitutions + performed. Arguments that are generic classes themselves are represented + as tuples (so that no new classes are created by this function). + For example: _subs_tree(List[Tuple[int, T]][str]) == [(Tuple, int, str)] + """ + + if cls.__origin__ is None: + return cls + # Make of chain of origins (i.e. cls -> cls.__origin__) + current = cls.__origin__ + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + # Replace type variables in __args__ if asked ... + tree_args = [] + for arg in cls.__args__: + tree_args.append(_replace_arg(arg, tvars, args)) + # ... then continue replacing down the origin chain. + for ocls in orig_chain: + new_tree_args = [] + for arg in ocls.__args__: + new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args)) + tree_args = new_tree_args + return tree_args + + +def _remove_dups_flatten(parameters): + """An internal helper for Union creation and substitution: flatten Union's + among parameters, then remove duplicates and strict subclasses. + """ + + # Flatten out Union[Union[...], ...]. + params = [] + for p in parameters: + if isinstance(p, _Union) and p.__origin__ is Union: + params.extend(p.__args__) + elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union: + params.extend(p[1:]) + else: + params.append(p) + # Weed out strict duplicates, preserving the first of each occurrence. + all_params = set(params) + if len(all_params) < len(params): + new_params = [] + for t in params: + if t in all_params: + new_params.append(t) + all_params.remove(t) + params = new_params + assert not all_params, all_params + # Weed out subclasses. + # E.g. Union[int, Employee, Manager] == Union[int, Employee]. + # If object is present it will be sole survivor among proper classes. + # Never discard type variables. + # (In particular, Union[str, AnyStr] != AnyStr.) + all_params = set(params) + for t1 in params: + if not isinstance(t1, type): + continue + if any(isinstance(t2, type) and issubclass(t1, t2) + for t2 in all_params - {t1} + if not (isinstance(t2, GenericMeta) and + t2.__origin__ is not None)): + all_params.remove(t1) + return tuple(t for t in params if t in all_params) + + +def _check_generic(cls, parameters): + # Check correct count for parameters of a generic cls (internal helper). + if not cls.__parameters__: + raise TypeError("%s is not a generic class" % repr(cls)) + alen = len(parameters) + elen = len(cls.__parameters__) + if alen != elen: + raise TypeError("Too %s parameters for %s; actual %s, expected %s" % + ("many" if alen > elen else "few", repr(cls), alen, elen)) + + +_cleanups = [] + + +def _tp_cache(func): + """Internal wrapper caching __getitem__ of generic types with a fallback to + original function for non-hashable arguments. + """ + + cached = functools.lru_cache()(func) + _cleanups.append(cached.cache_clear) + + @functools.wraps(func) + def inner(*args, **kwds): + try: + return cached(*args, **kwds) + except TypeError: + pass # All real errors (not unhashable args) are raised below. + return func(*args, **kwds) + return inner + + +class _Union(_FinalTypingBase, _root=True): + """Union type; Union[X, Y] means either X or Y. + + To define a union, use e.g. Union[int, str]. Details: + + - The arguments must be types and there must be at least one. + + - None as an argument is a special case and is replaced by + type(None). + + - Unions of unions are flattened, e.g.:: + + Union[Union[int, str], float] == Union[int, str, float] + + - Unions of a single argument vanish, e.g.:: + + Union[int] == int # The constructor actually returns int + + - Redundant arguments are skipped, e.g.:: + + Union[int, str, int] == Union[int, str] + + - When comparing unions, the argument order is ignored, e.g.:: + + Union[int, str] == Union[str, int] + + - When two arguments have a subclass relationship, the least + derived argument is kept, e.g.:: + + class Employee: pass + class Manager(Employee): pass + Union[int, Employee, Manager] == Union[int, Employee] + Union[Manager, int, Employee] == Union[int, Employee] + Union[Employee, Manager] == Employee + + - Similar for object:: + + Union[int, object] == object + + - You cannot subclass or instantiate a union. + + - You can use Optional[X] as a shorthand for Union[X, None]. + """ + + __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__') + + def __new__(cls, parameters=None, origin=None, *args, _root=False): + self = super().__new__(cls, parameters, origin, *args, _root=_root) + if origin is None: + self.__parameters__ = None + self.__args__ = None + self.__origin__ = None + self.__tree_hash__ = hash(frozenset(('Union',))) + return self + if not isinstance(parameters, tuple): + raise TypeError("Expected parameters=") + if origin is Union: + parameters = _remove_dups_flatten(parameters) + # It's not a union if there's only one type left. + if len(parameters) == 1: + return parameters[0] + self.__parameters__ = _type_vars(parameters) + self.__args__ = parameters + self.__origin__ = origin + # Pre-calculate the __hash__ on instantiation. + # This improves speed for complex substitutions. + subs_tree = self._subs_tree() + if isinstance(subs_tree, tuple): + self.__tree_hash__ = hash(frozenset(subs_tree)) + else: + self.__tree_hash__ = hash(subs_tree) + return self + + def _eval_type(self, globalns, localns): + if self.__args__ is None: + return self + ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__) + ev_origin = _eval_type(self.__origin__, globalns, localns) + if ev_args == self.__args__ and ev_origin == self.__origin__: + # Everything is already evaluated. + return self + return self.__class__(ev_args, ev_origin, _root=True) + + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + _get_type_vars(self.__parameters__, tvars) + + def __repr__(self): + if self.__origin__ is None: + return super().__repr__() + tree = self._subs_tree() + if not isinstance(tree, tuple): + return repr(tree) + return tree[0]._tree_repr(tree) + + def _tree_repr(self, tree): + arg_list = [] + for arg in tree[1:]: + if not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + return super().__repr__() + '[%s]' % ', '.join(arg_list) + + @_tp_cache + def __getitem__(self, parameters): + if parameters == (): + raise TypeError("Cannot take a Union of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if self.__origin__ is None: + msg = "Union[arg, ...]: each arg must be a type." + else: + msg = "Parameters to generic types must be types." + parameters = tuple(_type_check(p, msg) for p in parameters) + if self is not Union: + _check_generic(self, parameters) + return self.__class__(parameters, origin=self, _root=True) + + def _subs_tree(self, tvars=None, args=None): + if self is Union: + return Union # Nothing to substitute + tree_args = _subs_tree(self, tvars, args) + tree_args = _remove_dups_flatten(tree_args) + if len(tree_args) == 1: + return tree_args[0] # Union of a single type is that type + return (Union,) + tree_args + + def __eq__(self, other): + if isinstance(other, _Union): + return self.__tree_hash__ == other.__tree_hash__ + elif self is not Union: + return self._subs_tree() == other + else: + return self is other + + def __hash__(self): + return self.__tree_hash__ + + def __instancecheck__(self, obj): + raise TypeError("Unions cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Unions cannot be used with issubclass().") + + +Union = _Union(_root=True) + + +class _Optional(_FinalTypingBase, _root=True): + """Optional type. + + Optional[X] is equivalent to Union[X, None]. + """ + + __slots__ = () + + @_tp_cache + def __getitem__(self, arg): + arg = _type_check(arg, "Optional[t] requires a single type.") + return Union[arg, type(None)] + + +Optional = _Optional(_root=True) + + +def _next_in_mro(cls): + """Helper for Generic.__new__. + + Returns the class after the last occurrence of Generic or + Generic[...] in cls.__mro__. + """ + next_in_mro = object + # Look for the last occurrence of Generic or Generic[...]. + for i, c in enumerate(cls.__mro__[:-1]): + if isinstance(c, GenericMeta) and c._gorg is Generic: + next_in_mro = cls.__mro__[i + 1] + return next_in_mro + + +def _make_subclasshook(cls): + """Construct a __subclasshook__ callable that incorporates + the associated __extra__ class in subclass checks performed + against cls. + """ + if isinstance(cls.__extra__, abc.ABCMeta): + # The logic mirrors that of ABCMeta.__subclasscheck__. + # Registered classes need not be checked here because + # cls and its extra share the same _abc_registry. + def __extrahook__(subclass): + res = cls.__extra__.__subclasshook__(subclass) + if res is not NotImplemented: + return res + if cls.__extra__ in subclass.__mro__: + return True + for scls in cls.__extra__.__subclasses__(): + if isinstance(scls, GenericMeta): + continue + if issubclass(subclass, scls): + return True + return NotImplemented + else: + # For non-ABC extras we'll just call issubclass(). + def __extrahook__(subclass): + if cls.__extra__ and issubclass(subclass, cls.__extra__): + return True + return NotImplemented + return __extrahook__ + + +def _no_slots_copy(dct): + """Internal helper: copy class __dict__ and clean slots class variables. + (They will be re-created if necessary by normal class machinery.) + """ + dict_copy = dict(dct) + if '__slots__' in dict_copy: + for slot in dict_copy['__slots__']: + dict_copy.pop(slot, None) + return dict_copy + + +class GenericMeta(TypingMeta, abc.ABCMeta): + """Metaclass for generic types. + + This is a metaclass for typing.Generic and generic ABCs defined in + typing module. User defined subclasses of GenericMeta can override + __new__ and invoke super().__new__. Note that GenericMeta.__new__ + has strict rules on what is allowed in its bases argument: + * plain Generic is disallowed in bases; + * Generic[...] should appear in bases at most once; + * if Generic[...] is present, then it should list all type variables + that appear in other bases. + In addition, type of all generic bases is erased, e.g., C[int] is + stripped to plain C. + """ + + def __new__(cls, name, bases, namespace, + tvars=None, args=None, origin=None, extra=None, orig_bases=None): + """Create a new generic class. GenericMeta.__new__ accepts + keyword arguments that are used for internal bookkeeping, therefore + an override should pass unused keyword arguments to super(). + """ + if tvars is not None: + # Called from __getitem__() below. + assert origin is not None + assert all(isinstance(t, TypeVar) for t in tvars), tvars + else: + # Called from class statement. + assert tvars is None, tvars + assert args is None, args + assert origin is None, origin + + # Get the full set of tvars from the bases. + tvars = _type_vars(bases) + # Look for Generic[T1, ..., Tn]. + # If found, tvars must be a subset of it. + # If not found, tvars is it. + # Also check for and reject plain Generic, + # and reject multiple Generic[...]. + gvars = None + for base in bases: + if base is Generic: + raise TypeError("Cannot inherit from plain Generic") + if (isinstance(base, GenericMeta) and + base.__origin__ is Generic): + if gvars is not None: + raise TypeError( + "Cannot inherit from Generic[...] multiple types.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + raise TypeError( + "Some type variables (%s) " + "are not listed in Generic[%s]" % + (", ".join(str(t) for t in tvars if t not in gvarset), + ", ".join(str(g) for g in gvars))) + tvars = gvars + + initial_bases = bases + if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: + bases = (extra,) + bases + bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases) + + # remove bare Generic from bases if there are other generic bases + if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): + bases = tuple(b for b in bases if b is not Generic) + namespace.update({'__origin__': origin, '__extra__': extra, + '_gorg': None if not origin else origin._gorg}) + self = super().__new__(cls, name, bases, namespace, _root=True) + super(GenericMeta, self).__setattr__('_gorg', + self if not origin else origin._gorg) + self.__parameters__ = tvars + # Be prepared that GenericMeta will be subclassed by TupleMeta + # and CallableMeta, those two allow ..., (), or [] in __args___. + self.__args__ = tuple(... if a is _TypingEllipsis else + () if a is _TypingEmpty else + a for a in args) if args else None + # Speed hack (https://github.com/python/typing/issues/196). + self.__next_in_mro__ = _next_in_mro(self) + # Preserve base classes on subclassing (__bases__ are type erased now). + if orig_bases is None: + self.__orig_bases__ = initial_bases + + # This allows unparameterized generic collections to be used + # with issubclass() and isinstance() in the same way as their + # collections.abc counterparts (e.g., isinstance([], Iterable)). + if ( + '__subclasshook__' not in namespace and extra or + # allow overriding + getattr(self.__subclasshook__, '__name__', '') == '__extrahook__' + ): + self.__subclasshook__ = _make_subclasshook(self) + if isinstance(extra, abc.ABCMeta): + self._abc_registry = extra._abc_registry + self._abc_cache = extra._abc_cache + elif origin is not None: + self._abc_registry = origin._abc_registry + self._abc_cache = origin._abc_cache + + if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2. + self.__qualname__ = origin.__qualname__ + self.__tree_hash__ = (hash(self._subs_tree()) if origin else + super(GenericMeta, self).__hash__()) + return self + + # _abc_negative_cache and _abc_negative_cache_version + # realised as descriptors, since GenClass[t1, t2, ...] always + # share subclass info with GenClass. + # This is an important memory optimization. + @property + def _abc_negative_cache(self): + if isinstance(self.__extra__, abc.ABCMeta): + return self.__extra__._abc_negative_cache + return self._gorg._abc_generic_negative_cache + + @_abc_negative_cache.setter + def _abc_negative_cache(self, value): + if self.__origin__ is None: + if isinstance(self.__extra__, abc.ABCMeta): + self.__extra__._abc_negative_cache = value + else: + self._abc_generic_negative_cache = value + + @property + def _abc_negative_cache_version(self): + if isinstance(self.__extra__, abc.ABCMeta): + return self.__extra__._abc_negative_cache_version + return self._gorg._abc_generic_negative_cache_version + + @_abc_negative_cache_version.setter + def _abc_negative_cache_version(self, value): + if self.__origin__ is None: + if isinstance(self.__extra__, abc.ABCMeta): + self.__extra__._abc_negative_cache_version = value + else: + self._abc_generic_negative_cache_version = value + + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + _get_type_vars(self.__parameters__, tvars) + + def _eval_type(self, globalns, localns): + ev_origin = (self.__origin__._eval_type(globalns, localns) + if self.__origin__ else None) + ev_args = tuple(_eval_type(a, globalns, localns) for a + in self.__args__) if self.__args__ else None + if ev_origin == self.__origin__ and ev_args == self.__args__: + return self + return self.__class__(self.__name__, + self.__bases__, + _no_slots_copy(self.__dict__), + tvars=_type_vars(ev_args) if ev_args else None, + args=ev_args, + origin=ev_origin, + extra=self.__extra__, + orig_bases=self.__orig_bases__) + + def __repr__(self): + if self.__origin__ is None: + return super().__repr__() + return self._tree_repr(self._subs_tree()) + + def _tree_repr(self, tree): + arg_list = [] + for arg in tree[1:]: + if arg == (): + arg_list.append('()') + elif not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + return super().__repr__() + '[%s]' % ', '.join(arg_list) + + def _subs_tree(self, tvars=None, args=None): + if self.__origin__ is None: + return self + tree_args = _subs_tree(self, tvars, args) + return (self._gorg,) + tuple(tree_args) + + def __eq__(self, other): + if not isinstance(other, GenericMeta): + return NotImplemented + if self.__origin__ is None or other.__origin__ is None: + return self is other + return self.__tree_hash__ == other.__tree_hash__ + + def __hash__(self): + return self.__tree_hash__ + + @_tp_cache + def __getitem__(self, params): + if not isinstance(params, tuple): + params = (params,) + if not params and self._gorg is not Tuple: + raise TypeError( + "Parameter list to %s[...] cannot be empty" % _qualname(self)) + msg = "Parameters to generic types must be types." + params = tuple(_type_check(p, msg) for p in params) + if self is Generic: + # Generic can only be subscripted with unique type variables. + if not all(isinstance(p, TypeVar) for p in params): + raise TypeError( + "Parameters to Generic[...] must all be type variables") + if len(set(params)) != len(params): + raise TypeError( + "Parameters to Generic[...] must all be unique") + tvars = params + args = params + elif self in (Tuple, Callable): + tvars = _type_vars(params) + args = params + elif self is _Protocol: + # _Protocol is internal, don't check anything. + tvars = params + args = params + elif self.__origin__ in (Generic, _Protocol): + # Can't subscript Generic[...] or _Protocol[...]. + raise TypeError("Cannot subscript already-subscripted %s" % + repr(self)) + else: + # Subscripting a regular Generic subclass. + _check_generic(self, params) + tvars = _type_vars(params) + args = params + + prepend = (self,) if self.__origin__ is None else () + return self.__class__(self.__name__, + prepend + self.__bases__, + _no_slots_copy(self.__dict__), + tvars=tvars, + args=args, + origin=self, + extra=self.__extra__, + orig_bases=self.__orig_bases__) + + def __subclasscheck__(self, cls): + if self.__origin__ is not None: + if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") + return False + if self is Generic: + raise TypeError("Class %r cannot be used with class " + "or instance checks" % self) + return super().__subclasscheck__(cls) + + def __instancecheck__(self, instance): + # Since we extend ABC.__subclasscheck__ and + # ABC.__instancecheck__ inlines the cache checking done by the + # latter, we must extend __instancecheck__ too. For simplicity + # we just skip the cache check -- instance checks for generic + # classes are supposed to be rare anyways. + return issubclass(instance.__class__, self) + + def __setattr__(self, attr, value): + # We consider all the subscripted generics as proxies for original class + if ( + attr.startswith('__') and attr.endswith('__') or + attr.startswith('_abc_') or + self._gorg is None # The class is not fully created, see #typing/506 + ): + super(GenericMeta, self).__setattr__(attr, value) + else: + super(GenericMeta, self._gorg).__setattr__(attr, value) + + +# Prevent checks for Generic to crash when defining Generic. +Generic = None + + +def _generic_new(base_cls, cls, *args, **kwds): + # Assure type is erased on instantiation, + # but attempt to store it in __orig_class__ + if cls.__origin__ is None: + if (base_cls.__new__ is object.__new__ and + cls.__init__ is not object.__init__): + return base_cls.__new__(cls) + else: + return base_cls.__new__(cls, *args, **kwds) + else: + origin = cls._gorg + if (base_cls.__new__ is object.__new__ and + cls.__init__ is not object.__init__): + obj = base_cls.__new__(origin) + else: + obj = base_cls.__new__(origin, *args, **kwds) + try: + obj.__orig_class__ = cls + except AttributeError: + pass + obj.__init__(*args, **kwds) + return obj + + +class Generic(metaclass=GenericMeta): + """Abstract base class for generic types. + + A generic type is typically declared by inheriting from + this class parameterized with one or more type variables. + For example, a generic mapping type might be defined as:: + + class Mapping(Generic[KT, VT]): + def __getitem__(self, key: KT) -> VT: + ... + # Etc. + + This class can then be used as follows:: + + def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: + try: + return mapping[key] + except KeyError: + return default + """ + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Generic: + raise TypeError("Type Generic cannot be instantiated; " + "it can be used only as a base class") + return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) + + +class _TypingEmpty: + """Internal placeholder for () or []. Used by TupleMeta and CallableMeta + to allow empty list/tuple in specific places, without allowing them + to sneak in where prohibited. + """ + + +class _TypingEllipsis: + """Internal placeholder for ... (ellipsis).""" + + +class TupleMeta(GenericMeta): + """Metaclass for Tuple (internal).""" + + @_tp_cache + def __getitem__(self, parameters): + if self.__origin__ is not None or self._gorg is not Tuple: + # Normal generic rules apply if this is not the first subscription + # or a subscription of a subclass. + return super().__getitem__(parameters) + if parameters == (): + return super().__getitem__((_TypingEmpty,)) + if not isinstance(parameters, tuple): + parameters = (parameters,) + if len(parameters) == 2 and parameters[1] is ...: + msg = "Tuple[t, ...]: t must be a type." + p = _type_check(parameters[0], msg) + return super().__getitem__((p, _TypingEllipsis)) + msg = "Tuple[t0, t1, ...]: each t must be a type." + parameters = tuple(_type_check(p, msg) for p in parameters) + return super().__getitem__(parameters) + + def __instancecheck__(self, obj): + if self.__args__ is None: + return isinstance(obj, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with isinstance().") + + def __subclasscheck__(self, cls): + if self.__args__ is None: + return issubclass(cls, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with issubclass().") + + +class Tuple(tuple, extra=tuple, metaclass=TupleMeta): + """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. + + Example: Tuple[T1, T2] is a tuple of two elements corresponding + to type variables T1 and T2. Tuple[int, float, str] is a tuple + of an int, a float and a string. + + To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. + """ + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Tuple: + raise TypeError("Type Tuple cannot be instantiated; " + "use tuple() instead") + return _generic_new(tuple, cls, *args, **kwds) + + +class CallableMeta(GenericMeta): + """Metaclass for Callable (internal).""" + + def __repr__(self): + if self.__origin__ is None: + return super().__repr__() + return self._tree_repr(self._subs_tree()) + + def _tree_repr(self, tree): + if self._gorg is not Callable: + return super()._tree_repr(tree) + # For actual Callable (not its subclass) we override + # super()._tree_repr() for nice formatting. + arg_list = [] + for arg in tree[1:]: + if not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + if arg_list[0] == '...': + return repr(tree[0]) + '[..., %s]' % arg_list[1] + return (repr(tree[0]) + + '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1])) + + def __getitem__(self, parameters): + """A thin wrapper around __getitem_inner__ to provide the latter + with hashable arguments to improve speed. + """ + + if self.__origin__ is not None or self._gorg is not Callable: + return super().__getitem__(parameters) + if not isinstance(parameters, tuple) or len(parameters) != 2: + raise TypeError("Callable must be used as " + "Callable[[arg, ...], result].") + args, result = parameters + if args is Ellipsis: + parameters = (Ellipsis, result) + else: + if not isinstance(args, list): + raise TypeError("Callable[args, result]: args must be a list." + " Got %.100r." % (args,)) + parameters = (tuple(args), result) + return self.__getitem_inner__(parameters) + + @_tp_cache + def __getitem_inner__(self, parameters): + args, result = parameters + msg = "Callable[args, result]: result must be a type." + result = _type_check(result, msg) + if args is Ellipsis: + return super().__getitem__((_TypingEllipsis, result)) + msg = "Callable[[arg, ...], result]: each arg must be a type." + args = tuple(_type_check(arg, msg) for arg in args) + parameters = args + (result,) + return super().__getitem__(parameters) + + +class Callable(extra=collections_abc.Callable, metaclass=CallableMeta): + """Callable type; Callable[[int], str] is a function of (int) -> str. + + The subscription syntax must always be used with exactly two + values: the argument list and the return type. The argument list + must be a list of types or ellipsis; the return type must be a single type. + + There is no syntax to indicate optional or keyword arguments, + such function types are rarely used as callback types. + """ + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Callable: + raise TypeError("Type Callable cannot be instantiated; " + "use a non-abstract subclass instead") + return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) + + +class _ClassVar(_FinalTypingBase, _root=True): + """Special type construct to mark class variables. + + An annotation wrapped in ClassVar indicates that a given + attribute is intended to be used as a class variable and + should not be set on instances of that class. Usage:: + + class Starship: + stats: ClassVar[Dict[str, int]] = {} # class variable + damage: int = 10 # instance variable + + ClassVar accepts only types and cannot be further subscribed. + + Note that ClassVar is not a class itself, and should not + be used with isinstance() or issubclass(). + """ + + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(_type_check(item, + '{} accepts only single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = _eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(_type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _ClassVar): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + +ClassVar = _ClassVar(_root=True) + + +def cast(typ, val): + """Cast a value to a type. + + This returns the value unchanged. To the type checker this + signals that the return value has the designated type, but at + runtime we intentionally don't check anything (we want this + to be as fast as possible). + """ + return val + + +def _get_defaults(func): + """Internal helper to extract the default arguments, by name.""" + try: + code = func.__code__ + except AttributeError: + # Some built-in functions don't have __code__, __defaults__, etc. + return {} + pos_count = code.co_argcount + arg_names = code.co_varnames + arg_names = arg_names[:pos_count] + defaults = func.__defaults__ or () + kwdefaults = func.__kwdefaults__ + res = dict(kwdefaults) if kwdefaults else {} + pos_offset = pos_count - len(defaults) + for name, value in zip(arg_names[pos_offset:], defaults): + assert name not in res + res[name] = value + return res + + +_allowed_types = (types.FunctionType, types.BuiltinFunctionType, + types.MethodType, types.ModuleType, + WrapperDescriptorType, MethodWrapperType, MethodDescriptorType) + + +def get_type_hints(obj, globalns=None, localns=None): + """Return type hints for an object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, and if necessary + adds Optional[t] if a default value equal to None is set. + + The argument may be a module, class, method, or function. The annotations + are returned as a dictionary. For classes, annotations include also + inherited members. + + TypeError is raised if the argument is not of a type that can contain + annotations, and an empty dictionary is returned if no annotations are + present. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj (or the respective module's globals for classes), + and these are also used as the locals. If the object does not appear + to have globals, an empty dictionary is used. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + + if getattr(obj, '__no_type_check__', None): + return {} + # Classes require a special treatment. + if isinstance(obj, type): + hints = {} + for base in reversed(obj.__mro__): + if globalns is None: + base_globals = sys.modules[base.__module__].__dict__ + else: + base_globals = globalns + ann = base.__dict__.get('__annotations__', {}) + for name, value in ann.items(): + if value is None: + value = type(None) + if isinstance(value, str): + value = _ForwardRef(value) + value = _eval_type(value, base_globals, localns) + hints[name] = value + return hints + + if globalns is None: + if isinstance(obj, types.ModuleType): + globalns = obj.__dict__ + else: + globalns = getattr(obj, '__globals__', {}) + if localns is None: + localns = globalns + elif localns is None: + localns = globalns + hints = getattr(obj, '__annotations__', None) + if hints is None: + # Return empty annotations for something that _could_ have them. + if isinstance(obj, _allowed_types): + return {} + else: + raise TypeError('{!r} is not a module, class, method, ' + 'or function.'.format(obj)) + defaults = _get_defaults(obj) + hints = dict(hints) + for name, value in hints.items(): + if value is None: + value = type(None) + if isinstance(value, str): + value = _ForwardRef(value) + value = _eval_type(value, globalns, localns) + if name in defaults and defaults[name] is None: + value = Optional[value] + hints[name] = value + return hints + + +def no_type_check(arg): + """Decorator to indicate that annotations are not type hints. + + The argument must be a class or function; if it is a class, it + applies recursively to all methods and classes defined in that class + (but not to methods defined in its superclasses or subclasses). + + This mutates the function(s) or class(es) in place. + """ + if isinstance(arg, type): + arg_attrs = arg.__dict__.copy() + for attr, val in arg.__dict__.items(): + if val in arg.__bases__ + (arg,): + arg_attrs.pop(attr) + for obj in arg_attrs.values(): + if isinstance(obj, types.FunctionType): + obj.__no_type_check__ = True + if isinstance(obj, type): + no_type_check(obj) + try: + arg.__no_type_check__ = True + except TypeError: # built-in classes + pass + return arg + + +def no_type_check_decorator(decorator): + """Decorator to give another decorator the @no_type_check effect. + + This wraps the decorator with something that wraps the decorated + function in @no_type_check. + """ + + @functools.wraps(decorator) + def wrapped_decorator(*args, **kwds): + func = decorator(*args, **kwds) + func = no_type_check(func) + return func + + return wrapped_decorator + + +def _overload_dummy(*args, **kwds): + """Helper for @overload to raise when called.""" + raise NotImplementedError( + "You should not call an overloaded function. " + "A series of @overload-decorated functions " + "outside a stub module should always be followed " + "by an implementation that is not @overload-ed.") + + +def overload(func): + """Decorator for overloaded functions/methods. + + In a stub file, place two or more stub definitions for the same + function in a row, each decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + + In a non-stub file (i.e. a regular .py file), do the same but + follow it with an implementation. The implementation should *not* + be decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + def utf8(value): + # implementation goes here + """ + return _overload_dummy + + +class _ProtocolMeta(GenericMeta): + """Internal metaclass for _Protocol. + + This exists so _Protocol classes can be generic without deriving + from Generic. + """ + + def __instancecheck__(self, obj): + if _Protocol not in self.__bases__: + return super().__instancecheck__(obj) + raise TypeError("Protocols cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + if not self._is_protocol: + # No structural checks since this isn't a protocol. + return NotImplemented + + if self is _Protocol: + # Every class is a subclass of the empty protocol. + return True + + # Find all attributes defined in the protocol. + attrs = self._get_protocol_attrs() + + for attr in attrs: + if not any(attr in d.__dict__ for d in cls.__mro__): + return False + return True + + def _get_protocol_attrs(self): + # Get all Protocol base classes. + protocol_bases = [] + for c in self.__mro__: + if getattr(c, '_is_protocol', False) and c.__name__ != '_Protocol': + protocol_bases.append(c) + + # Get attributes included in protocol. + attrs = set() + for base in protocol_bases: + for attr in base.__dict__.keys(): + # Include attributes not defined in any non-protocol bases. + for c in self.__mro__: + if (c is not base and attr in c.__dict__ and + not getattr(c, '_is_protocol', False)): + break + else: + if (not attr.startswith('_abc_') and + attr != '__abstractmethods__' and + attr != '__annotations__' and + attr != '__weakref__' and + attr != '_is_protocol' and + attr != '_gorg' and + attr != '__dict__' and + attr != '__args__' and + attr != '__slots__' and + attr != '_get_protocol_attrs' and + attr != '__next_in_mro__' and + attr != '__parameters__' and + attr != '__origin__' and + attr != '__orig_bases__' and + attr != '__extra__' and + attr != '__tree_hash__' and + attr != '__module__'): + attrs.add(attr) + + return attrs + + +class _Protocol(metaclass=_ProtocolMeta): + """Internal base class for protocol classes. + + This implements a simple-minded structural issubclass check + (similar but more general than the one-offs in collections.abc + such as Hashable). + """ + + __slots__ = () + + _is_protocol = True + + +# Various ABCs mimicking those in collections.abc. +# A few are simply re-exported for completeness. + +Hashable = collections_abc.Hashable # Not generic. + + +if hasattr(collections_abc, 'Awaitable'): + class Awaitable(Generic[T_co], extra=collections_abc.Awaitable): + __slots__ = () + + __all__.append('Awaitable') + + +if hasattr(collections_abc, 'Coroutine'): + class Coroutine(Awaitable[V_co], Generic[T_co, T_contra, V_co], + extra=collections_abc.Coroutine): + __slots__ = () + + __all__.append('Coroutine') + + +if hasattr(collections_abc, 'AsyncIterable'): + + class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable): + __slots__ = () + + class AsyncIterator(AsyncIterable[T_co], + extra=collections_abc.AsyncIterator): + __slots__ = () + + __all__.append('AsyncIterable') + __all__.append('AsyncIterator') + + +class Iterable(Generic[T_co], extra=collections_abc.Iterable): + __slots__ = () + + +class Iterator(Iterable[T_co], extra=collections_abc.Iterator): + __slots__ = () + + +class SupportsInt(_Protocol): + __slots__ = () + + @abstractmethod + def __int__(self) -> int: + pass + + +class SupportsFloat(_Protocol): + __slots__ = () + + @abstractmethod + def __float__(self) -> float: + pass + + +class SupportsComplex(_Protocol): + __slots__ = () + + @abstractmethod + def __complex__(self) -> complex: + pass + + +class SupportsBytes(_Protocol): + __slots__ = () + + @abstractmethod + def __bytes__(self) -> bytes: + pass + + +class SupportsAbs(_Protocol[T_co]): + __slots__ = () + + @abstractmethod + def __abs__(self) -> T_co: + pass + + +class SupportsRound(_Protocol[T_co]): + __slots__ = () + + @abstractmethod + def __round__(self, ndigits: int = 0) -> T_co: + pass + + +if hasattr(collections_abc, 'Reversible'): + class Reversible(Iterable[T_co], extra=collections_abc.Reversible): + __slots__ = () +else: + class Reversible(_Protocol[T_co]): + __slots__ = () + + @abstractmethod + def __reversed__(self) -> 'Iterator[T_co]': + pass + + +Sized = collections_abc.Sized # Not generic. + + +class Container(Generic[T_co], extra=collections_abc.Container): + __slots__ = () + + +if hasattr(collections_abc, 'Collection'): + class Collection(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Collection): + __slots__ = () + + __all__.append('Collection') + + +# Callable was defined earlier. + +if hasattr(collections_abc, 'Collection'): + class AbstractSet(Collection[T_co], + extra=collections_abc.Set): + __slots__ = () +else: + class AbstractSet(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Set): + __slots__ = () + + +class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet): + __slots__ = () + + +# NOTE: It is only covariant in the value type. +if hasattr(collections_abc, 'Collection'): + class Mapping(Collection[KT], Generic[KT, VT_co], + extra=collections_abc.Mapping): + __slots__ = () +else: + class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co], + extra=collections_abc.Mapping): + __slots__ = () + + +class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping): + __slots__ = () + + +if hasattr(collections_abc, 'Reversible'): + if hasattr(collections_abc, 'Collection'): + class Sequence(Reversible[T_co], Collection[T_co], + extra=collections_abc.Sequence): + __slots__ = () + else: + class Sequence(Sized, Reversible[T_co], Container[T_co], + extra=collections_abc.Sequence): + __slots__ = () +else: + class Sequence(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Sequence): + __slots__ = () + + +class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence): + __slots__ = () + + +class ByteString(Sequence[int], extra=collections_abc.ByteString): + __slots__ = () + + +class List(list, MutableSequence[T], extra=list): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is List: + raise TypeError("Type List cannot be instantiated; " + "use list() instead") + return _generic_new(list, cls, *args, **kwds) + + +class Deque(collections.deque, MutableSequence[T], extra=collections.deque): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Deque: + return collections.deque(*args, **kwds) + return _generic_new(collections.deque, cls, *args, **kwds) + + +class Set(set, MutableSet[T], extra=set): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Set: + raise TypeError("Type Set cannot be instantiated; " + "use set() instead") + return _generic_new(set, cls, *args, **kwds) + + +class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is FrozenSet: + raise TypeError("Type FrozenSet cannot be instantiated; " + "use frozenset() instead") + return _generic_new(frozenset, cls, *args, **kwds) + + +class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView): + __slots__ = () + + +class KeysView(MappingView[KT], AbstractSet[KT], + extra=collections_abc.KeysView): + __slots__ = () + + +class ItemsView(MappingView[Tuple[KT, VT_co]], + AbstractSet[Tuple[KT, VT_co]], + Generic[KT, VT_co], + extra=collections_abc.ItemsView): + __slots__ = () + + +class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView): + __slots__ = () + + +if hasattr(contextlib, 'AbstractContextManager'): + class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager): + __slots__ = () +else: + class ContextManager(Generic[T_co]): + __slots__ = () + + def __enter__(self): + return self + + @abc.abstractmethod + def __exit__(self, exc_type, exc_value, traceback): + return None + + @classmethod + def __subclasshook__(cls, C): + if cls is ContextManager: + # In Python 3.6+, it is possible to set a method to None to + # explicitly indicate that the class does not implement an ABC + # (https://bugs.python.org/issue25958), but we do not support + # that pattern here because this fallback class is only used + # in Python 3.5 and earlier. + if (any("__enter__" in B.__dict__ for B in C.__mro__) and + any("__exit__" in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + + +if hasattr(contextlib, 'AbstractAsyncContextManager'): + class AsyncContextManager(Generic[T_co], + extra=contextlib.AbstractAsyncContextManager): + __slots__ = () + + __all__.append('AsyncContextManager') +elif sys.version_info[:2] >= (3, 5): + exec(""" +class AsyncContextManager(Generic[T_co]): + __slots__ = () + + async def __aenter__(self): + return self + + @abc.abstractmethod + async def __aexit__(self, exc_type, exc_value, traceback): + return None + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncContextManager: + if sys.version_info[:2] >= (3, 6): + return _collections_abc._check_methods(C, "__aenter__", "__aexit__") + if (any("__aenter__" in B.__dict__ for B in C.__mro__) and + any("__aexit__" in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + +__all__.append('AsyncContextManager') +""") + + +class Dict(dict, MutableMapping[KT, VT], extra=dict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Dict: + raise TypeError("Type Dict cannot be instantiated; " + "use dict() instead") + return _generic_new(dict, cls, *args, **kwds) + + +class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], + extra=collections.defaultdict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is DefaultDict: + return collections.defaultdict(*args, **kwds) + return _generic_new(collections.defaultdict, cls, *args, **kwds) + + +class Counter(collections.Counter, Dict[T, int], extra=collections.Counter): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Counter: + return collections.Counter(*args, **kwds) + return _generic_new(collections.Counter, cls, *args, **kwds) + + +if hasattr(collections, 'ChainMap'): + # ChainMap only exists in 3.3+ + __all__.append('ChainMap') + + class ChainMap(collections.ChainMap, MutableMapping[KT, VT], + extra=collections.ChainMap): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is ChainMap: + return collections.ChainMap(*args, **kwds) + return _generic_new(collections.ChainMap, cls, *args, **kwds) + + +# Determine what base class to use for Generator. +if hasattr(collections_abc, 'Generator'): + # Sufficiently recent versions of 3.5 have a Generator ABC. + _G_base = collections_abc.Generator +else: + # Fall back on the exact type. + _G_base = types.GeneratorType + + +class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co], + extra=_G_base): + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Generator: + raise TypeError("Type Generator cannot be instantiated; " + "create a subclass instead") + return _generic_new(_G_base, cls, *args, **kwds) + + +if hasattr(collections_abc, 'AsyncGenerator'): + class AsyncGenerator(AsyncIterator[T_co], Generic[T_co, T_contra], + extra=collections_abc.AsyncGenerator): + __slots__ = () + + __all__.append('AsyncGenerator') + + +# Internal type variable used for Type[]. +CT_co = TypeVar('CT_co', covariant=True, bound=type) + + +# This is not a real generic class. Don't use outside annotations. +class Type(Generic[CT_co], extra=type): + """A special construct usable to annotate class objects. + + For example, suppose we have the following classes:: + + class User: ... # Abstract base for User classes + class BasicUser(User): ... + class ProUser(User): ... + class TeamUser(User): ... + + And a function that takes a class argument that's a subclass of + User and returns an instance of the corresponding class:: + + U = TypeVar('U', bound=User) + def new_user(user_class: Type[U]) -> U: + user = user_class() + # (Here we could write the user object to a database) + return user + + joe = new_user(BasicUser) + + At this point the type checker knows that joe has type BasicUser. + """ + + __slots__ = () + + +def _make_nmtuple(name, types): + msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" + types = [(n, _type_check(t, msg)) for n, t in types] + nm_tpl = collections.namedtuple(name, [n for n, t in types]) + # Prior to PEP 526, only _field_types attribute was assigned. + # Now, both __annotations__ and _field_types are used to maintain compatibility. + nm_tpl.__annotations__ = nm_tpl._field_types = collections.OrderedDict(types) + try: + nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + return nm_tpl + + +_PY36 = sys.version_info[:2] >= (3, 6) + +# attributes prohibited to set in NamedTuple class syntax +_prohibited = ('__new__', '__init__', '__slots__', '__getnewargs__', + '_fields', '_field_defaults', '_field_types', + '_make', '_replace', '_asdict', '_source') + +_special = ('__module__', '__name__', '__qualname__', '__annotations__') + + +class NamedTupleMeta(type): + + def __new__(cls, typename, bases, ns): + if ns.get('_root', False): + return super().__new__(cls, typename, bases, ns) + if not _PY36: + raise TypeError("Class syntax for NamedTuple is only supported" + " in Python 3.6+") + types = ns.get('__annotations__', {}) + nm_tpl = _make_nmtuple(typename, types.items()) + defaults = [] + defaults_dict = {} + for field_name in types: + if field_name in ns: + default_value = ns[field_name] + defaults.append(default_value) + defaults_dict[field_name] = default_value + elif defaults: + raise TypeError("Non-default namedtuple field {field_name} cannot " + "follow default field(s) {default_names}" + .format(field_name=field_name, + default_names=', '.join(defaults_dict.keys()))) + nm_tpl.__new__.__annotations__ = collections.OrderedDict(types) + nm_tpl.__new__.__defaults__ = tuple(defaults) + nm_tpl._field_defaults = defaults_dict + # update from user namespace without overriding special namedtuple attributes + for key in ns: + if key in _prohibited: + raise AttributeError("Cannot overwrite NamedTuple attribute " + key) + elif key not in _special and key not in nm_tpl._fields: + setattr(nm_tpl, key, ns[key]) + return nm_tpl + + +class NamedTuple(metaclass=NamedTupleMeta): + """Typed version of namedtuple. + + Usage in Python versions >= 3.6:: + + class Employee(NamedTuple): + name: str + id: int + + This is equivalent to:: + + Employee = collections.namedtuple('Employee', ['name', 'id']) + + The resulting class has extra __annotations__ and _field_types + attributes, giving an ordered dict mapping field names to types. + __annotations__ should be preferred, while _field_types + is kept to maintain pre PEP 526 compatibility. (The field names + are in the _fields attribute, which is part of the namedtuple + API.) Alternative equivalent keyword syntax is also accepted:: + + Employee = NamedTuple('Employee', name=str, id=int) + + In Python versions <= 3.5 use:: + + Employee = NamedTuple('Employee', [('name', str), ('id', int)]) + """ + _root = True + + def __new__(self, typename, fields=None, **kwargs): + if kwargs and not _PY36: + raise TypeError("Keyword syntax for NamedTuple is only supported" + " in Python 3.6+") + if fields is None: + fields = kwargs.items() + elif kwargs: + raise TypeError("Either list of fields or keywords" + " can be provided to NamedTuple, not both") + return _make_nmtuple(typename, fields) + + +def NewType(name, tp): + """NewType creates simple unique types with almost zero + runtime overhead. NewType(name, tp) is considered a subtype of tp + by static type checkers. At runtime, NewType(name, tp) returns + a dummy function that simply returns its argument. Usage:: + + UserId = NewType('UserId', int) + + def name_by_id(user_id: UserId) -> str: + ... + + UserId('user') # Fails type check + + name_by_id(42) # Fails type check + name_by_id(UserId(42)) # OK + + num = UserId(5) + 1 # type: int + """ + + def new_type(x): + return x + + new_type.__name__ = name + new_type.__supertype__ = tp + return new_type + + +# Python-version-specific alias (Python 2: unicode; Python 3: str) +Text = str + + +# Constant that's True when type checking, but False here. +TYPE_CHECKING = False + + +class IO(Generic[AnyStr]): + """Generic base class for TextIO and BinaryIO. + + This is an abstract, generic version of the return of open(). + + NOTE: This does not distinguish between the different possible + classes (text vs. binary, read vs. write vs. read/write, + append-only, unbuffered). The TextIO and BinaryIO subclasses + below capture the distinctions between text vs. binary, which is + pervasive in the interface; however we currently do not offer a + way to track the other distinctions in the type system. + """ + + __slots__ = () + + @abstractproperty + def mode(self) -> str: + pass + + @abstractproperty + def name(self) -> str: + pass + + @abstractmethod + def close(self) -> None: + pass + + @abstractproperty + def closed(self) -> bool: + pass + + @abstractmethod + def fileno(self) -> int: + pass + + @abstractmethod + def flush(self) -> None: + pass + + @abstractmethod + def isatty(self) -> bool: + pass + + @abstractmethod + def read(self, n: int = -1) -> AnyStr: + pass + + @abstractmethod + def readable(self) -> bool: + pass + + @abstractmethod + def readline(self, limit: int = -1) -> AnyStr: + pass + + @abstractmethod + def readlines(self, hint: int = -1) -> List[AnyStr]: + pass + + @abstractmethod + def seek(self, offset: int, whence: int = 0) -> int: + pass + + @abstractmethod + def seekable(self) -> bool: + pass + + @abstractmethod + def tell(self) -> int: + pass + + @abstractmethod + def truncate(self, size: int = None) -> int: + pass + + @abstractmethod + def writable(self) -> bool: + pass + + @abstractmethod + def write(self, s: AnyStr) -> int: + pass + + @abstractmethod + def writelines(self, lines: List[AnyStr]) -> None: + pass + + @abstractmethod + def __enter__(self) -> 'IO[AnyStr]': + pass + + @abstractmethod + def __exit__(self, type, value, traceback) -> None: + pass + + +class BinaryIO(IO[bytes]): + """Typed version of the return of open() in binary mode.""" + + __slots__ = () + + @abstractmethod + def write(self, s: Union[bytes, bytearray]) -> int: + pass + + @abstractmethod + def __enter__(self) -> 'BinaryIO': + pass + + +class TextIO(IO[str]): + """Typed version of the return of open() in text mode.""" + + __slots__ = () + + @abstractproperty + def buffer(self) -> BinaryIO: + pass + + @abstractproperty + def encoding(self) -> str: + pass + + @abstractproperty + def errors(self) -> Optional[str]: + pass + + @abstractproperty + def line_buffering(self) -> bool: + pass + + @abstractproperty + def newlines(self) -> Any: + pass + + @abstractmethod + def __enter__(self) -> 'TextIO': + pass + + +class io: + """Wrapper namespace for IO generic classes.""" + + __all__ = ['IO', 'TextIO', 'BinaryIO'] + IO = IO + TextIO = TextIO + BinaryIO = BinaryIO + + +io.__name__ = __name__ + '.io' +sys.modules[io.__name__] = io + + +Pattern = _TypeAlias('Pattern', AnyStr, type(stdlib_re.compile('')), + lambda p: p.pattern) +Match = _TypeAlias('Match', AnyStr, type(stdlib_re.match('', '')), + lambda m: m.re.pattern) + + +class re: + """Wrapper namespace for re type aliases.""" + + __all__ = ['Pattern', 'Match'] + Pattern = Pattern + Match = Match + + +re.__name__ = __name__ + '.re' +sys.modules[re.__name__] = re From 7b12b1a2c370e2d2ac424322f81cc69db557b41f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 7 Feb 2019 13:22:21 +0100 Subject: [PATCH 281/326] Remove typing from requirements --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 227aacf6..955ee53d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ pyaes==1.6.1 -pysocks==1.6.8 -typing==3.6.6; python_version<"3.5" \ No newline at end of file +pysocks==1.6.8 \ No newline at end of file From 81d609dd33301baca58659c01f5afa4f2cba108f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 7 Feb 2019 13:22:51 +0100 Subject: [PATCH 282/326] Delete readthedocs.yml --- readthedocs.yml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 readthedocs.yml diff --git a/readthedocs.yml b/readthedocs.yml deleted file mode 100644 index 9b172987..00000000 --- a/readthedocs.yml +++ /dev/null @@ -1,6 +0,0 @@ -build: - image: latest - -python: - version: 3.6 - setup_py_install: true \ No newline at end of file From a23ca95173b3b207bc915450981b8be42705fb4e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 7 Feb 2019 16:26:12 +0100 Subject: [PATCH 283/326] Revert "Remove typing from requirements" This reverts commit 7b12b1a2 --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 955ee53d..227aacf6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ pyaes==1.6.1 -pysocks==1.6.8 \ No newline at end of file +pysocks==1.6.8 +typing==3.6.6; python_version<"3.5" \ No newline at end of file From c05e0086566e1fba856bc882d1320d7c6aa90f0f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 7 Feb 2019 16:27:17 +0100 Subject: [PATCH 284/326] Monkey patch "typing" only for Python 3.5.0, 3.5.1 and 3.5.2 --- pyrogram/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index d3fef004..e931fd70 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -18,10 +18,11 @@ import sys -from .vendor import typing +if sys.version_info[:3] in [(3, 5, 0), (3, 5, 1), (3, 5, 2)]: + from .vendor import typing -# Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one. -sys.modules["typing"] = typing + # Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one. + sys.modules["typing"] = typing __copyright__ = "Copyright (C) 2017-2019 Dan Tès ".replace( "\xe8", From 69f347ddecd42756cc1acb64c642f670229ed8e7 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 11 Feb 2019 13:07:02 +0300 Subject: [PATCH 285/326] Fixed loss of thumbnails after media editing. (#216) --- pyrogram/client/methods/messages/edit_message_media.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index bc423a01..90ef290b 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -123,6 +123,7 @@ class EditMessageMedia(BaseClient): peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=mimetypes.types_map[".mp4"], + thumb=None if media.thumb is None else self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ types.DocumentAttributeVideo( @@ -179,6 +180,7 @@ class EditMessageMedia(BaseClient): peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=mimetypes.types_map.get("." + media.media.split(".")[-1], "audio/mpeg"), + thumb=None if media.thumb is None else self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ types.DocumentAttributeAudio( @@ -234,6 +236,7 @@ class EditMessageMedia(BaseClient): peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=mimetypes.types_map[".mp4"], + thumb=None if media.thumb is None else self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ types.DocumentAttributeVideo( @@ -291,6 +294,7 @@ class EditMessageMedia(BaseClient): peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=mimetypes.types_map.get("." + media.media.split(".")[-1], "text/plain"), + thumb=None if media.thumb is None else self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ types.DocumentAttributeFilename(os.path.basename(media.media)) From 528c1d31868b131ce3d7364d1e98f3acd6968b65 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 11 Feb 2019 13:20:06 +0100 Subject: [PATCH 286/326] Update README.rst --- README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index baf64845..9f392197 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,9 @@ Pyrogram **Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C. It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the `MTProto API`_. - `A fully-asynchronous variant is also available » `_ + `Pyrogram in fully-asynchronous mode is also available » `_ + + `Working PoC of Telegram voice calls using Pyrogram » `_ Features -------- From 50cff068a071dac4cca352315f1462b229030f9e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 12 Feb 2019 20:29:05 +0100 Subject: [PATCH 287/326] Fix send_* methods sending the incorrect Telegram media message type This makes Pyrogram consistent with the official Bot API behaviour. --- pyrogram/client/methods/messages/edit_message_media.py | 9 ++++----- pyrogram/client/methods/messages/send_animation.py | 3 +-- pyrogram/client/methods/messages/send_audio.py | 3 +-- pyrogram/client/methods/messages/send_document.py | 3 +-- pyrogram/client/methods/messages/send_media_group.py | 3 +-- pyrogram/client/methods/messages/send_video.py | 3 +-- pyrogram/client/methods/messages/send_video_note.py | 3 +-- pyrogram/client/methods/messages/send_voice.py | 3 +-- 8 files changed, 11 insertions(+), 19 deletions(-) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 90ef290b..9ad2f199 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import binascii -import mimetypes import os import struct from typing import Union @@ -122,7 +121,7 @@ class EditMessageMedia(BaseClient): functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map[".mp4"], + mime_type="video/mp4", thumb=None if media.thumb is None else self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ @@ -179,7 +178,7 @@ class EditMessageMedia(BaseClient): functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map.get("." + media.media.split(".")[-1], "audio/mpeg"), + mime_type="audio/mpeg", thumb=None if media.thumb is None else self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ @@ -235,7 +234,7 @@ class EditMessageMedia(BaseClient): functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map[".mp4"], + mime_type="video/mp4", thumb=None if media.thumb is None else self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ @@ -293,7 +292,7 @@ class EditMessageMedia(BaseClient): functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map.get("." + media.media.split(".")[-1], "text/plain"), + mime_type="application/zip", thumb=None if media.thumb is None else self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 5b27c914..8fc31e08 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import binascii -import mimetypes import os import struct from typing import Union @@ -132,7 +131,7 @@ class SendAnimation(BaseClient): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map[".mp4"], + mime_type="video/mp4", file=file, thumb=thumb, attributes=[ diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index baa31a71..a956ba85 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import binascii -import mimetypes import os import struct from typing import Union @@ -134,7 +133,7 @@ class SendAudio(BaseClient): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"), + mime_type="audio/mpeg", file=file, thumb=thumb, attributes=[ diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 3a7e190d..35fe8c0e 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import binascii -import mimetypes import os import struct from typing import Union @@ -120,7 +119,7 @@ class SendDocument(BaseClient): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"), + mime_type="application/zip", file=file, thumb=thumb, attributes=[ diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 6eb0f415..0df273bc 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -18,7 +18,6 @@ import binascii import logging -import mimetypes import os import struct import time @@ -130,7 +129,7 @@ class SendMediaGroup(BaseClient): media=types.InputMediaUploadedDocument( file=self.save_file(i.media), thumb=None if i.thumb is None else self.save_file(i.thumb), - mime_type=mimetypes.types_map[".mp4"], + mime_type="video/mp4", attributes=[ types.DocumentAttributeVideo( supports_streaming=i.supports_streaming or None, diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 8b769054..b69b2185 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import binascii -import mimetypes import os import struct from typing import Union @@ -136,7 +135,7 @@ class SendVideo(BaseClient): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map[".mp4"], + mime_type="video/mp4", file=file, thumb=thumb, attributes=[ diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index bbd17e98..b35dbea6 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import binascii -import mimetypes import os import struct from typing import Union @@ -117,7 +116,7 @@ class SendVideoNote(BaseClient): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map[".mp4"], + mime_type="video/mp4", file=file, thumb=thumb, attributes=[ diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index b69d7d3a..33261db6 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import binascii -import mimetypes import os import struct from typing import Union @@ -116,7 +115,7 @@ class SendVoice(BaseClient): if os.path.exists(voice): file = self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=mimetypes.types_map.get("." + voice.split(".")[-1], "audio/mpeg"), + mime_type="audio/mpeg", file=file, attributes=[ types.DocumentAttributeAudio( From cc753702191dbb6810121c7ee93f987f971a0f58 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 12 Feb 2019 20:59:22 +0100 Subject: [PATCH 288/326] Update sticker file_id --- examples/hello.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/hello.py b/examples/hello.py index 54e86812..19d0ffe7 100644 --- a/examples/hello.py +++ b/examples/hello.py @@ -13,4 +13,4 @@ with app: app.send_location("me", 51.500729, -0.124583) # Send a sticker - app.send_sticker("me", "CAADBAADhw4AAvLQYAHICbZ5SUs_jwI") + app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") From 567e9611df80373c522cb3b1f3d1b4cd63a8720d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Feb 2019 18:22:24 +0100 Subject: [PATCH 289/326] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9f392197..50e848db 100644 --- a/README.rst +++ b/README.rst @@ -22,7 +22,7 @@ It enables you to easily create custom apps using both user and bot identities ( `Pyrogram in fully-asynchronous mode is also available » `_ - `Working PoC of Telegram voice calls using Pyrogram » `_ + `Working PoC of Telegram voice calls using Pyrogram » `_ Features -------- From b0c011c70c6a0520dc9382480c7a05bf8c46e8d7 Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Thu, 28 Feb 2019 00:27:50 +0300 Subject: [PATCH 290/326] Fix plugins import on windows (#223) Fixes #217 --- pyrogram/client/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index f62c046c..ae8ed111 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1139,7 +1139,7 @@ class Client(Methods, BaseClient): if include is None: for path in sorted(Path(root).rglob("*.py")): - module_path = os.path.splitext(str(path))[0].replace("/", ".") + module_path = '.'.join(path.parent.parts + (path.stem,)) module = import_module(module_path) for name in vars(module).keys(): From 5294c21e97e433f4b10b294273387f3120ba92f2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 1 Mar 2019 16:04:21 +0100 Subject: [PATCH 291/326] Automatically coerce any text to string for keyboard buttons --- pyrogram/client/types/bots/inline_keyboard_button.py | 2 +- pyrogram/client/types/bots/keyboard_button.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/bots/inline_keyboard_button.py b/pyrogram/client/types/bots/inline_keyboard_button.py index cd30f373..cc829cb1 100644 --- a/pyrogram/client/types/bots/inline_keyboard_button.py +++ b/pyrogram/client/types/bots/inline_keyboard_button.py @@ -63,7 +63,7 @@ class InlineKeyboardButton(PyrogramType): callback_game: CallbackGame = None): super().__init__(None) - self.text = text + self.text = str(text) self.url = url self.callback_data = callback_data self.switch_inline_query = switch_inline_query diff --git a/pyrogram/client/types/bots/keyboard_button.py b/pyrogram/client/types/bots/keyboard_button.py index e93eccb3..3c7c2bd6 100644 --- a/pyrogram/client/types/bots/keyboard_button.py +++ b/pyrogram/client/types/bots/keyboard_button.py @@ -46,7 +46,7 @@ class KeyboardButton(PyrogramType): request_location: bool = None): super().__init__(None) - self.text = text + self.text = str(text) self.request_contact = request_contact self.request_location = request_location From fda25f65348ec0cdcff59fe69f8c744c1f6b19c5 Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Fri, 1 Mar 2019 20:06:17 +0300 Subject: [PATCH 292/326] Add client.set_username() method (#208) * Add client.set_username() method * Rename set_username() to update_user_username(); allow None as username * Add client.update_chat_username() method * Update update_chat_username.py * Update update_user_username.py Rename update_user_username to update_username Add more details in docstrings Fix style * Rename update_user_username.py to update_username.py * Update __init__.py * Update 400_BAD_REQUEST.tsv --- compiler/error/source/400_BAD_REQUEST.tsv | 1 + pyrogram/client/methods/chats/__init__.py | 4 +- .../methods/chats/update_chat_username.py | 59 +++++++++++++++++++ pyrogram/client/methods/users/__init__.py | 4 +- .../client/methods/users/update_username.py | 51 ++++++++++++++++ 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 pyrogram/client/methods/chats/update_chat_username.py create mode 100644 pyrogram/client/methods/users/update_username.py diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index b7a629be..953b96af 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -88,5 +88,6 @@ MEDIA_INVALID The media is invalid BOT_SCORE_NOT_MODIFIED The bot score was not modified USER_BOT_REQUIRED The method can be used by bots only IMAGE_PROCESS_FAILED The server failed to process your image +USERNAME_NOT_MODIFIED The username was not modified CALL_ALREADY_ACCEPTED The call is already accepted CALL_ALREADY_DECLINED The call is already declined diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 6cc034e4..961038a8 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -37,6 +37,7 @@ from .set_chat_photo import SetChatPhoto from .set_chat_title import SetChatTitle from .unban_chat_member import UnbanChatMember from .unpin_chat_message import UnpinChatMessage +from .update_chat_username import UpdateChatUsername class Chats( @@ -60,6 +61,7 @@ class Chats( GetChatMembersCount, GetChatPreview, IterDialogs, - IterChatMembers + IterChatMembers, + UpdateChatUsername ): pass diff --git a/pyrogram/client/methods/chats/update_chat_username.py b/pyrogram/client/methods/chats/update_chat_username.py new file mode 100644 index 00000000..156ee6f8 --- /dev/null +++ b/pyrogram/client/methods/chats/update_chat_username.py @@ -0,0 +1,59 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class UpdateChatUsername(BaseClient): + def update_chat_username(self, + chat_id: Union[int, str], + username: Union[str, None]) -> bool: + """Use this method to update a channel or a supergroup username. + + To update your own username (for users only, not bots) you can use :meth:`update_username`. + + Args: + chat_id (``int`` | ``str``) + Unique identifier (int) or username (str) of the target chat. + username (``str`` | ``None``): + Username to set. Pass "" (empty string) or None to remove the username. + + Returns: + True on success. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + ``ValueError`` if a chat_id belongs to a user or chat. + """ + + peer = self.resolve_peer(chat_id) + + if isinstance(peer, types.InputPeerChannel): + return bool( + self.send( + functions.channels.UpdateUsername( + channel=peer, + username=username or "" + ) + ) + ) + else: + raise ValueError("The chat_id \"{}\" belongs to a user or chat".format(chat_id)) diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index db5e5869..f8c39650 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -21,6 +21,7 @@ from .get_me import GetMe from .get_user_profile_photos import GetUserProfilePhotos from .get_users import GetUsers from .set_user_profile_photo import SetUserProfilePhoto +from .update_username import UpdateUsername class Users( @@ -28,6 +29,7 @@ class Users( SetUserProfilePhoto, DeleteUserProfilePhotos, GetUsers, - GetMe + GetMe, + UpdateUsername ): pass diff --git a/pyrogram/client/methods/users/update_username.py b/pyrogram/client/methods/users/update_username.py new file mode 100644 index 00000000..9a4feb23 --- /dev/null +++ b/pyrogram/client/methods/users/update_username.py @@ -0,0 +1,51 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +from pyrogram.api import functions +from ...ext import BaseClient + + +class UpdateUsername(BaseClient): + def update_username(self, + username: Union[str, None]) -> bool: + """Use this method to update your own username. + + This method only works for users, not bots. Bot usernames must be changed via Bot Support or by recreating + them from scratch using BotFather. To update a channel or supergroup username you can use + :meth:`update_chat_username`. + + Args: + username (``str`` | ``None``): + Username to set. "" (empty string) or None to remove the username. + + Returns: + True on success. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + + return bool( + self.send( + functions.account.UpdateUsername( + username=username or "" + ) + ) + ) From 87c4d08d9ce6adacfe81148b1c93e782d27abea7 Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Fri, 1 Mar 2019 20:28:46 +0300 Subject: [PATCH 293/326] client.join_chat() now returns pyrogram.Chat instead of MTProto Update (#206) * client.join_chat() now returns pyrogram.Chat instead of MTProto Update * Do not use Chat._parse_mtproto_chat() method * Update chat.py Rename _parse_mtproto_chat to a generic _parse_chat_chat Hint about its current usage (none). --- pyrogram/client/methods/chats/join_chat.py | 14 ++++++++++++-- pyrogram/client/types/user_and_chats/chat.py | 11 +++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py index f5b632fc..b50e50c6 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/client/methods/chats/join_chat.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.api import functions, types from ...ext import BaseClient @@ -30,17 +31,24 @@ class JoinChat(BaseClient): Unique identifier for the target chat in form of a *t.me/joinchat/* link or username of the target channel/supergroup (in the format @username). + Returns: + On success, a :obj:`Chat ` object is returned. + Raises: :class:`Error ` in case of a Telegram RPC error. """ match = self.INVITE_LINK_RE.match(chat_id) if match: - return self.send( + chat = self.send( functions.messages.ImportChatInvite( hash=match.group(1) ) ) + if isinstance(chat.chats[0], types.Chat): + return pyrogram.Chat._parse_chat_chat(self, chat.chats[0]) + elif isinstance(chat.chats[0], types.Channel): + return pyrogram.Chat._parse_channel_chat(self, chat.chats[0]) else: resolved_peer = self.send( functions.contacts.ResolveUsername( @@ -53,8 +61,10 @@ class JoinChat(BaseClient): access_hash=resolved_peer.chats[0].access_hash ) - return self.send( + chat = self.send( functions.channels.JoinChannel( channel=channel ) ) + + return pyrogram.Chat._parse_channel_chat(self, chat.chats[0]) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index ec30b866..1f2c0e6e 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -209,3 +209,14 @@ class Chat(PyrogramType): parsed_chat.invite_link = full_chat.exported_invite.link return parsed_chat + + @staticmethod + def _parse_chat(client, chat): + # A wrapper around each entity parser: User, Chat and Channel. + # Currently unused, might become useful in future. + if isinstance(chat, types.Chat): + return Chat._parse_chat_chat(client, chat) + elif isinstance(chat, types.User): + return Chat._parse_user_chat(client, chat) + else: + return Chat._parse_channel_chat(client, chat) From f84f9ec4dfba430022ebcc6ede0e3d1691c30f6f Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Fri, 1 Mar 2019 20:30:39 +0300 Subject: [PATCH 294/326] Add bot_token argument (#221) * Add bot_token argument (closes #123) * Make session_name docs more readable and detailed * Explicitely set is_bot=False --- pyrogram/client/client.py | 29 +++++++++++++++++++++++------ pyrogram/client/ext/base_client.py | 2 +- pyrogram/client/ext/syncer.py | 1 + 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index ae8ed111..edbe29d1 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -29,6 +29,7 @@ import struct import tempfile import threading import time +import warnings from configparser import ConfigParser from datetime import datetime from hashlib import sha256, md5 @@ -67,10 +68,10 @@ class Client(Methods, BaseClient): Args: session_name (``str``): - Name to uniquely identify a session of either a User or a Bot. - For Users: pass a string of your choice, e.g.: "my_main_account". - For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" - Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number. + Name to uniquely identify a session of either a User or a Bot, e.g.: "my_account". This name will be used + to save a file to disk that stores details needed for reconnecting without asking again for credentials. + Note for bots: You can pass a bot token here, but this usage will be deprecated in next releases. + Use *bot_token* instead. api_id (``int``, *optional*): The *api_id* part of your Telegram API Key, as integer. E.g.: 12345 @@ -144,6 +145,10 @@ class Client(Methods, BaseClient): a new Telegram account in case the phone number you passed is not registered yet. Only applicable for new sessions. + bot_token (``str``, *optional*): + Pass your Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" + Only applicable for new sessions. + last_name (``str``, *optional*): Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can be an empty string: "". Only applicable for new sessions. @@ -192,6 +197,7 @@ class Client(Methods, BaseClient): password: str = None, recovery_code: callable = None, force_sms: bool = False, + bot_token: str = None, first_name: str = None, last_name: str = None, workers: int = BaseClient.WORKERS, @@ -218,6 +224,7 @@ class Client(Methods, BaseClient): self.password = password self.recovery_code = recovery_code self.force_sms = force_sms + self.bot_token = bot_token self.first_name = first_name self.last_name = last_name self.workers = workers @@ -263,8 +270,13 @@ class Client(Methods, BaseClient): raise ConnectionError("Client has already been started") if self.BOT_TOKEN_RE.match(self.session_name): + self.is_bot = True self.bot_token = self.session_name self.session_name = self.session_name.split(":")[0] + warnings.warn('\nYou are using a bot token as session name.\n' + 'It will be deprecated in next update, please use session file name to load ' + 'existing sessions and bot_token argument to create new sessions.', + DeprecationWarning, stacklevel=2) self.load_config() self.load_session() @@ -282,13 +294,15 @@ class Client(Methods, BaseClient): try: if self.user_id is None: if self.bot_token is None: + self.is_bot = False self.authorize_user() else: + self.is_bot = True self.authorize_bot() self.save_session() - if self.bot_token is None: + if not self.is_bot: if self.takeout: self.takeout_id = self.send(functions.account.InitTakeoutSession()).id log.warning("Takeout session {} initiated".format(self.takeout_id)) @@ -1113,6 +1127,8 @@ class Client(Methods, BaseClient): self.auth_key = base64.b64decode("".join(s["auth_key"])) self.user_id = s["user_id"] self.date = s.get("date", 0) + # TODO: replace default with False once token session name will be deprecated + self.is_bot = s.get("is_bot", self.is_bot) for k, v in s.get("peers_by_id", {}).items(): self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) @@ -1246,7 +1262,8 @@ class Client(Methods, BaseClient): test_mode=self.test_mode, auth_key=auth_key, user_id=self.user_id, - date=self.date + date=self.date, + is_bot=self.is_bot, ), f, indent=4 diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index d2c348a8..c90d5e8a 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -68,7 +68,7 @@ class BaseClient: } def __init__(self): - self.bot_token = None + self.is_bot = None self.dc_id = None self.auth_key = None self.user_id = None diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index e169d2a3..71dc3f35 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -94,6 +94,7 @@ class Syncer: auth_key=auth_key, user_id=client.user_id, date=int(time.time()), + is_bot=client.is_bot, peers_by_id={ k: getattr(v, "access_hash", None) for k, v in client.peers_by_id.copy().items() From e42599051c3d1e593cfe5afa2068e899889ae645 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 3 Mar 2019 16:50:25 +0100 Subject: [PATCH 295/326] Use a lower timeout when starting a session to speed up re-connections Sometimes the server drops right after a successful connection and pyrogram keeps waiting up 15 seconds (current WAIT_TIMEOUT) for the first query to time out and start again a new connection. --- pyrogram/session/session.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 21bb1133..5392728f 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -48,6 +48,7 @@ class Result: class Session: INITIAL_SALT = 0x616e67656c696361 NET_WORKERS = 1 + START_TIMEOUT = 1 WAIT_TIMEOUT = 15 MAX_RETRIES = 5 ACKS_THRESHOLD = 8 @@ -130,8 +131,14 @@ class Session: Thread(target=self.recv, name="RecvThread").start() self.current_salt = FutureSalt(0, 0, self.INITIAL_SALT) - self.current_salt = FutureSalt(0, 0, self._send(functions.Ping(0)).new_server_salt) - self.current_salt = self._send(functions.GetFutureSalts(1)).salts[0] + self.current_salt = FutureSalt( + 0, 0, + self._send( + functions.Ping(0), + timeout=self.START_TIMEOUT + ).new_server_salt + ) + self.current_salt = self._send(functions.GetFutureSalts(1), timeout=self.START_TIMEOUT).salts[0] self.next_salt_thread = Thread(target=self.next_salt, name="NextSaltThread") self.next_salt_thread.start() @@ -150,7 +157,8 @@ class Session: lang_pack="", query=functions.help.GetConfig(), ) - ) + ), + timeout=self.START_TIMEOUT ) self.ping_thread = Thread(target=self.ping, name="PingThread") From 2a9c34481f1ab55a2ca967594cf41c861317c55c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 3 Mar 2019 17:55:53 +0100 Subject: [PATCH 296/326] Add PHOTO_EXT_INVALID error --- compiler/error/source/400_BAD_REQUEST.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 953b96af..8537e43d 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -91,3 +91,4 @@ IMAGE_PROCESS_FAILED The server failed to process your image USERNAME_NOT_MODIFIED The username was not modified CALL_ALREADY_ACCEPTED The call is already accepted CALL_ALREADY_DECLINED The call is already declined +PHOTO_EXT_INVALID The photo extension is invalid \ No newline at end of file From 64775f52092d306ece84c19eaf322fd84a98e72c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Mar 2019 12:50:42 +0100 Subject: [PATCH 297/326] Fix "ModuleNotFoundError is not defined" error for Python <3.6 --- pyrogram/client/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index edbe29d1..fbf9f038 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1179,7 +1179,7 @@ class Client(Methods, BaseClient): try: module = import_module(module_path) - except ModuleNotFoundError: + except ImportError: log.warning('[LOAD] Ignoring non-existent module "{}"'.format(module_path)) continue @@ -1215,7 +1215,7 @@ class Client(Methods, BaseClient): try: module = import_module(module_path) - except ModuleNotFoundError: + except ImportError: log.warning('[UNLOAD] Ignoring non-existent module "{}"'.format(module_path)) continue From a4ea3f768c71c02cd40e20f2bd30a1b36f75efa2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Mar 2019 12:56:47 +0100 Subject: [PATCH 298/326] Fix get_chat_member not working when passing "me" in basic groups --- pyrogram/client/methods/chats/get_chat_member.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index faf0c33b..7ed4104b 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -55,7 +55,7 @@ class GetChatMember(BaseClient): ) for member in pyrogram.ChatMembers._parse(self, full_chat).chat_members: - if member.user.id == user_id.user_id: + if member.user.is_self: return member else: raise errors.UserNotParticipant From b3ef53a29a8dc153fa6f5f5576f79526897d428e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 11 Mar 2019 21:26:07 +0100 Subject: [PATCH 299/326] Add send_cached_media method --- pyrogram/client/methods/messages/__init__.py | 4 +- .../methods/messages/send_cached_media.py | 135 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/messages/send_cached_media.py diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index f76d0a22..dde50b7b 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -30,6 +30,7 @@ from .iter_history import IterHistory from .retract_vote import RetractVote from .send_animation import SendAnimation from .send_audio import SendAudio +from .send_cached_media import SendCachedMedia from .send_chat_action import SendChatAction from .send_contact import SendContact from .send_document import SendDocument @@ -74,6 +75,7 @@ class Messages( ClosePoll, RetractVote, DownloadMedia, - IterHistory + IterHistory, + SendCachedMedia ): pass diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py new file mode 100644 index 00000000..843b7197 --- /dev/null +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -0,0 +1,135 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import binascii +import struct +from typing import Union + +import pyrogram +from pyrogram.api import functions, types +from pyrogram.api.errors import FileIdInvalid +from pyrogram.client.ext import BaseClient, utils + + +class SendCachedMedia(BaseClient): + def send_cached_media( + self, + chat_id: Union[int, str], + file_id: str, + caption: str = "", + parse_mode: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> Union["pyrogram.Message", None]: + """Use this method to send any media stored on the Telegram servers using a file_id. + + This convenience method works with any valid file_id only. + It does the same as calling the relevant method for sending media using a file_id, thus saving you from the + hassle of using the correct method for the media the file_id is pointing to. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + file_id (``str``): + Media to send. + Pass a file_id as string to send a media that exists on the Telegram servers. + + caption (``bool``, *optional*): + Media caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + style = self.html if parse_mode.lower() == "html" else self.markdown + + try: + decoded = utils.decode(file_id) + fmt = " 24 else " Date: Mon, 11 Mar 2019 21:26:15 +0100 Subject: [PATCH 300/326] Add EXTERNAL_URL_INVALID error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 8537e43d..0b9804a7 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -91,4 +91,5 @@ IMAGE_PROCESS_FAILED The server failed to process your image USERNAME_NOT_MODIFIED The username was not modified CALL_ALREADY_ACCEPTED The call is already accepted CALL_ALREADY_DECLINED The call is already declined -PHOTO_EXT_INVALID The photo extension is invalid \ No newline at end of file +PHOTO_EXT_INVALID The photo extension is invalid +EXTERNAL_URL_INVALID The external media URL is invalid \ No newline at end of file From 90b959555da61419d817673e3fcc3ee4f8a18fe2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 12 Mar 2019 16:46:54 +0100 Subject: [PATCH 301/326] Fix flood waits generated when iterating over basic chat members --- pyrogram/client/methods/chats/iter_chat_members.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index 5d0fa911..b0aeb51b 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -20,6 +20,7 @@ from string import ascii_lowercase from typing import Union, Generator import pyrogram +from pyrogram.api import types from ...ext import BaseClient @@ -82,6 +83,7 @@ class IterChatMembers(BaseClient): queries = [query] if query else QUERIES total = limit or (1 << 31) - 1 limit = min(200, total) + resolved_chat_id = self.resolve_peer(chat_id) filter = ( Filters.RECENT @@ -107,6 +109,9 @@ class IterChatMembers(BaseClient): if not chat_members: break + if isinstance(resolved_chat_id, types.InputPeerChat): + total = len(chat_members) + offset += len(chat_members) for chat_member in chat_members: From 0aee909c1a8b8c6238b59cdc01a82c4db609bf0e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Mar 2019 11:34:40 +0100 Subject: [PATCH 302/326] Update API schema (Layer 95 patch) --- compiler/api/source/main_api.tl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 8ad94a4e..eb7777c8 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -83,7 +83,7 @@ fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileL fileLocation#91d11eb dc_id:int volume_id:long local_id:int secret:long file_reference:bytes = FileLocation; userEmpty#200250ba id:int = User; -user#2e13f4c3 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 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?string bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; +user#2e13f4c3 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 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?string bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#d559d8c8 photo_id:long photo_small:FileLocation photo_big:FileLocation = UserProfilePhoto; @@ -1288,7 +1288,7 @@ phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; -langpack.getDifference#9d51e814 lang_code:string from_version:int = LangPackDifference; +langpack.getDifference#cd984aa5 lang_pack:string lang_code:string from_version:int = LangPackDifference; langpack.getLanguages#42c6978f lang_pack:string = Vector; langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLanguage; From 8c9e5e6753674f6caa4f829e3cce3e3322e1efd5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Mar 2019 12:14:08 +0100 Subject: [PATCH 303/326] Fix PhotoSize parsing --- pyrogram/client/types/messages_and_media/photo_size.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index e1be74df..116b564c 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -81,6 +81,6 @@ class PhotoSize(PyrogramType): ), width=getattr(photo_size, "w", 0), height=getattr(photo_size, "h", 0), - file_size=getattr(photo_size, "size", len(photo_size.bytes)), + file_size=getattr(photo_size, "size", len(getattr(photo_size, "bytes", b""))), client=client ) From b6038c4f2e8be79bca5d64a445a65247f73d1934 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Mar 2019 13:02:40 +0100 Subject: [PATCH 304/326] Update Chat and ChatPermissions to accommodate default chat permissions --- pyrogram/client/types/user_and_chats/chat.py | 18 ++++++---------- .../types/user_and_chats/chat_permissions.py | 21 +++++++++++++------ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 89498bd2..96b80bc4 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -18,6 +18,7 @@ import pyrogram from pyrogram.api import types +from .chat_permissions import ChatPermissions from .chat_photo import ChatPhoto from ..pyrogram_type import PyrogramType @@ -44,9 +45,6 @@ class Chat(PyrogramType): last_name (``str``, *optional*): Last name of the other party in a private chat. - all_members_are_administrators (``bool``, *optional*): - True if a basic group has "All Members Are Admins" enabled. - photo (:obj:`ChatPhoto `, *optional*): Chat photo. Suitable for downloads only. @@ -86,7 +84,6 @@ class Chat(PyrogramType): username: str = None, first_name: str = None, last_name: str = None, - all_members_are_administrators: bool = None, photo: ChatPhoto = None, description: str = None, invite_link: str = None, @@ -94,7 +91,8 @@ class Chat(PyrogramType): sticker_set_name: str = None, can_set_sticker_set: bool = None, members_count: int = None, - restriction_reason: str = None): + restriction_reason: str = None, + default_permissions: "pyrogram.ChatPermissions" = None): super().__init__(client) self.id = id @@ -103,7 +101,6 @@ class Chat(PyrogramType): self.username = username self.first_name = first_name self.last_name = last_name - self.all_members_are_administrators = all_members_are_administrators self.photo = photo self.description = description self.invite_link = invite_link @@ -112,6 +109,7 @@ class Chat(PyrogramType): self.can_set_sticker_set = can_set_sticker_set self.members_count = members_count self.restriction_reason = restriction_reason + self.default_permissions = default_permissions @staticmethod def _parse_user_chat(client, user: types.User) -> "Chat": @@ -128,17 +126,12 @@ class Chat(PyrogramType): @staticmethod def _parse_chat_chat(client, chat: types.Chat) -> "Chat": - admins_enabled = getattr(chat, "admins_enabled", None) - - if admins_enabled is not None: - admins_enabled = not admins_enabled - return Chat( id=-chat.id, type="group", title=chat.title, - all_members_are_administrators=admins_enabled, photo=ChatPhoto._parse(client, getattr(chat, "photo", None)), + default_permissions=ChatPermissions._parse(chat.default_banned_rights), client=client ) @@ -151,6 +144,7 @@ class Chat(PyrogramType): username=getattr(channel, "username", None), photo=ChatPhoto._parse(client, getattr(channel, "photo", None)), restriction_reason=getattr(channel, "restriction_reason", None), + default_permissions=ChatPermissions._parse(channel.default_banned_rights), client=client ) diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/client/types/user_and_chats/chat_permissions.py index 68b4567e..a931cd7a 100644 --- a/pyrogram/client/types/user_and_chats/chat_permissions.py +++ b/pyrogram/client/types/user_and_chats/chat_permissions.py @@ -138,12 +138,18 @@ class ChatPermissions(PyrogramType): self.can_send_polls = can_send_polls @staticmethod - def _parse(member: Union[types.ChannelParticipantAdmin, types.ChannelParticipantBanned]) -> "ChatPermissions": - if isinstance(member, types.ChannelParticipantAdmin): - permissions = member.admin_rights + def _parse( + entity: Union[ + types.ChannelParticipantAdmin, + types.ChannelParticipantBanned, + types.ChatBannedRights + ] + ) -> "ChatPermissions": + if isinstance(entity, types.ChannelParticipantAdmin): + permissions = entity.admin_rights return ChatPermissions( - can_be_edited=member.can_edit, + can_be_edited=entity.can_edit, can_change_info=permissions.change_info, can_post_messages=permissions.post_messages, can_edit_messages=permissions.edit_messages, @@ -154,8 +160,11 @@ class ChatPermissions(PyrogramType): can_promote_members=permissions.add_admins ) - if isinstance(member, types.ChannelParticipantBanned): - denied_permissions = member.banned_rights # type: types.ChatBannedRights + if isinstance(entity, (types.ChannelParticipantBanned, types.ChatBannedRights)): + if isinstance(entity, types.ChannelParticipantBanned): + denied_permissions = entity.banned_rights # type: types.ChatBannedRights + else: + denied_permissions = entity return ChatPermissions( until_date=0 if denied_permissions.until_date == (1 << 31) - 1 else denied_permissions.until_date, From 5be5446a777fb7d7f6b1657f130d28cf290b1c71 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Mar 2019 13:58:57 +0100 Subject: [PATCH 305/326] Update restrict_chat_member with new permissions --- .../methods/chats/restrict_chat_member.py | 60 +++++++++++++++---- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index f3e43df3..f585a1b0 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -23,14 +23,20 @@ from ...ext import BaseClient class RestrictChatMember(BaseClient): - def restrict_chat_member(self, - chat_id: Union[int, str], - user_id: Union[int, str], - until_date: int = 0, - can_send_messages: bool = False, - can_send_media_messages: bool = False, - can_send_other_messages: bool = False, - can_add_web_page_previews: bool = False) -> bool: + def restrict_chat_member( + self, + chat_id: Union[int, str], + user_id: Union[int, str], + until_date: int = 0, + can_send_messages: bool = False, + can_send_media_messages: bool = False, + can_send_other_messages: bool = False, + can_add_web_page_previews: bool = False, + can_send_polls: bool = False, + can_change_info: bool = False, + can_invite_users: bool = False, + can_pin_messages: bool = False, + ) -> bool: """Use this method to restrict a user in a supergroup. The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. Pass True for all boolean parameters to lift restrictions from a user. @@ -60,8 +66,19 @@ class RestrictChatMember(BaseClient): implies can_send_media_messages. can_add_web_page_previews (``bool``, *optional*): - Pass True, if the user may add web page previews to their messages, implies can_send_media_messages + Pass True, if the user may add web page previews to their messages, implies can_send_media_messages. + can_send_polls (``bool``, *optional*): + Pass True, if the user can send polls, implies can_send_media_messages. + + can_change_info (``bool``, *optional*): + Pass True, if the user can change the chat title, photo and other settings. + + can_invite_users (``bool``, *optional*): + Pass True, if the user can invite new users to the chat. + + can_pin_messages (``bool``, *optional*): + Pass True, if the user can pin messages. Returns: True on success. @@ -75,6 +92,10 @@ class RestrictChatMember(BaseClient): send_games = True send_inline = True embed_links = True + send_polls = True + change_info = True + invite_users = True + pin_messages = True if can_send_messages: send_messages = None @@ -84,6 +105,7 @@ class RestrictChatMember(BaseClient): send_media = None if can_send_other_messages: + send_messages = None send_media = None send_stickers = None send_gifs = None @@ -91,9 +113,23 @@ class RestrictChatMember(BaseClient): send_inline = None if can_add_web_page_previews: + send_messages = None send_media = None embed_links = None + if can_send_polls: + send_messages = None + send_polls = None + + if can_change_info: + change_info = None + + if can_invite_users: + invite_users = None + + if can_pin_messages: + pin_messages = None + self.send( functions.channels.EditBanned( channel=self.resolve_peer(chat_id), @@ -106,7 +142,11 @@ class RestrictChatMember(BaseClient): send_gifs=send_gifs, send_games=send_games, send_inline=send_inline, - embed_links=embed_links + embed_links=embed_links, + send_polls=send_polls, + change_info=change_info, + invite_users=invite_users, + pin_messages=pin_messages ) ) ) From 6d0396441807f0bad4e4a130ea375e00ffa8eb29 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Mar 2019 14:04:35 +0100 Subject: [PATCH 306/326] Add CHAT_NOT_MODIFIED error --- compiler/error/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 0b9804a7..a2bbd345 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -92,4 +92,5 @@ USERNAME_NOT_MODIFIED The username was not modified CALL_ALREADY_ACCEPTED The call is already accepted CALL_ALREADY_DECLINED The call is already declined PHOTO_EXT_INVALID The photo extension is invalid -EXTERNAL_URL_INVALID The external media URL is invalid \ No newline at end of file +EXTERNAL_URL_INVALID The external media URL is invalid +CHAT_NOT_MODIFIED The chat settings were not modified \ No newline at end of file From b01caf10a9c60235671d30d3fc99465803ef306a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Mar 2019 14:47:39 +0100 Subject: [PATCH 307/326] Rename default_permissions to just permissions --- pyrogram/client/types/user_and_chats/chat.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 96b80bc4..db15db58 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import types from .chat_permissions import ChatPermissions @@ -73,6 +75,9 @@ class Chat(PyrogramType): restriction_reason (``str``, *optional*): The reason why this chat might be unavailable to some users. + + permissions (:obj:`ChatPermissions ` *optional*): + Information about the chat default permissions. """ def __init__(self, @@ -92,7 +97,7 @@ class Chat(PyrogramType): can_set_sticker_set: bool = None, members_count: int = None, restriction_reason: str = None, - default_permissions: "pyrogram.ChatPermissions" = None): + permissions: "pyrogram.ChatPermissions" = None): super().__init__(client) self.id = id @@ -109,7 +114,7 @@ class Chat(PyrogramType): self.can_set_sticker_set = can_set_sticker_set self.members_count = members_count self.restriction_reason = restriction_reason - self.default_permissions = default_permissions + self.permissions = permissions @staticmethod def _parse_user_chat(client, user: types.User) -> "Chat": @@ -131,7 +136,7 @@ class Chat(PyrogramType): type="group", title=chat.title, photo=ChatPhoto._parse(client, getattr(chat, "photo", None)), - default_permissions=ChatPermissions._parse(chat.default_banned_rights), + permissions=ChatPermissions._parse(chat.default_banned_rights), client=client ) @@ -144,7 +149,7 @@ class Chat(PyrogramType): username=getattr(channel, "username", None), photo=ChatPhoto._parse(client, getattr(channel, "photo", None)), restriction_reason=getattr(channel, "restriction_reason", None), - default_permissions=ChatPermissions._parse(channel.default_banned_rights), + permissions=ChatPermissions._parse(channel.default_banned_rights), client=client ) @@ -205,9 +210,7 @@ class Chat(PyrogramType): return parsed_chat @staticmethod - def _parse_chat(client, chat): - # A wrapper around each entity parser: User, Chat and Channel. - # Currently unused, might become useful in future. + def _parse_chat(client, chat: Union[types.Chat, types.User, types.Channel]) -> "Chat": if isinstance(chat, types.Chat): return Chat._parse_chat_chat(client, chat) elif isinstance(chat, types.User): From 0c8b5f02fcf34a9baf750be7169f229d87c5dbcf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Mar 2019 14:48:30 +0100 Subject: [PATCH 308/326] Make restrict_chat_member return Chat instead of a simple boolean --- .../client/methods/chats/restrict_chat_member.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index f585a1b0..f4545cb1 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -20,6 +20,7 @@ from typing import Union from pyrogram.api import functions, types from ...ext import BaseClient +from ...types.user_and_chats import Chat class RestrictChatMember(BaseClient): @@ -35,8 +36,8 @@ class RestrictChatMember(BaseClient): can_send_polls: bool = False, can_change_info: bool = False, can_invite_users: bool = False, - can_pin_messages: bool = False, - ) -> bool: + can_pin_messages: bool = False + ) -> Chat: """Use this method to restrict a user in a supergroup. The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. Pass True for all boolean parameters to lift restrictions from a user. @@ -79,8 +80,9 @@ class RestrictChatMember(BaseClient): can_pin_messages (``bool``, *optional*): Pass True, if the user can pin messages. + Returns: - True on success. + On success, a :obj:`Chat ` object is returned. Raises: :class:`Error ` in case of a Telegram RPC error. @@ -130,7 +132,7 @@ class RestrictChatMember(BaseClient): if can_pin_messages: pin_messages = None - self.send( + r = self.send( functions.channels.EditBanned( channel=self.resolve_peer(chat_id), user_id=self.resolve_peer(user_id), @@ -151,4 +153,4 @@ class RestrictChatMember(BaseClient): ) ) - return True + return Chat._parse_chat(self, r.chats[0]) From ad42b4c236d154e8fe0f9ac7fb836e9fea5a07dc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Mar 2019 14:48:55 +0100 Subject: [PATCH 309/326] Add restrict_chat method --- pyrogram/client/methods/chats/__init__.py | 4 +- .../client/methods/chats/restrict_chat.py | 143 ++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/chats/restrict_chat.py diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 961038a8..c708453f 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -31,6 +31,7 @@ from .kick_chat_member import KickChatMember from .leave_chat import LeaveChat from .pin_chat_message import PinChatMessage from .promote_chat_member import PromoteChatMember +from .restrict_chat import RestrictChat from .restrict_chat_member import RestrictChatMember from .set_chat_description import SetChatDescription from .set_chat_photo import SetChatPhoto @@ -62,6 +63,7 @@ class Chats( GetChatPreview, IterDialogs, IterChatMembers, - UpdateChatUsername + UpdateChatUsername, + RestrictChat ): pass diff --git a/pyrogram/client/methods/chats/restrict_chat.py b/pyrogram/client/methods/chats/restrict_chat.py new file mode 100644 index 00000000..502d9566 --- /dev/null +++ b/pyrogram/client/methods/chats/restrict_chat.py @@ -0,0 +1,143 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +from pyrogram.api import functions, types +from ...ext import BaseClient +from ...types.user_and_chats import Chat + + +class RestrictChat(BaseClient): + def restrict_chat( + self, + chat_id: Union[int, str], + can_send_messages: bool = False, + can_send_media_messages: bool = False, + can_send_other_messages: bool = False, + can_add_web_page_previews: bool = False, + can_send_polls: bool = False, + can_change_info: bool = False, + can_invite_users: bool = False, + can_pin_messages: bool = False + ) -> Chat: + """Use this method to restrict a chat. + Pass True for all boolean parameters to lift restrictions from a chat. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + can_send_messages (``bool``, *optional*): + Pass True, if the user can send text messages, contacts, locations and venues. + + can_send_media_messages (``bool``, *optional*): + Pass True, if the user can send audios, documents, photos, videos, video notes and voice notes, + implies can_send_messages. + + can_send_other_messages (``bool``, *optional*): + Pass True, if the user can send animations, games, stickers and use inline bots, + implies can_send_media_messages. + + can_add_web_page_previews (``bool``, *optional*): + Pass True, if the user may add web page previews to their messages, implies can_send_media_messages. + + can_send_polls (``bool``, *optional*): + Pass True, if the user can send polls, implies can_send_media_messages. + + can_change_info (``bool``, *optional*): + Pass True, if the user can change the chat title, photo and other settings. + + can_invite_users (``bool``, *optional*): + Pass True, if the user can invite new users to the chat. + + can_pin_messages (``bool``, *optional*): + Pass True, if the user can pin messages. + + Returns: + On success, a :obj:`Chat ` object is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + send_messages = True + send_media = True + send_stickers = True + send_gifs = True + send_games = True + send_inline = True + embed_links = True + send_polls = True + change_info = True + invite_users = True + pin_messages = True + + if can_send_messages: + send_messages = None + + if can_send_media_messages: + send_messages = None + send_media = None + + if can_send_other_messages: + send_messages = None + send_media = None + send_stickers = None + send_gifs = None + send_games = None + send_inline = None + + if can_add_web_page_previews: + send_messages = None + send_media = None + embed_links = None + + if can_send_polls: + send_messages = None + send_polls = None + + if can_change_info: + change_info = None + + if can_invite_users: + invite_users = None + + if can_pin_messages: + pin_messages = None + + r = self.send( + functions.messages.EditChatDefaultBannedRights( + peer=self.resolve_peer(chat_id), + banned_rights=types.ChatBannedRights( + until_date=0, + send_messages=send_messages, + send_media=send_media, + send_stickers=send_stickers, + send_gifs=send_gifs, + send_games=send_games, + send_inline=send_inline, + embed_links=embed_links, + send_polls=send_polls, + change_info=change_info, + invite_users=invite_users, + pin_messages=pin_messages + ) + ) + ) + + return Chat._parse_chat(self, r.chats[0]) From f7b864f320d66d03f70579986c28c488fe2ef49d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Mar 2019 14:51:23 +0100 Subject: [PATCH 310/326] Update README.rst --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 50e848db..2bb0725b 100644 --- a/README.rst +++ b/README.rst @@ -32,7 +32,7 @@ Features - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. - **Documented**: Pyrogram API methods, types and public interfaces are well documented. - **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted. -- **Updated**, to the latest Telegram API version, currently Layer 91 on top of `MTProto 2.0`_. +- **Updated**, to the latest Telegram API version, currently Layer 95 on top of `MTProto 2.0`_. - **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code. - **Comprehensive**: Execute any advanced action an official client is able to do, and even more. @@ -107,7 +107,7 @@ Copyright & License
- Schema Layer @@ -122,7 +122,7 @@ Copyright & License .. |description| replace:: **Telegram MTProto API Framework for Python** -.. |schema| image:: https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30 +.. |schema| image:: https://img.shields.io/badge/schema-layer%2095-eda738.svg?longCache=true&colorA=262b30 :target: compiler/api/source/main_api.tl :alt: Schema Layer From 3a1c02738da76171da34cc14b19db3c11f7242e3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Mar 2019 14:54:40 +0100 Subject: [PATCH 311/326] Update doc sources --- docs/source/index.rst | 4 ++-- docs/source/start/Installation.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 4d913d23..6a333d6d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -26,7 +26,7 @@ Welcome to Pyrogram
- Schema Layer @@ -67,7 +67,7 @@ Features - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. - **Documented**: Pyrogram API methods, types and public interfaces are well documented. - **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted. -- **Updated**, to the latest Telegram API version, currently Layer 91 on top of `MTProto 2.0`_. +- **Updated**, to the latest Telegram API version, currently Layer 95 on top of `MTProto 2.0`_. - **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code. - **Comprehensive**: Execute any advanced action an official client is able to do, and even more. diff --git a/docs/source/start/Installation.rst b/docs/source/start/Installation.rst index c5c6cb31..6a6ceef8 100644 --- a/docs/source/start/Installation.rst +++ b/docs/source/start/Installation.rst @@ -82,7 +82,7 @@ If no error shows up you are good to go. >>> import pyrogram >>> pyrogram.__version__ - '0.11.0' + '0.12.0' .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto .. _develop: http://github.com/pyrogram/pyrogram From 36635625f344629523bcec29fc1bb87b3e6a04bc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Mar 2019 14:55:02 +0100 Subject: [PATCH 312/326] Update develop version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index bad806d5..9ec7fc9f 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -29,7 +29,7 @@ __copyright__ = "Copyright (C) 2017-2019 Dan Tès Date: Sat, 16 Mar 2019 15:30:55 +0100 Subject: [PATCH 313/326] Add __slots__ to Telegram TL types --- compiler/api/compiler.py | 4 ++- compiler/api/template/mtproto.txt | 3 ++ pyrogram/api/core/object.py | 47 +++++++++++++------------------ 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 9e671e80..c9ea5f34 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -456,7 +456,9 @@ def start(): fields=fields, read_types=read_types, write_types=write_types, - return_arguments=", ".join([i[0] for i in sorted_args if i != ("flags", "#")]) + return_arguments=", ".join([i[0] for i in sorted_args if i != ("flags", "#")]), + slots=", ".join(['"{}"'.format(i[0]) for i in sorted_args if i != ("flags", "#")]), + qualname="{}{}".format("{}.".format(c.namespace) if c.namespace else "", c.name) ) ) diff --git a/compiler/api/template/mtproto.txt b/compiler/api/template/mtproto.txt index 9a65b52d..c63525d6 100644 --- a/compiler/api/template/mtproto.txt +++ b/compiler/api/template/mtproto.txt @@ -9,7 +9,10 @@ class {class_name}(Object): """{docstring_args} """ + __slots__ = [{slots}] + ID = {object_id} + QUALNAME = "{qualname}" def __init__(self{arguments}): {fields} diff --git a/pyrogram/api/core/object.py b/pyrogram/api/core/object.py index 1a3cb388..8f8015fb 100644 --- a/pyrogram/api/core/object.py +++ b/pyrogram/api/core/object.py @@ -19,14 +19,16 @@ from collections import OrderedDict from datetime import datetime from io import BytesIO -from json import JSONEncoder, dumps - -from ..all import objects +from json import dumps class Object: all = {} + __slots__ = [] + + QUALNAME = "Base" + @staticmethod def read(b: BytesIO, *args): return Object.all[int.from_bytes(b.read(4), "little")].read(b, *args) @@ -35,7 +37,7 @@ class Object: pass def __str__(self) -> str: - return dumps(self, cls=Encoder, indent=4) + return dumps(self, indent=4, default=default) def __bool__(self) -> bool: return True @@ -62,29 +64,18 @@ def remove_none(obj): return obj -class Encoder(JSONEncoder): - def default(self, o: Object): - try: - content = o.__dict__ - except AttributeError: - if isinstance(o, datetime): - return o.strftime("%d-%b-%Y %H:%M:%S") - else: - return repr(o) +def default(o: "Object"): + try: + content = {i: getattr(o, i) for i in o.__slots__} - name = o.__class__.__name__ - o = objects.get(getattr(o, "ID", None), None) - - if o is not None: - if o.startswith("pyrogram.client"): - r = remove_none(OrderedDict([("_", "pyrogram:" + name)] + [i for i in content.items()])) - r.pop("_client", None) - - return r - else: - return OrderedDict( - [("_", o.replace("pyrogram.api.types.", "telegram:"))] - + [i for i in content.items()] - ) + return remove_none( + OrderedDict( + [("_", o.QUALNAME)] + + [i for i in content.items()] + ) + ) + except AttributeError: + if isinstance(o, datetime): + return o.strftime("%d-%b-%Y %H:%M:%S") else: - return None + return repr(o) From c611944d458599fbc383b28778cad3bc46a08685 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Mar 2019 15:39:35 +0100 Subject: [PATCH 314/326] Don't ensure ascii when printing objects This will break in case of non-utf8 terminals --- pyrogram/api/core/object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/api/core/object.py b/pyrogram/api/core/object.py index 8f8015fb..d4715d3c 100644 --- a/pyrogram/api/core/object.py +++ b/pyrogram/api/core/object.py @@ -37,7 +37,7 @@ class Object: pass def __str__(self) -> str: - return dumps(self, indent=4, default=default) + return dumps(self, indent=4, default=default, ensure_ascii=False) def __bool__(self) -> bool: return True From ef9ed315896965d5095c768a80dc0c25f15c1950 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Mar 2019 15:45:35 +0100 Subject: [PATCH 315/326] Add __slots__ to PyrogramType and Update types --- pyrogram/client/types/pyrogram_type.py | 20 +++++++++----------- pyrogram/client/types/update.py | 2 ++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py index 3708cdc5..d746e6a7 100644 --- a/pyrogram/client/types/pyrogram_type.py +++ b/pyrogram/client/types/pyrogram_type.py @@ -17,15 +17,17 @@ # along with Pyrogram. If not, see . from collections import OrderedDict -from json import dumps, JSONEncoder +from json import dumps class PyrogramType: + __slots__ = ["_client"] + def __init__(self, client): self._client = client def __str__(self): - return dumps(self, cls=Encoder, indent=4) + return dumps(self, indent=4, default=default, ensure_ascii=False) def __getitem__(self, item): return getattr(self, item) @@ -40,15 +42,9 @@ def remove_none(obj): return obj -class Encoder(JSONEncoder): - def default(self, o: PyrogramType): - try: - content = { - i: getattr(o, i) - for i in filter(lambda x: not x.startswith("_"), o.__dict__) - } - except AttributeError: - return repr(o) +def default(o: PyrogramType): + try: + content = {i: getattr(o, i) for i in o.__slots__} return remove_none( OrderedDict( @@ -56,3 +52,5 @@ class Encoder(JSONEncoder): + [i for i in content.items()] ) ) + except AttributeError: + return repr(o) diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py index 2ec22f5a..48179ac0 100644 --- a/pyrogram/client/types/update.py +++ b/pyrogram/client/types/update.py @@ -26,6 +26,8 @@ class ContinuePropagation(StopIteration): class Update: + __slots__ = [] + def stop_propagation(self): raise StopPropagation From e0f1f6aaeb6ffb795cf54657d020ca683d9386af Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Mar 2019 16:15:27 +0100 Subject: [PATCH 316/326] Add __slots__ to every single Pyrogram types --- pyrogram/client/types/bots/callback_game.py | 2 + pyrogram/client/types/bots/callback_query.py | 24 ++-- pyrogram/client/types/bots/force_reply.py | 8 +- pyrogram/client/types/bots/game_high_score.py | 16 ++- .../client/types/bots/game_high_scores.py | 14 +- .../types/bots/inline_keyboard_button.py | 20 ++- .../types/bots/inline_keyboard_markup.py | 8 +- pyrogram/client/types/bots/keyboard_button.py | 12 +- .../types/bots/reply_keyboard_markup.py | 14 +- .../types/bots/reply_keyboard_remove.py | 8 +- .../client/types/input_media/input_media.py | 12 +- .../input_media/input_media_animation.py | 20 +-- .../types/input_media/input_media_audio.py | 20 +-- .../types/input_media/input_media_document.py | 14 +- .../types/input_media/input_media_photo.py | 12 +- .../types/input_media/input_media_video.py | 22 +-- .../types/input_media/input_phone_contact.py | 12 +- .../types/messages_and_media/animation.py | 28 ++-- .../client/types/messages_and_media/audio.py | 28 ++-- .../types/messages_and_media/contact.py | 20 +-- .../types/messages_and_media/document.py | 22 +-- .../client/types/messages_and_media/game.py | 22 +-- .../types/messages_and_media/location.py | 14 +- .../types/messages_and_media/message.py | 133 ++++++++++-------- .../messages_and_media/message_entity.py | 20 +-- .../types/messages_and_media/messages.py | 14 +- .../client/types/messages_and_media/photo.py | 16 ++- .../types/messages_and_media/photo_size.py | 18 ++- .../client/types/messages_and_media/poll.py | 22 +-- .../types/messages_and_media/poll_option.py | 16 ++- .../types/messages_and_media/sticker.py | 32 +++-- .../messages_and_media/user_profile_photos.py | 14 +- .../client/types/messages_and_media/venue.py | 20 +-- .../client/types/messages_and_media/video.py | 28 ++-- .../types/messages_and_media/video_note.py | 24 ++-- .../client/types/messages_and_media/voice.py | 22 +-- pyrogram/client/types/user_and_chats/chat.py | 44 +++--- .../types/user_and_chats/chat_member.py | 24 ++-- .../types/user_and_chats/chat_members.py | 15 +- .../types/user_and_chats/chat_permissions.py | 7 + .../client/types/user_and_chats/chat_photo.py | 14 +- .../types/user_and_chats/chat_preview.py | 20 +-- .../client/types/user_and_chats/dialog.py | 22 +-- .../client/types/user_and_chats/dialogs.py | 14 +- pyrogram/client/types/user_and_chats/user.py | 41 +++--- .../types/user_and_chats/user_status.py | 26 ++-- 46 files changed, 592 insertions(+), 386 deletions(-) diff --git a/pyrogram/client/types/bots/callback_game.py b/pyrogram/client/types/bots/callback_game.py index be026360..fc2d9884 100644 --- a/pyrogram/client/types/bots/callback_game.py +++ b/pyrogram/client/types/bots/callback_game.py @@ -25,5 +25,7 @@ class CallbackGame(PyrogramType): Use BotFather to set up your game. """ + __slots__ = [] + def __init__(self): super().__init__(None) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index c2558844..a7a9ad53 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -58,16 +58,20 @@ class CallbackQuery(PyrogramType, Update): """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - id: str, - from_user: User, - chat_instance: str, - message: "pyrogram.Message" = None, - inline_message_id: str = None, - data: bytes = None, - game_short_name: str = None): + __slots__ = ["id", "from_user", "chat_instance", "message", "inline_message_id", "data", "game_short_name"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: str, + from_user: User, + chat_instance: str, + message: "pyrogram.Message" = None, + inline_message_id: str = None, + data: bytes = None, + game_short_name: str = None + ): super().__init__(client) self.id = id diff --git a/pyrogram/client/types/bots/force_reply.py b/pyrogram/client/types/bots/force_reply.py index 2e5c2f42..7838ee88 100644 --- a/pyrogram/client/types/bots/force_reply.py +++ b/pyrogram/client/types/bots/force_reply.py @@ -33,8 +33,12 @@ class ForceReply(PyrogramType): 2) if the bot's message is a reply (has reply_to_message_id), sender of the original message. """ - def __init__(self, - selective: bool = None): + __slots__ = ["selective"] + + def __init__( + self, + selective: bool = None + ): super().__init__(None) self.selective = selective diff --git a/pyrogram/client/types/bots/game_high_score.py b/pyrogram/client/types/bots/game_high_score.py index 0541c18c..4fc3c6f8 100644 --- a/pyrogram/client/types/bots/game_high_score.py +++ b/pyrogram/client/types/bots/game_high_score.py @@ -37,12 +37,16 @@ class GameHighScore(PyrogramType): Position in high score table for the game. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - user: User, - score: int, - position: int = None): + __slots__ = ["user", "score", "position"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + user: User, + score: int, + position: int = None + ): super().__init__(client) self.user = user diff --git a/pyrogram/client/types/bots/game_high_scores.py b/pyrogram/client/types/bots/game_high_scores.py index 1717effa..25e4af5d 100644 --- a/pyrogram/client/types/bots/game_high_scores.py +++ b/pyrogram/client/types/bots/game_high_scores.py @@ -35,11 +35,15 @@ class GameHighScores(PyrogramType): Game scores. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - total_count: int, - game_high_scores: List[GameHighScore]): + __slots__ = ["total_count", "game_high_scores"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + total_count: int, + game_high_scores: List[GameHighScore] + ): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/bots/inline_keyboard_button.py b/pyrogram/client/types/bots/inline_keyboard_button.py index cc829cb1..505b7c87 100644 --- a/pyrogram/client/types/bots/inline_keyboard_button.py +++ b/pyrogram/client/types/bots/inline_keyboard_button.py @@ -54,13 +54,19 @@ class InlineKeyboardButton(PyrogramType): # TODO: Add callback_game and pay fields - def __init__(self, - text: str, - callback_data: bytes = None, - url: str = None, - switch_inline_query: str = None, - switch_inline_query_current_chat: str = None, - callback_game: CallbackGame = None): + __slots__ = [ + "text", "url", "callback_data", "switch_inline_query", "switch_inline_query_current_chat", "callback_game" + ] + + def __init__( + self, + text: str, + callback_data: bytes = None, + url: str = None, + switch_inline_query: str = None, + switch_inline_query_current_chat: str = None, + callback_game: CallbackGame = None + ): super().__init__(None) self.text = str(text) diff --git a/pyrogram/client/types/bots/inline_keyboard_markup.py b/pyrogram/client/types/bots/inline_keyboard_markup.py index f18bd605..cb5327b1 100644 --- a/pyrogram/client/types/bots/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots/inline_keyboard_markup.py @@ -31,8 +31,12 @@ class InlineKeyboardMarkup(PyrogramType): List of button rows, each represented by a List of InlineKeyboardButton objects. """ - def __init__(self, - inline_keyboard: List[List[InlineKeyboardButton]]): + __slots__ = ["inline_keyboard"] + + def __init__( + self, + inline_keyboard: List[List[InlineKeyboardButton]] + ): super().__init__(None) self.inline_keyboard = inline_keyboard diff --git a/pyrogram/client/types/bots/keyboard_button.py b/pyrogram/client/types/bots/keyboard_button.py index 3c7c2bd6..dc4ed1e7 100644 --- a/pyrogram/client/types/bots/keyboard_button.py +++ b/pyrogram/client/types/bots/keyboard_button.py @@ -40,10 +40,14 @@ class KeyboardButton(PyrogramType): Available in private chats only. """ - def __init__(self, - text: str, - request_contact: bool = None, - request_location: bool = None): + __slots__ = ["text", "request_contact", "request_location"] + + def __init__( + self, + text: str, + request_contact: bool = None, + request_location: bool = None + ): super().__init__(None) self.text = str(text) diff --git a/pyrogram/client/types/bots/reply_keyboard_markup.py b/pyrogram/client/types/bots/reply_keyboard_markup.py index afae236d..229899d0 100644 --- a/pyrogram/client/types/bots/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots/reply_keyboard_markup.py @@ -49,11 +49,15 @@ class ReplyKeyboardMarkup(PyrogramType): select the new language. Other users in the group don't see the keyboard. """ - def __init__(self, - keyboard: List[List[Union[KeyboardButton, str]]], - resize_keyboard: bool = None, - one_time_keyboard: bool = None, - selective: bool = None): + __slots__ = ["keyboard", "resize_keyboard", "one_time_keyboard", "selective"] + + def __init__( + self, + keyboard: List[List[Union[KeyboardButton, str]]], + resize_keyboard: bool = None, + one_time_keyboard: bool = None, + selective: bool = None + ): super().__init__(None) self.keyboard = keyboard diff --git a/pyrogram/client/types/bots/reply_keyboard_remove.py b/pyrogram/client/types/bots/reply_keyboard_remove.py index 5b67fbb4..2fb5fe19 100644 --- a/pyrogram/client/types/bots/reply_keyboard_remove.py +++ b/pyrogram/client/types/bots/reply_keyboard_remove.py @@ -35,8 +35,12 @@ class ReplyKeyboardRemove(PyrogramType): keyboard for that user, while still showing the keyboard with poll options to users who haven't voted yet. """ - def __init__(self, - selective: bool = None): + __slots__ = ["selective"] + + def __init__( + self, + selective: bool = None + ): super().__init__(None) self.selective = selective diff --git a/pyrogram/client/types/input_media/input_media.py b/pyrogram/client/types/input_media/input_media.py index f55d8aa0..677d4918 100644 --- a/pyrogram/client/types/input_media/input_media.py +++ b/pyrogram/client/types/input_media/input_media.py @@ -18,10 +18,14 @@ class InputMedia: - def __init__(self, - media: str, - caption: str, - parse_mode: str): + __slots__ = ["media", "caption", "parse_mode"] + + def __init__( + self, + media: str, + caption: str, + parse_mode: str + ): self.media = media self.caption = caption self.parse_mode = parse_mode diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index af0c2b2a..20debb19 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -52,14 +52,18 @@ class InputMediaAnimation(InputMedia): Animation duration. """ - def __init__(self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = "", - width: int = 0, - height: int = 0, - duration: int = 0): + __slots__ = ["thumb", "width", "height", "duration"] + + def __init__( + self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + width: int = 0, + height: int = 0, + duration: int = 0 + ): super().__init__(media, caption, parse_mode) self.thumb = thumb diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index a2dc18db..2709a409 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -53,14 +53,18 @@ class InputMediaAudio(InputMedia): Title of the audio """ - def __init__(self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - performer: int = "", - title: str = ""): + __slots__ = ["thumb", "duration", "performer", "title"] + + def __init__( + self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + performer: int = "", + title: str = "" + ): super().__init__(media, caption, parse_mode) self.thumb = thumb diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index 25e17d0f..2c0c628d 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -43,11 +43,15 @@ class InputMediaDocument(InputMedia): Defaults to Markdown. """ - def __init__(self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = ""): + __slots__ = ["thumb"] + + def __init__( + self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "" + ): super().__init__(media, caption, parse_mode) self.thumb = thumb diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py index 5917fbf0..5618b843 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -39,8 +39,12 @@ class InputMediaPhoto(InputMedia): Defaults to Markdown. """ - def __init__(self, - media: str, - caption: str = "", - parse_mode: str = ""): + __slots__ = [] + + def __init__( + self, + media: str, + caption: str = "", + parse_mode: str = "" + ): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index 6fa7936e..b55a9438 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -57,15 +57,19 @@ class InputMediaVideo(InputMedia): Pass True, if the uploaded video is suitable for streaming. """ - def __init__(self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = "", - width: int = 0, - height: int = 0, - duration: int = 0, - supports_streaming: bool = True): + __slots__ = ["thumb", "width", "height", "duration", "supports_streaming"] + + def __init__( + self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + width: int = 0, + height: int = 0, + duration: int = 0, + supports_streaming: bool = True + ): super().__init__(media, caption, parse_mode) self.thumb = thumb diff --git a/pyrogram/client/types/input_media/input_phone_contact.py b/pyrogram/client/types/input_media/input_phone_contact.py index 1a89759a..340b335f 100644 --- a/pyrogram/client/types/input_media/input_phone_contact.py +++ b/pyrogram/client/types/input_media/input_phone_contact.py @@ -35,10 +35,14 @@ class InputPhoneContact: Contact's last name """ - def __init__(self, - phone: str, - first_name: str, - last_name: str = ""): + __slots__ = [] + + def __init__( + self, + phone: str, + first_name: str, + last_name: str = "" + ): pass def __new__(cls, diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index 9f3ba886..978c88c9 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -57,18 +57,22 @@ class Animation(PyrogramType): Date the animation was sent in Unix time. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - width: int, - height: int, - duration: int, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None): + __slots__ = ["file_id", "thumb", "file_name", "mime_type", "file_size", "date", "width", "height", "duration"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + width: int, + height: int, + duration: int, + thumb: PhotoSize = None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None + ): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 9c5698f1..334e022e 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -57,18 +57,22 @@ class Audio(PyrogramType): Title of the audio as defined by sender or by audio tags. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - duration: int, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None, - performer: str = None, - title: str = None): + __slots__ = ["file_id", "thumb", "file_name", "mime_type", "file_size", "date", "duration", "performer", "title"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + duration: int, + thumb: PhotoSize = None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None, + performer: str = None, + title: str = None + ): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index 51acb59f..0a897c05 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -42,14 +42,18 @@ class Contact(PyrogramType): Additional data about the contact in the form of a vCard. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - phone_number: str, - first_name: str, - last_name: str = None, - user_id: int = None, - vcard: str = None): + __slots__ = ["phone_number", "first_name", "last_name", "user_id", "vcard"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + phone_number: str, + first_name: str, + last_name: str = None, + user_id: int = None, + vcard: str = None + ): super().__init__(client) self.phone_number = phone_number diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index 5cd01a92..b3f5b586 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -48,15 +48,19 @@ class Document(PyrogramType): Date the document was sent in Unix time. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None): + __slots__ = ["file_id", "thumb", "file_name", "mime_type", "file_size", "date"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + thumb: PhotoSize = None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None + ): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/game.py b/pyrogram/client/types/messages_and_media/game.py index 01af7226..ddfa84af 100644 --- a/pyrogram/client/types/messages_and_media/game.py +++ b/pyrogram/client/types/messages_and_media/game.py @@ -48,15 +48,19 @@ class Game(PyrogramType): Upload via BotFather. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - id: int, - title: str, - short_name: str, - description: str, - photo: Photo, - animation: Animation = None): + __slots__ = ["id", "title", "short_name", "description", "photo", "animation"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: int, + title: str, + short_name: str, + description: str, + photo: Photo, + animation: Animation = None + ): super().__init__(client) self.id = id diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index 64acdbf5..0d2e6c43 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -33,11 +33,15 @@ class Location(PyrogramType): Latitude as defined by sender. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - longitude: float, - latitude: float): + __slots__ = ["longitude", "latitude"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + longitude: float, + latitude: float + ): super().__init__(client) self.longitude = longitude diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index badd3689..405c26d9 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -234,65 +234,80 @@ class Message(PyrogramType, Update): # TODO: Add game missing field. Also invoice, successful_payment, connected_website - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - message_id: int, - date: int = None, - chat: Chat = None, - from_user: User = None, - forward_from: User = None, - forward_from_chat: Chat = None, - forward_from_message_id: int = None, - forward_signature: str = None, - forward_date: int = None, - reply_to_message: "Message" = None, - mentioned: bool = None, - empty: bool = None, - service: bool = None, - media: bool = None, - edit_date: int = None, - media_group_id: str = None, - author_signature: str = None, - text: str = None, - entities: List["pyrogram.MessageEntity"] = None, - caption_entities: List["pyrogram.MessageEntity"] = None, - audio: "pyrogram.Audio" = None, - document: "pyrogram.Document" = None, - photo: "pyrogram.Photo" = None, - sticker: "pyrogram.Sticker" = None, - animation: "pyrogram.Animation" = None, - game: "pyrogram.Game" = None, - video: "pyrogram.Video" = None, - voice: "pyrogram.Voice" = None, - video_note: "pyrogram.VideoNote" = None, - caption: str = None, - contact: "pyrogram.Contact" = None, - location: "pyrogram.Location" = None, - venue: "pyrogram.Venue" = None, - web_page: bool = None, - poll: "pyrogram.Poll" = None, - new_chat_members: List[User] = None, - left_chat_member: User = None, - new_chat_title: str = None, - new_chat_photo: "pyrogram.Photo" = None, - delete_chat_photo: bool = None, - group_chat_created: bool = None, - supergroup_chat_created: bool = None, - channel_chat_created: bool = None, - migrate_to_chat_id: int = None, - migrate_from_chat_id: int = None, - pinned_message: "Message" = None, - game_high_score: int = None, - views: int = None, - via_bot: User = None, - outgoing: bool = None, - matches: List[Match] = None, - command: List[str] = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None): + __slots__ = [ + "message_id", "date", "chat", "from_user", "forward_from", "forward_from_chat", "forward_from_message_id", + "forward_signature", "forward_date", "reply_to_message", "mentioned", "empty", "service", "media", "edit_date", + "media_group_id", "author_signature", "text", "entities", "caption_entities", "audio", "document", "photo", + "sticker", "animation", "game", "video", "voice", "video_note", "caption", "contact", "location", "venue", + "web_page", "poll", "new_chat_members", "left_chat_member", "new_chat_title", "new_chat_photo", + "delete_chat_photo", "group_chat_created", "supergroup_chat_created", "channel_chat_created", + "migrate_to_chat_id", "migrate_from_chat_id", "pinned_message", "game_high_score", "views", "via_bot", + "outgoing", "matches", "command", "reply_markup" + ] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + message_id: int, + date: int = None, + chat: Chat = None, + from_user: User = None, + forward_from: User = None, + forward_from_chat: Chat = None, + forward_from_message_id: int = None, + forward_signature: str = None, + forward_date: int = None, + reply_to_message: "Message" = None, + mentioned: bool = None, + empty: bool = None, + service: bool = None, + media: bool = None, + edit_date: int = None, + media_group_id: str = None, + author_signature: str = None, + text: str = None, + entities: List["pyrogram.MessageEntity"] = None, + caption_entities: List["pyrogram.MessageEntity"] = None, + audio: "pyrogram.Audio" = None, + document: "pyrogram.Document" = None, + photo: "pyrogram.Photo" = None, + sticker: "pyrogram.Sticker" = None, + animation: "pyrogram.Animation" = None, + game: "pyrogram.Game" = None, + video: "pyrogram.Video" = None, + voice: "pyrogram.Voice" = None, + video_note: "pyrogram.VideoNote" = None, + caption: str = None, + contact: "pyrogram.Contact" = None, + location: "pyrogram.Location" = None, + venue: "pyrogram.Venue" = None, + web_page: bool = None, + poll: "pyrogram.Poll" = None, + new_chat_members: List[User] = None, + left_chat_member: User = None, + new_chat_title: str = None, + new_chat_photo: "pyrogram.Photo" = None, + delete_chat_photo: bool = None, + group_chat_created: bool = None, + supergroup_chat_created: bool = None, + channel_chat_created: bool = None, + migrate_to_chat_id: int = None, + migrate_from_chat_id: int = None, + pinned_message: "Message" = None, + game_high_score: int = None, + views: int = None, + via_bot: User = None, + outgoing: bool = None, + matches: List[Match] = None, + command: List[str] = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ): super().__init__(client) self.message_id = message_id diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 88beeb2f..fa59a5bc 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -47,6 +47,8 @@ class MessageEntity(PyrogramType): For "text_mention" only, the mentioned user. """ + __slots__ = ["type", "offset", "length", "url", "user"] + ENTITIES = { types.MessageEntityMention.ID: "mention", types.MessageEntityHashtag.ID: "hashtag", @@ -63,14 +65,16 @@ class MessageEntity(PyrogramType): types.MessageEntityPhone.ID: "phone_number" } - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - type: str, - offset: int, - length: int, - url: str = None, - user: User = None): + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + type: str, + offset: int, + length: int, + url: str = None, + user: User = None + ): super().__init__(client) self.type = type diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index d89f0bad..4a203a4e 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -37,11 +37,15 @@ class Messages(PyrogramType, Update): Requested messages. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - total_count: int, - messages: List[Message]): + __slots__ = ["total_count", "messages"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + total_count: int, + messages: List[Message] + ): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index aa2b3a26..12be919c 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -41,12 +41,16 @@ class Photo(PyrogramType): Available sizes of this photo. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - id: str, - date: int, - sizes: List[PhotoSize]): + __slots__ = ["id", "date", "sizes"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: str, + date: int, + sizes: List[PhotoSize] + ): super().__init__(client) self.id = id diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index 116b564c..03569062 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -42,13 +42,17 @@ class PhotoSize(PyrogramType): File size. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - width: int, - height: int, - file_size: int): + __slots__ = ["file_id", "width", "height", "file_size"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + width: int, + height: int, + file_size: int + ): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py index 72735c28..fa6372d9 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -47,15 +47,19 @@ class Poll(PyrogramType): The index of your chosen option (in case you voted already), None otherwise. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - id: int, - closed: bool, - question: str, - options: List[PollOption], - total_voters: int, - option_chosen: int = None): + __slots__ = ["id", "closed", "question", "options", "total_voters", "option_chosen"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: int, + closed: bool, + question: str, + options: List[PollOption], + total_voters: int, + option_chosen: int = None + ): super().__init__(client) self.id = id diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/client/types/messages_and_media/poll_option.py index 227fe74f..1c2d6f1b 100644 --- a/pyrogram/client/types/messages_and_media/poll_option.py +++ b/pyrogram/client/types/messages_and_media/poll_option.py @@ -34,12 +34,16 @@ class PollOption(PyrogramType): Unique data that identifies this option among all the other options in a poll. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - text: str, - voters: int, - data: bytes): + __slots__ = ["text", "voters", "data"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + text: str, + voters: int, + data: bytes + ): super().__init__(client) self.text = text diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index a1ebbe32..70322577 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -64,19 +64,25 @@ class Sticker(PyrogramType): # TODO: Add mask position - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - width: int, - height: int, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None, - emoji: str = None, - set_name: str = None): + __slots__ = [ + "file_id", "thumb", "file_name", "mime_type", "file_size", "date", "width", "height", "emoji", "set_name" + ] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + width: int, + height: int, + thumb: PhotoSize = None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None, + emoji: str = None, + set_name: str = None + ): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index 8f8525cc..770b0ec8 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -34,11 +34,15 @@ class UserProfilePhotos(PyrogramType): Requested profile pictures. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - total_count: int, - photos: List[Photo]): + __slots__ = ["total_count", "photos"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + total_count: int, + photos: List[Photo] + ): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index 91b3455a..5dd81093 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -44,14 +44,18 @@ class Venue(PyrogramType): """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - location: Location, - title: str, - address: str, - foursquare_id: str = None, - foursquare_type: str = None): + __slots__ = ["location", "title", "address", "foursquare_id", "foursquare_type"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + location: Location, + title: str, + address: str, + foursquare_id: str = None, + foursquare_type: str = None + ): super().__init__(client) self.location = location diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 2ec9a947..39ec1455 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -57,18 +57,22 @@ class Video(PyrogramType): Date the video was sent in Unix time. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - width: int, - height: int, - duration: int, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None): + __slots__ = ["file_id", "thumb", "file_name", "mime_type", "file_size", "date", "width", "height", "duration"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + width: int, + height: int, + duration: int, + thumb: PhotoSize = None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None + ): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index 73bb85dc..afa4ad46 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -51,16 +51,20 @@ class VideoNote(PyrogramType): Date the video note was sent in Unix time. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - length: int, - duration: int, - thumb: PhotoSize = None, - mime_type: str = None, - file_size: int = None, - date: int = None): + __slots__ = ["file_id", "thumb", "mime_type", "file_size", "date", "length", "duration"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + length: int, + duration: int, + thumb: PhotoSize = None, + mime_type: str = None, + file_size: int = None, + date: int = None + ): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index e6bba45b..d5028cd3 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -47,15 +47,19 @@ class Voice(PyrogramType): Date the voice was sent in Unix time. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - duration: int, - waveform: bytes = None, - mime_type: str = None, - file_size: int = None, - date: int = None): + __slots__ = ["file_id", "duration", "waveform", "mime_type", "file_size", "date"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + duration: int, + waveform: bytes = None, + mime_type: str = None, + file_size: int = None, + date: int = None + ): super().__init__(client) self.file_id = file_id diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index db15db58..81067821 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -80,24 +80,32 @@ class Chat(PyrogramType): Information about the chat default permissions. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - id: int, - type: str, - title: str = None, - username: str = None, - first_name: str = None, - last_name: str = None, - photo: ChatPhoto = None, - description: str = None, - invite_link: str = None, - pinned_message=None, - sticker_set_name: str = None, - can_set_sticker_set: bool = None, - members_count: int = None, - restriction_reason: str = None, - permissions: "pyrogram.ChatPermissions" = None): + __slots__ = [ + "id", "type", "title", "username", "first_name", "last_name", "photo", "description", "invite_link", + "pinned_message", "sticker_set_name", "can_set_sticker_set", "members_count", "restriction_reason", + "permissions" + ] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: int, + type: str, + title: str = None, + username: str = None, + first_name: str = None, + last_name: str = None, + photo: ChatPhoto = None, + description: str = None, + invite_link: str = None, + pinned_message=None, + sticker_set_name: str = None, + can_set_sticker_set: bool = None, + members_count: int = None, + restriction_reason: str = None, + permissions: "pyrogram.ChatPermissions" = None + ): super().__init__(client) self.id = id diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 5769b3f8..07a3d190 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -51,16 +51,20 @@ class ChatMember(PyrogramType): Information about the member permissions. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - user: "pyrogram.User", - status: str, - date: int = None, - invited_by: "pyrogram.User" = None, - promoted_by: "pyrogram.User" = None, - restricted_by: "pyrogram.User" = None, - permissions: "pyrogram.ChatPermissions" = None): + __slots__ = ["user", "status", "date", "invited_by", "promoted_by", "restricted_by", "permissions"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + user: "pyrogram.User", + status: str, + date: int = None, + invited_by: "pyrogram.User" = None, + promoted_by: "pyrogram.User" = None, + restricted_by: "pyrogram.User" = None, + permissions: "pyrogram.ChatPermissions" = None + ): super().__init__(client) self.user = user diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index 39d69089..0578e04e 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -21,7 +21,6 @@ from typing import List import pyrogram from pyrogram.api import types from .chat_member import ChatMember -from .user import User from ..pyrogram_type import PyrogramType @@ -36,11 +35,15 @@ class ChatMembers(PyrogramType): Requested chat members. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - total_count: int, - chat_members: List[ChatMember]): + __slots__ = ["total_count", "chat_members"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + total_count: int, + chat_members: List[ChatMember] + ): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/client/types/user_and_chats/chat_permissions.py index a931cd7a..4a4785b5 100644 --- a/pyrogram/client/types/user_and_chats/chat_permissions.py +++ b/pyrogram/client/types/user_and_chats/chat_permissions.py @@ -94,6 +94,13 @@ class ChatPermissions(PyrogramType): True, if polls can be sent, implies can_send_media_messages. """ + __slots__ = [ + "until_date", "can_be_edited", "can_change_info", "can_post_messages", "can_edit_messages", + "can_delete_messages", "can_restrict_members", "can_invite_users", "can_pin_messages", "can_promote_members", + "can_send_messages", "can_send_media_messages", "can_send_other_messages", "can_add_web_page_previews", + "can_send_polls" + ] + def __init__( self, *, diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index 848f7250..deec5770 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -35,11 +35,15 @@ class ChatPhoto(PyrogramType): Unique file identifier of big (640x640) chat photo. This file_id can be used only for photo download. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - small_file_id: str, - big_file_id: str): + __slots__ = ["small_file_id", "big_file_id"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + small_file_id: str, + big_file_id: str + ): super().__init__(client) self.small_file_id = small_file_id diff --git a/pyrogram/client/types/user_and_chats/chat_preview.py b/pyrogram/client/types/user_and_chats/chat_preview.py index 45048637..9c9c71ec 100644 --- a/pyrogram/client/types/user_and_chats/chat_preview.py +++ b/pyrogram/client/types/user_and_chats/chat_preview.py @@ -45,14 +45,18 @@ class ChatPreview(PyrogramType): Preview of some of the chat members. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - title: str, - photo: ChatPhoto, - type: str, - members_count: int, - members: List[User] = None): + __slots__ = ["title", "photo", "type", "members_count", "members"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + title: str, + photo: ChatPhoto, + type: str, + members_count: int, + members: List[User] = None + ): super().__init__(client) self.title = title diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index b71caba7..11a1ae19 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -46,15 +46,19 @@ class Dialog(PyrogramType): True, if the dialog is pinned. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - chat: Chat, - top_message: "pyrogram.Message", - unread_messages_count: int, - unread_mentions_count: int, - unread_mark: bool, - is_pinned: bool): + __slots__ = ["chat", "top_message", "unread_messages_count", "unread_mentions_count", "unread_mark", "is_pinned"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + chat: Chat, + top_message: "pyrogram.Message", + unread_messages_count: int, + unread_mentions_count: int, + unread_mark: bool, + is_pinned: bool + ): super().__init__(client) self.chat = chat diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py index 394ddd28..27222a91 100644 --- a/pyrogram/client/types/user_and_chats/dialogs.py +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -36,11 +36,15 @@ class Dialogs(PyrogramType): Requested dialogs. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - total_count: int, - dialogs: List[Dialog]): + __slots__ = ["total_count", "dialogs"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + total_count: int, + dialogs: List[Dialog] + ): super().__init__(client) self.total_count = total_count diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index c6ee03e8..f5c00ac0 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -70,23 +70,30 @@ class User(PyrogramType): The reason why this bot might be unavailable to some users. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - id: int, - is_self: bool, - is_contact: bool, - is_mutual_contact: bool, - is_deleted: bool, - is_bot: bool, - first_name: str, - last_name: str = None, - status: UserStatus = None, - username: str = None, - language_code: str = None, - phone_number: str = None, - photo: ChatPhoto = None, - restriction_reason: str = None): + __slots__ = [ + "id", "is_self", "is_contact", "is_mutual_contact", "is_deleted", "is_bot", "first_name", "last_name", "status", + "username", "language_code", "phone_number", "photo", "restriction_reason" + ] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: int, + is_self: bool, + is_contact: bool, + is_mutual_contact: bool, + is_deleted: bool, + is_bot: bool, + first_name: str, + last_name: str = None, + status: UserStatus = None, + username: str = None, + language_code: str = None, + phone_number: str = None, + photo: ChatPhoto = None, + restriction_reason: str = None + ): super().__init__(client) self.id = id diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index 8c936f8e..871978d0 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -65,17 +65,21 @@ class UserStatus(PyrogramType, Update): always shown to blocked users), None otherwise. """ - def __init__(self, - *, - client: "pyrogram.client.ext.BaseClient", - user_id: int, - online: bool = None, - offline: bool = None, - date: int = None, - recently: bool = None, - within_week: bool = None, - within_month: bool = None, - long_time_ago: bool = None): + __slots__ = ["user_id", "online", "offline", "date", "recently", "within_week", "within_month", "long_time_ago"] + + def __init__( + self, + *, + client: "pyrogram.client.ext.BaseClient", + user_id: int, + online: bool = None, + offline: bool = None, + date: int = None, + recently: bool = None, + within_week: bool = None, + within_month: bool = None, + long_time_ago: bool = None + ): super().__init__(client) self.user_id = user_id From 34b51b6481de6166d43aa4c3f37bbe50ef8e6c40 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Mar 2019 16:50:40 +0100 Subject: [PATCH 317/326] Force keyword arguments for all TL types --- compiler/api/compiler.py | 12 ++-- pyrogram/client/client.py | 34 ++++++----- pyrogram/client/ext/utils.py | 6 +- .../methods/chats/export_chat_invite_link.py | 2 +- pyrogram/client/methods/chats/get_chat.py | 6 +- .../client/methods/chats/get_chat_members.py | 2 +- .../client/methods/contacts/get_contacts.py | 2 +- .../methods/messages/edit_message_media.py | 16 ++++-- .../client/methods/messages/get_messages.py | 2 +- .../client/methods/messages/send_animation.py | 2 +- .../client/methods/messages/send_audio.py | 2 +- .../client/methods/messages/send_document.py | 2 +- .../client/methods/messages/send_location.py | 6 +- .../methods/messages/send_media_group.py | 2 +- .../client/methods/messages/send_sticker.py | 2 +- .../client/methods/messages/send_video.py | 2 +- pyrogram/client/methods/password/utils.py | 2 +- pyrogram/client/methods/users/get_me.py | 2 +- .../methods/users/set_user_profile_photo.py | 2 +- pyrogram/client/style/html.py | 14 ++--- pyrogram/client/style/markdown.py | 14 ++--- .../types/bots/inline_keyboard_button.py | 14 +++-- .../types/bots/inline_keyboard_markup.py | 4 +- pyrogram/client/types/bots/keyboard_button.py | 6 +- .../types/bots/reply_keyboard_markup.py | 8 ++- .../types/messages_and_media/sticker.py | 5 +- pyrogram/session/auth.py | 56 +++++++++---------- pyrogram/session/session.py | 14 ++--- 28 files changed, 133 insertions(+), 108 deletions(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index c9ea5f34..fe204a98 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -287,9 +287,11 @@ def start(): sorted_args = sort_args(c.args) - arguments = ", " + ", ".join( - [get_argument_type(i) for i in sorted_args if i != ("flags", "#")] - ) if c.args else "" + arguments = ( + ", " + + ("*, " if c.args else "") + + (", ".join([get_argument_type(i) for i in sorted_args if i != ("flags", "#")]) if c.args else "") + ) fields = "\n ".join( ["self.{0} = {0} # {1}".format(i[0], i[1]) for i in c.args if i != ("flags", "#")] @@ -456,7 +458,9 @@ def start(): fields=fields, read_types=read_types, write_types=write_types, - return_arguments=", ".join([i[0] for i in sorted_args if i != ("flags", "#")]), + return_arguments=", ".join( + ["{0}={0}".format(i[0]) for i in sorted_args if i != ("flags", "#")] + ), slots=", ".join(['"{}"'.format(i[0]) for i in sorted_args if i != ("flags", "#")]), qualname="{}{}".format("{}.".format(c.namespace) if c.namespace else "", c.name) ) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index df1ff1c5..253cc754 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -627,9 +627,9 @@ class Client(Methods, BaseClient): try: r = self.send( functions.auth.SignIn( - self.phone_number, - phone_code_hash, - self.phone_code + phone_number=self.phone_number, + phone_code_hash=phone_code_hash, + phone_code=self.phone_code ) ) except PhoneNumberUnoccupied: @@ -640,11 +640,11 @@ class Client(Methods, BaseClient): try: r = self.send( functions.auth.SignUp( - self.phone_number, - phone_code_hash, - self.phone_code, - self.first_name, - self.last_name + phone_number=self.phone_number, + phone_code_hash=phone_code_hash, + phone_code=self.phone_code, + first_name=self.first_name, + last_name=self.last_name ) ) except PhoneNumberOccupied: @@ -738,7 +738,11 @@ class Client(Methods, BaseClient): break if terms_of_service: - assert self.send(functions.help.AcceptTermsOfService(terms_of_service.id)) + assert self.send( + functions.help.AcceptTermsOfService( + id=terms_of_service.id + ) + ) self.password = None self.user_id = r.user.id @@ -1036,10 +1040,10 @@ class Client(Methods, BaseClient): raise ConnectionError("Client has not been started") if self.no_updates: - data = functions.InvokeWithoutUpdates(data) + data = functions.InvokeWithoutUpdates(query=data) if self.takeout_id: - data = functions.InvokeWithTakeout(self.takeout_id, data) + data = functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=data) r = self.session.send(data, retries, timeout) @@ -1353,7 +1357,7 @@ class Client(Methods, BaseClient): self.fetch_peers( self.send( functions.users.GetUsers( - id=[types.InputUser(peer_id, 0)] + id=[types.InputUser(user_id=peer_id, access_hash=0)] ) ) ) @@ -1361,7 +1365,7 @@ class Client(Methods, BaseClient): if str(peer_id).startswith("-100"): self.send( functions.channels.GetChannels( - id=[types.InputChannel(int(str(peer_id)[4:]), 0)] + id=[types.InputChannel(channel_id=int(str(peer_id)[4:]), access_hash=0)] ) ) else: @@ -1668,8 +1672,8 @@ class Client(Methods, BaseClient): hashes = session.send( functions.upload.GetCdnFileHashes( - r.file_token, - offset + file_token=r.file_token, + offset=offset ) ) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index 087773f4..981752fa 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -67,10 +67,10 @@ def get_peer_id(input_peer) -> int: def get_input_peer(peer_id: int, access_hash: int): return ( - types.InputPeerUser(peer_id, access_hash) if peer_id > 0 - else types.InputPeerChannel(int(str(peer_id)[4:]), access_hash) + types.InputPeerUser(user_id=peer_id, access_hash=access_hash) if peer_id > 0 + else types.InputPeerChannel(channel_id=int(str(peer_id)[4:]), access_hash=access_hash) if (str(peer_id).startswith("-100") and access_hash) - else types.InputPeerChat(-peer_id) + else types.InputPeerChat(chat_id=-peer_id) ) diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index 9ff8c9c0..39f1a2fe 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -45,7 +45,7 @@ class ExportChatInviteLink(BaseClient): if isinstance(peer, types.InputPeerChat): return self.send( functions.messages.ExportChatInvite( - chat_id=peer.chat_id + peer=peer.chat_id ) ).link elif isinstance(peer, types.InputPeerChannel): diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index d30fd9bb..9db34669 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -67,10 +67,10 @@ class GetChat(BaseClient): peer = self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): - r = self.send(functions.channels.GetFullChannel(peer)) + r = self.send(functions.channels.GetFullChannel(channel=peer)) elif isinstance(peer, (types.InputPeerUser, types.InputPeerSelf)): - r = self.send(functions.users.GetFullUser(peer)) + r = self.send(functions.users.GetFullUser(id=peer)) else: - r = self.send(functions.messages.GetFullChat(peer.chat_id)) + r = self.send(functions.messages.GetFullChat(chat_id=peer.chat_id)) return pyrogram.Chat._parse_full(self, r) diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index 382d7f0f..6c56b532 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -92,7 +92,7 @@ class GetChatMembers(BaseClient): self, self.send( functions.messages.GetFullChat( - peer.chat_id + chat_id=peer.chat_id ) ) ) diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py index 29b7e176..30310913 100644 --- a/pyrogram/client/methods/contacts/get_contacts.py +++ b/pyrogram/client/methods/contacts/get_contacts.py @@ -39,7 +39,7 @@ class GetContacts(BaseClient): """ while True: try: - contacts = self.send(functions.contacts.GetContacts(0)) + contacts = self.send(functions.contacts.GetContacts(hash=0)) except FloodWait as e: log.warning("get_contacts flood: waiting {} seconds".format(e.x)) time.sleep(e.x) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 9ad2f199..2f82e2ea 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -131,7 +131,9 @@ class EditMessageMedia(BaseClient): w=media.width, h=media.height ), - types.DocumentAttributeFilename(os.path.basename(media.media)) + types.DocumentAttributeFilename( + file_name=os.path.basename(media.media) + ) ] ) ) @@ -187,7 +189,9 @@ class EditMessageMedia(BaseClient): performer=media.performer, title=media.title ), - types.DocumentAttributeFilename(os.path.basename(media.media)) + types.DocumentAttributeFilename( + file_name=os.path.basename(media.media) + ) ] ) ) @@ -244,7 +248,9 @@ class EditMessageMedia(BaseClient): w=media.width, h=media.height ), - types.DocumentAttributeFilename(os.path.basename(media.media)), + types.DocumentAttributeFilename( + file_name=os.path.basename(media.media) + ), types.DocumentAttributeAnimated() ] ) @@ -296,7 +302,9 @@ class EditMessageMedia(BaseClient): thumb=None if media.thumb is None else self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ - types.DocumentAttributeFilename(os.path.basename(media.media)) + types.DocumentAttributeFilename( + file_name=os.path.basename(media.media) + ) ] ) ) diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index b1c90d10..40b1b0ee 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -76,7 +76,7 @@ class GetMessages(BaseClient): is_iterable = not isinstance(ids, int) ids = list(ids) if is_iterable else [ids] - ids = [ids_type(i) for i in ids] + ids = [ids_type(id=i) for i in ids] if isinstance(peer, types.InputPeerChannel): rpc = functions.channels.GetMessages(channel=peer, id=ids) diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 8fc31e08..5ac902ed 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -141,7 +141,7 @@ class SendAnimation(BaseClient): w=width, h=height ), - types.DocumentAttributeFilename(os.path.basename(animation)), + types.DocumentAttributeFilename(file_name=os.path.basename(animation)), types.DocumentAttributeAnimated() ] ) diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index a956ba85..a4f5512b 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -142,7 +142,7 @@ class SendAudio(BaseClient): performer=performer, title=title ), - types.DocumentAttributeFilename(os.path.basename(audio)) + types.DocumentAttributeFilename(file_name=os.path.basename(audio)) ] ) elif audio.startswith("http"): diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 35fe8c0e..6dc496e4 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -123,7 +123,7 @@ class SendDocument(BaseClient): file=file, thumb=thumb, attributes=[ - types.DocumentAttributeFilename(os.path.basename(document)) + types.DocumentAttributeFilename(file_name=os.path.basename(document)) ] ) elif document.startswith("http"): diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py index b899a938..5fe57904 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/client/methods/messages/send_location.py @@ -69,9 +69,9 @@ class SendLocation(BaseClient): functions.messages.SendMedia( peer=self.resolve_peer(chat_id), media=types.InputMediaGeoPoint( - types.InputGeoPoint( - latitude, - longitude + geo_point=types.InputGeoPoint( + lat=latitude, + long=longitude ) ), message="", diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 0df273bc..42e4ddde 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -137,7 +137,7 @@ class SendMediaGroup(BaseClient): w=i.width, h=i.height ), - types.DocumentAttributeFilename(os.path.basename(i.media)) + types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) ] ) ) diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index e41c074e..ba16a2e7 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -103,7 +103,7 @@ class SendSticker(BaseClient): mime_type="image/webp", file=file, attributes=[ - types.DocumentAttributeFilename(os.path.basename(sticker)) + types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) ] ) elif sticker.startswith("http"): diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index b69b2185..0224eaf6 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -145,7 +145,7 @@ class SendVideo(BaseClient): w=width, h=height ), - types.DocumentAttributeFilename(os.path.basename(video)) + types.DocumentAttributeFilename(file_name=os.path.basename(video)) ] ) elif video.startswith("http"): diff --git a/pyrogram/client/methods/password/utils.py b/pyrogram/client/methods/password/utils.py index 24c4dd28..3a29976a 100644 --- a/pyrogram/client/methods/password/utils.py +++ b/pyrogram/client/methods/password/utils.py @@ -101,4 +101,4 @@ def compute_check(r: types.account.Password, password: str) -> types.InputCheckP + K_bytes ) - return types.InputCheckPasswordSRP(srp_id, A_bytes, M1_bytes) + return types.InputCheckPasswordSRP(srp_id=srp_id, A=A_bytes, M1=M1_bytes) diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index beea1243..fdceeaba 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -35,7 +35,7 @@ class GetMe(BaseClient): self, self.send( functions.users.GetFullUser( - types.InputPeerSelf() + id=types.InputPeerSelf() ) ).user ) diff --git a/pyrogram/client/methods/users/set_user_profile_photo.py b/pyrogram/client/methods/users/set_user_profile_photo.py index 0863c695..359a0cd0 100644 --- a/pyrogram/client/methods/users/set_user_profile_photo.py +++ b/pyrogram/client/methods/users/set_user_profile_photo.py @@ -43,7 +43,7 @@ class SetUserProfilePhoto(BaseClient): return bool( self.send( functions.photos.UploadProfilePhoto( - self.save_file(photo) + file=self.save_file(photo) ) ) ) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 9a72a565..040e770b 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -55,20 +55,20 @@ class HTML: input_user = self.peers_by_id.get(user_id, None) entity = ( - Mention(start, len(body), input_user) - if input_user else MentionInvalid(start, len(body), user_id) + Mention(offset=start, length=len(body), user_id=input_user) + if input_user else MentionInvalid(offset=start, length=len(body), user_id=user_id) ) else: - entity = Url(start, len(body), url) + entity = Url(offset=start, length=len(body), url=url) else: if style == "b" or style == "strong": - entity = Bold(start, len(body)) + entity = Bold(offset=start, length=len(body)) elif style == "i" or style == "em": - entity = Italic(start, len(body)) + entity = Italic(offset=start, length=len(body)) elif style == "code": - entity = Code(start, len(body)) + entity = Code(offset=start, length=len(body)) elif style == "pre": - entity = Pre(start, len(body), "") + entity = Pre(offset=start, length=len(body), language="") else: continue diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index 05a11a25..04ce95c8 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -72,24 +72,24 @@ class Markdown: input_user = self.peers_by_id.get(user_id, None) entity = ( - Mention(start, len(text), input_user) + Mention(offset=start, length=len(text), user_id=input_user) if input_user - else MentionInvalid(start, len(text), user_id) + else MentionInvalid(offset=start, length=len(text), user_id=user_id) ) else: - entity = Url(start, len(text), url) + entity = Url(offset=start, length=len(text), url=url) body = text offset += len(url) + 4 else: if style == self.BOLD_DELIMITER: - entity = Bold(start, len(body)) + entity = Bold(offset=start, length=len(body)) elif style == self.ITALIC_DELIMITER: - entity = Italic(start, len(body)) + entity = Italic(offset=start, length=len(body)) elif style == self.CODE_DELIMITER: - entity = Code(start, len(body)) + entity = Code(offset=start, length=len(body)) elif style == self.PRE_DELIMITER: - entity = Pre(start, len(body), "") + entity = Pre(offset=start, length=len(body), language="") else: continue diff --git a/pyrogram/client/types/bots/inline_keyboard_button.py b/pyrogram/client/types/bots/inline_keyboard_button.py index 505b7c87..cca42b33 100644 --- a/pyrogram/client/types/bots/inline_keyboard_button.py +++ b/pyrogram/client/types/bots/inline_keyboard_button.py @@ -111,16 +111,20 @@ class InlineKeyboardButton(PyrogramType): def write(self): if self.callback_data: - return KeyboardButtonCallback(self.text, self.callback_data) + return KeyboardButtonCallback(text=self.text, data=self.callback_data) if self.url: - return KeyboardButtonUrl(self.text, self.url) + return KeyboardButtonUrl(text=self.text, url=self.url) if self.switch_inline_query: - return KeyboardButtonSwitchInline(self.text, self.switch_inline_query) + return KeyboardButtonSwitchInline(text=self.text, query=self.switch_inline_query) if self.switch_inline_query_current_chat: - return KeyboardButtonSwitchInline(self.text, self.switch_inline_query_current_chat, same_peer=True) + return KeyboardButtonSwitchInline( + text=self.text, + query=self.switch_inline_query_current_chat, + same_peer=True + ) if self.callback_game: - return KeyboardButtonGame(self.text) + return KeyboardButtonGame(text=self.text) diff --git a/pyrogram/client/types/bots/inline_keyboard_markup.py b/pyrogram/client/types/bots/inline_keyboard_markup.py index cb5327b1..77e9cfeb 100644 --- a/pyrogram/client/types/bots/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots/inline_keyboard_markup.py @@ -59,7 +59,7 @@ class InlineKeyboardMarkup(PyrogramType): def write(self): return ReplyInlineMarkup( - [KeyboardButtonRow( - [j.write() for j in i] + rows=[KeyboardButtonRow( + buttons=[j.write() for j in i] ) for i in self.inline_keyboard] ) diff --git a/pyrogram/client/types/bots/keyboard_button.py b/pyrogram/client/types/bots/keyboard_button.py index dc4ed1e7..b8786cee 100644 --- a/pyrogram/client/types/bots/keyboard_button.py +++ b/pyrogram/client/types/bots/keyboard_button.py @@ -75,8 +75,8 @@ class KeyboardButton(PyrogramType): # TODO: Enforce optional args mutual exclusiveness if self.request_contact: - return KeyboardButtonRequestPhone(self.text) + return KeyboardButtonRequestPhone(text=self.text) elif self.request_location: - return KeyboardButtonRequestGeoLocation(self.text) + return KeyboardButtonRequestGeoLocation(text=self.text) else: - return RawKeyboardButton(self.text) + return RawKeyboardButton(text=self.text) diff --git a/pyrogram/client/types/bots/reply_keyboard_markup.py b/pyrogram/client/types/bots/reply_keyboard_markup.py index 229899d0..431a55eb 100644 --- a/pyrogram/client/types/bots/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots/reply_keyboard_markup.py @@ -87,9 +87,11 @@ class ReplyKeyboardMarkup(PyrogramType): def write(self): return RawReplyKeyboardMarkup( rows=[KeyboardButtonRow( - [KeyboardButton(j).write() - if isinstance(j, str) else j.write() - for j in i] + buttons=[ + KeyboardButton(j).write() + if isinstance(j, str) else j.write() + for j in i + ] ) for i in self.keyboard], resize=self.resize_keyboard or None, single_use=self.one_time_keyboard or None, diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index 70322577..ce390b1d 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -103,7 +103,10 @@ class Sticker(PyrogramType): try: return send( functions.messages.GetStickerSet( - types.InputStickerSetID(*input_sticker_set_id) + stickerset=types.InputStickerSetID( + id=input_sticker_set_id[0], + access_hash=input_sticker_set_id[1] + ) ) ).set.short_name except StickersetInvalid: diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 05b20eec..9d8e4b16 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -45,10 +45,10 @@ class Auth: @staticmethod def pack(data: Object) -> bytes: return ( - bytes(8) - + Long(MsgId()) - + Int(len(data.write())) - + data.write() + bytes(8) + + Long(MsgId()) + + Int(len(data.write())) + + data.write() ) @staticmethod @@ -83,7 +83,7 @@ class Auth: # Step 1; Step 2 nonce = int.from_bytes(urandom(16), "little", signed=True) log.debug("Send req_pq: {}".format(nonce)) - res_pq = self.send(functions.ReqPqMulti(nonce)) + res_pq = self.send(functions.ReqPqMulti(nonce=nonce)) log.debug("Got ResPq: {}".format(res_pq.server_nonce)) log.debug("Server public key fingerprints: {}".format(res_pq.server_public_key_fingerprints)) @@ -110,12 +110,12 @@ class Auth: new_nonce = int.from_bytes(urandom(32), "little", signed=True) data = types.PQInnerData( - res_pq.pq, - p.to_bytes(4, "big"), - q.to_bytes(4, "big"), - nonce, - server_nonce, - new_nonce, + pq=res_pq.pq, + p=p.to_bytes(4, "big"), + q=q.to_bytes(4, "big"), + nonce=nonce, + server_nonce=server_nonce, + new_nonce=new_nonce, ).write() sha = sha1(data).digest() @@ -129,12 +129,12 @@ class Auth: log.debug("Send req_DH_params") server_dh_params = self.send( functions.ReqDHParams( - nonce, - server_nonce, - p.to_bytes(4, "big"), - q.to_bytes(4, "big"), - public_key_fingerprint, - encrypted_data + nonce=nonce, + server_nonce=server_nonce, + p=p.to_bytes(4, "big"), + q=q.to_bytes(4, "big"), + public_key_fingerprint=public_key_fingerprint, + encrypted_data=encrypted_data ) ) @@ -144,13 +144,13 @@ class Auth: new_nonce = new_nonce.to_bytes(32, "little", signed=True) tmp_aes_key = ( - sha1(new_nonce + server_nonce).digest() - + sha1(server_nonce + new_nonce).digest()[:12] + sha1(new_nonce + server_nonce).digest() + + sha1(server_nonce + new_nonce).digest()[:12] ) tmp_aes_iv = ( - sha1(server_nonce + new_nonce).digest()[12:] - + sha1(new_nonce + new_nonce).digest() + new_nonce[:4] + sha1(server_nonce + new_nonce).digest()[12:] + + sha1(new_nonce + new_nonce).digest() + new_nonce[:4] ) server_nonce = int.from_bytes(server_nonce, "little", signed=True) @@ -175,10 +175,10 @@ class Auth: retry_id = 0 data = types.ClientDHInnerData( - nonce, - server_nonce, - retry_id, - g_b + nonce=nonce, + server_nonce=server_nonce, + retry_id=retry_id, + g_b=g_b ).write() sha = sha1(data).digest() @@ -189,9 +189,9 @@ class Auth: log.debug("Send set_client_DH_params") set_client_dh_params_answer = self.send( functions.SetClientDHParams( - nonce, - server_nonce, - encrypted_data + nonce=nonce, + server_nonce=server_nonce, + encrypted_data=encrypted_data ) ) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 5392728f..4ebdf4fc 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -134,11 +134,11 @@ class Session: self.current_salt = FutureSalt( 0, 0, self._send( - functions.Ping(0), + functions.Ping(ping_id=0), timeout=self.START_TIMEOUT ).new_server_salt ) - self.current_salt = self._send(functions.GetFutureSalts(1), timeout=self.START_TIMEOUT).salts[0] + self.current_salt = self._send(functions.GetFutureSalts(num=1), timeout=self.START_TIMEOUT).salts[0] self.next_salt_thread = Thread(target=self.next_salt, name="NextSaltThread") self.next_salt_thread.start() @@ -146,8 +146,8 @@ class Session: if not self.is_cdn: self._send( functions.InvokeWithLayer( - layer, - functions.InitConnection( + layer=layer, + query=functions.InitConnection( api_id=self.client.api_id, app_version=self.client.app_version, device_model=self.client.device_model, @@ -314,7 +314,7 @@ class Session: log.info("Send {} acks".format(len(self.pending_acks))) try: - self._send(types.MsgsAck(list(self.pending_acks)), False) + self._send(types.MsgsAck(msg_ids=list(self.pending_acks)), False) except (OSError, TimeoutError): pass else: @@ -335,7 +335,7 @@ class Session: try: self._send(functions.PingDelayDisconnect( - 0, self.WAIT_TIMEOUT + 10 + ping_id=0, disconnect_delay=self.WAIT_TIMEOUT + 10 ), False) except (OSError, TimeoutError, Error): pass @@ -365,7 +365,7 @@ class Session: break try: - self.current_salt = self._send(functions.GetFutureSalts(1)).salts[0] + self.current_salt = self._send(functions.GetFutureSalts(num=1)).salts[0] except (OSError, TimeoutError, Error): self.connection.close() break From def3bdaa63b00e3bceca5ee33bb831fcd61fd153 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Mar 2019 17:51:37 +0100 Subject: [PATCH 318/326] Reformat code --- compiler/api/compiler.py | 4 +- compiler/error/compiler.py | 2 +- .../client/methods/chats/restrict_chat.py | 20 ++--- .../methods/chats/restrict_chat_member.py | 24 +++--- .../methods/chats/update_chat_username.py | 2 +- .../client/methods/messages/download_media.py | 18 ++-- .../methods/messages/send_cached_media.py | 26 +++--- .../types/user_and_chats/chat_members.py | 1 - .../types/user_and_chats/chat_permissions.py | 52 ++++++------ .../transport/tcp/tcp_abridged_o.py | 4 +- .../transport/tcp/tcp_intermediate_o.py | 4 +- pyrogram/vendor/typing/typing.py | 83 +++++++++---------- 12 files changed, 114 insertions(+), 126 deletions(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 9e671e80..4589074b 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -171,8 +171,8 @@ def start(): shutil.rmtree("{}/functions".format(DESTINATION), ignore_errors=True) with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \ - open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \ - open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api: + open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \ + open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api: schema = (auth.read() + system.read() + api.read()).splitlines() with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f: diff --git a/compiler/error/compiler.py b/compiler/error/compiler.py index b86222e7..751db1bc 100644 --- a/compiler/error/compiler.py +++ b/compiler/error/compiler.py @@ -73,7 +73,7 @@ def start(): f_init.write("from .{}_{} import *\n".format(name.lower(), code)) with open("{}/source/{}".format(HOME, i), encoding="utf-8") as f_csv, \ - open("{}/{}_{}.py".format(DEST, name.lower(), code), "w", encoding="utf-8") as f_class: + open("{}/{}_{}.py".format(DEST, name.lower(), code), "w", encoding="utf-8") as f_class: reader = csv.reader(f_csv, delimiter="\t") super_class = caml(name) diff --git a/pyrogram/client/methods/chats/restrict_chat.py b/pyrogram/client/methods/chats/restrict_chat.py index 502d9566..ca3e8055 100644 --- a/pyrogram/client/methods/chats/restrict_chat.py +++ b/pyrogram/client/methods/chats/restrict_chat.py @@ -25,16 +25,16 @@ from ...types.user_and_chats import Chat class RestrictChat(BaseClient): def restrict_chat( - self, - chat_id: Union[int, str], - can_send_messages: bool = False, - can_send_media_messages: bool = False, - can_send_other_messages: bool = False, - can_add_web_page_previews: bool = False, - can_send_polls: bool = False, - can_change_info: bool = False, - can_invite_users: bool = False, - can_pin_messages: bool = False + self, + chat_id: Union[int, str], + can_send_messages: bool = False, + can_send_media_messages: bool = False, + can_send_other_messages: bool = False, + can_add_web_page_previews: bool = False, + can_send_polls: bool = False, + can_change_info: bool = False, + can_invite_users: bool = False, + can_pin_messages: bool = False ) -> Chat: """Use this method to restrict a chat. Pass True for all boolean parameters to lift restrictions from a chat. diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index f4545cb1..d75d1fa4 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -25,18 +25,18 @@ from ...types.user_and_chats import Chat class RestrictChatMember(BaseClient): def restrict_chat_member( - self, - chat_id: Union[int, str], - user_id: Union[int, str], - until_date: int = 0, - can_send_messages: bool = False, - can_send_media_messages: bool = False, - can_send_other_messages: bool = False, - can_add_web_page_previews: bool = False, - can_send_polls: bool = False, - can_change_info: bool = False, - can_invite_users: bool = False, - can_pin_messages: bool = False + self, + chat_id: Union[int, str], + user_id: Union[int, str], + until_date: int = 0, + can_send_messages: bool = False, + can_send_media_messages: bool = False, + can_send_other_messages: bool = False, + can_add_web_page_previews: bool = False, + can_send_polls: bool = False, + can_change_info: bool = False, + can_invite_users: bool = False, + can_pin_messages: bool = False ) -> Chat: """Use this method to restrict a user in a supergroup. The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. Pass True for all boolean parameters to lift diff --git a/pyrogram/client/methods/chats/update_chat_username.py b/pyrogram/client/methods/chats/update_chat_username.py index 156ee6f8..353c338e 100644 --- a/pyrogram/client/methods/chats/update_chat_username.py +++ b/pyrogram/client/methods/chats/update_chat_username.py @@ -45,7 +45,7 @@ class UpdateChatUsername(BaseClient): """ peer = self.resolve_peer(chat_id) - + if isinstance(peer, types.InputPeerChannel): return bool( self.send( diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 181daa14..1c80f63a 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -106,15 +106,15 @@ class DownloadMedia(BaseClient): else: raise ValueError(error_message) elif isinstance(message, ( - pyrogram.Photo, - pyrogram.PhotoSize, - pyrogram.Audio, - pyrogram.Document, - pyrogram.Video, - pyrogram.Voice, - pyrogram.VideoNote, - pyrogram.Sticker, - pyrogram.Animation + pyrogram.Photo, + pyrogram.PhotoSize, + pyrogram.Audio, + pyrogram.Document, + pyrogram.Video, + pyrogram.Voice, + pyrogram.VideoNote, + pyrogram.Sticker, + pyrogram.Animation )): if isinstance(message, pyrogram.Photo): media = pyrogram.Document( diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 843b7197..90c9b09f 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -28,19 +28,19 @@ from pyrogram.client.ext import BaseClient, utils class SendCachedMedia(BaseClient): def send_cached_media( - self, - chat_id: Union[int, str], - file_id: str, - caption: str = "", - parse_mode: str = "", - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None + self, + chat_id: Union[int, str], + file_id: str, + caption: str = "", + parse_mode: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None ) -> Union["pyrogram.Message", None]: """Use this method to send any media stored on the Telegram servers using a file_id. diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index 39d69089..81d63231 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -21,7 +21,6 @@ from typing import List import pyrogram from pyrogram.api import types from .chat_member import ChatMember -from .user import User from ..pyrogram_type import PyrogramType diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/client/types/user_and_chats/chat_permissions.py index a931cd7a..419f3cd2 100644 --- a/pyrogram/client/types/user_and_chats/chat_permissions.py +++ b/pyrogram/client/types/user_and_chats/chat_permissions.py @@ -95,27 +95,27 @@ class ChatPermissions(PyrogramType): """ def __init__( - self, - *, - until_date: int = None, + self, + *, + until_date: int = None, - # Admin permissions - can_be_edited: bool = None, - can_change_info: bool = None, - can_post_messages: bool = None, # Channels only - can_edit_messages: bool = None, # Channels only - can_delete_messages: bool = None, - can_restrict_members: bool = None, - can_invite_users: bool = None, - can_pin_messages: bool = None, # Supergroups only - can_promote_members: bool = None, + # Admin permissions + can_be_edited: bool = None, + can_change_info: bool = None, + can_post_messages: bool = None, # Channels only + can_edit_messages: bool = None, # Channels only + can_delete_messages: bool = None, + can_restrict_members: bool = None, + can_invite_users: bool = None, + can_pin_messages: bool = None, # Supergroups only + can_promote_members: bool = None, - # Restricted user permissions - can_send_messages: bool = None, # Text, contacts, locations and venues - can_send_media_messages: bool = None, # Audios, documents, photos, videos, video notes and voice notes - can_send_other_messages: bool = None, # Animations (GIFs), games, stickers, inline bot results - can_add_web_page_previews: bool = None, - can_send_polls: bool = None + # Restricted user permissions + can_send_messages: bool = None, # Text, contacts, locations and venues + can_send_media_messages: bool = None, # Audios, documents, photos, videos, video notes and voice notes + can_send_other_messages: bool = None, # Animations (GIFs), games, stickers, inline bot results + can_add_web_page_previews: bool = None, + can_send_polls: bool = None ): super().__init__(None) @@ -139,11 +139,11 @@ class ChatPermissions(PyrogramType): @staticmethod def _parse( - entity: Union[ - types.ChannelParticipantAdmin, - types.ChannelParticipantBanned, - types.ChatBannedRights - ] + entity: Union[ + types.ChannelParticipantAdmin, + types.ChannelParticipantBanned, + types.ChatBannedRights + ] ) -> "ChatPermissions": if isinstance(entity, types.ChannelParticipantAdmin): permissions = entity.admin_rights @@ -171,8 +171,8 @@ class ChatPermissions(PyrogramType): can_send_messages=not denied_permissions.send_messages, can_send_media_messages=not denied_permissions.send_media, can_send_other_messages=( - not denied_permissions.send_stickers or not denied_permissions.send_gifs or - not denied_permissions.send_games or not denied_permissions.send_inline + not denied_permissions.send_stickers or not denied_permissions.send_gifs or + not denied_permissions.send_games or not denied_permissions.send_inline ), can_add_web_page_previews=not denied_permissions.embed_links, can_send_polls=not denied_permissions.send_polls, diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index d15d0389..136d22ef 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -40,9 +40,7 @@ class TCPAbridgedO(TCP): while True: nonce = bytearray(os.urandom(64)) - if (nonce[0] != b"\xef" - and nonce[:4] not in self.RESERVED - and nonce[4:4] != b"\x00" * 4): + if nonce[0] != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:4] != b"\x00" * 4: nonce[56] = nonce[57] = nonce[58] = nonce[59] = 0xef break diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py index c59deed7..a92acb7f 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py @@ -41,9 +41,7 @@ class TCPIntermediateO(TCP): while True: nonce = bytearray(os.urandom(64)) - if (nonce[0] != b"\xef" - and nonce[:4] not in self.RESERVED - and nonce[4:4] != b"\x00" * 4): + if nonce[0] != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:4] != b"\x00" * 4: nonce[56] = nonce[57] = nonce[58] = nonce[59] = 0xee break diff --git a/pyrogram/vendor/typing/typing.py b/pyrogram/vendor/typing/typing.py index 2189cd48..16888d3f 100644 --- a/pyrogram/vendor/typing/typing.py +++ b/pyrogram/vendor/typing/typing.py @@ -1,17 +1,18 @@ import abc -from abc import abstractmethod, abstractproperty import collections import contextlib import functools import re as stdlib_re # Avoid confusion with the re we export. import sys import types +from abc import abstractmethod, abstractproperty + try: import collections.abc as collections_abc except ImportError: import collections as collections_abc # Fallback for PY3.2. if sys.version_info[:2] >= (3, 6): - import _collections_abc # Needed for private function _check_methods # noqa + pass try: from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType except ImportError: @@ -19,7 +20,6 @@ except ImportError: MethodWrapperType = type(object().__str__) MethodDescriptorType = type(str.join) - # Please keep __all__ alphabetized within each category. __all__ = [ # Super-special typing primitives. @@ -36,7 +36,7 @@ __all__ = [ # ABCs (from collections.abc). 'AbstractSet', # collections.abc.Set. 'GenericMeta', # subclass of abc.ABCMeta and a metaclass - # for 'Generic' and ABCs below. + # for 'Generic' and ABCs below. 'ByteString', 'Container', 'ContextManager', @@ -96,6 +96,7 @@ __all__ = [ 'TYPE_CHECKING', ] + # The pseudo-submodules 're' and 'io' are part of the public # namespace, but excluded from __all__ because they might stomp on # legitimate imports of those modules. @@ -173,8 +174,8 @@ class _TypingBase(metaclass=TypingMeta, _root=True): someone tries to subclass a special typing object (not a good idea). """ if (len(args) == 3 and - isinstance(args[0], str) and - isinstance(args[1], tuple)): + isinstance(args[0], str) and + isinstance(args[1], tuple)): # Close enough. raise TypeError("Cannot subclass %r" % cls) return super().__new__(cls) @@ -396,7 +397,7 @@ def _type_repr(obj): return _qualname(obj) return '%s.%s' % (obj.__module__, _qualname(obj)) if obj is ...: - return('...') + return ('...') if isinstance(obj, types.FunctionType): return obj.__name__ return repr(obj) @@ -681,6 +682,7 @@ def _tp_cache(func): except TypeError: pass # All real errors (not unhashable args) are raised below. return func(*args, **kwds) + return inner @@ -948,7 +950,7 @@ class GenericMeta(TypingMeta, abc.ABCMeta): if base is Generic: raise TypeError("Cannot inherit from plain Generic") if (isinstance(base, GenericMeta) and - base.__origin__ is Generic): + base.__origin__ is Generic): if gvars is not None: raise TypeError( "Cannot inherit from Generic[...] multiple types.") @@ -1183,14 +1185,14 @@ def _generic_new(base_cls, cls, *args, **kwds): # but attempt to store it in __orig_class__ if cls.__origin__ is None: if (base_cls.__new__ is object.__new__ and - cls.__init__ is not object.__init__): + cls.__init__ is not object.__init__): return base_cls.__new__(cls) else: return base_cls.__new__(cls, *args, **kwds) else: origin = cls._gorg if (base_cls.__new__ is object.__new__ and - cls.__init__ is not object.__init__): + cls.__init__ is not object.__init__): obj = base_cls.__new__(origin) else: obj = base_cls.__new__(origin, *args, **kwds) @@ -1399,7 +1401,7 @@ class _ClassVar(_FinalTypingBase, _root=True): cls = type(self) if self.__type__ is None: return cls(_type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), + '{} accepts only single type.'.format(cls.__name__[1:])), _root=True) raise TypeError('{} cannot be further subscripted' .format(cls.__name__[1:])) @@ -1671,26 +1673,26 @@ class _ProtocolMeta(GenericMeta): # Include attributes not defined in any non-protocol bases. for c in self.__mro__: if (c is not base and attr in c.__dict__ and - not getattr(c, '_is_protocol', False)): + not getattr(c, '_is_protocol', False)): break else: if (not attr.startswith('_abc_') and - attr != '__abstractmethods__' and - attr != '__annotations__' and - attr != '__weakref__' and - attr != '_is_protocol' and - attr != '_gorg' and - attr != '__dict__' and - attr != '__args__' and - attr != '__slots__' and - attr != '_get_protocol_attrs' and - attr != '__next_in_mro__' and - attr != '__parameters__' and - attr != '__origin__' and - attr != '__orig_bases__' and - attr != '__extra__' and - attr != '__tree_hash__' and - attr != '__module__'): + attr != '__abstractmethods__' and + attr != '__annotations__' and + attr != '__weakref__' and + attr != '_is_protocol' and + attr != '_gorg' and + attr != '__dict__' and + attr != '__args__' and + attr != '__slots__' and + attr != '_get_protocol_attrs' and + attr != '__next_in_mro__' and + attr != '__parameters__' and + attr != '__origin__' and + attr != '__orig_bases__' and + attr != '__extra__' and + attr != '__tree_hash__' and + attr != '__module__'): attrs.add(attr) return attrs @@ -1714,31 +1716,31 @@ class _Protocol(metaclass=_ProtocolMeta): Hashable = collections_abc.Hashable # Not generic. - if hasattr(collections_abc, 'Awaitable'): class Awaitable(Generic[T_co], extra=collections_abc.Awaitable): __slots__ = () - __all__.append('Awaitable') + __all__.append('Awaitable') if hasattr(collections_abc, 'Coroutine'): class Coroutine(Awaitable[V_co], Generic[T_co, T_contra, V_co], extra=collections_abc.Coroutine): __slots__ = () + __all__.append('Coroutine') - if hasattr(collections_abc, 'AsyncIterable'): - class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable): __slots__ = () + class AsyncIterator(AsyncIterable[T_co], extra=collections_abc.AsyncIterator): __slots__ = () + __all__.append('AsyncIterable') __all__.append('AsyncIterator') @@ -1810,7 +1812,6 @@ else: def __reversed__(self) -> 'Iterator[T_co]': pass - Sized = collections_abc.Sized # Not generic. @@ -1823,8 +1824,8 @@ if hasattr(collections_abc, 'Collection'): extra=collections_abc.Collection): __slots__ = () - __all__.append('Collection') + __all__.append('Collection') # Callable was defined earlier. @@ -1881,7 +1882,6 @@ class ByteString(Sequence[int], extra=collections_abc.ByteString): class List(list, MutableSequence[T], extra=list): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1892,7 +1892,6 @@ class List(list, MutableSequence[T], extra=list): class Deque(collections.deque, MutableSequence[T], extra=collections.deque): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1902,7 +1901,6 @@ class Deque(collections.deque, MutableSequence[T], extra=collections.deque): class Set(set, MutableSet[T], extra=set): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1969,12 +1967,12 @@ else: return True return NotImplemented - if hasattr(contextlib, 'AbstractAsyncContextManager'): class AsyncContextManager(Generic[T_co], extra=contextlib.AbstractAsyncContextManager): __slots__ = () + __all__.append('AsyncContextManager') elif sys.version_info[:2] >= (3, 5): exec(""" @@ -2003,7 +2001,6 @@ __all__.append('AsyncContextManager') class Dict(dict, MutableMapping[KT, VT], extra=dict): - __slots__ = () def __new__(cls, *args, **kwds): @@ -2015,7 +2012,6 @@ class Dict(dict, MutableMapping[KT, VT], extra=dict): class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], extra=collections.defaultdict): - __slots__ = () def __new__(cls, *args, **kwds): @@ -2025,7 +2021,6 @@ class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], class Counter(collections.Counter, Dict[T, int], extra=collections.Counter): - __slots__ = () def __new__(cls, *args, **kwds): @@ -2038,6 +2033,7 @@ if hasattr(collections, 'ChainMap'): # ChainMap only exists in 3.3+ __all__.append('ChainMap') + class ChainMap(collections.ChainMap, MutableMapping[KT, VT], extra=collections.ChainMap): @@ -2048,7 +2044,6 @@ if hasattr(collections, 'ChainMap'): return collections.ChainMap(*args, **kwds) return _generic_new(collections.ChainMap, cls, *args, **kwds) - # Determine what base class to use for Generator. if hasattr(collections_abc, 'Generator'): # Sufficiently recent versions of 3.5 have a Generator ABC. @@ -2074,8 +2069,8 @@ if hasattr(collections_abc, 'AsyncGenerator'): extra=collections_abc.AsyncGenerator): __slots__ = () - __all__.append('AsyncGenerator') + __all__.append('AsyncGenerator') # Internal type variable used for Type[]. CT_co = TypeVar('CT_co', covariant=True, bound=type) @@ -2237,7 +2232,6 @@ def NewType(name, tp): # Python-version-specific alias (Python 2: unicode; Python 3: str) Text = str - # Constant that's True when type checking, but False here. TYPE_CHECKING = False @@ -2394,7 +2388,6 @@ class io: io.__name__ = __name__ + '.io' sys.modules[io.__name__] = io - Pattern = _TypeAlias('Pattern', AnyStr, type(stdlib_re.compile('')), lambda p: p.pattern) Match = _TypeAlias('Match', AnyStr, type(stdlib_re.match('', '')), From 3e18945f3ccbbce3eb7a4b506c531a616a981583 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Mar 2019 19:23:23 +0100 Subject: [PATCH 319/326] Reformat code --- pyrogram/api/errors/__init__.py | 2 +- .../methods/bots/answer_callback_query.py | 14 +- .../methods/bots/get_game_high_scores.py | 10 +- .../methods/bots/get_inline_bot_results.py | 14 +- .../methods/bots/request_callback_answer.py | 10 +- pyrogram/client/methods/bots/send_game.py | 22 ++-- .../methods/bots/send_inline_bot_result.py | 16 ++- .../client/methods/bots/set_game_score.py | 16 ++- .../client/methods/chats/delete_chat_photo.py | 6 +- .../methods/chats/export_chat_invite_link.py | 6 +- pyrogram/client/methods/chats/get_chat.py | 6 +- .../client/methods/chats/get_chat_member.py | 8 +- .../client/methods/chats/get_chat_members.py | 14 +- .../methods/chats/get_chat_members_count.py | 6 +- .../client/methods/chats/get_chat_preview.py | 6 +- pyrogram/client/methods/chats/get_dialogs.py | 10 +- .../client/methods/chats/iter_chat_members.py | 12 +- pyrogram/client/methods/chats/iter_dialogs.py | 8 +- pyrogram/client/methods/chats/join_chat.py | 6 +- .../client/methods/chats/kick_chat_member.py | 10 +- pyrogram/client/methods/chats/leave_chat.py | 8 +- .../client/methods/chats/pin_chat_message.py | 12 +- .../methods/chats/promote_chat_member.py | 24 ++-- .../methods/chats/set_chat_description.py | 8 +- .../client/methods/chats/set_chat_photo.py | 8 +- .../client/methods/chats/set_chat_title.py | 8 +- .../client/methods/chats/unban_chat_member.py | 8 +- .../methods/chats/unpin_chat_message.py | 6 +- .../methods/chats/update_chat_username.py | 8 +- .../client/methods/contacts/add_contacts.py | 6 +- .../methods/contacts/delete_contacts.py | 6 +- .../methods/decorators/on_callback_query.py | 8 +- .../methods/decorators/on_deleted_messages.py | 8 +- .../client/methods/decorators/on_message.py | 8 +- .../methods/decorators/on_raw_update.py | 6 +- .../methods/decorators/on_user_status.py | 8 +- .../client/methods/messages/close_poll.py | 8 +- .../methods/messages/delete_messages.py | 10 +- .../client/methods/messages/download_media.py | 14 +- .../methods/messages/edit_message_caption.py | 14 +- .../methods/messages/edit_message_media.py | 12 +- .../messages/edit_message_reply_markup.py | 10 +- .../methods/messages/edit_message_text.py | 16 ++- .../methods/messages/forward_messages.py | 12 +- .../client/methods/messages/get_history.py | 16 ++- .../client/methods/messages/get_messages.py | 12 +- .../client/methods/messages/iter_history.py | 16 ++- .../client/methods/messages/retract_vote.py | 8 +- .../client/methods/messages/send_animation.py | 38 +++--- .../client/methods/messages/send_audio.py | 38 +++--- .../methods/messages/send_chat_action.py | 10 +- .../client/methods/messages/send_contact.py | 28 ++-- .../client/methods/messages/send_document.py | 32 +++-- .../client/methods/messages/send_location.py | 24 ++-- .../methods/messages/send_media_group.py | 12 +- .../client/methods/messages/send_message.py | 26 ++-- .../client/methods/messages/send_photo.py | 32 +++-- pyrogram/client/methods/messages/send_poll.py | 24 ++-- .../client/methods/messages/send_sticker.py | 26 ++-- .../client/methods/messages/send_venue.py | 32 +++-- .../client/methods/messages/send_video.py | 40 +++--- .../methods/messages/send_video_note.py | 32 +++-- .../client/methods/messages/send_voice.py | 32 +++-- pyrogram/client/methods/messages/vote_poll.py | 10 +- .../methods/password/change_cloud_password.py | 10 +- .../methods/password/enable_cloud_password.py | 10 +- .../methods/password/remove_cloud_password.py | 6 +- .../users/delete_user_profile_photos.py | 6 +- .../methods/users/get_user_profile_photos.py | 10 +- pyrogram/client/methods/users/get_users.py | 6 +- .../methods/users/set_user_profile_photo.py | 6 +- .../client/methods/users/update_username.py | 6 +- pyrogram/client/types/bots/callback_query.py | 20 +-- pyrogram/client/types/bots/force_reply.py | 4 +- pyrogram/client/types/bots/game_high_score.py | 12 +- .../client/types/bots/game_high_scores.py | 10 +- .../types/bots/inline_keyboard_button.py | 14 +- .../types/bots/inline_keyboard_markup.py | 4 +- pyrogram/client/types/bots/keyboard_button.py | 8 +- .../types/bots/reply_keyboard_markup.py | 10 +- .../types/bots/reply_keyboard_remove.py | 4 +- .../client/types/input_media/input_media.py | 8 +- .../input_media/input_media_animation.py | 16 +-- .../types/input_media/input_media_audio.py | 16 +-- .../types/input_media/input_media_document.py | 10 +- .../types/input_media/input_media_photo.py | 8 +- .../types/input_media/input_media_video.py | 18 +-- .../types/input_media/input_phone_contact.py | 8 +- .../types/messages_and_media/animation.py | 24 ++-- .../client/types/messages_and_media/audio.py | 24 ++-- .../types/messages_and_media/contact.py | 16 +-- .../types/messages_and_media/document.py | 18 +-- .../client/types/messages_and_media/game.py | 18 +-- .../types/messages_and_media/location.py | 10 +- .../types/messages_and_media/message.py | 122 +++++++++--------- .../messages_and_media/message_entity.py | 16 +-- .../types/messages_and_media/messages.py | 10 +- .../client/types/messages_and_media/photo.py | 12 +- .../types/messages_and_media/photo_size.py | 14 +- .../client/types/messages_and_media/poll.py | 18 +-- .../types/messages_and_media/poll_option.py | 12 +- .../types/messages_and_media/sticker.py | 26 ++-- .../messages_and_media/user_profile_photos.py | 10 +- .../client/types/messages_and_media/venue.py | 16 +-- .../client/types/messages_and_media/video.py | 24 ++-- .../types/messages_and_media/video_note.py | 20 +-- .../client/types/messages_and_media/voice.py | 18 +-- pyrogram/client/types/user_and_chats/chat.py | 36 +++--- .../types/user_and_chats/chat_member.py | 20 +-- .../types/user_and_chats/chat_members.py | 10 +- .../client/types/user_and_chats/chat_photo.py | 10 +- .../types/user_and_chats/chat_preview.py | 16 +-- .../client/types/user_and_chats/dialog.py | 18 +-- .../client/types/user_and_chats/dialogs.py | 10 +- pyrogram/client/types/user_and_chats/user.py | 34 ++--- .../types/user_and_chats/user_status.py | 22 ++-- pyrogram/session/auth.py | 16 +-- 117 files changed, 974 insertions(+), 802 deletions(-) diff --git a/pyrogram/api/errors/__init__.py b/pyrogram/api/errors/__init__.py index ca65619c..8a1dc699 100644 --- a/pyrogram/api/errors/__init__.py +++ b/pyrogram/api/errors/__init__.py @@ -16,5 +16,5 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .exceptions import * from .error import UnknownError +from .exceptions import * diff --git a/pyrogram/client/methods/bots/answer_callback_query.py b/pyrogram/client/methods/bots/answer_callback_query.py index 5e8468e5..87fc458a 100644 --- a/pyrogram/client/methods/bots/answer_callback_query.py +++ b/pyrogram/client/methods/bots/answer_callback_query.py @@ -21,12 +21,14 @@ from pyrogram.client.ext import BaseClient class AnswerCallbackQuery(BaseClient): - def answer_callback_query(self, - callback_query_id: str, - text: str = None, - show_alert: bool = None, - url: str = None, - cache_time: int = 0): + def answer_callback_query( + self, + callback_query_id: str, + text: str = None, + show_alert: bool = None, + url: str = None, + cache_time: int = 0 + ): """Use this method to send answers to callback queries sent from inline keyboards. The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. diff --git a/pyrogram/client/methods/bots/get_game_high_scores.py b/pyrogram/client/methods/bots/get_game_high_scores.py index ad4f8b4a..e782dadc 100644 --- a/pyrogram/client/methods/bots/get_game_high_scores.py +++ b/pyrogram/client/methods/bots/get_game_high_scores.py @@ -24,10 +24,12 @@ from pyrogram.client.ext import BaseClient class GetGameHighScores(BaseClient): - def get_game_high_scores(self, - user_id: Union[int, str], - chat_id: Union[int, str], - message_id: int = None): + def get_game_high_scores( + self, + user_id: Union[int, str], + chat_id: Union[int, str], + message_id: int = None + ): """Use this method to get data for high score tables. Args: diff --git a/pyrogram/client/methods/bots/get_inline_bot_results.py b/pyrogram/client/methods/bots/get_inline_bot_results.py index e76a0d1d..7c94bcf3 100644 --- a/pyrogram/client/methods/bots/get_inline_bot_results.py +++ b/pyrogram/client/methods/bots/get_inline_bot_results.py @@ -24,12 +24,14 @@ from pyrogram.client.ext import BaseClient class GetInlineBotResults(BaseClient): - def get_inline_bot_results(self, - bot: Union[int, str], - query: str, - offset: str = "", - latitude: float = None, - longitude: float = None): + def get_inline_bot_results( + self, + bot: Union[int, str], + query: str, + offset: str = "", + latitude: float = None, + longitude: float = None + ): """Use this method to get bot results via inline queries. You can then send a result using :obj:`send_inline_bot_result ` diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py index 74e2d26d..0d440fd9 100644 --- a/pyrogram/client/methods/bots/request_callback_answer.py +++ b/pyrogram/client/methods/bots/request_callback_answer.py @@ -23,10 +23,12 @@ from pyrogram.client.ext import BaseClient class RequestCallbackAnswer(BaseClient): - def request_callback_answer(self, - chat_id: Union[int, str], - message_id: int, - callback_data: bytes): + def request_callback_answer( + self, + chat_id: Union[int, str], + message_id: int, + callback_data: bytes + ): """Use this method to request a callback answer from bots. This is the equivalent of clicking an inline button containing callback data. diff --git a/pyrogram/client/methods/bots/send_game.py b/pyrogram/client/methods/bots/send_game.py index 401a5aa6..c396ee85 100644 --- a/pyrogram/client/methods/bots/send_game.py +++ b/pyrogram/client/methods/bots/send_game.py @@ -24,15 +24,19 @@ from pyrogram.client.ext import BaseClient class SendGame(BaseClient): - def send_game(self, - chat_id: Union[int, str], - game_short_name: str, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None) -> "pyrogram.Message": + def send_game( + self, + chat_id: Union[int, str], + game_short_name: str, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "pyrogram.Message": """Use this method to send a game. Args: diff --git a/pyrogram/client/methods/bots/send_inline_bot_result.py b/pyrogram/client/methods/bots/send_inline_bot_result.py index 66caab16..6cfc6295 100644 --- a/pyrogram/client/methods/bots/send_inline_bot_result.py +++ b/pyrogram/client/methods/bots/send_inline_bot_result.py @@ -23,13 +23,15 @@ from pyrogram.client.ext import BaseClient class SendInlineBotResult(BaseClient): - def send_inline_bot_result(self, - chat_id: Union[int, str], - query_id: int, - result_id: str, - disable_notification: bool = None, - reply_to_message_id: int = None, - hide_via: bool = None): + def send_inline_bot_result( + self, + chat_id: Union[int, str], + query_id: int, + result_id: str, + disable_notification: bool = None, + reply_to_message_id: int = None, + hide_via: bool = None + ): """Use this method to send an inline bot result. Bot results can be retrieved using :obj:`get_inline_bot_results ` diff --git a/pyrogram/client/methods/bots/set_game_score.py b/pyrogram/client/methods/bots/set_game_score.py index e9d20844..337576a9 100644 --- a/pyrogram/client/methods/bots/set_game_score.py +++ b/pyrogram/client/methods/bots/set_game_score.py @@ -24,13 +24,15 @@ from pyrogram.client.ext import BaseClient class SetGameScore(BaseClient): - def set_game_score(self, - user_id: Union[int, str], - score: int, - force: bool = None, - disable_edit_message: bool = None, - chat_id: Union[int, str] = None, - message_id: int = None): + def set_game_score( + self, + user_id: Union[int, str], + score: int, + force: bool = None, + disable_edit_message: bool = None, + chat_id: Union[int, str] = None, + message_id: int = None + ): # inline_message_id: str = None): TODO Add inline_message_id """Use this method to set the score of the specified user in a game. diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/client/methods/chats/delete_chat_photo.py index 0164ff43..2473e123 100644 --- a/pyrogram/client/methods/chats/delete_chat_photo.py +++ b/pyrogram/client/methods/chats/delete_chat_photo.py @@ -23,8 +23,10 @@ from ...ext import BaseClient class DeleteChatPhoto(BaseClient): - def delete_chat_photo(self, - chat_id: Union[int, str]) -> bool: + def delete_chat_photo( + self, + chat_id: Union[int, str] + ) -> bool: """Use this method to delete a chat photo. Photos can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index 39f1a2fe..88056344 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -23,8 +23,10 @@ from ...ext import BaseClient class ExportChatInviteLink(BaseClient): - def export_chat_invite_link(self, - chat_id: Union[int, str]) -> str: + def export_chat_invite_link( + self, + chat_id: Union[int, str] + ) -> str: """Use this method to generate a new invite link for a chat; any previously generated link is revoked. You must be an administrator in the chat for this to work and have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 9db34669..89a72722 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -24,8 +24,10 @@ from ...ext import BaseClient class GetChat(BaseClient): - def get_chat(self, - chat_id: Union[int, str]) -> "pyrogram.Chat": + def get_chat( + self, + chat_id: Union[int, str] + ) -> "pyrogram.Chat": """Use this method to get up to date information about the chat (current name of the user for one-on-one conversations, current username of a user, group or channel, etc.) diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index 7ed4104b..d8315010 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -24,9 +24,11 @@ from ...ext import BaseClient class GetChatMember(BaseClient): - def get_chat_member(self, - chat_id: Union[int, str], - user_id: Union[int, str]) -> "pyrogram.ChatMember": + def get_chat_member( + self, + chat_id: Union[int, str], + user_id: Union[int, str] + ) -> "pyrogram.ChatMember": """Use this method to get information about one member of a chat. Args: diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index 6c56b532..0b07f674 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -33,12 +33,14 @@ class Filters: class GetChatMembers(BaseClient): - def get_chat_members(self, - chat_id: Union[int, str], - offset: int = 0, - limit: int = 200, - query: str = "", - filter: str = Filters.ALL) -> "pyrogram.ChatMembers": + def get_chat_members( + self, + chat_id: Union[int, str], + offset: int = 0, + limit: int = 200, + query: str = "", + filter: str = Filters.ALL + ) -> "pyrogram.ChatMembers": """Use this method to get a chunk of the members list of a chat. You can get up to 200 chat members at once. diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py index 9360b64f..9e79d5fa 100644 --- a/pyrogram/client/methods/chats/get_chat_members_count.py +++ b/pyrogram/client/methods/chats/get_chat_members_count.py @@ -23,8 +23,10 @@ from ...ext import BaseClient class GetChatMembersCount(BaseClient): - def get_chat_members_count(self, - chat_id: Union[int, str]) -> int: + def get_chat_members_count( + self, + chat_id: Union[int, str] + ) -> int: """Use this method to get the number of members in a chat. Args: diff --git a/pyrogram/client/methods/chats/get_chat_preview.py b/pyrogram/client/methods/chats/get_chat_preview.py index 434c385b..28e84c79 100644 --- a/pyrogram/client/methods/chats/get_chat_preview.py +++ b/pyrogram/client/methods/chats/get_chat_preview.py @@ -22,8 +22,10 @@ from ...ext import BaseClient class GetChatPreview(BaseClient): - def get_chat_preview(self, - invite_link: str): + def get_chat_preview( + self, + invite_link: str + ): """Use this method to get the preview of a chat using the invite link. This method only returns a chat preview, if you want to join a chat use :meth:`join_chat` diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index c5fe6cfb..b73d0efa 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -28,10 +28,12 @@ log = logging.getLogger(__name__) class GetDialogs(BaseClient): - def get_dialogs(self, - offset_date: int = 0, - limit: int = 100, - pinned_only: bool = False) -> "pyrogram.Dialogs": + def get_dialogs( + self, + offset_date: int = 0, + limit: int = 100, + pinned_only: bool = False + ) -> "pyrogram.Dialogs": """Use this method to get a chunk of the user's dialogs You can get up to 100 dialogs at once. diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index b0aeb51b..0886d6c6 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -38,11 +38,13 @@ QUERYABLE_FILTERS = (Filters.ALL, Filters.KICKED, Filters.RESTRICTED) class IterChatMembers(BaseClient): - def iter_chat_members(self, - chat_id: Union[int, str], - limit: int = 0, - query: str = "", - filter: str = Filters.ALL) -> Generator["pyrogram.ChatMember", None, None]: + def iter_chat_members( + self, + chat_id: Union[int, str], + limit: int = 0, + query: str = "", + filter: str = Filters.ALL + ) -> Generator["pyrogram.ChatMember", None, None]: """Use this method to iterate through the members of a chat sequentially. This convenience method does the same as repeatedly calling :meth:`get_chat_members` in a loop, thus saving you diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/client/methods/chats/iter_dialogs.py index 6058cd17..a5fdb35e 100644 --- a/pyrogram/client/methods/chats/iter_dialogs.py +++ b/pyrogram/client/methods/chats/iter_dialogs.py @@ -23,9 +23,11 @@ from ...ext import BaseClient class IterDialogs(BaseClient): - def iter_dialogs(self, - offset_date: int = 0, - limit: int = 0) -> Generator["pyrogram.Dialog", None, None]: + def iter_dialogs( + self, + offset_date: int = 0, + limit: int = 0 + ) -> Generator["pyrogram.Dialog", None, None]: """Use this method to iterate through a user's dialogs sequentially. This convenience method does the same as repeatedly calling :meth:`get_dialogs` in a loop, thus saving you from diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py index b50e50c6..1ee680bf 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/client/methods/chats/join_chat.py @@ -22,8 +22,10 @@ from ...ext import BaseClient class JoinChat(BaseClient): - def join_chat(self, - chat_id: str): + def join_chat( + self, + chat_id: str + ): """Use this method to join a group chat or channel. Args: diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index 49b3dcfd..ef0d7d55 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -24,10 +24,12 @@ from ...ext import BaseClient class KickChatMember(BaseClient): - def kick_chat_member(self, - chat_id: Union[int, str], - user_id: Union[int, str], - until_date: int = 0) -> Union["pyrogram.Message", bool]: + def kick_chat_member( + self, + chat_id: Union[int, str], + user_id: Union[int, str], + until_date: int = 0 + ) -> Union["pyrogram.Message", bool]: """Use this method to kick a user from a group, a supergroup or a channel. In the case of supergroups and channels, the user will not be able to return to the group on their own using invite links, etc., unless unbanned first. You must be an administrator in the chat for this to work and must diff --git a/pyrogram/client/methods/chats/leave_chat.py b/pyrogram/client/methods/chats/leave_chat.py index 5b765ac0..9f41a0cc 100644 --- a/pyrogram/client/methods/chats/leave_chat.py +++ b/pyrogram/client/methods/chats/leave_chat.py @@ -23,9 +23,11 @@ from ...ext import BaseClient class LeaveChat(BaseClient): - def leave_chat(self, - chat_id: Union[int, str], - delete: bool = False): + def leave_chat( + self, + chat_id: Union[int, str], + delete: bool = False + ): """Use this method to leave a group chat or channel. Args: diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py index bb70f9e8..682f595d 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -23,10 +23,12 @@ from ...ext import BaseClient class PinChatMessage(BaseClient): - def pin_chat_message(self, - chat_id: Union[int, str], - message_id: int, - disable_notification: bool = None) -> bool: + def pin_chat_message( + self, + chat_id: Union[int, str], + message_id: int, + disable_notification: bool = None + ) -> bool: """Use this method to pin a message in a group, channel or your own chat. You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. @@ -55,3 +57,5 @@ class PinChatMessage(BaseClient): silent=disable_notification or None ) ) + + return True diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py index d1f942c2..f3359c5f 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/client/methods/chats/promote_chat_member.py @@ -23,17 +23,19 @@ from ...ext import BaseClient class PromoteChatMember(BaseClient): - def promote_chat_member(self, - chat_id: Union[int, str], - user_id: Union[int, str], - can_change_info: bool = True, - can_post_messages: bool = False, - can_edit_messages: bool = False, - can_delete_messages: bool = True, - can_restrict_members: bool = True, - can_invite_users: bool = True, - can_pin_messages: bool = False, - can_promote_members: bool = False) -> bool: + def promote_chat_member( + self, + chat_id: Union[int, str], + user_id: Union[int, str], + can_change_info: bool = True, + can_post_messages: bool = False, + can_edit_messages: bool = False, + can_delete_messages: bool = True, + can_restrict_members: bool = True, + can_invite_users: bool = True, + can_pin_messages: bool = False, + can_promote_members: bool = False + ) -> bool: """Use this method to promote or demote a user in a supergroup or a channel. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/client/methods/chats/set_chat_description.py index e6c5bba1..795c0504 100644 --- a/pyrogram/client/methods/chats/set_chat_description.py +++ b/pyrogram/client/methods/chats/set_chat_description.py @@ -23,9 +23,11 @@ from ...ext import BaseClient class SetChatDescription(BaseClient): - def set_chat_description(self, - chat_id: Union[int, str], - description: str) -> bool: + def set_chat_description( + self, + chat_id: Union[int, str], + description: str + ) -> bool: """Use this method to change the description of a supergroup or a channel. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index cc5c776a..e2fdaab2 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -26,9 +26,11 @@ from ...ext import BaseClient class SetChatPhoto(BaseClient): - def set_chat_photo(self, - chat_id: Union[int, str], - photo: str) -> bool: + def set_chat_photo( + self, + chat_id: Union[int, str], + photo: str + ) -> bool: """Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/set_chat_title.py b/pyrogram/client/methods/chats/set_chat_title.py index fff330ee..1c953ee1 100644 --- a/pyrogram/client/methods/chats/set_chat_title.py +++ b/pyrogram/client/methods/chats/set_chat_title.py @@ -23,9 +23,11 @@ from ...ext import BaseClient class SetChatTitle(BaseClient): - def set_chat_title(self, - chat_id: Union[int, str], - title: str) -> bool: + def set_chat_title( + self, + chat_id: Union[int, str], + title: str + ) -> bool: """Use this method to change the title of a chat. Titles can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/pyrogram/client/methods/chats/unban_chat_member.py b/pyrogram/client/methods/chats/unban_chat_member.py index 4c68b14f..3000648f 100644 --- a/pyrogram/client/methods/chats/unban_chat_member.py +++ b/pyrogram/client/methods/chats/unban_chat_member.py @@ -23,9 +23,11 @@ from ...ext import BaseClient class UnbanChatMember(BaseClient): - def unban_chat_member(self, - chat_id: Union[int, str], - user_id: Union[int, str]) -> bool: + def unban_chat_member( + self, + chat_id: Union[int, str], + user_id: Union[int, str] + ) -> bool: """Use this method to unban a previously kicked user in a supergroup or channel. The user will **not** return to the group or channel automatically, but will be able to join via link, etc. You must be an administrator for this to work. diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/client/methods/chats/unpin_chat_message.py index 4355010d..8030d966 100644 --- a/pyrogram/client/methods/chats/unpin_chat_message.py +++ b/pyrogram/client/methods/chats/unpin_chat_message.py @@ -23,8 +23,10 @@ from ...ext import BaseClient class UnpinChatMessage(BaseClient): - def unpin_chat_message(self, - chat_id: Union[int, str]) -> bool: + def unpin_chat_message( + self, + chat_id: Union[int, str] + ) -> bool: """Use this method to unpin a message in a group, channel or your own chat. You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. diff --git a/pyrogram/client/methods/chats/update_chat_username.py b/pyrogram/client/methods/chats/update_chat_username.py index 353c338e..cc6416a9 100644 --- a/pyrogram/client/methods/chats/update_chat_username.py +++ b/pyrogram/client/methods/chats/update_chat_username.py @@ -23,9 +23,11 @@ from ...ext import BaseClient class UpdateChatUsername(BaseClient): - def update_chat_username(self, - chat_id: Union[int, str], - username: Union[str, None]) -> bool: + def update_chat_username( + self, + chat_id: Union[int, str], + username: Union[str, None] + ) -> bool: """Use this method to update a channel or a supergroup username. To update your own username (for users only, not bots) you can use :meth:`update_username`. diff --git a/pyrogram/client/methods/contacts/add_contacts.py b/pyrogram/client/methods/contacts/add_contacts.py index 48575a78..aa98fef2 100644 --- a/pyrogram/client/methods/contacts/add_contacts.py +++ b/pyrogram/client/methods/contacts/add_contacts.py @@ -24,8 +24,10 @@ from ...ext import BaseClient class AddContacts(BaseClient): - def add_contacts(self, - contacts: List["pyrogram.InputPhoneContact"]): + def add_contacts( + self, + contacts: List["pyrogram.InputPhoneContact"] + ): """Use this method to add contacts to your Telegram address book. Args: diff --git a/pyrogram/client/methods/contacts/delete_contacts.py b/pyrogram/client/methods/contacts/delete_contacts.py index dba2581d..c7e7c0e6 100644 --- a/pyrogram/client/methods/contacts/delete_contacts.py +++ b/pyrogram/client/methods/contacts/delete_contacts.py @@ -24,8 +24,10 @@ from ...ext import BaseClient class DeleteContacts(BaseClient): - def delete_contacts(self, - ids: List[int]): + def delete_contacts( + self, + ids: List[int] + ): """Use this method to delete contacts from your Telegram address book Args: diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py index bf0a823a..f030f929 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/client/methods/decorators/on_callback_query.py @@ -25,9 +25,11 @@ from ...ext import BaseClient class OnCallbackQuery(BaseClient): - def on_callback_query(self=None, - filters=None, - group: int = 0) -> callable: + def on_callback_query( + self=None, + filters=None, + group: int = 0 + ) -> callable: """Use this decorator to automatically register a function for handling callback queries. This does the same thing as :meth:`add_handler` using the :class:`CallbackQueryHandler`. diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index 1b1a602b..b5a95381 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.py @@ -25,9 +25,11 @@ from ...ext import BaseClient class OnDeletedMessages(BaseClient): - def on_deleted_messages(self=None, - filters=None, - group: int = 0) -> callable: + def on_deleted_messages( + self=None, + filters=None, + group: int = 0 + ) -> callable: """Use this decorator to automatically register a function for handling deleted messages. This does the same thing as :meth:`add_handler` using the :class:`DeletedMessagesHandler`. diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index c2f35a19..41eb73e5 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.py @@ -25,9 +25,11 @@ from ...ext import BaseClient class OnMessage(BaseClient): - def on_message(self=None, - filters=None, - group: int = 0) -> callable: + def on_message( + self=None, + filters=None, + group: int = 0 + ) -> callable: """Use this decorator to automatically register a function for handling messages. This does the same thing as :meth:`add_handler` using the :class:`MessageHandler`. diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py index 2deedb00..a176ab50 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -24,8 +24,10 @@ from ...ext import BaseClient class OnRawUpdate(BaseClient): - def on_raw_update(self=None, - group: int = 0) -> callable: + def on_raw_update( + self=None, + group: int = 0 + ) -> callable: """Use this decorator to automatically register a function for handling raw updates. This does the same thing as :meth:`add_handler` using the :class:`RawUpdateHandler`. diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py index 6198c9cd..580dc498 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/client/methods/decorators/on_user_status.py @@ -25,9 +25,11 @@ from ...ext import BaseClient class OnUserStatus(BaseClient): - def on_user_status(self=None, - filters=None, - group: int = 0) -> callable: + def on_user_status( + self=None, + filters=None, + group: int = 0 + ) -> callable: """Use this decorator to automatically register a function for handling user status updates. This does the same thing as :meth:`add_handler` using the :class:`UserStatusHandler`. diff --git a/pyrogram/client/methods/messages/close_poll.py b/pyrogram/client/methods/messages/close_poll.py index c2d2706b..ac4fc197 100644 --- a/pyrogram/client/methods/messages/close_poll.py +++ b/pyrogram/client/methods/messages/close_poll.py @@ -23,9 +23,11 @@ from pyrogram.client.ext import BaseClient class ClosePoll(BaseClient): - def close_poll(self, - chat_id: Union[int, str], - message_id: id) -> bool: + def close_poll( + self, + chat_id: Union[int, str], + message_id: id + ) -> bool: """Use this method to close (stop) a poll. Closed polls can't be reopened and nobody will be able to vote in it anymore. diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/client/methods/messages/delete_messages.py index 06acbb1f..8ea729ff 100644 --- a/pyrogram/client/methods/messages/delete_messages.py +++ b/pyrogram/client/methods/messages/delete_messages.py @@ -23,10 +23,12 @@ from pyrogram.client.ext import BaseClient class DeleteMessages(BaseClient): - def delete_messages(self, - chat_id: Union[int, str], - message_ids: Iterable[int], - revoke: bool = True) -> bool: + def delete_messages( + self, + chat_id: Union[int, str], + message_ids: Iterable[int], + revoke: bool = True + ) -> bool: """Use this method to delete messages, including service messages, with the following limitations: - A message can only be deleted if it was sent less than 48 hours ago. diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 1c80f63a..29ba7af5 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -24,12 +24,14 @@ from pyrogram.client.ext import BaseClient class DownloadMedia(BaseClient): - def download_media(self, - message: Union["pyrogram.Message", str], - file_name: str = "", - block: bool = True, - progress: callable = None, - progress_args: tuple = ()) -> Union[str, None]: + def download_media( + self, + message: Union["pyrogram.Message", str], + file_name: str = "", + block: bool = True, + progress: callable = None, + progress_args: tuple = () + ) -> Union[str, None]: """Use this method to download the media from a Message. Args: diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index fe704e41..ce393319 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -24,12 +24,14 @@ from pyrogram.client.ext import BaseClient class EditMessageCaption(BaseClient): - def edit_message_caption(self, - chat_id: Union[int, str], - message_id: int, - caption: str, - parse_mode: str = "", - reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "pyrogram.Message": + def edit_message_caption( + self, + chat_id: Union[int, str], + message_id: int, + caption: str, + parse_mode: str = "", + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> "pyrogram.Message": """Use this method to edit captions of messages. Args: diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 2f82e2ea..cbb00aa3 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -33,11 +33,13 @@ from pyrogram.client.types.input_media import InputMedia class EditMessageMedia(BaseClient): - def edit_message_media(self, - chat_id: Union[int, str], - message_id: int, - media: InputMedia, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "pyrogram.Message": + def edit_message_media( + self, + chat_id: Union[int, str], + message_id: int, + media: InputMedia, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> "pyrogram.Message": """Use this method to edit audio, document, photo, or video messages. If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, diff --git a/pyrogram/client/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py index f8b5c0a5..68455131 100644 --- a/pyrogram/client/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py @@ -24,10 +24,12 @@ from pyrogram.client.ext import BaseClient class EditMessageReplyMarkup(BaseClient): - def edit_message_reply_markup(self, - chat_id: Union[int, str], - message_id: int, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "pyrogram.Message": + def edit_message_reply_markup( + self, + chat_id: Union[int, str], + message_id: int, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> "pyrogram.Message": """Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots). Args: diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index 1d2a065b..f4c5f6cf 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -24,13 +24,15 @@ from pyrogram.client.ext import BaseClient class EditMessageText(BaseClient): - def edit_message_text(self, - chat_id: Union[int, str], - message_id: int, - text: str, - parse_mode: str = "", - disable_web_page_preview: bool = None, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "pyrogram.Message": + def edit_message_text( + self, + chat_id: Union[int, str], + message_id: int, + text: str, + parse_mode: str = "", + disable_web_page_preview: bool = None, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> "pyrogram.Message": """Use this method to edit text messages. Args: diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index 72e43e08..35d406ae 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -24,11 +24,13 @@ from ...ext import BaseClient class ForwardMessages(BaseClient): - def forward_messages(self, - chat_id: Union[int, str], - from_chat_id: Union[int, str], - message_ids: Iterable[int], - disable_notification: bool = None) -> "pyrogram.Messages": + def forward_messages( + self, + chat_id: Union[int, str], + from_chat_id: Union[int, str], + message_ids: Iterable[int], + disable_notification: bool = None + ) -> "pyrogram.Messages": """Use this method to forward messages of any kind. Args: diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 73923b44..ca357204 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -29,13 +29,15 @@ log = logging.getLogger(__name__) class GetHistory(BaseClient): - def get_history(self, - chat_id: Union[int, str], - limit: int = 100, - offset: int = 0, - offset_id: int = 0, - offset_date: int = 0, - reverse: bool = False): + def get_history( + self, + chat_id: Union[int, str], + limit: int = 100, + offset: int = 0, + offset_id: int = 0, + offset_date: int = 0, + reverse: bool = False + ): """Use this method to retrieve a chunk of the history of a chat. You can get up to 100 messages at once. diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index 40b1b0ee..63fee163 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -29,11 +29,13 @@ log = logging.getLogger(__name__) class GetMessages(BaseClient): - def get_messages(self, - chat_id: Union[int, str], - message_ids: Union[int, Iterable[int]] = None, - reply_to_message_ids: Union[int, Iterable[int]] = None, - replies: int = 1) -> Union["pyrogram.Message", "pyrogram.Messages"]: + def get_messages( + self, + chat_id: Union[int, str], + message_ids: Union[int, Iterable[int]] = None, + reply_to_message_ids: Union[int, Iterable[int]] = None, + replies: int = 1 + ) -> Union["pyrogram.Message", "pyrogram.Messages"]: """Use this method to get one or more messages that belong to a specific chat. You can retrieve up to 200 messages at once. diff --git a/pyrogram/client/methods/messages/iter_history.py b/pyrogram/client/methods/messages/iter_history.py index ab587988..92dc7584 100644 --- a/pyrogram/client/methods/messages/iter_history.py +++ b/pyrogram/client/methods/messages/iter_history.py @@ -23,13 +23,15 @@ from ...ext import BaseClient class IterHistory(BaseClient): - def iter_history(self, - chat_id: Union[int, str], - limit: int = 0, - offset: int = 0, - offset_id: int = 0, - offset_date: int = 0, - reverse: bool = False) -> Generator["pyrogram.Message", None, None]: + def iter_history( + self, + chat_id: Union[int, str], + limit: int = 0, + offset: int = 0, + offset_id: int = 0, + offset_date: int = 0, + reverse: bool = False + ) -> Generator["pyrogram.Message", None, None]: """Use this method to iterate through a chat history sequentially. This convenience method does the same as repeatedly calling :meth:`get_history` in a loop, thus saving you from diff --git a/pyrogram/client/methods/messages/retract_vote.py b/pyrogram/client/methods/messages/retract_vote.py index 7893f768..e7ffe19b 100644 --- a/pyrogram/client/methods/messages/retract_vote.py +++ b/pyrogram/client/methods/messages/retract_vote.py @@ -23,9 +23,11 @@ from pyrogram.client.ext import BaseClient class RetractVote(BaseClient): - def retract_vote(self, - chat_id: Union[int, str], - message_id: id) -> bool: + def retract_vote( + self, + chat_id: Union[int, str], + message_id: id + ) -> bool: """Use this method to retract your vote in a poll. Args: diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 5ac902ed..454d25ff 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -28,23 +28,27 @@ from pyrogram.client.ext import BaseClient, utils class SendAnimation(BaseClient): - def send_animation(self, - chat_id: Union[int, str], - animation: str, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - width: int = 0, - height: int = 0, - thumb: str = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None, - progress: callable = None, - progress_args: tuple = ()) -> Union["pyrogram.Message", None]: + def send_animation( + self, + chat_id: Union[int, str], + animation: str, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + width: int = 0, + height: int = 0, + thumb: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> Union["pyrogram.Message", None]: """Use this method to send animation files (animation or H.264/MPEG-4 AVC video without sound). Args: diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index a4f5512b..d99cf744 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -28,23 +28,27 @@ from pyrogram.client.ext import BaseClient, utils class SendAudio(BaseClient): - def send_audio(self, - chat_id: Union[int, str], - audio: str, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - performer: str = None, - title: str = None, - thumb: str = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None, - progress: callable = None, - progress_args: tuple = ()) -> Union["pyrogram.Message", None]: + def send_audio( + self, + chat_id: Union[int, str], + audio: str, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + performer: str = None, + title: str = None, + thumb: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> Union["pyrogram.Message", None]: """Use this method to send audio files. For sending voice messages, use the :obj:`send_voice()` method instead. diff --git a/pyrogram/client/methods/messages/send_chat_action.py b/pyrogram/client/methods/messages/send_chat_action.py index 479e7abb..bfddf90b 100644 --- a/pyrogram/client/methods/messages/send_chat_action.py +++ b/pyrogram/client/methods/messages/send_chat_action.py @@ -23,10 +23,12 @@ from pyrogram.client.ext import BaseClient, ChatAction class SendChatAction(BaseClient): - def send_chat_action(self, - chat_id: Union[int, str], - action: Union[ChatAction, str], - progress: int = 0): + def send_chat_action( + self, + chat_id: Union[int, str], + action: Union[ChatAction, str], + progress: int = 0 + ): """Use this method when you need to tell the other party that something is happening on your side. Args: diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index 96b23a9b..bed9a37e 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -24,18 +24,22 @@ from pyrogram.client.ext import BaseClient class SendContact(BaseClient): - def send_contact(self, - chat_id: Union[int, str], - phone_number: str, - first_name: str, - last_name: str = "", - vcard: str = "", - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None) -> "pyrogram.Message": + def send_contact( + self, + chat_id: Union[int, str], + phone_number: str, + first_name: str, + last_name: str = "", + vcard: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "pyrogram.Message": """Use this method to send phone contacts. Args: diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 6dc496e4..2693697a 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -28,20 +28,24 @@ from pyrogram.client.ext import BaseClient, utils class SendDocument(BaseClient): - def send_document(self, - chat_id: Union[int, str], - document: str, - thumb: str = None, - caption: str = "", - parse_mode: str = "", - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None, - progress: callable = None, - progress_args: tuple = ()) -> Union["pyrogram.Message", None]: + def send_document( + self, + chat_id: Union[int, str], + document: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> Union["pyrogram.Message", None]: """Use this method to send general files. Args: diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py index 5fe57904..c59ea380 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/client/methods/messages/send_location.py @@ -24,16 +24,20 @@ from pyrogram.client.ext import BaseClient class SendLocation(BaseClient): - def send_location(self, - chat_id: Union[int, str], - latitude: float, - longitude: float, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None) -> "pyrogram.Message": + def send_location( + self, + chat_id: Union[int, str], + latitude: float, + longitude: float, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "pyrogram.Message": """Use this method to send points on the map. Args: diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 42e4ddde..aff0a29f 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -34,11 +34,13 @@ log = logging.getLogger(__name__) class SendMediaGroup(BaseClient): # TODO: Add progress parameter # TODO: Figure out how to send albums using URLs - def send_media_group(self, - chat_id: Union[int, str], - media: List[Union["pyrogram.InputMediaPhoto", "pyrogram.InputMediaVideo"]], - disable_notification: bool = None, - reply_to_message_id: int = None): + def send_media_group( + self, + chat_id: Union[int, str], + media: List[Union["pyrogram.InputMediaPhoto", "pyrogram.InputMediaVideo"]], + disable_notification: bool = None, + reply_to_message_id: int = None + ): """Use this method to send a group of photos or videos as an album. Args: diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 6589fcd6..3913e97d 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -24,17 +24,21 @@ from ...ext import BaseClient class SendMessage(BaseClient): - def send_message(self, - chat_id: Union[int, str], - text: str, - parse_mode: str = "", - disable_web_page_preview: bool = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None) -> "pyrogram.Message": + def send_message( + self, + chat_id: Union[int, str], + text: str, + parse_mode: str = "", + disable_web_page_preview: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "pyrogram.Message": """Use this method to send text messages. Args: diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 84647245..6892f92d 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -28,20 +28,24 @@ from pyrogram.client.ext import BaseClient, utils class SendPhoto(BaseClient): - def send_photo(self, - chat_id: Union[int, str], - photo: str, - caption: str = "", - parse_mode: str = "", - ttl_seconds: int = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None, - progress: callable = None, - progress_args: tuple = ()) -> Union["pyrogram.Message", None]: + def send_photo( + self, + chat_id: Union[int, str], + photo: str, + caption: str = "", + parse_mode: str = "", + ttl_seconds: int = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> Union["pyrogram.Message", None]: """Use this method to send photos. Args: diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index 8e938a1a..423f962d 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -24,16 +24,20 @@ from pyrogram.client.ext import BaseClient class SendPoll(BaseClient): - def send_poll(self, - chat_id: Union[int, str], - question: str, - options: List[str], - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None) -> "pyrogram.Message": + def send_poll \ + (self, + chat_id: Union[int, str], + question: str, + options: List[str], + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "pyrogram.Message": """Use this method to send a new poll. Args: diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index ba16a2e7..b4441cb1 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -28,17 +28,21 @@ from pyrogram.client.ext import BaseClient, utils class SendSticker(BaseClient): - def send_sticker(self, - chat_id: Union[int, str], - sticker: str, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None, - progress: callable = None, - progress_args: tuple = ()) -> Union["pyrogram.Message", None]: + def send_sticker( + self, + chat_id: Union[int, str], + sticker: str, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> Union["pyrogram.Message", None]: """Use this method to send .webp stickers. Args: diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/client/methods/messages/send_venue.py index 163be38e..cc98eb28 100644 --- a/pyrogram/client/methods/messages/send_venue.py +++ b/pyrogram/client/methods/messages/send_venue.py @@ -24,20 +24,24 @@ from pyrogram.client.ext import BaseClient class SendVenue(BaseClient): - def send_venue(self, - chat_id: Union[int, str], - latitude: float, - longitude: float, - title: str, - address: str, - foursquare_id: str = "", - foursquare_type: str = "", - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None) -> "pyrogram.Message": + def send_venue( + self, + chat_id: Union[int, str], + latitude: float, + longitude: float, + title: str, + address: str, + foursquare_id: str = "", + foursquare_type: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "pyrogram.Message": """Use this method to send information about a venue. Args: diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 0224eaf6..bb90bdf6 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -28,24 +28,28 @@ from pyrogram.client.ext import BaseClient, utils class SendVideo(BaseClient): - def send_video(self, - chat_id: Union[int, str], - video: str, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - width: int = 0, - height: int = 0, - thumb: str = None, - supports_streaming: bool = True, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None, - progress: callable = None, - progress_args: tuple = ()) -> Union["pyrogram.Message", None]: + def send_video( + self, + chat_id: Union[int, str], + video: str, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + width: int = 0, + height: int = 0, + thumb: str = None, + supports_streaming: bool = True, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> Union["pyrogram.Message", None]: """Use this method to send video files. Args: diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index b35dbea6..349b76a6 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -28,20 +28,24 @@ from pyrogram.client.ext import BaseClient, utils class SendVideoNote(BaseClient): - def send_video_note(self, - chat_id: Union[int, str], - video_note: str, - duration: int = 0, - length: int = 1, - thumb: str = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None, - progress: callable = None, - progress_args: tuple = ()) -> Union["pyrogram.Message", None]: + def send_video_note( + self, + chat_id: Union[int, str], + video_note: str, + duration: int = 0, + length: int = 1, + thumb: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> Union["pyrogram.Message", None]: """Use this method to send video messages. Args: diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 33261db6..10b3c906 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -28,20 +28,24 @@ from pyrogram.client.ext import BaseClient, utils class SendVoice(BaseClient): - def send_voice(self, - chat_id: Union[int, str], - voice: str, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None, - progress: callable = None, - progress_args: tuple = ()) -> Union["pyrogram.Message", None]: + def send_voice( + self, + chat_id: Union[int, str], + voice: str, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> Union["pyrogram.Message", None]: """Use this method to send audio files. Args: diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py index 9e400e62..bf44c831 100644 --- a/pyrogram/client/methods/messages/vote_poll.py +++ b/pyrogram/client/methods/messages/vote_poll.py @@ -23,10 +23,12 @@ from pyrogram.client.ext import BaseClient class VotePoll(BaseClient): - def vote_poll(self, - chat_id: Union[int, str], - message_id: id, - option: int) -> bool: + def vote_poll( + self, + chat_id: Union[int, str], + message_id: id, + option: int + ) -> bool: """Use this method to vote a poll. Args: diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index 3504dab1..163144bf 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -24,10 +24,12 @@ from ...ext import BaseClient class ChangeCloudPassword(BaseClient): - def change_cloud_password(self, - current_password: str, - new_password: str, - new_hint: str = "") -> bool: + def change_cloud_password( + self, + current_password: str, + new_password: str, + new_hint: str = "" + ) -> bool: """Use this method to change your Two-Step Verification password (Cloud Password) with a new one. Args: diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index 980f50fd..6e7a0bc9 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -24,10 +24,12 @@ from ...ext import BaseClient class EnableCloudPassword(BaseClient): - def enable_cloud_password(self, - password: str, - hint: str = "", - email: str = None) -> bool: + def enable_cloud_password( + self, + password: str, + hint: str = "", + email: str = None + ) -> bool: """Use this method to enable the Two-Step Verification security feature (Cloud Password) on your account. This password will be asked when you log-in on a new device in addition to the SMS code. diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py index 6817ab12..e7c7ed2d 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -22,8 +22,10 @@ from ...ext import BaseClient class RemoveCloudPassword(BaseClient): - def remove_cloud_password(self, - password: str) -> bool: + def remove_cloud_password( + self, + password: str + ) -> bool: """Use this method to turn off the Two-Step Verification security feature (Cloud Password) on your account. Args: diff --git a/pyrogram/client/methods/users/delete_user_profile_photos.py b/pyrogram/client/methods/users/delete_user_profile_photos.py index 025a5e95..84c68dd4 100644 --- a/pyrogram/client/methods/users/delete_user_profile_photos.py +++ b/pyrogram/client/methods/users/delete_user_profile_photos.py @@ -25,8 +25,10 @@ from ...ext import BaseClient class DeleteUserProfilePhotos(BaseClient): - def delete_user_profile_photos(self, - id: Union[str, List[str]]) -> bool: + def delete_user_profile_photos( + self, + id: Union[str, List[str]] + ) -> bool: """Use this method to delete your own profile photos Args: diff --git a/pyrogram/client/methods/users/get_user_profile_photos.py b/pyrogram/client/methods/users/get_user_profile_photos.py index 2129dba3..d23ec48d 100644 --- a/pyrogram/client/methods/users/get_user_profile_photos.py +++ b/pyrogram/client/methods/users/get_user_profile_photos.py @@ -24,10 +24,12 @@ from ...ext import BaseClient class GetUserProfilePhotos(BaseClient): - def get_user_profile_photos(self, - user_id: Union[int, str], - offset: int = 0, - limit: int = 100) -> "pyrogram.UserProfilePhotos": + def get_user_profile_photos( + self, + user_id: Union[int, str], + offset: int = 0, + limit: int = 100 + ) -> "pyrogram.UserProfilePhotos": """Use this method to get a list of profile pictures for a user. Args: diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py index 6c340ffa..d3822dc9 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/client/methods/users/get_users.py @@ -24,8 +24,10 @@ from ...ext import BaseClient class GetUsers(BaseClient): - def get_users(self, - user_ids: Iterable[Union[int, str]]) -> Union["pyrogram.User", List["pyrogram.User"]]: + def get_users( + self, + user_ids: Iterable[Union[int, str]] + ) -> Union["pyrogram.User", List["pyrogram.User"]]: """Use this method to get information about a user. You can retrieve up to 200 users at once. diff --git a/pyrogram/client/methods/users/set_user_profile_photo.py b/pyrogram/client/methods/users/set_user_profile_photo.py index 359a0cd0..705631fd 100644 --- a/pyrogram/client/methods/users/set_user_profile_photo.py +++ b/pyrogram/client/methods/users/set_user_profile_photo.py @@ -21,8 +21,10 @@ from ...ext import BaseClient class SetUserProfilePhoto(BaseClient): - def set_user_profile_photo(self, - photo: str) -> bool: + def set_user_profile_photo( + self, + photo: str + ) -> bool: """Use this method to set a new profile photo. This method only works for Users. diff --git a/pyrogram/client/methods/users/update_username.py b/pyrogram/client/methods/users/update_username.py index 9a4feb23..5fd1a711 100644 --- a/pyrogram/client/methods/users/update_username.py +++ b/pyrogram/client/methods/users/update_username.py @@ -23,8 +23,10 @@ from ...ext import BaseClient class UpdateUsername(BaseClient): - def update_username(self, - username: Union[str, None]) -> bool: + def update_username( + self, + username: Union[str, None] + ) -> bool: """Use this method to update your own username. This method only works for users, not bots. Bot usernames must be changed via Bot Support or by recreating diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index a7a9ad53..4497747e 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -61,16 +61,16 @@ class CallbackQuery(PyrogramType, Update): __slots__ = ["id", "from_user", "chat_instance", "message", "inline_message_id", "data", "game_short_name"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - id: str, - from_user: User, - chat_instance: str, - message: "pyrogram.Message" = None, - inline_message_id: str = None, - data: bytes = None, - game_short_name: str = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: str, + from_user: User, + chat_instance: str, + message: "pyrogram.Message" = None, + inline_message_id: str = None, + data: bytes = None, + game_short_name: str = None ): super().__init__(client) diff --git a/pyrogram/client/types/bots/force_reply.py b/pyrogram/client/types/bots/force_reply.py index 7838ee88..fca2c061 100644 --- a/pyrogram/client/types/bots/force_reply.py +++ b/pyrogram/client/types/bots/force_reply.py @@ -36,8 +36,8 @@ class ForceReply(PyrogramType): __slots__ = ["selective"] def __init__( - self, - selective: bool = None + self, + selective: bool = None ): super().__init__(None) diff --git a/pyrogram/client/types/bots/game_high_score.py b/pyrogram/client/types/bots/game_high_score.py index 4fc3c6f8..da6b2881 100644 --- a/pyrogram/client/types/bots/game_high_score.py +++ b/pyrogram/client/types/bots/game_high_score.py @@ -40,12 +40,12 @@ class GameHighScore(PyrogramType): __slots__ = ["user", "score", "position"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - user: User, - score: int, - position: int = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + user: User, + score: int, + position: int = None ): super().__init__(client) diff --git a/pyrogram/client/types/bots/game_high_scores.py b/pyrogram/client/types/bots/game_high_scores.py index 25e4af5d..3c197969 100644 --- a/pyrogram/client/types/bots/game_high_scores.py +++ b/pyrogram/client/types/bots/game_high_scores.py @@ -38,11 +38,11 @@ class GameHighScores(PyrogramType): __slots__ = ["total_count", "game_high_scores"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - total_count: int, - game_high_scores: List[GameHighScore] + self, + *, + client: "pyrogram.client.ext.BaseClient", + total_count: int, + game_high_scores: List[GameHighScore] ): super().__init__(client) diff --git a/pyrogram/client/types/bots/inline_keyboard_button.py b/pyrogram/client/types/bots/inline_keyboard_button.py index cca42b33..ff6f3cdb 100644 --- a/pyrogram/client/types/bots/inline_keyboard_button.py +++ b/pyrogram/client/types/bots/inline_keyboard_button.py @@ -59,13 +59,13 @@ class InlineKeyboardButton(PyrogramType): ] def __init__( - self, - text: str, - callback_data: bytes = None, - url: str = None, - switch_inline_query: str = None, - switch_inline_query_current_chat: str = None, - callback_game: CallbackGame = None + self, + text: str, + callback_data: bytes = None, + url: str = None, + switch_inline_query: str = None, + switch_inline_query_current_chat: str = None, + callback_game: CallbackGame = None ): super().__init__(None) diff --git a/pyrogram/client/types/bots/inline_keyboard_markup.py b/pyrogram/client/types/bots/inline_keyboard_markup.py index 77e9cfeb..54476c5e 100644 --- a/pyrogram/client/types/bots/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots/inline_keyboard_markup.py @@ -34,8 +34,8 @@ class InlineKeyboardMarkup(PyrogramType): __slots__ = ["inline_keyboard"] def __init__( - self, - inline_keyboard: List[List[InlineKeyboardButton]] + self, + inline_keyboard: List[List[InlineKeyboardButton]] ): super().__init__(None) diff --git a/pyrogram/client/types/bots/keyboard_button.py b/pyrogram/client/types/bots/keyboard_button.py index b8786cee..477442cc 100644 --- a/pyrogram/client/types/bots/keyboard_button.py +++ b/pyrogram/client/types/bots/keyboard_button.py @@ -43,10 +43,10 @@ class KeyboardButton(PyrogramType): __slots__ = ["text", "request_contact", "request_location"] def __init__( - self, - text: str, - request_contact: bool = None, - request_location: bool = None + self, + text: str, + request_contact: bool = None, + request_location: bool = None ): super().__init__(None) diff --git a/pyrogram/client/types/bots/reply_keyboard_markup.py b/pyrogram/client/types/bots/reply_keyboard_markup.py index 431a55eb..b0216803 100644 --- a/pyrogram/client/types/bots/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots/reply_keyboard_markup.py @@ -52,11 +52,11 @@ class ReplyKeyboardMarkup(PyrogramType): __slots__ = ["keyboard", "resize_keyboard", "one_time_keyboard", "selective"] def __init__( - self, - keyboard: List[List[Union[KeyboardButton, str]]], - resize_keyboard: bool = None, - one_time_keyboard: bool = None, - selective: bool = None + self, + keyboard: List[List[Union[KeyboardButton, str]]], + resize_keyboard: bool = None, + one_time_keyboard: bool = None, + selective: bool = None ): super().__init__(None) diff --git a/pyrogram/client/types/bots/reply_keyboard_remove.py b/pyrogram/client/types/bots/reply_keyboard_remove.py index 2fb5fe19..3298ab6f 100644 --- a/pyrogram/client/types/bots/reply_keyboard_remove.py +++ b/pyrogram/client/types/bots/reply_keyboard_remove.py @@ -38,8 +38,8 @@ class ReplyKeyboardRemove(PyrogramType): __slots__ = ["selective"] def __init__( - self, - selective: bool = None + self, + selective: bool = None ): super().__init__(None) diff --git a/pyrogram/client/types/input_media/input_media.py b/pyrogram/client/types/input_media/input_media.py index 677d4918..aeeef350 100644 --- a/pyrogram/client/types/input_media/input_media.py +++ b/pyrogram/client/types/input_media/input_media.py @@ -21,10 +21,10 @@ class InputMedia: __slots__ = ["media", "caption", "parse_mode"] def __init__( - self, - media: str, - caption: str, - parse_mode: str + self, + media: str, + caption: str, + parse_mode: str ): self.media = media self.caption = caption diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index 20debb19..e77499b5 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -55,14 +55,14 @@ class InputMediaAnimation(InputMedia): __slots__ = ["thumb", "width", "height", "duration"] def __init__( - self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = "", - width: int = 0, - height: int = 0, - duration: int = 0 + self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + width: int = 0, + height: int = 0, + duration: int = 0 ): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index 2709a409..e8f1c257 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -56,14 +56,14 @@ class InputMediaAudio(InputMedia): __slots__ = ["thumb", "duration", "performer", "title"] def __init__( - self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - performer: int = "", - title: str = "" + self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + performer: int = "", + title: str = "" ): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index 2c0c628d..9391e7d8 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -46,11 +46,11 @@ class InputMediaDocument(InputMedia): __slots__ = ["thumb"] def __init__( - self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = "" + self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "" ): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py index 5618b843..e6bba03b 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -42,9 +42,9 @@ class InputMediaPhoto(InputMedia): __slots__ = [] def __init__( - self, - media: str, - caption: str = "", - parse_mode: str = "" + self, + media: str, + caption: str = "", + parse_mode: str = "" ): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index b55a9438..5c918f13 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -60,15 +60,15 @@ class InputMediaVideo(InputMedia): __slots__ = ["thumb", "width", "height", "duration", "supports_streaming"] def __init__( - self, - media: str, - thumb: str = None, - caption: str = "", - parse_mode: str = "", - width: int = 0, - height: int = 0, - duration: int = 0, - supports_streaming: bool = True + self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + width: int = 0, + height: int = 0, + duration: int = 0, + supports_streaming: bool = True ): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_phone_contact.py b/pyrogram/client/types/input_media/input_phone_contact.py index 340b335f..4fe7ee60 100644 --- a/pyrogram/client/types/input_media/input_phone_contact.py +++ b/pyrogram/client/types/input_media/input_phone_contact.py @@ -38,10 +38,10 @@ class InputPhoneContact: __slots__ = [] def __init__( - self, - phone: str, - first_name: str, - last_name: str = "" + self, + phone: str, + first_name: str, + last_name: str = "" ): pass diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index 978c88c9..21a01e0f 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -60,18 +60,18 @@ class Animation(PyrogramType): __slots__ = ["file_id", "thumb", "file_name", "mime_type", "file_size", "date", "width", "height", "duration"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - width: int, - height: int, - duration: int, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + width: int, + height: int, + duration: int, + thumb: PhotoSize = None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 334e022e..db49f2eb 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -60,18 +60,18 @@ class Audio(PyrogramType): __slots__ = ["file_id", "thumb", "file_name", "mime_type", "file_size", "date", "duration", "performer", "title"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - duration: int, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None, - performer: str = None, - title: str = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + duration: int, + thumb: PhotoSize = None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None, + performer: str = None, + title: str = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index 0a897c05..5abe5319 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -45,14 +45,14 @@ class Contact(PyrogramType): __slots__ = ["phone_number", "first_name", "last_name", "user_id", "vcard"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - phone_number: str, - first_name: str, - last_name: str = None, - user_id: int = None, - vcard: str = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + phone_number: str, + first_name: str, + last_name: str = None, + user_id: int = None, + vcard: str = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index b3f5b586..f3ccc4f8 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -51,15 +51,15 @@ class Document(PyrogramType): __slots__ = ["file_id", "thumb", "file_name", "mime_type", "file_size", "date"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + thumb: PhotoSize = None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/game.py b/pyrogram/client/types/messages_and_media/game.py index ddfa84af..cf0b4fa6 100644 --- a/pyrogram/client/types/messages_and_media/game.py +++ b/pyrogram/client/types/messages_and_media/game.py @@ -51,15 +51,15 @@ class Game(PyrogramType): __slots__ = ["id", "title", "short_name", "description", "photo", "animation"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - id: int, - title: str, - short_name: str, - description: str, - photo: Photo, - animation: Animation = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: int, + title: str, + short_name: str, + description: str, + photo: Photo, + animation: Animation = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index 0d2e6c43..3a7f6d38 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -36,11 +36,11 @@ class Location(PyrogramType): __slots__ = ["longitude", "latitude"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - longitude: float, - latitude: float + self, + *, + client: "pyrogram.client.ext.BaseClient", + longitude: float, + latitude: float ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 405c26d9..9a4e4314 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -246,67 +246,67 @@ class Message(PyrogramType, Update): ] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - message_id: int, - date: int = None, - chat: Chat = None, - from_user: User = None, - forward_from: User = None, - forward_from_chat: Chat = None, - forward_from_message_id: int = None, - forward_signature: str = None, - forward_date: int = None, - reply_to_message: "Message" = None, - mentioned: bool = None, - empty: bool = None, - service: bool = None, - media: bool = None, - edit_date: int = None, - media_group_id: str = None, - author_signature: str = None, - text: str = None, - entities: List["pyrogram.MessageEntity"] = None, - caption_entities: List["pyrogram.MessageEntity"] = None, - audio: "pyrogram.Audio" = None, - document: "pyrogram.Document" = None, - photo: "pyrogram.Photo" = None, - sticker: "pyrogram.Sticker" = None, - animation: "pyrogram.Animation" = None, - game: "pyrogram.Game" = None, - video: "pyrogram.Video" = None, - voice: "pyrogram.Voice" = None, - video_note: "pyrogram.VideoNote" = None, - caption: str = None, - contact: "pyrogram.Contact" = None, - location: "pyrogram.Location" = None, - venue: "pyrogram.Venue" = None, - web_page: bool = None, - poll: "pyrogram.Poll" = None, - new_chat_members: List[User] = None, - left_chat_member: User = None, - new_chat_title: str = None, - new_chat_photo: "pyrogram.Photo" = None, - delete_chat_photo: bool = None, - group_chat_created: bool = None, - supergroup_chat_created: bool = None, - channel_chat_created: bool = None, - migrate_to_chat_id: int = None, - migrate_from_chat_id: int = None, - pinned_message: "Message" = None, - game_high_score: int = None, - views: int = None, - via_bot: User = None, - outgoing: bool = None, - matches: List[Match] = None, - command: List[str] = None, - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + message_id: int, + date: int = None, + chat: Chat = None, + from_user: User = None, + forward_from: User = None, + forward_from_chat: Chat = None, + forward_from_message_id: int = None, + forward_signature: str = None, + forward_date: int = None, + reply_to_message: "Message" = None, + mentioned: bool = None, + empty: bool = None, + service: bool = None, + media: bool = None, + edit_date: int = None, + media_group_id: str = None, + author_signature: str = None, + text: str = None, + entities: List["pyrogram.MessageEntity"] = None, + caption_entities: List["pyrogram.MessageEntity"] = None, + audio: "pyrogram.Audio" = None, + document: "pyrogram.Document" = None, + photo: "pyrogram.Photo" = None, + sticker: "pyrogram.Sticker" = None, + animation: "pyrogram.Animation" = None, + game: "pyrogram.Game" = None, + video: "pyrogram.Video" = None, + voice: "pyrogram.Voice" = None, + video_note: "pyrogram.VideoNote" = None, + caption: str = None, + contact: "pyrogram.Contact" = None, + location: "pyrogram.Location" = None, + venue: "pyrogram.Venue" = None, + web_page: bool = None, + poll: "pyrogram.Poll" = None, + new_chat_members: List[User] = None, + left_chat_member: User = None, + new_chat_title: str = None, + new_chat_photo: "pyrogram.Photo" = None, + delete_chat_photo: bool = None, + group_chat_created: bool = None, + supergroup_chat_created: bool = None, + channel_chat_created: bool = None, + migrate_to_chat_id: int = None, + migrate_from_chat_id: int = None, + pinned_message: "Message" = None, + game_high_score: int = None, + views: int = None, + via_bot: User = None, + outgoing: bool = None, + matches: List[Match] = None, + command: List[str] = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index fa59a5bc..160d0d1e 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -66,14 +66,14 @@ class MessageEntity(PyrogramType): } def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - type: str, - offset: int, - length: int, - url: str = None, - user: User = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + type: str, + offset: int, + length: int, + url: str = None, + user: User = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 4a203a4e..9983a01d 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -40,11 +40,11 @@ class Messages(PyrogramType, Update): __slots__ = ["total_count", "messages"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - total_count: int, - messages: List[Message] + self, + *, + client: "pyrogram.client.ext.BaseClient", + total_count: int, + messages: List[Message] ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 12be919c..6f1852fb 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -44,12 +44,12 @@ class Photo(PyrogramType): __slots__ = ["id", "date", "sizes"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - id: str, - date: int, - sizes: List[PhotoSize] + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: str, + date: int, + sizes: List[PhotoSize] ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py index 03569062..10d00a86 100644 --- a/pyrogram/client/types/messages_and_media/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -45,13 +45,13 @@ class PhotoSize(PyrogramType): __slots__ = ["file_id", "width", "height", "file_size"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - width: int, - height: int, - file_size: int + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + width: int, + height: int, + file_size: int ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py index fa6372d9..68667334 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -50,15 +50,15 @@ class Poll(PyrogramType): __slots__ = ["id", "closed", "question", "options", "total_voters", "option_chosen"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - id: int, - closed: bool, - question: str, - options: List[PollOption], - total_voters: int, - option_chosen: int = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: int, + closed: bool, + question: str, + options: List[PollOption], + total_voters: int, + option_chosen: int = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/client/types/messages_and_media/poll_option.py index 1c2d6f1b..a2be866e 100644 --- a/pyrogram/client/types/messages_and_media/poll_option.py +++ b/pyrogram/client/types/messages_and_media/poll_option.py @@ -37,12 +37,12 @@ class PollOption(PyrogramType): __slots__ = ["text", "voters", "data"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - text: str, - voters: int, - data: bytes + self, + *, + client: "pyrogram.client.ext.BaseClient", + text: str, + voters: int, + data: bytes ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index ce390b1d..43fb6e98 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -69,19 +69,19 @@ class Sticker(PyrogramType): ] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - width: int, - height: int, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None, - emoji: str = None, - set_name: str = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + width: int, + height: int, + thumb: PhotoSize = None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None, + emoji: str = None, + set_name: str = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py index 770b0ec8..f162b077 100644 --- a/pyrogram/client/types/messages_and_media/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -37,11 +37,11 @@ class UserProfilePhotos(PyrogramType): __slots__ = ["total_count", "photos"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - total_count: int, - photos: List[Photo] + self, + *, + client: "pyrogram.client.ext.BaseClient", + total_count: int, + photos: List[Photo] ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index 5dd81093..97829142 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -47,14 +47,14 @@ class Venue(PyrogramType): __slots__ = ["location", "title", "address", "foursquare_id", "foursquare_type"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - location: Location, - title: str, - address: str, - foursquare_id: str = None, - foursquare_type: str = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + location: Location, + title: str, + address: str, + foursquare_id: str = None, + foursquare_type: str = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 39ec1455..caf34ce9 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -60,18 +60,18 @@ class Video(PyrogramType): __slots__ = ["file_id", "thumb", "file_name", "mime_type", "file_size", "date", "width", "height", "duration"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - width: int, - height: int, - duration: int, - thumb: PhotoSize = None, - file_name: str = None, - mime_type: str = None, - file_size: int = None, - date: int = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + width: int, + height: int, + duration: int, + thumb: PhotoSize = None, + file_name: str = None, + mime_type: str = None, + file_size: int = None, + date: int = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index afa4ad46..a1b3856c 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -54,16 +54,16 @@ class VideoNote(PyrogramType): __slots__ = ["file_id", "thumb", "mime_type", "file_size", "date", "length", "duration"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - length: int, - duration: int, - thumb: PhotoSize = None, - mime_type: str = None, - file_size: int = None, - date: int = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + length: int, + duration: int, + thumb: PhotoSize = None, + mime_type: str = None, + file_size: int = None, + date: int = None ): super().__init__(client) diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index d5028cd3..b4063088 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -50,15 +50,15 @@ class Voice(PyrogramType): __slots__ = ["file_id", "duration", "waveform", "mime_type", "file_size", "date"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - file_id: str, - duration: int, - waveform: bytes = None, - mime_type: str = None, - file_size: int = None, - date: int = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + file_id: str, + duration: int, + waveform: bytes = None, + mime_type: str = None, + file_size: int = None, + date: int = None ): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 81067821..5488b846 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -87,24 +87,24 @@ class Chat(PyrogramType): ] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - id: int, - type: str, - title: str = None, - username: str = None, - first_name: str = None, - last_name: str = None, - photo: ChatPhoto = None, - description: str = None, - invite_link: str = None, - pinned_message=None, - sticker_set_name: str = None, - can_set_sticker_set: bool = None, - members_count: int = None, - restriction_reason: str = None, - permissions: "pyrogram.ChatPermissions" = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: int, + type: str, + title: str = None, + username: str = None, + first_name: str = None, + last_name: str = None, + photo: ChatPhoto = None, + description: str = None, + invite_link: str = None, + pinned_message=None, + sticker_set_name: str = None, + can_set_sticker_set: bool = None, + members_count: int = None, + restriction_reason: str = None, + permissions: "pyrogram.ChatPermissions" = None ): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 07a3d190..35911210 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -54,16 +54,16 @@ class ChatMember(PyrogramType): __slots__ = ["user", "status", "date", "invited_by", "promoted_by", "restricted_by", "permissions"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - user: "pyrogram.User", - status: str, - date: int = None, - invited_by: "pyrogram.User" = None, - promoted_by: "pyrogram.User" = None, - restricted_by: "pyrogram.User" = None, - permissions: "pyrogram.ChatPermissions" = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + user: "pyrogram.User", + status: str, + date: int = None, + invited_by: "pyrogram.User" = None, + promoted_by: "pyrogram.User" = None, + restricted_by: "pyrogram.User" = None, + permissions: "pyrogram.ChatPermissions" = None ): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py index 0578e04e..3c89b124 100644 --- a/pyrogram/client/types/user_and_chats/chat_members.py +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -38,11 +38,11 @@ class ChatMembers(PyrogramType): __slots__ = ["total_count", "chat_members"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - total_count: int, - chat_members: List[ChatMember] + self, + *, + client: "pyrogram.client.ext.BaseClient", + total_count: int, + chat_members: List[ChatMember] ): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index deec5770..6fbc779d 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -38,11 +38,11 @@ class ChatPhoto(PyrogramType): __slots__ = ["small_file_id", "big_file_id"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - small_file_id: str, - big_file_id: str + self, + *, + client: "pyrogram.client.ext.BaseClient", + small_file_id: str, + big_file_id: str ): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/chat_preview.py b/pyrogram/client/types/user_and_chats/chat_preview.py index 9c9c71ec..ddd84b09 100644 --- a/pyrogram/client/types/user_and_chats/chat_preview.py +++ b/pyrogram/client/types/user_and_chats/chat_preview.py @@ -48,14 +48,14 @@ class ChatPreview(PyrogramType): __slots__ = ["title", "photo", "type", "members_count", "members"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - title: str, - photo: ChatPhoto, - type: str, - members_count: int, - members: List[User] = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + title: str, + photo: ChatPhoto, + type: str, + members_count: int, + members: List[User] = None ): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index 11a1ae19..1bbd3b4b 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -49,15 +49,15 @@ class Dialog(PyrogramType): __slots__ = ["chat", "top_message", "unread_messages_count", "unread_mentions_count", "unread_mark", "is_pinned"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - chat: Chat, - top_message: "pyrogram.Message", - unread_messages_count: int, - unread_mentions_count: int, - unread_mark: bool, - is_pinned: bool + self, + *, + client: "pyrogram.client.ext.BaseClient", + chat: Chat, + top_message: "pyrogram.Message", + unread_messages_count: int, + unread_mentions_count: int, + unread_mark: bool, + is_pinned: bool ): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py index 27222a91..bd29ea83 100644 --- a/pyrogram/client/types/user_and_chats/dialogs.py +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -39,11 +39,11 @@ class Dialogs(PyrogramType): __slots__ = ["total_count", "dialogs"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - total_count: int, - dialogs: List[Dialog] + self, + *, + client: "pyrogram.client.ext.BaseClient", + total_count: int, + dialogs: List[Dialog] ): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index f5c00ac0..5718b917 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -76,23 +76,23 @@ class User(PyrogramType): ] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - id: int, - is_self: bool, - is_contact: bool, - is_mutual_contact: bool, - is_deleted: bool, - is_bot: bool, - first_name: str, - last_name: str = None, - status: UserStatus = None, - username: str = None, - language_code: str = None, - phone_number: str = None, - photo: ChatPhoto = None, - restriction_reason: str = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + id: int, + is_self: bool, + is_contact: bool, + is_mutual_contact: bool, + is_deleted: bool, + is_bot: bool, + first_name: str, + last_name: str = None, + status: UserStatus = None, + username: str = None, + language_code: str = None, + phone_number: str = None, + photo: ChatPhoto = None, + restriction_reason: str = None ): super().__init__(client) diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index 871978d0..170ce373 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -68,17 +68,17 @@ class UserStatus(PyrogramType, Update): __slots__ = ["user_id", "online", "offline", "date", "recently", "within_week", "within_month", "long_time_ago"] def __init__( - self, - *, - client: "pyrogram.client.ext.BaseClient", - user_id: int, - online: bool = None, - offline: bool = None, - date: int = None, - recently: bool = None, - within_week: bool = None, - within_month: bool = None, - long_time_ago: bool = None + self, + *, + client: "pyrogram.client.ext.BaseClient", + user_id: int, + online: bool = None, + offline: bool = None, + date: int = None, + recently: bool = None, + within_week: bool = None, + within_month: bool = None, + long_time_ago: bool = None ): super().__init__(client) diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 9d8e4b16..89e5b61f 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -45,10 +45,10 @@ class Auth: @staticmethod def pack(data: Object) -> bytes: return ( - bytes(8) - + Long(MsgId()) - + Int(len(data.write())) - + data.write() + bytes(8) + + Long(MsgId()) + + Int(len(data.write())) + + data.write() ) @staticmethod @@ -144,13 +144,13 @@ class Auth: new_nonce = new_nonce.to_bytes(32, "little", signed=True) tmp_aes_key = ( - sha1(new_nonce + server_nonce).digest() - + sha1(server_nonce + new_nonce).digest()[:12] + sha1(new_nonce + server_nonce).digest() + + sha1(server_nonce + new_nonce).digest()[:12] ) tmp_aes_iv = ( - sha1(server_nonce + new_nonce).digest()[12:] - + sha1(new_nonce + new_nonce).digest() + new_nonce[:4] + sha1(server_nonce + new_nonce).digest()[12:] + + sha1(new_nonce + new_nonce).digest() + new_nonce[:4] ) server_nonce = int.from_bytes(server_nonce, "little", signed=True) From 9d701bc226d67b7eba64cf681e7636aa8c616cc4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Mar 2019 20:15:25 +0100 Subject: [PATCH 320/326] Fix import order causing errors --- pyrogram/api/errors/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/api/errors/__init__.py b/pyrogram/api/errors/__init__.py index 8a1dc699..ca65619c 100644 --- a/pyrogram/api/errors/__init__.py +++ b/pyrogram/api/errors/__init__.py @@ -16,5 +16,5 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .error import UnknownError from .exceptions import * +from .error import UnknownError From 5aa93b8287ca4fbe1de306296d47dd43f95abf5f Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Wed, 20 Mar 2019 16:20:38 +0300 Subject: [PATCH 321/326] Show TOS on signup just once while program is running (#231) --- pyrogram/client/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 253cc754..737139ca 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -181,6 +181,8 @@ class Client(Methods, BaseClient): Defaults to False (normal session). """ + terms_of_service_displayed = False + def __init__(self, session_name: str, api_id: Union[int, str] = None, @@ -591,8 +593,9 @@ class Client(Methods, BaseClient): phone_code_hash = r.phone_code_hash terms_of_service = r.terms_of_service - if terms_of_service: + if terms_of_service and not Client.terms_of_service_displayed: print("\n" + terms_of_service.text + "\n") + Client.terms_of_service_displayed = True if self.force_sms: self.send( From ac591cf3c7e6f7da20d957d6f3f911dad6282cab Mon Sep 17 00:00:00 2001 From: Eric Solinas Date: Wed, 20 Mar 2019 15:44:20 +0100 Subject: [PATCH 322/326] Add more Message convenience methods (#233) * Added convenience methods message.reply_animation message.reply_audio message.reply_cached_media message.reply_chat_action message.reply_contact message.reply_document message.reply_game message.reply_inline_bot_result message.reply_location message.reply_media_group message.reply_photo message.reply_poll message.reply_sticker message.reply_venue message.reply_video message.reply_video_note message.reply_voice message.edit_caption message.edit_media message.edit_reply_markup message.pin fixed send_document docstrings while doing so uniformed function declaration of send_poll * Update style and small fixes --- .../client/methods/messages/send_document.py | 2 +- pyrogram/client/methods/messages/send_poll.py | 28 +- .../types/messages_and_media/message.py | 1855 ++++++++++++++++- 3 files changed, 1854 insertions(+), 31 deletions(-) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 2693697a..343a63a3 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -60,7 +60,7 @@ class SendDocument(BaseClient): pass an HTTP URL as a string for Telegram to get a file from the Internet, or pass a file path as string to upload a new file that exists on your local machine. - thumb (``str``): + thumb (``str``, *optional*): Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 90 pixels. diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index 423f962d..c9525c83 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -24,20 +24,20 @@ from pyrogram.client.ext import BaseClient class SendPoll(BaseClient): - def send_poll \ - (self, - chat_id: Union[int, str], - question: str, - options: List[str], - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None - ) -> "pyrogram.Message": + def send_poll( + self, + chat_id: Union[int, str], + question: str, + options: List[str], + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "pyrogram.Message": """Use this method to send a new poll. Args: diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 9a4e4314..558efd38 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -21,6 +21,8 @@ from typing import List, Match, Union import pyrogram from pyrogram.api import types from pyrogram.api.errors import MessageIdsEmpty +from pyrogram.client.ext import ChatAction +from pyrogram.client.types.input_media import InputMedia from .contact import Contact from .location import Location from .message_entity import MessageEntity @@ -611,14 +613,16 @@ class Message(PyrogramType, Update): return parsed_message - def reply(self, - text: str, - quote: bool = None, - parse_mode: str = "", - disable_web_page_preview: bool = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None): + def reply( + self, + text: str, + quote: bool = None, + parse_mode: str = "", + disable_web_page_preview: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None + ) -> "Message": """Bound method *reply* of :obj:`Message `. Use as a shortcut for: @@ -686,7 +690,1661 @@ class Message(PyrogramType, Update): reply_markup=reply_markup ) - def edit(self, text: str, parse_mode: str = "", disable_web_page_preview: bool = None, reply_markup=None): + def reply_animation( + self, + animation: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + width: int = 0, + height: int = 0, + thumb: str = None, + disable_notification: bool = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + reply_to_message_id: int = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_animation* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_animation( + chat_id=message.chat.id, + animation=animation + ) + + Example: + .. code-block:: python + + message.reply_animation(animation) + + Args: + animation (``str``): + Animation to send. + Pass a file_id as string to send an animation that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get an animation from the Internet, or + pass a file path as string to upload a new animation that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``str``, *optional*): + Animation caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of sent animation in seconds. + + width (``int``, *optional*): + Animation width. + + height (``int``, *optional*): + Animation height. + + thumb (``str``, *optional*): + Thumbnail of the animation file sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_animation( + chat_id=self.chat.id, + animation=animation, + caption=caption, + parse_mode=parse_mode, + duration=duration, + width=width, + height=height, + thumb=thumb, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_audio( + self, + audio: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + performer: str = None, + title: str = None, + thumb: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_audio* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_audio( + chat_id=message.chat.id, + audio=audio + ) + + Example: + .. code-block:: python + + message.reply_audio(audio) + + Args: + audio (``str``): + Audio file to send. + Pass a file_id as string to send an audio file that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get an audio file from the Internet, or + pass a file path as string to upload a new audio file that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``str``, *optional*): + Audio caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of the audio in seconds. + + performer (``str``, *optional*): + Performer. + + title (``str``, *optional*): + Track name. + + thumb (``str``, *optional*): + Thumbnail of the music file album cover. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_audio( + chat_id=self.chat.id, + audio=audio, + caption=caption, + parse_mode=parse_mode, + duration=duration, + performer=performer, + title=title, + thumb=thumb, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_cached_media( + self, + file_id: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_cached_media* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_cached_media( + chat_id=message.chat.id, + file_id=file_id + ) + + Example: + .. code-block:: python + + message.reply_cached_media(file_id) + + Args: + file_id (``str``): + Media to send. + Pass a file_id as string to send a media that exists on the Telegram servers. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``bool``, *optional*): + Media caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_cached_media( + chat_id=self.chat.id, + file_id=file_id, + caption=caption, + parse_mode=parse_mode, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_chat_action( + self, + action: Union[ChatAction, str], + progress: int = 0 + ) -> "Message": + """Bound method *reply_chat_action* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_chat_action( + chat_id=message.chat.id, + action="typing" + ) + + Example: + .. code-block:: python + + message.reply_chat_action("typing") + + Args: + action (:obj:`ChatAction ` | ``str``): + Type of action to broadcast. + Choose one from the :class:`ChatAction ` enumeration, + depending on what the user is about to receive. + You can also provide a string (e.g. "typing", "upload_photo", "record_audio", ...). + + progress (``int``, *optional*): + Progress of the upload process. + Currently useless because official clients don't seem to be handling this. + + Returns: + On success, True is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + ``ValueError`` if the provided string is not a valid ChatAction. + """ + return self._client.send_chat_action( + chat_id=self.chat.id, + action=action, + progress=progress + ) + + def reply_contact( + self, + phone_number: str, + first_name: str, + quote: bool = None, + last_name: str = "", + vcard: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_contact* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_contact( + chat_id=message.chat.id, + phone_number=phone_number, + first_name=first_name + ) + + Example: + .. code-block:: python + + message.reply_contact(phone_number, "Dan") + + Args: + phone_number (``str``): + Contact's phone number. + + first_name (``str``): + Contact's first name. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + last_name (``str``, *optional*): + Contact's last name. + + vcard (``str``, *optional*): + Additional data about the contact in the form of a vCard, 0-2048 bytes + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_contact( + chat_id=self.chat.id, + phone_number=phone_number, + first_name=first_name, + last_name=last_name, + vcard=vcard, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_document( + self, + document: str, + quote: bool = None, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_document* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_document( + chat_id=message.chat.id, + document=document + ) + + Example: + .. code-block:: python + + message.reply_document(document) + + Args: + document (``str``): + File to send. + Pass a file_id as string to send a file that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a file from the Internet, or + pass a file path as string to upload a new file that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + thumb (``str``, *optional*): + Thumbnail of the file sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + caption (``str``, *optional*): + Document caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_document( + chat_id=self.chat.id, + document=document, + thumb=thumb, + caption=caption, + parse_mode=parse_mode, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_game( + self, + game_short_name: str, + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_game* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_game( + chat_id=message.chat.id, + game_short_name="lumberjack" + ) + + Example: + .. code-block:: python + + message.reply_game("lumberjack") + + Args: + game_short_name (``str``): + Short name of the game, serves as the unique identifier for the game. Set up your games via Botfather. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An object for an inline keyboard. If empty, one ‘Play game_title’ button will be shown automatically. + If not empty, the first button must launch the game. + + Returns: + On success, the sent :obj:`Message` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_game( + chat_id=self.chat.id, + game_short_name=game_short_name, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_inline_bot_result( + self, + query_id: int, + result_id: str, + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + hide_via: bool = None + ) -> "Message": + """Bound method *reply_inline_bot_result* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_inline_bot_result( + chat_id=message.chat.id, + query_id=query_id, + result_id=result_id + ) + + Example: + .. code-block:: python + + message.reply_inline_bot_result(query_id, result_id) + + Args: + query_id (``int``): + Unique identifier for the answered query. + + result_id (``str``): + Unique identifier for the result that was chosen. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``bool``, *optional*): + If the message is a reply, ID of the original message. + + hide_via (``bool``): + Sends the message with *via @bot* hidden. + + Returns: + On success, the sent Message is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_inline_bot_result( + chat_id=self.chat.id, + query_id=query_id, + result_id=result_id, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + hide_via=hide_via + ) + + def reply_location( + self, + latitude: float, + longitude: float, + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_location* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_location( + chat_id=message.chat.id, + latitude=41.890251, + longitude=12.492373 + ) + + Example: + .. code-block:: python + + message.reply_location(41.890251, 12.492373) + + Args: + latitude (``float``): + Latitude of the location. + + longitude (``float``): + Longitude of the location. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_location( + chat_id=self.chat.id, + latitude=latitude, + longitude=longitude, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_media_group( + self, + media: List[Union["pyrogram.InputMediaPhoto", "pyrogram.InputMediaVideo"]], + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None + ) -> "Message": + """Bound method *reply_media_group* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_media_group( + chat_id=message.chat.id, + media=list_of_media + ) + + Example: + .. code-block:: python + + message.reply_media_group(list_of_media) + + Args: + media (``list``): + A list containing either :obj:`InputMediaPhoto ` or + :obj:`InputMediaVideo ` objects + describing photos and videos to be sent, must include 2–10 items. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + Returns: + On success, a :obj:`Messages ` object is returned containing all the + single messages sent. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_media_group( + chat_id=self.chat.id, + media=media, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id + ) + + def reply_photo( + self, + photo: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + ttl_seconds: int = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_photo* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_photo( + chat_id=message.chat.id, + photo=photo + ) + + Example: + .. code-block:: python + + message.reply_photo(photo) + + Args: + photo (``str``): + Photo to send. + Pass a file_id as string to send a photo that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a photo from the Internet, or + pass a file path as string to upload a new photo that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``bool``, *optional*): + Photo caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + ttl_seconds (``int``, *optional*): + Self-Destruct Timer. + If you set a timer, the photo will self-destruct in *ttl_seconds* + seconds after it was viewed. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_photo( + chat_id=self.chat.id, + photo=photo, + caption=caption, + parse_mode=parse_mode, + ttl_seconds=ttl_seconds, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_poll( + self, + question: str, + options: List[str], + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_poll* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_poll( + chat_id=message.chat.id, + question="Is Pyrogram the best?", + options=["Yes", "Yes"] + ) + + Example: + .. code-block:: python + + message.reply_poll("Is Pyrogram the best?", ["Yes", "Yes"]) + + Args: + question (``str``): + The poll question, as string. + + options (List of ``str``): + The poll options, as list of strings (2 to 10 options are allowed). + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_poll( + chat_id=self.chat.id, + question=question, + options=options, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_sticker( + self, + sticker: str, + quote: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_sticker* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_sticker( + chat_id=message.chat.id, + sticker=sticker + ) + + Example: + .. code-block:: python + + message.reply_sticker(sticker) + + Args: + sticker (``str``): + Sticker to send. + Pass a file_id as string to send a sticker that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or + pass a file path as string to upload a new sticker that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_sticker( + chat_id=self.chat.id, + sticker=sticker, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_venue( + self, + latitude: float, + longitude: float, + title: str, + address: str, + quote: bool = None, + foursquare_id: str = "", + foursquare_type: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *reply_venue* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_venue( + chat_id=message.chat.id, + latitude=41.890251, + longitude=12.492373, + title="Coliseum", + address="Piazza del Colosseo, 1, 00184 Roma RM" + ) + + Example: + .. code-block:: python + + message.reply_venue(41.890251, 12.492373, "Coliseum", "Piazza del Colosseo, 1, 00184 Roma RM") + + Args: + latitude (``float``): + Latitude of the venue. + + longitude (``float``): + Longitude of the venue. + + title (``str``): + Name of the venue. + + address (``str``): + Address of the venue. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + foursquare_id (``str``, *optional*): + Foursquare identifier of the venue. + + foursquare_type (``str``, *optional*): + Foursquare type of the venue, if known. + (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or "food/icecream".) + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + On success, the sent :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_venue( + chat_id=self.chat.id, + latitude=latitude, + longitude=longitude, + title=title, + address=address, + foursquare_id=foursquare_id, + foursquare_type=foursquare_type, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup + ) + + def reply_video( + self, + video: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + width: int = 0, + height: int = 0, + thumb: str = None, + supports_streaming: bool = True, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_video* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_video( + chat_id=message.chat.id, + video=video + ) + + Example: + .. code-block:: python + + message.reply_video(video) + + Args: + video (``str``): + Video to send. + Pass a file_id as string to send a video that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a video from the Internet, or + pass a file path as string to upload a new video that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``str``, *optional*): + Video caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of sent video in seconds. + + width (``int``, *optional*): + Video width. + + height (``int``, *optional*): + Video height. + + thumb (``str``, *optional*): + Thumbnail of the video sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + supports_streaming (``bool``, *optional*): + Pass True, if the uploaded video is suitable for streaming. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_video( + chat_id=self.chat.id, + video=video, + caption=caption, + parse_mode=parse_mode, + duration=duration, + width=width, + height=height, + thumb=thumb, + supports_streaming=supports_streaming, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_video_note( + self, + video_note: str, + quote: bool = None, + duration: int = 0, + length: int = 1, + thumb: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_video_note* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_video_note( + chat_id=message.chat.id, + video_note=video_note + ) + + Example: + .. code-block:: python + + message.reply_video_note(video_note) + + Args: + video_note (``str``): + Video note to send. + Pass a file_id as string to send a video note that exists on the Telegram servers, or + pass a file path as string to upload a new video note that exists on your local machine. + Sending video notes by a URL is currently unsupported. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + duration (``int``, *optional*): + Duration of sent video in seconds. + + length (``int``, *optional*): + Video width and height. + + thumb (``str``, *optional*): + Thumbnail of the video sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_video_note( + chat_id=self.chat.id, + video_note=video_note, + duration=duration, + length=length, + thumb=thumb, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def reply_voice( + self, + voice: str, + quote: bool = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": + """Bound method *reply_voice* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.send_voice( + chat_id=message.chat.id, + voice=voice + ) + + Example: + .. code-block:: python + + message.reply_voice(voice) + + Args: + voice (``str``): + Audio file to send. + Pass a file_id as string to send an audio that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get an audio from the Internet, or + pass a file path as string to upload a new audio that exists on your local machine. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. + + caption (``str``, *optional*): + Voice message caption, 0-1024 characters. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of the voice message in seconds. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + On success, the sent :obj:`Message ` is returned. + In case the upload is deliberately stopped with :meth:`stop_transmission`, None is returned instead. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + if quote is None: + quote = self.chat.type != "private" + + if reply_to_message_id is None and quote: + reply_to_message_id = self.message_id + + return self._client.send_voice( + chat_id=self.chat.id, + voice=voice, + caption=caption, + parse_mode=parse_mode, + duration=duration, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + progress=progress, + progress_args=progress_args + ) + + def edit( + self, + text: str, + parse_mode: str = "", + disable_web_page_preview: bool = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": """Bound method *edit* of :obj:`Message ` Use as a shortcut for: @@ -696,7 +2354,7 @@ class Message(PyrogramType, Update): client.edit_message_text( chat_id=message.chat.id, message_id=message.message_id, - text="hello", + text="hello" ) Example: @@ -734,9 +2392,134 @@ class Message(PyrogramType, Update): reply_markup=reply_markup ) - def forward(self, - chat_id: int or str, - disable_notification: bool = None): + def edit_caption( + self, + caption: str, + parse_mode: str = "", + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> "Message": + """Bound method *edit_caption* of :obj:`Message ` + + Use as a shortcut for: + + .. code-block:: python + + client.edit_message_caption( + chat_id=message.chat.id, + message_id=message.message_id, + caption="hello" + ) + + Example: + .. code-block:: python + + message.edit_caption("hello") + + Args: + caption (``str``): + New caption of the message. + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your message. + Defaults to Markdown. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + On success, the edited :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + return self._client.edit_message_caption( + chat_id=self.chat.id, + message_id=self.message_id, + caption=caption, + parse_mode=parse_mode, + reply_markup=reply_markup + ) + + def edit_media(self, media: InputMedia, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message": + """Bound method *edit_media* of :obj:`Message ` + + Use as a shortcut for: + + .. code-block:: python + + client.edit_message_media( + chat_id=message.chat.id, + message_id=message.message_id, + media=media + ) + + Example: + .. code-block:: python + + message.edit_media(media) + + Args: + media (:obj:`InputMediaAnimation` | :obj:`InputMediaAudio` | :obj:`InputMediaDocument` | :obj:`InputMediaPhoto` | :obj:`InputMediaVideo`) + One of the InputMedia objects describing an animation, audio, document, photo or video. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + On success, the edited :obj:`Message ` is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + return self._client.edit_message_media( + chat_id=self.chat.id, + message_id=self.message_id, + media=media, + reply_markup=reply_markup + ) + + def edit_reply_markup(self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message": + """Bound method *edit_reply_markup* of :obj:`Message ` + + Use as a shortcut for: + + .. code-block:: python + + client.edit_message_reply_markup( + chat_id=message.chat.id, + message_id=message.message_id, + reply_markup=inline_reply_markup + ) + + Example: + .. code-block:: python + + message.edit_reply_markup(inline_reply_markup) + + Args: + reply_markup (:obj:`InlineKeyboardMarkup`): + An InlineKeyboardMarkup object. + + Returns: + On success, if edited message is sent by the bot, the edited + :obj:`Message ` is returned, otherwise True is returned. + + Raises: + :class:`Error ` in case of a Telegram RPC error. + """ + return self._client.edit_message_reply_markup( + chat_id=self.chat.id, + message_id=self.message_id, + reply_markup=reply_markup + ) + + def forward(self, chat_id: int or str, disable_notification: bool = None) -> "Message": """Bound method *forward* of :obj:`Message `. Use as a shortcut for: @@ -746,7 +2529,7 @@ class Message(PyrogramType, Update): client.forward_messages( chat_id=chat_id, from_chat_id=message.chat.id, - message_ids=message.message_id, + message_ids=message.message_id ) Example: @@ -875,7 +2658,7 @@ class Message(PyrogramType, Update): ``TimeoutError``: If, after clicking an inline button, the bot fails to answer within 10 seconds """ if isinstance(self.reply_markup, pyrogram.ReplyKeyboardMarkup): - return self.reply(x) + return self.reply(x, quote=quote) elif isinstance(self.reply_markup, pyrogram.InlineKeyboardMarkup): if isinstance(x, int) and y is None: try: @@ -927,7 +2710,13 @@ class Message(PyrogramType, Update): else: raise ValueError("The message doesn't contain any keyboard") - def download(self, file_name: str = "", block: bool = True, progress: callable = None, progress_args: tuple = ()): + def download( + self, + file_name: str = "", + block: bool = True, + progress: callable = None, + progress_args: tuple = () + ) -> "Message": """Bound method *download* of :obj:`Message `. Use as a shortcut for: @@ -976,6 +2765,40 @@ class Message(PyrogramType, Update): progress_args=progress_args, ) + def pin(self, disable_notification: bool = None) -> "Message": + """Bound method *pin* of :obj:`Message `. + + Use as a shortcut for: + + .. code-block:: python + + client.pin_chat_message( + chat_id=message.chat.id, + message_id=message_id + ) + + Example: + .. code-block:: python + + message.pin() + + Args: + disable_notification (``bool``): + Pass True, if it is not necessary to send a notification to all chat members about the new pinned + message. Notifications are always disabled in channels. + + Returns: + True on success. + + Raises: + :class:`Error ` + """ + return self._client.pin_chat_message( + chat_id=self.chat.id, + message_id=self.message_id, + disable_notification=disable_notification + ) + class Str(str): def __init__(self, *args): From 081b9b280a2a42d451b9a86b3daaa415bed6bfcd Mon Sep 17 00:00:00 2001 From: bakatrouble Date: Thu, 21 Mar 2019 15:53:07 +0300 Subject: [PATCH 323/326] Add ability to forward messages as copies (#227) * Add ability to forward messages as copies * Add Messages.forward() method * Update and clean up code --- .../methods/messages/forward_messages.py | 85 +++++--- .../client/methods/messages/send_contact.py | 8 +- pyrogram/client/style/html.py | 2 +- pyrogram/client/style/markdown.py | 2 +- .../types/messages_and_media/message.py | 181 ++++++++++++++---- .../types/messages_and_media/messages.py | 54 +++++- pyrogram/client/types/pyrogram_type.py | 4 +- 7 files changed, 265 insertions(+), 71 deletions(-) diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index 35d406ae..39db4bee 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -29,7 +29,9 @@ class ForwardMessages(BaseClient): chat_id: Union[int, str], from_chat_id: Union[int, str], message_ids: Iterable[int], - disable_notification: bool = None + disable_notification: bool = None, + as_copy: bool = False, + remove_caption: bool = False ) -> "pyrogram.Messages": """Use this method to forward messages of any kind. @@ -52,6 +54,15 @@ class ForwardMessages(BaseClient): Sends the message silently. Users will receive a notification with no sound. + as_copy (``bool``, *optional*): + Pass True to forward messages without the forward header (i.e.: send a copy of the message content). + Defaults to False. + + remove_caption (``bool``, *optional*): + If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the + message. Has no effect if *as_copy* is not enabled. + Defaults to False. + Returns: On success and in case *message_ids* was an iterable, the returned value will be a list of the forwarded :obj:`Messages ` even if a list contains just one element, otherwise if @@ -61,35 +72,55 @@ class ForwardMessages(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ + is_iterable = not isinstance(message_ids, int) message_ids = list(message_ids) if is_iterable else [message_ids] - r = self.send( - functions.messages.ForwardMessages( - to_peer=self.resolve_peer(chat_id), - from_peer=self.resolve_peer(from_chat_id), - id=message_ids, - silent=disable_notification or None, - random_id=[self.rnd_id() for _ in message_ids] - ) - ) - - messages = [] - - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - messages.append( - pyrogram.Message._parse( - self, i.message, - users, chats + if as_copy: + sent_messages = [] + for chunk in [message_ids[i:i + 200] for i in range(0, len(message_ids), 200)]: + messages = self.get_messages(chat_id=from_chat_id, message_ids=chunk) # type: pyrogram.Messages + for message in messages.messages: + sent_messages.append( + message.forward( + chat_id, + disable_notification=disable_notification, + as_copy=True, + remove_caption=remove_caption + ) ) + return pyrogram.Messages( + client=self, + total_count=len(sent_messages), + messages=sent_messages + ) if is_iterable else sent_messages[0] + else: + r = self.send( + functions.messages.ForwardMessages( + to_peer=self.resolve_peer(chat_id), + from_peer=self.resolve_peer(from_chat_id), + id=message_ids, + silent=disable_notification or None, + random_id=[self.rnd_id() for _ in message_ids] ) + ) - return pyrogram.Messages( - client=self, - total_count=len(messages), - messages=messages - ) if is_iterable else messages[0] + forwarded_messages = [] + + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} + + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + forwarded_messages.append( + pyrogram.Message._parse( + self, i.message, + users, chats + ) + ) + + return pyrogram.Messages( + client=self, + total_count=len(forwarded_messages), + messages=forwarded_messages + ) if is_iterable else forwarded_messages[0] diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index bed9a37e..14ce61ec 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -29,8 +29,8 @@ class SendContact(BaseClient): chat_id: Union[int, str], phone_number: str, first_name: str, - last_name: str = "", - vcard: str = "", + last_name: str = None, + vcard: str = None, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -83,8 +83,8 @@ class SendContact(BaseClient): media=types.InputMediaContact( phone_number=phone_number, first_name=first_name, - last_name=last_name, - vcard=vcard + last_name=last_name or "", + vcard=vcard or "" ), message="", silent=disable_notification or None, diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 040e770b..211716e1 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -40,7 +40,7 @@ class HTML: def parse(self, message: str): entities = [] - message = utils.add_surrogates(str(message)) + message = utils.add_surrogates(str(message or "")) offset = 0 for match in self.HTML_RE.finditer(message): diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index 04ce95c8..45037a35 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -56,7 +56,7 @@ class Markdown: self.peers_by_id = peers_by_id def parse(self, message: str): - message = utils.add_surrogates(str(message)).strip() + message = utils.add_surrogates(str(message or "")).strip() entities = [] offset = 0 diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 558efd38..4ead0d30 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -16,12 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from functools import partial from typing import List, Match, Union import pyrogram from pyrogram.api import types from pyrogram.api.errors import MessageIdsEmpty -from pyrogram.client.ext import ChatAction +from pyrogram.client.ext import ChatAction, ParseMode from pyrogram.client.types.input_media import InputMedia from .contact import Contact from .location import Location @@ -33,6 +34,32 @@ from ..user_and_chats.chat import Chat from ..user_and_chats.user import User +class Str(str): + def __init__(self, *args): + super().__init__() + + self._client = None + self._entities = None + + def init(self, client, entities): + self._client = client + self._entities = entities + + return self + + @property + def text(self): + return self + + @property + def markdown(self): + return self._client.markdown.unparse(self, self._entities) + + @property + def html(self): + return self._client.html.unparse(self, self._entities) + + class Message(PyrogramType, Update): """This object represents a message. @@ -268,7 +295,7 @@ class Message(PyrogramType, Update): edit_date: int = None, media_group_id: str = None, author_signature: str = None, - text: str = None, + text: Str = None, entities: List["pyrogram.MessageEntity"] = None, caption_entities: List["pyrogram.MessageEntity"] = None, audio: "pyrogram.Audio" = None, @@ -280,7 +307,7 @@ class Message(PyrogramType, Update): video: "pyrogram.Video" = None, voice: "pyrogram.Voice" = None, video_note: "pyrogram.VideoNote" = None, - caption: str = None, + caption: Str = None, contact: "pyrogram.Contact" = None, location: "pyrogram.Location" = None, venue: "pyrogram.Venue" = None, @@ -2519,7 +2546,13 @@ class Message(PyrogramType, Update): reply_markup=reply_markup ) - def forward(self, chat_id: int or str, disable_notification: bool = None) -> "Message": + def forward( + self, + chat_id: int or str, + disable_notification: bool = None, + as_copy: bool = False, + remove_caption: bool = False + ) -> "Message": """Bound method *forward* of :obj:`Message `. Use as a shortcut for: @@ -2547,18 +2580,120 @@ class Message(PyrogramType, Update): Sends the message silently. Users will receive a notification with no sound. + as_copy (``bool``, *optional*): + Pass True to forward messages without the forward header (i.e.: send a copy of the message content). + Defaults to False. + + remove_caption (``bool``, *optional*): + If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the + message. Has no effect if *as_copy* is not enabled. + Defaults to False. + Returns: On success, the forwarded Message is returned. Raises: :class:`Error ` """ - return self._client.forward_messages( - chat_id=chat_id, - from_chat_id=self.chat.id, - message_ids=self.message_id, - disable_notification=disable_notification - ) + if as_copy: + if self.service: + raise ValueError("Unable to copy service messages") + + if self.game and not self._client.is_bot: + raise ValueError("Users cannot send messages with Game media type") + + # TODO: Improve markdown parser. Currently html appears to be more stable, thus we use it here because users + # can"t choose. + + if self.text: + return self._client.send_message( + chat_id, + text=self.text.html, + parse_mode="html", + disable_web_page_preview=not self.web_page, + disable_notification=disable_notification + ) + elif self.media: + caption = self.caption.html if self.caption and not remove_caption else None + + send_media = partial( + self._client.send_cached_media, + chat_id=chat_id, + disable_notification=disable_notification + ) + + if self.photo: + file_id = self.photo.sizes[-1].file_id + elif self.audio: + file_id = self.audio.file_id + elif self.document: + file_id = self.document.file_id + elif self.video: + file_id = self.video.file_id + elif self.animation: + file_id = self.animation.file_id + elif self.voice: + file_id = self.voice.file_id + elif self.sticker: + file_id = self.sticker.file_id + elif self.video_note: + file_id = self.video_note.file_id + elif self.contact: + return self._client.send_contact( + chat_id, + phone_number=self.contact.phone_number, + first_name=self.contact.first_name, + last_name=self.contact.last_name, + vcard=self.contact.vcard, + disable_notification=disable_notification + ) + elif self.location: + return self._client.send_location( + chat_id, + latitude=self.location.latitude, + longitude=self.location.longitude, + disable_notification=disable_notification + ) + elif self.venue: + return self._client.send_venue( + chat_id, + latitude=self.venue.location.latitude, + longitude=self.venue.location.longitude, + title=self.venue.title, + address=self.venue.address, + foursquare_id=self.venue.foursquare_id, + foursquare_type=self.venue.foursquare_type, + disable_notification=disable_notification + ) + elif self.poll: + return self._client.send_poll( + chat_id, + question=self.poll.question, + options=[opt.text for opt in self.poll.options], + disable_notification=disable_notification + ) + elif self.game: + return self._client.send_game( + chat_id, + game_short_name=self.game.short_name, + disable_notification=disable_notification + ) + else: + raise ValueError("Unknown media type") + + if self.sticker or self.video_note: # Sticker and VideoNote should have no caption + return send_media(file_id) + else: + return send_media(file_id=file_id, caption=caption, parse_mode=ParseMode.HTML) + else: + raise ValueError("Can't copy this message") + else: + return self._client.forward_messages( + chat_id=chat_id, + from_chat_id=self.chat.id, + message_ids=self.message_id, + disable_notification=disable_notification + ) def delete(self, revoke: bool = True): """Bound method *delete* of :obj:`Message `. @@ -2798,29 +2933,3 @@ class Message(PyrogramType, Update): message_id=self.message_id, disable_notification=disable_notification ) - - -class Str(str): - def __init__(self, *args): - super().__init__() - - self.client = None - self.entities = None - - def init(self, client, entities): - self.client = client - self.entities = entities - - return self - - @property - def text(self): - return self - - @property - def markdown(self): - return self.client.markdown.unparse(self, self.entities) - - @property - def html(self): - return self.client.html.unparse(self, self.entities) diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 9983a01d..aae31a82 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import List +from typing import List, Union import pyrogram from pyrogram.api import types @@ -116,3 +116,55 @@ class Messages(PyrogramType, Update): messages=parsed_messages, client=client ) + + def forward( + self, + chat_id: Union[int, str], + disable_notification: bool = None, + as_copy: bool = False, + remove_caption: bool = False + ): + """Bound method *forward* of :obj:`Message `. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + disable_notification (``bool``, *optional*): + Sends messages silently. + Users will receive a notification with no sound. + + as_copy (``bool``, *optional*): + Pass True to forward messages without the forward header (i.e.: send a copy of the message content). + Defaults to False. + + remove_caption (``bool``, *optional*): + If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the + message. Has no effect if *as_copy* is not enabled. + Defaults to False. + + Returns: + On success, a :class:`Messages ` containing forwarded messages is returned. + + Raises: + :class:`Error ` + """ + forwarded_messages = [] + + for message in self.messages: + forwarded_messages.append( + message.forward( + chat_id=chat_id, + as_copy=as_copy, + disable_notification=disable_notification, + remove_caption=remove_caption + ) + ) + + return Messages( + total_count=len(forwarded_messages), + messages=forwarded_messages, + client=self._client + ) diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py index d746e6a7..af828926 100644 --- a/pyrogram/client/types/pyrogram_type.py +++ b/pyrogram/client/types/pyrogram_type.py @@ -19,11 +19,13 @@ from collections import OrderedDict from json import dumps +import pyrogram + class PyrogramType: __slots__ = ["_client"] - def __init__(self, client): + def __init__(self, client: "pyrogram.client.ext.BaseClient"): self._client = client def __str__(self): From 159090483f47e93f7c6b212c829c0b59589ec101 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 21 Mar 2019 14:04:46 +0100 Subject: [PATCH 324/326] Update style --- .../client/methods/messages/forward_messages.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index 39db4bee..dad01a7f 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -77,11 +77,13 @@ class ForwardMessages(BaseClient): message_ids = list(message_ids) if is_iterable else [message_ids] if as_copy: - sent_messages = [] + forwarded_messages = [] + for chunk in [message_ids[i:i + 200] for i in range(0, len(message_ids), 200)]: messages = self.get_messages(chat_id=from_chat_id, message_ids=chunk) # type: pyrogram.Messages + for message in messages.messages: - sent_messages.append( + forwarded_messages.append( message.forward( chat_id, disable_notification=disable_notification, @@ -89,11 +91,12 @@ class ForwardMessages(BaseClient): remove_caption=remove_caption ) ) + return pyrogram.Messages( client=self, - total_count=len(sent_messages), - messages=sent_messages - ) if is_iterable else sent_messages[0] + total_count=len(forwarded_messages), + messages=forwarded_messages + ) if is_iterable else forwarded_messages[0] else: r = self.send( functions.messages.ForwardMessages( From 41729cbdda3fe03496217d82b9a6ea0a37884354 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 21 Mar 2019 14:08:09 +0100 Subject: [PATCH 325/326] Fix argument not passed with name --- pyrogram/client/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 4ead0d30..08e20975 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2682,7 +2682,7 @@ class Message(PyrogramType, Update): raise ValueError("Unknown media type") if self.sticker or self.video_note: # Sticker and VideoNote should have no caption - return send_media(file_id) + return send_media(file_id=file_id) else: return send_media(file_id=file_id, caption=caption, parse_mode=ParseMode.HTML) else: From afcde95b30f25eacce718454d9a956461185be85 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 21 Mar 2019 17:03:35 +0100 Subject: [PATCH 326/326] Fix parsing ChannelForbidden; it doesn't have default permissions --- pyrogram/client/types/user_and_chats/chat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 5488b846..a13f8a2b 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -144,7 +144,7 @@ class Chat(PyrogramType): type="group", title=chat.title, photo=ChatPhoto._parse(client, getattr(chat, "photo", None)), - permissions=ChatPermissions._parse(chat.default_banned_rights), + permissions=ChatPermissions._parse(getattr(chat, "default_banned_rights", None)), client=client ) @@ -157,7 +157,7 @@ class Chat(PyrogramType): username=getattr(channel, "username", None), photo=ChatPhoto._parse(client, getattr(channel, "photo", None)), restriction_reason=getattr(channel, "restriction_reason", None), - permissions=ChatPermissions._parse(channel.default_banned_rights), + permissions=ChatPermissions._parse(getattr(channel, "default_banned_rights", None)), client=client )