Skip to content

Commit

Permalink
Added .start() convenience method to quickly connect/authorize (#528)
Browse files Browse the repository at this point in the history
  • Loading branch information
JosXa authored and Lonami committed Jan 11, 2018
1 parent eaef392 commit 80f81fe
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 21 deletions.
6 changes: 1 addition & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@ Creating a client
phone = '+34600000000'
client = TelegramClient('session_name', api_id, api_hash)
client.connect()
# If you already have a previous 'session_name.session' file, skip this.
client.sign_in(phone=phone)
me = client.sign_in(code=77777) # Put whatever code you received here.
client.start()
Doing stuff
Expand Down
19 changes: 19 additions & 0 deletions readthedocs/extra/basic/creating-a-client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ As a full example:
me = client.sign_in(phone_number, input('Enter code: '))
All of this, however, can be done through a call to ``.start()``:

.. code-block:: python
client = TelegramClient('anon', api_id, api_hash)
client.start()
The code shown is just what ``.start()`` will be doing behind the scenes
(with a few extra checks), so that you know how to sign in case you want
to avoid using ``input()`` (the default) for whatever reason.

You can use either, as both will work. Determining which
is just a matter of taste, and how much control you need.


.. note::
If you want to use a **proxy**, you have to `install PySocks`__
(via pip or manual) and then set the appropriated parameters:
Expand Down Expand Up @@ -113,6 +129,9 @@ account, calling :meth:`telethon.TelegramClient.sign_in` will raise a
client.sign_in(password=getpass.getpass())
The mentioned ``.start()`` method will handle this for you as well, but
you must set the ``password=`` parameter beforehand (it won't be asked).

If you don't have 2FA enabled, but you would like to do so through the library,
take as example the following code snippet:

Expand Down
125 changes: 109 additions & 16 deletions telethon/telegram_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import itertools
import os
import sys
import time
from collections import OrderedDict, UserList
from datetime import datetime, timedelta
Expand All @@ -14,8 +15,8 @@
from . import helpers, utils
from .errors import (
RPCError, UnauthorizedError, PhoneCodeEmptyError, PhoneCodeExpiredError,
PhoneCodeHashEmptyError, PhoneCodeInvalidError, LocationInvalidError
)
PhoneCodeHashEmptyError, PhoneCodeInvalidError, LocationInvalidError,
SessionPasswordNeededError)
from .network import ConnectionMode
from .tl import TLObject
from .tl.custom import Draft, Dialog
Expand Down Expand Up @@ -184,6 +185,104 @@ def send_code_request(self, phone, force_sms=False):

return result

def start(self, phone=None, password=None, bot_token=None,
force_sms=False, code_callback=None):
"""
Convenience method to interactively connect and sign in if required,
also taking into consideration that 2FA may be enabled in the account.
Example usage:
>>> client = TelegramClient(session, api_id, api_hash).start(phone)
Please enter the code you received: 12345
Please enter your password: *******
(You are now logged in)
Args:
phone (:obj:`str` | :obj:`int`):
The phone to which the code will be sent.
password (:obj:`callable`, optional):
The password for 2 Factor Authentication (2FA).
This is only required if it is enabled in your account.
bot_token (:obj:`str`):
Bot Token obtained by @BotFather to log in as a bot.
Cannot be specified with `phone` (only one of either allowed).
force_sms (:obj:`bool`, optional):
Whether to force sending the code request as SMS.
This only makes sense when signing in with a `phone`.
code_callback (:obj:`callable`, optional):
A callable that will be used to retrieve the Telegram
login code. Defaults to `input()`.
Returns:
:obj:`TelegramClient`:
This client, so initialization can be chained with `.start()`.
"""

if code_callback is None:
def code_callback():
return input('Please enter the code you received: ')
elif not callable(code_callback):
raise ValueError(
'The code_callback parameter needs to be a callable '
'function that returns the code you received by Telegram.'
)

if (phone and bot_token) or (not phone and not bot_token):
raise ValueError(
'You must provide either a phone number or a bot token, '
'not both (or neither).'
)

if not self.is_connected():
self.connect()

if self.is_user_authorized():
return self

if bot_token:
self.sign_in(bot_token=bot_token)
return self

me = None
attempts = 0
max_attempts = 3
two_step_detected = False

self.send_code_request(phone, force_sms=force_sms)
while attempts < max_attempts:
try:
# Raises SessionPasswordNeededError if 2FA enabled
me = self.sign_in(phone, code_callback())
break
except SessionPasswordNeededError:
two_step_detected = True
break
except (PhoneCodeEmptyError, PhoneCodeExpiredError,
PhoneCodeHashEmptyError, PhoneCodeInvalidError):
print('Invalid code. Please try again.', file=sys.stderr)
attempts += 1
else:
raise RuntimeError(
'{} consecutive sign-in attempts failed. Aborting'
.format(max_attempts)
)

if two_step_detected:
if not password:
raise ValueError(
"Two-step verification is enabled for this account. "
"Please provide the 'password' argument to 'start()'."
)
me = self.sign_in(phone=phone, password=password)

# We won't reach here if any step failed (exit by exception)
print('Signed in successfully as', utils.get_display_name(me))
return self

def sign_in(self, phone=None, code=None,
password=None, bot_token=None, phone_code_hash=None):
"""
Expand Down Expand Up @@ -216,7 +315,7 @@ def sign_in(self, phone=None, code=None,
:meth:`.send_code_request()`.
"""

if phone and not code:
if phone and not code and not password:
return self.send_code_request(phone)
elif code:
phone = utils.parse_phone(phone) or self._phone
Expand All @@ -230,15 +329,9 @@ def sign_in(self, phone=None, code=None,
if not phone_code_hash:
raise ValueError('You also need to provide a phone_code_hash.')

try:
if isinstance(code, int):
code = str(code)

result = self(SignInRequest(phone, phone_code_hash, code))

except (PhoneCodeEmptyError, PhoneCodeExpiredError,
PhoneCodeHashEmptyError, PhoneCodeInvalidError):
return None
# May raise PhoneCodeEmptyError, PhoneCodeExpiredError,
# PhoneCodeHashEmptyError or PhoneCodeInvalidError.
result = self(SignInRequest(phone, phone_code_hash, str(code)))
elif password:
salt = self(GetPasswordRequest()).current_salt
result = self(CheckPasswordRequest(
Expand Down Expand Up @@ -310,7 +403,7 @@ def get_me(self):
or None if the request fails (hence, not authenticated).
Returns:
Your own user.
:obj:`User`: Your own user.
"""
try:
return self(GetUsersRequest([InputUserSelf()]))[0]
Expand Down Expand Up @@ -779,14 +872,14 @@ def send_file(self, entity, file, caption='',
mime_type = guess_type(file)[0]
attr_dict = {
DocumentAttributeFilename:
DocumentAttributeFilename(os.path.basename(file))
DocumentAttributeFilename(os.path.basename(file))
# TODO If the input file is an audio, find out:
# Performer and song title and add DocumentAttributeAudio
}
else:
attr_dict = {
DocumentAttributeFilename:
DocumentAttributeFilename('unnamed')
DocumentAttributeFilename('unnamed')
}

if 'is_voice_note' in kwargs:
Expand Down Expand Up @@ -1305,4 +1398,4 @@ def get_input_entity(self, peer):
'Make sure you have encountered this peer before.'.format(peer)
)

# endregion
# endregion

0 comments on commit 80f81fe

Please sign in to comment.