U
    h+d                     @   s   d dl Z d dlZd dlZd dlZd dlZd dlZddlmZmZm	Z	m
Z ddlmZmZmZ ddlmZ ejrzddlmZ G dd	 d	ZdS )
    N   )utilshelperserrorspassword)types	functionscustom)SessionState   )TelegramClientc                   @   s  e Zd Zdd dd fddddddd	d
ejejg ef ef ejejg ef ef eeejg ejeef f eeed
d
ddZ	d
dddZ
dd Zd3ddddd
eejeef eeeddddZd4dddd
ejeef eeeeddddZdd Zddd d
eeed!d"d#d$Zd5d
eje ejd%d&d'Zd
ed(d)d*Zd6dddd+d
eeeeejegef ed,d-d.Zd/d0 Zd1d2 ZejZejZdS )7AuthMethodsc                   C   s   t dS )Nz(Please enter your phone (or bot token): input r   r   8/tmp/pip-unpacked-wheel-c81u5j2r/telethon/client/auth.py<lambda>       zAuthMethods.<lambda>c                   C   s
   t  dS )NzPlease enter your password: )getpassr   r   r   r   r      r   NFzNew User    )	bot_token	force_smscode_callback
first_name	last_namemax_attemptsr   )
selfphoner   r   r   r   r   r   r   returnc          
   
   C   s~   |dkrdd }nt |s"td|s2|s2td|rJ|rJt |sJtd| j||||||||d}	| j rr|	S | j|	S )a
  
        Starts the client (connects and logs in if necessary).

        By default, this method will be interactive (asking for
        user input if needed), and will handle 2FA if enabled too.

        If the event loop is already running, this method returns a
        coroutine that you should await on your own code; otherwise
        the loop is ran until said coroutine completes.

        Arguments
            phone (`str` | `int` | `callable`):
                The phone (or callable without arguments to get it)
                to which the code will be sent. If a bot-token-like
                string is given, it will be used as such instead.
                The argument may be a coroutine.

            password (`str`, `callable`, optional):
                The password for 2 Factor Authentication (2FA).
                This is only required if it is enabled in your account.
                The argument may be a coroutine.

            bot_token (`str`):
                Bot Token obtained by `@BotFather <https://t.me/BotFather>`_
                to log in as a bot. Cannot be specified with ``phone`` (only
                one of either allowed).

            force_sms (`bool`, optional):
                Whether to force sending the code request as SMS.
                This only makes sense when signing in with a `phone`.

            code_callback (`callable`, optional):
                A callable that will be used to retrieve the Telegram
                login code. Defaults to `input()`.
                The argument may be a coroutine.

            first_name (`str`, optional):
                The first name to be used if signing up. This has no
                effect if the account already exists and you sign in.

            last_name (`str`, optional):
                Similar to the first name, but for the last. Optional.

            max_attempts (`int`, optional):
                How many times the code/password callback should be
                retried or switching between signing in and signing up.

        Returns
            This `TelegramClient`, so initialization
            can be chained with ``.start()``.

        Example
            .. code-block:: python

                client = TelegramClient('anon', api_id, api_hash)

                # Starting as a bot account
                await client.start(bot_token=bot_token)

                # Starting as a user account
                await client.start(phone)
                # Please enter the code you received: 12345
                # Please enter your password: *******
                # (You are now logged in)

                # Starting using a context manager (this calls start()):
                with client:
                    pass
        Nc                   S   s   t dS )Nz$Please enter the code you received: r   r   r   r   r   r   e   s    z(AuthMethods.start.<locals>.code_callbackzkThe code_callback parameter needs to be a callable function that returns the code you received by Telegram.z&No phone number or bot token provided.zFBoth a phone and a bot token provided, must only provide one of either)r   r   r   r   r   r   r   r   )callable
ValueError_startZloopZ
is_runningZrun_until_complete)
r   r   r   r   r   r   r   r   r   coror   r   r   start   s.    P

zAuthMethods.startr   c	                    s  |   s|  I d H  |  I d H }	|	d k	r|rX|d |d t|	jkr~td n&|r~t|s~t	
||	jkr~td | S |st|r| }
t|
r|
I d H }
d|
kr|
}qt	
|
p|}q|r| j|dI d H  | S d }	d}d}| j||dI d H  ||k rzL| }
t|
r(|
I d H }
|
s:tjd d| j||
d	I d H }	W qW nT tjk
rx   d
}Y qY n4 tjtjtjtjfk
r   tdtjd Y nX |d7 }q td||rz|stdt|rft|D ]j}z<| }
t|
r|
I d H }
| j||
dI d H }	W  qzW n& tjk
rR   tdtjd Y nX qtjd dn| j||dI d H }	dt	|	 }}d}zt|||dd W n: tk
r   t||j dddj!ddd|dd Y nX | S )N:zthe session already had an authorized user so it did not login to the bot account using the provided bot_token; if you were expecting a different user, check whether you are accidentally reusing an existing sessionzthe session already had an authorized user so it did not login to the user account using the provided phone; if you were expecting a different user, check whether you are accidentally reusing an existing session)r   r   F)r   request)codeTzInvalid code. Please try again.)filer   z0{} consecutive sign-in attempts failed. AbortingzgTwo-step verification is enabled for this account. Please provide the 'password' argument to 'start()'.)r   r   z"Invalid password. Please try againzSigned in successfully as z@; remember to not break the ToS or you will risk an account ban!r   )sepzutf-8ignore)r   ascii)"Zis_connectedconnectget_mefindstridwarningswarnr    r   parse_phoner   inspectisawaitablesign_insend_code_requestr   ZPhoneCodeEmptyErrorZSessionPasswordNeededErrorPhoneCodeExpiredErrorZPhoneCodeHashEmptyErrorZPhoneCodeInvalidErrorprintsysstderrRuntimeErrorformatr!   rangeZPasswordHashInvalidErrorZget_display_nameUnicodeEncodeErrorencodedecode)r   r   r   r   r   r   r   r   r   mevalueattemptsZtwo_step_detected_signednameZtosr   r   r   r"      s    







  zAuthMethods._startc                 C   sB   t |p| j}|std|p,| j|d}|s:td||fS )zN
        Helper method to both parse and validate phone and its hash.
        z1Please make sure to call send_code_request first.Nz+You also need to provide a phone_code_hash.)r   r5   _phoner!   _phone_code_hashget)r   r   
phone_hashr   r   r   _parse_phone_and_hash   s    z!AuthMethods._parse_phone_and_hash)r   r   phone_code_hashz-typing.Union[types.User, types.auth.SentCode])r   r   r)   r   r   rO   r   c          
         s   |   I dH }|r|S |r2|s2|s2| |I dH S |r\| ||\}}tj||t|}nT|r| tj I dH }tj	t
||}n&|rtjjd|| j| jd}ntdz| |I dH }	W n& tjk
r   | j|d  Y nX t|	tjjr|	j| _tj|d| |	jI dH S )a  
        Logs in to Telegram to an existing user or bot account.

        You should only use this if you are not authorized yet.

        This method will send the code if it's not provided.

        .. note::

            In most cases, you should simply use `start()` and not this method.

        Arguments
            phone (`str` | `int`):
                The phone to send the code to if no code was provided,
                or to override the phone that was previously used with
                these requests.

            code (`str` | `int`):
                The code that Telegram sent. Note that if you have sent this
                code through the application itself it will immediately
                expire. If you want to send the code, obfuscate it somehow.
                If you're not doing any of this you can ignore this note.

            password (`str`):
                2FA password, should be used if a previous call raised
                ``SessionPasswordNeededError``.

            bot_token (`str`):
                Used to sign in as a bot. Not all requests will be available.
                This should be the hash the `@BotFather <https://t.me/BotFather>`_
                gave you.

            phone_code_hash (`str`, optional):
                The hash returned by `send_code_request`. This can be left as
                `None` to use the last hash known for the phone to be used.

        Returns
            The signed in user, or the information about
            :meth:`send_code_request`.

        Example
            .. code-block:: python

                phone = '+34 123 123 123'
                await client.sign_in(phone)  # send code

                code = input('enter code: ')
                await client.sign_in(phone, code)
        Nr   )flagsZbot_auth_tokenapi_idapi_hashziYou must provide a phone and a code the first time, and a password only if an RPCError was raised before.r'   )r/   r9   rN   r   authZSignInRequestr1   accountGetPasswordRequestZCheckPasswordRequestpwd_modcompute_checkZImportBotAuthorizationRequestrQ   rR   r!   r   r:   rK   pop
isinstancer   ZAuthorizationSignUpRequiredZterms_of_serviceZ_tosZPhoneNumberUnoccupiedError	_on_loginuser)
r   r   r)   r   r   rO   rD   r(   pwdresultr   r   r   r8     sJ    9
  
  zAuthMethods.sign_in)r   rO   z
types.User)r   r)   r   r   r   rO   r   c                   s   t ddS )z
        This method can no longer be used, and will immediately raise a ``ValueError``.
        See `issue #4050 <https://github.com/LonamiWebs/Telethon/issues/4050>`_ for context.
        zxThird-party applications cannot sign up for Telegram. See https://github.com/LonamiWebs/Telethon/issues/4050 for detailsN)r!   )r   r)   r   r   r   rO   r   r   r   sign_ups  s    zAuthMethods.sign_upc                    s   | j |j|j|j d| _| tj I dH }| tjj	|j
|j|jdI dH }t|tjjrh|j}n,t|tjjr~|j}nt|tjjr|j
|_
| jtddd|j
|jt|j |jdg  |S )z}
        Callback called whenever the login or sign up process completes.

        Returns the input user parameter.
        TN)ptsdateqtsr   )_mb_entity_cacheset_self_userr2   ZbotZaccess_hash_authorizedr   ZupdatesZGetStateRequestZGetDifferenceRequestr_   r`   ra   rY   r   Z
DifferencestateZDifferenceSliceZintermediate_stateZDifferenceTooLongZ_message_boxloadr
   int	timestampseq)r   r[   re   
differencer   r   r   rZ     s    "0zAuthMethods._on_loginr   r   _retry_countztypes.auth.SentCode)r   r   r   rl   r   c                   s  |rt d d}d}t|p$| j}| j|}|sz(| tj	|| j
| jt I dH }W n: tjk
r   |dkrz | j|||d dI dH  Y S X t|tjjrtdt|jtjjrd}|jr|j | j|< }nd}|| _|rz| tj||I dH }W n\ tjk
rd   |dkr( | j|d | jt d	 | j|d|d dI dH  Y S X t|tjjr~td
|j| j|< |S )a  
        Sends the Telegram code needed to login to the given phone number.

        Arguments
            phone (`str` | `int`):
                The phone to which the code will be sent.

            force_sms (`bool`, optional):
                Whether to force sending as SMS. This has been deprecated.
                See `issue #4050 <https://github.com/LonamiWebs/Telethon/issues/4050>`_ for context.

        Returns
            An instance of :tl:`SentCode`.

        Example
            .. code-block:: python

                phone = '+34 123 123 123'
                sent = await client.send_code_request(phone)
                print(sent)
        z1force_sms has been deprecated and no longer worksFNr   r   rk   z&logged in right after sending the codeTz>Phone code expired in ResendCodeRequest, requesting a new codez(logged in right after resending the code)r3   r4   r   r5   rJ   rK   rL   r   rS   ZSendCodeRequestrQ   rR   r   ZCodeSettingsr   ZAuthRestartErrorr9   rY   ZSentCodeSuccessr>   typeZSentCodeTypeSmsrO   ZResendCodeRequestr:   rX   _log__name__info)r   r   r   rl   r]   rM   r   r   r   r9     sf    
     

  zAuthMethods.send_code_request)r   ignored_idsr   c                    s"   t | |pg }| I dH  |S )a  
        Initiates the QR login procedure.

        Note that you must be connected before invoking this, as with any
        other request.

        It is up to the caller to decide how to present the code to the user,
        whether it's the URL, using the token bytes directly, or generating
        a QR code and displaying it by other means.

        See the documentation for `QRLogin` to see how to proceed after this.

        Arguments
            ignored_ids (List[`int`]):
                List of already logged-in user IDs, to prevent logging in
                twice with the same user.

        Returns
            An instance of `QRLogin`.

        Example
            .. code-block:: python

                def display_url_as_qr(url):
                    pass  # do whatever to show url as a qr to the user

                qr_login = await client.qr_login()
                display_url_as_qr(qr_login.url)

                # Important! You need to wait for the login to complete!
                await qr_login.wait()

                # If you have 2FA enabled, `wait` will raise `telethon.errors.SessionPasswordNeededError`.
                # You should except that error and call `sign_in` with the password if this happens.
        N)r	   QRLoginZrecreate)r   rq   qr_loginr   r   r   rs     s    $zAuthMethods.qr_login)r   r   c                    sj   z| t j I dH  W n tjk
r0   Y dS X | jddd d| _|  I dH  | j	
  d| _	dS )a~  
        Logs out Telegram and deletes the current ``*.session`` file.

        The client is unusable after logging out and a new instance should be created.

        Returns
            `True` if the operation was successful.

        Example
            .. code-block:: python

                # Note: you will need to login again!
                await client.log_out()
        NFT)r   rS   ZLogOutRequestr   ZRPCErrorrb   rc   rd   
disconnectsessiondeleter%   r   r   r   log_out  s    
zAuthMethods.log_out)hintemailemail_code_callback)r   current_passwordnew_passwordrx   ry   rz   r   c             
      s@  |dkr|dkrdS |r(t |s(td| tj I dH }|j jtd7  _t	|t
jjsdt|jsr|rrd}|rt||}nt
 }|rt|j|}nd}z2| tjj|t
jj|j|||dddI dH  W nd tjk
r: }	 zB||	j}
t|
r|
I dH }
t|
}
| tj|
I dH  W 5 d}	~	X Y nX dS )	a  
        Changes the 2FA settings of the logged in user.

        Review carefully the parameter explanations before using this method.

        Note that this method may be *incredibly* slow depending on the
        prime numbers that must be used during the process to make sure
        that everything is safe.

        Has no effect if both current and new password are omitted.

        Arguments
            current_password (`str`, optional):
                The current password, to authorize changing to ``new_password``.
                Must be set if changing existing 2FA settings.
                Must **not** be set if 2FA is currently disabled.
                Passing this by itself will remove 2FA (if correct).

            new_password (`str`, optional):
                The password to set as 2FA.
                If 2FA was already enabled, ``current_password`` **must** be set.
                Leaving this blank or `None` will remove the password.

            hint (`str`, optional):
                Hint to be displayed by Telegram when it asks for 2FA.
                Leaving unspecified is highly discouraged.
                Has no effect if ``new_password`` is not set.

            email (`str`, optional):
                Recovery and verification email. If present, you must also
                set `email_code_callback`, else it raises ``ValueError``.

            email_code_callback (`callable`, optional):
                If an email is provided, a callback that returns the code sent
                to it must also be set. This callback may be asynchronous.
                It should return a string with the code. The length of the
                code will be passed to the callback as an input parameter.

                If the callback returns an invalid code, it will raise
                ``CodeInvalidError``.

        Returns
            `True` if successful, `False` otherwise.

        Example
            .. code-block:: python

                # Setting a password for your account which didn't have
                await client.edit_2fa(new_password='I_<3_Telethon')

                # Removing the password
                await client.edit_2fa(current_password='I_<3_Telethon')
        NFz)email present without email_code_callback    r   )new_algonew_password_hashrx   ry   Znew_secure_settings)r   Znew_settingsT)r    r!   r   rT   rU   r~   Zsalt1osurandomrY   r   ZPasswordAssertionErrorZhas_passwordrV   rW   ZInputCheckPasswordEmptyZcompute_digestZUpdatePasswordSettingsRequestZPasswordInputSettingsr   ZEmailUnconfirmedErrorZcode_lengthr6   r7   r1   ZConfirmPasswordEmailRequest)r   r{   r|   rx   ry   rz   r\   r   r   er)   r   r   r   edit_2fa/  sH    =
 


(zAuthMethods.edit_2fac                    s   |   I d H S N)r$   r%   r   r   r   
__aenter__  s    zAuthMethods.__aenter__c                    s   |   I d H  d S r   )rt   )r   argsr   r   r   	__aexit__  s    zAuthMethods.__aexit__)NN)r   )N)NN)ro   
__module____qualname__typingUnionCallabler1   boolrg   r$   r"   rN   r8   r^   rZ   r9   Listr	   rr   rs   rw   r   r   r   r   Z_sync_enter	__enter__Z
_sync_exit__exit__r   r   r   r   r      s    p{   i   R(   mr   )r   r6   r   r<   r   r3   r   r   r   r   r   rV   tlr   r   r	   Z_updatesr
   TYPE_CHECKINGZtelegramclientr   r   r   r   r   r   <module>   s   