From 66f70450aa9eb8de3b04315bbbfed2704f1bfb5c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 8 Jan 2019 14:28:52 +0100 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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."""