From 3e5421f55f8c81172fc71233f6fcd5f0384722de Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 23 Jan 2018 18:16:46 +0100 Subject: [PATCH 1/9] Remove unused regex pattern --- pyrogram/client/style/html.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 28091aa0..d1e876a1 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -31,7 +31,6 @@ from . import utils class HTML: - SMP_RE = re.compile(r"[\U00010000-\U0010FFFF]") HTML_RE = re.compile(r"<(\w+)(?: href=\"(.*)\")?>(.*)") MENTION_RE = re.compile(r"tg://user\?id=(\d+)") From d01d852dc2341d4c72f4680e81b7d309b65a268b Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Wed, 24 Jan 2018 15:40:39 +0100 Subject: [PATCH 2/9] Support custom callbacks on Client.authorize() --- pyrogram/client/client.py | 41 ++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 9783df41..bdc83446 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -170,17 +170,19 @@ class Client: """ return self.session.send(data) - def authorize(self): + def authorize(self, phone_number=None, code_callback=None, password=None): + invalid_phone_raises = phone_number is not None while True: - phone_number = input("Enter phone number: ") + if phone_number is None: + phone_number = input("Enter phone number: ") - while True: - confirm = input("Is \"{}\" correct? (y/n): ".format(phone_number)) + while True: + confirm = input("Is \"{}\" correct? (y/n): ".format(phone_number)) - if confirm in ("y", "1"): - break - elif confirm in ("n", "2"): - phone_number = input("Enter phone number: ") + if confirm in ("y", "1"): + break + elif confirm in ("n", "2"): + phone_number = input("Enter phone number: ") try: r = self.send( @@ -208,7 +210,11 @@ class Client: ) break except PhoneNumberInvalid as e: - print(e.MESSAGE) + if invalid_phone_raises: + raise + else: + print(e.MESSAGE) + phone_number = None except FloodWait as e: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) @@ -219,9 +225,12 @@ class Client: phone_registered = r.phone_registered phone_code_hash = r.phone_code_hash + if not code_callback: + def code_callback(): + return input("Enter phone code: ") while True: - phone_code = input("Enter phone code: ") + phone_code = code_callback() try: if phone_registered: @@ -260,20 +269,26 @@ class Client: print(e.MESSAGE) except SessionPasswordNeeded as e: print(e.MESSAGE) + invalid_password_raises = password is not None while True: try: r = self.send(functions.account.GetPassword()) - print("Hint: {}".format(r.hint)) - password = input("Enter password: ") # TODO: Use getpass + if password is None: + print("Hint: {}".format(r.hint)) + password = input("Enter password: ") # TODO: Use getpass password = r.current_salt + password.encode() + r.current_salt password_hash = sha256(password).digest() r = self.send(functions.auth.CheckPassword(password_hash)) except PasswordHashInvalid as e: - print(e.MESSAGE) + if invalid_password_raises: + raise + else: + print(e.MESSAGE) + password = None except FloodWait as e: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) From f65d369fd1448390aa6ae3dc792216bfee53746a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 24 Jan 2018 17:53:30 +0100 Subject: [PATCH 3/9] Clean the code and add some more functionality: - The phone_code can also be passed in advance (for test numbers). - Pass first_name and last_name for automatic account creation. - Handle PhoneCodeInvalid errors. --- pyrogram/client/client.py | 86 ++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index bdc83446..5920fbbd 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -75,10 +75,23 @@ class Client: INVITE_LINK_RE = re.compile(r"^(?:https?://)?t\.me/joinchat/(.+)$") DIALOGS_AT_ONCE = 100 - def __init__(self, session_name: str, test_mode: bool = False): + def __init__(self, + session_name: str, + test_mode: bool = False, + phone_number: str = None, + phone_code: str or callable = None, + password: str = None, + first_name: str = None, + last_name: str = None): self.session_name = session_name self.test_mode = test_mode + self.phone_number = phone_number + self.password = password + self.phone_code = phone_code + self.first_name = first_name + self.last_name = last_name + self.dc_id = None self.auth_key = None self.user_id = None @@ -170,24 +183,27 @@ class Client: """ return self.session.send(data) - def authorize(self, phone_number=None, code_callback=None, password=None): - invalid_phone_raises = phone_number is not None + def authorize(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 + while True: - if phone_number is None: - phone_number = input("Enter phone number: ") + if self.phone_number is None: + self.phone_number = input("Enter phone number: ") while True: - confirm = input("Is \"{}\" correct? (y/n): ".format(phone_number)) + confirm = input("Is \"{}\" correct? (y/n): ".format(self.phone_number)) if confirm in ("y", "1"): break elif confirm in ("n", "2"): - phone_number = input("Enter phone number: ") + self.phone_number = input("Enter phone number: ") try: r = self.send( functions.auth.SendCode( - phone_number, + self.phone_number, self.config.api_id, self.config.api_hash ) @@ -203,18 +219,18 @@ class Client: r = self.send( functions.auth.SendCode( - phone_number, + self.phone_number, self.config.api_id, self.config.api_hash ) ) break except PhoneNumberInvalid as e: - if invalid_phone_raises: + if phone_number_invalid_raises: raise else: print(e.MESSAGE) - phone_number = None + self.phone_number = None except FloodWait as e: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) @@ -225,70 +241,74 @@ class Client: phone_registered = r.phone_registered phone_code_hash = r.phone_code_hash - if not code_callback: - def code_callback(): - return input("Enter phone code: ") while True: - phone_code = code_callback() + 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 self.phone_code() + ) try: if phone_registered: r = self.send( functions.auth.SignIn( - phone_number, + self.phone_number, phone_code_hash, - phone_code + self.phone_code ) ) else: try: self.send( functions.auth.SignIn( - phone_number, + self.phone_number, phone_code_hash, - phone_code + self.phone_code ) ) except PhoneNumberUnoccupied: pass - first_name = input("First name: ") - last_name = input("Last name: ") + self.first_name = self.first_name or input("First name: ") + self.last_name = self.last_name or input("Last name: ") r = self.send( functions.auth.SignUp( - phone_number, + self.phone_number, phone_code_hash, - phone_code, - first_name, - last_name + self.phone_code, + self.first_name, + self.last_name ) ) except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e: - print(e.MESSAGE) + if phone_code_invalid_raises: + raise + else: + print(e.MESSAGE) + self.phone_code = None except SessionPasswordNeeded as e: print(e.MESSAGE) - invalid_password_raises = password is not None while True: try: r = self.send(functions.account.GetPassword()) - if password is None: + if self.password is None: print("Hint: {}".format(r.hint)) - password = input("Enter password: ") # TODO: Use getpass + self.password = input("Enter password: ") # TODO: Use getpass - password = r.current_salt + password.encode() + r.current_salt - password_hash = sha256(password).digest() + self.password = r.current_salt + self.password.encode() + r.current_salt + password_hash = sha256(self.password).digest() r = self.send(functions.auth.CheckPassword(password_hash)) except PasswordHashInvalid as e: - if invalid_password_raises: + if password_hash_invalid_raises: raise else: print(e.MESSAGE) - password = None + self.password = None except FloodWait as e: print(e.MESSAGE.format(x=e.x)) time.sleep(e.x) From a662c1734fb7d8db633871df9452e456d9a814c4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 24 Jan 2018 18:35:17 +0100 Subject: [PATCH 4/9] Move GetPassword request outside the loop --- 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 5920fbbd..bf8b1e51 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -290,10 +290,10 @@ class Client: self.phone_code = 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)) From 5b7459cb7109a0634fe8db74e45fd63c460ad836 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 24 Jan 2018 18:38:18 +0100 Subject: [PATCH 5/9] Fix infinite loop in case a flood wait is triggered --- 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 bf8b1e51..9c282a82 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -299,7 +299,9 @@ class Client: print("Hint: {}".format(r.hint)) self.password = input("Enter password: ") # TODO: Use getpass - self.password = r.current_salt + self.password.encode() + r.current_salt + if type(self.password) is str: + self.password = r.current_salt + self.password.encode() + r.current_salt + password_hash = sha256(self.password).digest() r = self.send(functions.auth.CheckPassword(password_hash)) From ee41955db09d61710603985b1a273672e21df2f9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 24 Jan 2018 18:41:23 +0100 Subject: [PATCH 6/9] Set the password to None after successfully authorizing the user --- pyrogram/client/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 9c282a82..677ef69c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -125,6 +125,7 @@ class Client: print("\n".join(terms.splitlines()), "\n") self.user_id = self.authorize() + self.password = None self.save_session() self.rnd_id = self.session.msg_id From e6fdc6a4e999a189e8e7fe525315339ed6e93abe Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 24 Jan 2018 21:46:28 +0100 Subject: [PATCH 7/9] Update docstrings --- pyrogram/client/client.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 677ef69c..bcf5f994 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -64,12 +64,35 @@ class Client: Args: session_name (:obj:`str`): Name to uniquely identify an authorized session. It will be used - to save the session to a file named ``.session``. + to save the session to a file named *.session* and to load + it when you restart your script. As long as a valid session file exists, + Pyrogram won't ask you again to input your phone number. test_mode (:obj:`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 (:obj:`str`, optional): + Pass your phone number (with your Country Code prefix included) to avoid + entering it manually. Only applicable for new sessions. + + phone_code (:obj:`str` | :obj:`callable`, optional): + Pass the phone code as string (for test numbers only), or pass a callback function + which must return the correct phone code as string (e.g., "12345"). + Only applicable for new sessions. + + password (:obj:`str`, optional): + Pass your Two-Step Verification password (if you have one) to avoid entering it + manually. Only applicable for new sessions. + + first_name (:obj:`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. + + last_name (:obj:`str`, optional): + Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can + be an empty string: "" """ INVITE_LINK_RE = re.compile(r"^(?:https?://)?t\.me/joinchat/(.+)$") @@ -113,7 +136,11 @@ class Client: def start(self): """Use this method to start the Client after creating it. - Requires no parameters.""" + Requires no parameters. + + Raises: + :class:`pyrogram.Error` + """ self.load_config() self.load_session(self.session_name) From 604fc7af71d0f1f3d34e166eecfc0ce275e696fd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 24 Jan 2018 21:53:27 +0100 Subject: [PATCH 8/9] Handle FirstnameInvalid error --- pyrogram/client/client.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index bcf5f994..a0b4586a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -37,7 +37,7 @@ from pyrogram.api.errors import ( PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty, PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, PasswordHashInvalid, FloodWait, PeerIdInvalid, FilePartMissing, - ChatAdminRequired + ChatAdminRequired, FirstnameInvalid ) from pyrogram.api.types import ( User, Chat, Channel, @@ -215,6 +215,7 @@ class Client: 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 + first_name_invalid_raises = self.first_name is not None while True: if self.phone_number is None: @@ -298,8 +299,8 @@ class Client: except PhoneNumberUnoccupied: pass - self.first_name = self.first_name or input("First name: ") - self.last_name = self.last_name or input("Last name: ") + 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( @@ -316,6 +317,12 @@ class Client: 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) r = self.send(functions.account.GetPassword()) From 894a0d03694144f932c4e4f73fd45036e8b3316a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 24 Jan 2018 21:53:58 +0100 Subject: [PATCH 9/9] Update docs --- docs/source/getting_started/ProjectSetup.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/getting_started/ProjectSetup.rst b/docs/source/getting_started/ProjectSetup.rst index 77f2f834..bc2b6b74 100644 --- a/docs/source/getting_started/ProjectSetup.rst +++ b/docs/source/getting_started/ProjectSetup.rst @@ -60,7 +60,7 @@ Pyrogram executing API calls with your identity. .. note:: The authorization process is executed only once. - However, the code above is always required; as soon as a valid session file exists, + However, the code above is always required; as long as a valid session file exists, Pyrogram will use that and won't ask you to enter your phone number again when you restart your script. .. _`Country Code`: https://en.wikipedia.org/wiki/List_of_country_calling_codes \ No newline at end of file