mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-22 09:26:37 +03:00
Added two-step verification (fixes #4) and more info for errors
This commit is contained in:
parent
be94bff576
commit
6c93d08b8d
|
@ -126,6 +126,8 @@ class RPCError(Exception):
|
||||||
'CHAT_ADMIN_REQUIRED': 'Chat admin privileges are required to do that in the specified chat '
|
'CHAT_ADMIN_REQUIRED': 'Chat admin privileges are required to do that in the specified chat '
|
||||||
'(for example, to send a message in a channel which is not yours).',
|
'(for example, to send a message in a channel which is not yours).',
|
||||||
|
|
||||||
|
'PASSWORD_HASH_INVALID': 'The password (and thus its hash value) you entered is invalid.',
|
||||||
|
|
||||||
# 401 UNAUTHORIZED
|
# 401 UNAUTHORIZED
|
||||||
'AUTH_KEY_UNREGISTERED': 'The key is not registered in the system.',
|
'AUTH_KEY_UNREGISTERED': 'The key is not registered in the system.',
|
||||||
|
|
||||||
|
@ -141,6 +143,8 @@ class RPCError(Exception):
|
||||||
|
|
||||||
'AUTH_KEY_PERM_EMPTY': 'The method is unavailable for temporary authorization key, not bound to permanent.',
|
'AUTH_KEY_PERM_EMPTY': 'The method is unavailable for temporary authorization key, not bound to permanent.',
|
||||||
|
|
||||||
|
'SESSION_PASSWORD_NEEDED': 'Two-steps verification is enabled and a password is required.',
|
||||||
|
|
||||||
# 420 FLOOD
|
# 420 FLOOD
|
||||||
'FLOOD_WAIT_(\d+)': 'A wait of {} seconds is required.'
|
'FLOOD_WAIT_(\d+)': 'A wait of {} seconds is required.'
|
||||||
}
|
}
|
||||||
|
@ -164,6 +168,10 @@ class RPCError(Exception):
|
||||||
self.additional_data = None
|
self.additional_data = None
|
||||||
super().__init__(self, error_msg)
|
super().__init__(self, error_msg)
|
||||||
|
|
||||||
|
# Add another field to easily determine whether this error
|
||||||
|
# should be handled as a password-required error
|
||||||
|
self.password_required = message == 'SESSION_PASSWORD_NEEDED'
|
||||||
|
|
||||||
called_super = True
|
called_super = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
@ -58,4 +58,24 @@ def sha1(data):
|
||||||
sha.update(data)
|
sha.update(data)
|
||||||
return sha.digest()
|
return sha.digest()
|
||||||
|
|
||||||
|
|
||||||
|
def sha256(data):
|
||||||
|
"""Calculates the SHA256 digest for the given data"""
|
||||||
|
sha = hashlib.sha256()
|
||||||
|
sha.update(data)
|
||||||
|
return sha.digest()
|
||||||
|
|
||||||
|
|
||||||
|
def get_password_hash(pw, current_salt):
|
||||||
|
"""Gets the password hash for the two-step verification.
|
||||||
|
curent_salt should be the byte array provided by invoking GetPasswordRequest()"""
|
||||||
|
|
||||||
|
# Passwords are encoded as UTF-8
|
||||||
|
# https://github.com/DrKLO/Telegram/blob/e31388/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java#L2003
|
||||||
|
data = pw.encode('utf-8')
|
||||||
|
|
||||||
|
pw_hash = current_salt+data+current_salt
|
||||||
|
return sha256(pw_hash)
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
from telethon.tl.types import UpdateShortChatMessage
|
from telethon.tl.types import UpdateShortChatMessage
|
||||||
from telethon.tl.types import UpdateShortMessage
|
from telethon.tl.types import UpdateShortMessage
|
||||||
from telethon import TelegramClient
|
from telethon import TelegramClient, RPCError
|
||||||
|
|
||||||
from telethon.utils import get_display_name, get_input_peer
|
from telethon.utils import get_display_name, get_input_peer
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
|
from getpass import getpass
|
||||||
|
|
||||||
# Get the (current) number of lines in the terminal
|
# Get the (current) number of lines in the terminal
|
||||||
cols, rows = shutil.get_terminal_size()
|
cols, rows = shutil.get_terminal_size()
|
||||||
|
@ -51,8 +52,17 @@ class InteractiveTelegramClient(TelegramClient):
|
||||||
code_ok = False
|
code_ok = False
|
||||||
while not code_ok:
|
while not code_ok:
|
||||||
code = input('Enter the code you just received: ')
|
code = input('Enter the code you just received: ')
|
||||||
|
try:
|
||||||
code_ok = self.sign_in(user_phone, code)
|
code_ok = self.sign_in(user_phone, code)
|
||||||
|
|
||||||
|
# Two-step verification may be enabled
|
||||||
|
except RPCError as e:
|
||||||
|
if e.password_required:
|
||||||
|
pw = getpass('Two step verification is enabled. Please enter your password: ')
|
||||||
|
code_ok = self.sign_in(password=pw)
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# Listen for updates
|
# Listen for updates
|
||||||
self.add_update_handler(self.update_handler)
|
self.add_update_handler(self.update_handler)
|
||||||
|
|
|
@ -12,13 +12,19 @@ from telethon.tl import Session
|
||||||
from telethon.tl.functions.upload import SaveBigFilePartRequest
|
from telethon.tl.functions.upload import SaveBigFilePartRequest
|
||||||
from telethon.tl.functions import InvokeWithLayerRequest, InitConnectionRequest
|
from telethon.tl.functions import InvokeWithLayerRequest, InitConnectionRequest
|
||||||
from telethon.tl.functions.help import GetConfigRequest
|
from telethon.tl.functions.help import GetConfigRequest
|
||||||
from telethon.tl.functions.auth import SendCodeRequest, SignInRequest, SignUpRequest, LogOutRequest
|
|
||||||
from telethon.tl.functions.upload import SaveFilePartRequest, GetFileRequest
|
from telethon.tl.functions.upload import SaveFilePartRequest, GetFileRequest
|
||||||
from telethon.tl.functions.messages import \
|
from telethon.tl.functions.messages import \
|
||||||
GetDialogsRequest, GetHistoryRequest, \
|
GetDialogsRequest, GetHistoryRequest, \
|
||||||
SendMessageRequest, SendMediaRequest, \
|
SendMessageRequest, SendMediaRequest, \
|
||||||
ReadHistoryRequest
|
ReadHistoryRequest
|
||||||
|
|
||||||
|
from telethon.tl.functions.auth import \
|
||||||
|
SendCodeRequest, CheckPasswordRequest, \
|
||||||
|
SignInRequest, SignUpRequest, LogOutRequest
|
||||||
|
|
||||||
|
# The following is required to get the password salt
|
||||||
|
from telethon.tl.functions.account import GetPasswordRequest
|
||||||
|
|
||||||
# All the types we need to work with
|
# All the types we need to work with
|
||||||
from telethon.tl.types import \
|
from telethon.tl.types import \
|
||||||
InputPeerEmpty, \
|
InputPeerEmpty, \
|
||||||
|
@ -41,7 +47,7 @@ from telethon.tl.all_tlobjects import layer
|
||||||
class TelegramClient:
|
class TelegramClient:
|
||||||
|
|
||||||
# Current TelegramClient version
|
# Current TelegramClient version
|
||||||
__version__ = '0.6'
|
__version__ = '0.7'
|
||||||
|
|
||||||
# region Initialization
|
# region Initialization
|
||||||
|
|
||||||
|
@ -161,8 +167,13 @@ class TelegramClient:
|
||||||
except InvalidDCError as error:
|
except InvalidDCError as error:
|
||||||
self.reconnect_to_dc(error.new_dc)
|
self.reconnect_to_dc(error.new_dc)
|
||||||
|
|
||||||
def sign_in(self, phone_number, code):
|
def sign_in(self, phone_number=None, code=None, password=None):
|
||||||
"""Completes the authorization of a phone number by providing the received code"""
|
"""Completes the authorization of a phone number by providing the received code.
|
||||||
|
|
||||||
|
If no phone or code is provided, then the sole password will be used. The password
|
||||||
|
should be used after a normal authorization attempt has happened and an RPCError
|
||||||
|
with `.password_required = True` was raised"""
|
||||||
|
if phone_number and code:
|
||||||
if phone_number not in self.phone_code_hashes:
|
if phone_number not in self.phone_code_hashes:
|
||||||
raise ValueError('Please make sure you have called send_code_request first.')
|
raise ValueError('Please make sure you have called send_code_request first.')
|
||||||
|
|
||||||
|
@ -176,6 +187,12 @@ class TelegramClient:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
raise error
|
raise error
|
||||||
|
elif password:
|
||||||
|
salt = self.invoke(GetPasswordRequest()).current_salt
|
||||||
|
result = self.invoke(CheckPasswordRequest(utils.get_password_hash(password, salt)))
|
||||||
|
else:
|
||||||
|
raise ValueError('You must provide a phone_number and a code for the first time, '
|
||||||
|
'and a password only if an RPCError was raised before.')
|
||||||
|
|
||||||
# Result is an Auth.Authorization TLObject
|
# Result is an Auth.Authorization TLObject
|
||||||
self.session.user = result.user
|
self.session.user = result.user
|
||||||
|
|
Loading…
Reference in New Issue
Block a user