mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-26 03:13:45 +03:00
Move auth_key generation and InitConnection logic to .invoke()
The reasoning behind this is that .connect() should not call any request at all, it should only connect to the servers although it currently still calls GetStateRequest. There were some issues (#291, #360) where the auth_key was None (possibly due to .connect() returning False), so this may fix some of the cases where it returned False. This way we also ensure that we always have an auth_key, or even if it "breaks" (it's not the right key for the server anymore). A few additional changes have been introduced to accommodate this, such as moving InitConnection logic too or importing auths.
This commit is contained in:
parent
b3ca68b7d9
commit
ceb37cd4c5
|
@ -33,6 +33,7 @@ from .tl.functions.upload import (
|
||||||
GetFileRequest, SaveBigFilePartRequest, SaveFilePartRequest
|
GetFileRequest, SaveBigFilePartRequest, SaveFilePartRequest
|
||||||
)
|
)
|
||||||
from .tl.types import InputFile, InputFileBig
|
from .tl.types import InputFile, InputFileBig
|
||||||
|
from .tl.types.auth import ExportedAuthorization
|
||||||
from .tl.types.upload import FileCdnRedirect
|
from .tl.types.upload import FileCdnRedirect
|
||||||
from .update_state import UpdateState
|
from .update_state import UpdateState
|
||||||
from .utils import get_appropriated_part_size
|
from .utils import get_appropriated_part_size
|
||||||
|
@ -62,7 +63,7 @@ class TelegramBareClient:
|
||||||
__version__ = '0.15.3'
|
__version__ = '0.15.3'
|
||||||
|
|
||||||
# TODO Make this thread-safe, all connections share the same DC
|
# TODO Make this thread-safe, all connections share the same DC
|
||||||
_dc_options = None
|
_config = None # Server configuration (with .dc_options)
|
||||||
|
|
||||||
# region Initialization
|
# region Initialization
|
||||||
|
|
||||||
|
@ -161,7 +162,7 @@ class TelegramBareClient:
|
||||||
|
|
||||||
# region Connecting
|
# region Connecting
|
||||||
|
|
||||||
def connect(self, _exported_auth=None, _sync_updates=True, _cdn=False):
|
def connect(self, _sync_updates=True):
|
||||||
"""Connects to the Telegram servers, executing authentication if
|
"""Connects to the Telegram servers, executing authentication if
|
||||||
required. Note that authenticating to the Telegram servers is
|
required. Note that authenticating to the Telegram servers is
|
||||||
not the same as authenticating the desired user itself, which
|
not the same as authenticating the desired user itself, which
|
||||||
|
@ -169,62 +170,24 @@ class TelegramBareClient:
|
||||||
|
|
||||||
Note that the optional parameters are meant for internal use.
|
Note that the optional parameters are meant for internal use.
|
||||||
|
|
||||||
If '_exported_auth' is not None, it will be used instead to
|
|
||||||
determine the authorization key for the current session.
|
|
||||||
|
|
||||||
If '_sync_updates', sync_updates() will be called and a
|
If '_sync_updates', sync_updates() will be called and a
|
||||||
second thread will be started if necessary. Note that this
|
second thread will be started if necessary. Note that this
|
||||||
will FAIL if the client is not connected to the user's
|
will FAIL if the client is not connected to the user's
|
||||||
native data center, raising a "UserMigrateError", and
|
native data center, raising a "UserMigrateError", and
|
||||||
calling .disconnect() in the process.
|
calling .disconnect() in the process.
|
||||||
|
|
||||||
If '_cdn' is False, methods that are not allowed on such data
|
|
||||||
centers won't be invoked.
|
|
||||||
"""
|
"""
|
||||||
self._main_thread_ident = threading.get_ident()
|
self._main_thread_ident = threading.get_ident()
|
||||||
self._background_error = None # Clear previous errors
|
self._background_error = None # Clear previous errors
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._sender.connect()
|
self._sender.connect()
|
||||||
if not self.session.auth_key:
|
|
||||||
# New key, we need to tell the server we're going to use
|
|
||||||
# the latest layer
|
|
||||||
try:
|
|
||||||
self.session.auth_key, self.session.time_offset = \
|
|
||||||
authenticator.do_authentication(self._sender.connection)
|
|
||||||
except BrokenAuthKeyError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.session.layer = LAYER
|
|
||||||
self.session.save()
|
|
||||||
init_connection = True
|
|
||||||
else:
|
|
||||||
init_connection = self.session.layer != LAYER
|
|
||||||
|
|
||||||
if init_connection:
|
|
||||||
if _exported_auth is not None:
|
|
||||||
self._init_connection(ImportAuthorizationRequest(
|
|
||||||
_exported_auth.id, _exported_auth.bytes
|
|
||||||
))
|
|
||||||
elif not _cdn:
|
|
||||||
TelegramBareClient._dc_options = \
|
|
||||||
self._init_connection(GetConfigRequest()).dc_options
|
|
||||||
|
|
||||||
elif _exported_auth is not None:
|
|
||||||
self(ImportAuthorizationRequest(
|
|
||||||
_exported_auth.id, _exported_auth.bytes
|
|
||||||
))
|
|
||||||
|
|
||||||
if TelegramBareClient._dc_options is None and not _cdn:
|
|
||||||
TelegramBareClient._dc_options = \
|
|
||||||
self(GetConfigRequest()).dc_options
|
|
||||||
|
|
||||||
# Connection was successful! Try syncing the update state
|
# Connection was successful! Try syncing the update state
|
||||||
# UNLESS '_sync_updates' is False (we probably are in
|
# UNLESS '_sync_updates' is False (we probably are in
|
||||||
# another data center and this would raise UserMigrateError)
|
# another data center and this would raise UserMigrateError)
|
||||||
# to also assert whether the user is logged in or not.
|
# to also assert whether the user is logged in or not.
|
||||||
self._user_connected = True
|
self._user_connected = True
|
||||||
if self._authorized is None and _sync_updates and not _cdn:
|
if self._authorized is None and _sync_updates:
|
||||||
try:
|
try:
|
||||||
self.sync_updates()
|
self.sync_updates()
|
||||||
self._set_connected_and_authorized()
|
self._set_connected_and_authorized()
|
||||||
|
@ -239,11 +202,7 @@ class TelegramBareClient:
|
||||||
# This is fine, probably layer migration
|
# This is fine, probably layer migration
|
||||||
self._logger.debug('Found invalid item, probably migrating', e)
|
self._logger.debug('Found invalid item, probably migrating', e)
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
return self.connect(
|
return self.connect(_sync_updates=_sync_updates)
|
||||||
_exported_auth=_exported_auth,
|
|
||||||
_sync_updates=_sync_updates,
|
|
||||||
_cdn=_cdn
|
|
||||||
)
|
|
||||||
|
|
||||||
except (RPCError, ConnectionError) as error:
|
except (RPCError, ConnectionError) as error:
|
||||||
# Probably errors from the previous session, ignore them
|
# Probably errors from the previous session, ignore them
|
||||||
|
@ -256,8 +215,9 @@ class TelegramBareClient:
|
||||||
def is_connected(self):
|
def is_connected(self):
|
||||||
return self._sender.is_connected()
|
return self._sender.is_connected()
|
||||||
|
|
||||||
def _init_connection(self, query=None):
|
def _wrap_init_connection(self, query):
|
||||||
result = self(InvokeWithLayerRequest(LAYER, InitConnectionRequest(
|
"""Wraps query around InvokeWithLayerRequest(InitConnectionRequest())"""
|
||||||
|
return InvokeWithLayerRequest(LAYER, InitConnectionRequest(
|
||||||
api_id=self.api_id,
|
api_id=self.api_id,
|
||||||
device_model=self.session.device_model,
|
device_model=self.session.device_model,
|
||||||
system_version=self.session.system_version,
|
system_version=self.session.system_version,
|
||||||
|
@ -266,10 +226,7 @@ class TelegramBareClient:
|
||||||
system_lang_code=self.session.system_lang_code,
|
system_lang_code=self.session.system_lang_code,
|
||||||
lang_pack='', # "langPacks are for official apps only"
|
lang_pack='', # "langPacks are for official apps only"
|
||||||
query=query
|
query=query
|
||||||
)))
|
))
|
||||||
self.session.layer = LAYER
|
|
||||||
self.session.save()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
"""Disconnects from the Telegram server
|
"""Disconnects from the Telegram server
|
||||||
|
@ -308,13 +265,18 @@ class TelegramBareClient:
|
||||||
except ConnectionResetError:
|
except ConnectionResetError:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
self.disconnect()
|
# Since we're reconnecting possibly due to a UserMigrateError,
|
||||||
self.session.auth_key = None # Force creating new auth_key
|
# we need to first know the Data Centers we can connect to. Do
|
||||||
|
# that before disconnecting.
|
||||||
dc = self._get_dc(new_dc)
|
dc = self._get_dc(new_dc)
|
||||||
ip = dc.ip_address
|
|
||||||
self.session.server_address = ip
|
self.session.server_address = dc.ip_address
|
||||||
self.session.port = dc.port
|
self.session.port = dc.port
|
||||||
|
# auth_key's are associated with a server, which has now changed
|
||||||
|
# so it's not valid anymore. Set to None to force recreating it.
|
||||||
|
self.session.auth_key = None
|
||||||
self.session.save()
|
self.session.save()
|
||||||
|
self.disconnect()
|
||||||
return self.connect()
|
return self.connect()
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
@ -327,10 +289,8 @@ class TelegramBareClient:
|
||||||
|
|
||||||
def _get_dc(self, dc_id, ipv6=False, cdn=False):
|
def _get_dc(self, dc_id, ipv6=False, cdn=False):
|
||||||
"""Gets the Data Center (DC) associated to 'dc_id'"""
|
"""Gets the Data Center (DC) associated to 'dc_id'"""
|
||||||
if TelegramBareClient._dc_options is None:
|
if not TelegramBareClient._config:
|
||||||
raise ConnectionError(
|
TelegramBareClient._config = self(GetConfigRequest())
|
||||||
'Cannot determine the required data center IP address. '
|
|
||||||
'Stabilise a successful initial connection first.')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if cdn:
|
if cdn:
|
||||||
|
@ -339,15 +299,15 @@ class TelegramBareClient:
|
||||||
rsa.add_key(pk.public_key)
|
rsa.add_key(pk.public_key)
|
||||||
|
|
||||||
return next(
|
return next(
|
||||||
dc for dc in TelegramBareClient._dc_options if dc.id == dc_id
|
dc for dc in TelegramBareClient._config.dc_options
|
||||||
and bool(dc.ipv6) == ipv6 and bool(dc.cdn) == cdn
|
if dc.id == dc_id and bool(dc.ipv6) == ipv6 and bool(dc.cdn) == cdn
|
||||||
)
|
)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
if not cdn:
|
if not cdn:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# New configuration, perhaps a new CDN was added?
|
# New configuration, perhaps a new CDN was added?
|
||||||
TelegramBareClient._dc_options = self(GetConfigRequest()).dc_options
|
TelegramBareClient._config = self(GetConfigRequest())
|
||||||
return self._get_dc(dc_id, ipv6=ipv6, cdn=cdn)
|
return self._get_dc(dc_id, ipv6=ipv6, cdn=cdn)
|
||||||
|
|
||||||
def _get_exported_client(self, dc_id):
|
def _get_exported_client(self, dc_id):
|
||||||
|
@ -387,7 +347,14 @@ class TelegramBareClient:
|
||||||
proxy=self._sender.connection.conn.proxy,
|
proxy=self._sender.connection.conn.proxy,
|
||||||
timeout=self._sender.connection.get_timeout()
|
timeout=self._sender.connection.get_timeout()
|
||||||
)
|
)
|
||||||
client.connect(_exported_auth=export_auth, _sync_updates=False)
|
client.connect(_sync_updates=False)
|
||||||
|
if isinstance(export_auth, ExportedAuthorization):
|
||||||
|
client(ImportAuthorizationRequest(
|
||||||
|
id=export_auth.id, bytes=export_auth.bytes
|
||||||
|
))
|
||||||
|
elif export_auth is not None:
|
||||||
|
self._logger.warning('Unknown return export_auth type', export_auth)
|
||||||
|
|
||||||
client._authorized = True # We exported the auth, so we got auth
|
client._authorized = True # We exported the auth, so we got auth
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
@ -409,9 +376,10 @@ class TelegramBareClient:
|
||||||
|
|
||||||
# This will make use of the new RSA keys for this specific CDN.
|
# This will make use of the new RSA keys for this specific CDN.
|
||||||
#
|
#
|
||||||
# This relies on the fact that TelegramBareClient._dc_options is
|
# We won't be calling GetConfigRequest because it's only called
|
||||||
# static and it won't be called from this DC (it would fail).
|
# when needed by ._get_dc, and also it's static so it's likely
|
||||||
client.connect(_cdn=True) # Avoid invoking non-CDN specific methods
|
# set already. Avoid invoking non-CDN methods by not syncing updates.
|
||||||
|
client.connect(_sync_updates=False)
|
||||||
client._authorized = self._authorized
|
client._authorized = self._authorized
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
@ -472,12 +440,34 @@ class TelegramBareClient:
|
||||||
invoke = __call__
|
invoke = __call__
|
||||||
|
|
||||||
def _invoke(self, sender, call_receive, update_state, *requests):
|
def _invoke(self, sender, call_receive, update_state, *requests):
|
||||||
|
# We need to specify the new layer (by initializing a new
|
||||||
|
# connection) if it has changed from the latest known one.
|
||||||
|
init_connection = self.session.layer != LAYER
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Ensure that we start with no previous errors (i.e. resending)
|
# Ensure that we start with no previous errors (i.e. resending)
|
||||||
for x in requests:
|
for x in requests:
|
||||||
x.confirm_received.clear()
|
x.confirm_received.clear()
|
||||||
x.rpc_error = None
|
x.rpc_error = None
|
||||||
|
|
||||||
|
if not self.session.auth_key:
|
||||||
|
# New key, we need to tell the server we're going to use
|
||||||
|
# the latest layer and initialize the connection doing so.
|
||||||
|
self.session.auth_key, self.session.time_offset = \
|
||||||
|
authenticator.do_authentication(self._sender.connection)
|
||||||
|
init_connection = True
|
||||||
|
|
||||||
|
if init_connection:
|
||||||
|
if len(requests) == 1:
|
||||||
|
requests = [self._wrap_init_connection(requests[0])]
|
||||||
|
else:
|
||||||
|
# We need a SINGLE request (like GetConfig) to init conn.
|
||||||
|
# Once that's done, the N original requests will be
|
||||||
|
# invoked.
|
||||||
|
TelegramBareClient._config = self(
|
||||||
|
self._wrap_init_connection(GetConfigRequest())
|
||||||
|
)
|
||||||
|
|
||||||
sender.send(*requests)
|
sender.send(*requests)
|
||||||
|
|
||||||
if not call_receive:
|
if not call_receive:
|
||||||
|
@ -493,6 +483,10 @@ class TelegramBareClient:
|
||||||
while not all(x.confirm_received.is_set() for x in requests):
|
while not all(x.confirm_received.is_set() for x in requests):
|
||||||
sender.receive(update_state=update_state)
|
sender.receive(update_state=update_state)
|
||||||
|
|
||||||
|
except BrokenAuthKeyError:
|
||||||
|
self._logger.error('Broken auth key, a new one will be generated')
|
||||||
|
self.session.auth_key = None
|
||||||
|
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass # We will just retry
|
pass # We will just retry
|
||||||
|
|
||||||
|
@ -513,6 +507,12 @@ class TelegramBareClient:
|
||||||
sleep(0.1) # Retry forever until we can send the request
|
sleep(0.1) # Retry forever until we can send the request
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if init_connection:
|
||||||
|
# We initialized the connection successfully, even if
|
||||||
|
# a request had an RPC error we have invoked it fine.
|
||||||
|
self.session.layer = LAYER
|
||||||
|
self.session.save()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
raise next(x.rpc_error for x in requests if x.rpc_error)
|
raise next(x.rpc_error for x in requests if x.rpc_error)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user