Merge branch 'master' into asyncio

This commit is contained in:
Lonami Exo
2017-10-21 15:45:56 +02:00
20 changed files with 343 additions and 694 deletions

View File

@@ -8,15 +8,8 @@ from .common import (
CdnFileTamperedError
)
from .rpc_errors import (
RPCError, InvalidDCError, BadRequestError, UnauthorizedError,
ForbiddenError, NotFoundError, FloodError, ServerError, BadMessageError
)
from .rpc_errors_303 import *
from .rpc_errors_400 import *
from .rpc_errors_401 import *
from .rpc_errors_420 import *
# This imports the base errors too, as they're imported there
from .rpc_error_list import *
def report_error(code, message, report_method):
@@ -43,27 +36,31 @@ def rpc_message_to_error(code, message, report_method=None):
args=(code, message, report_method)
).start()
errors = {
303: rpc_errors_303_all,
400: rpc_errors_400_all,
401: rpc_errors_401_all,
420: rpc_errors_420_all
}.get(code, None)
# Try to get the error by direct look-up, otherwise regex
# TODO Maybe regexes could live in a separate dictionary?
cls = rpc_errors_all.get(message, None)
if cls:
return cls()
if errors is not None:
for msg, cls in errors.items():
m = re.match(msg, message)
if m:
extra = int(m.group(1)) if m.groups() else None
return cls(extra=extra)
for msg_regex, cls in rpc_errors_all.items():
m = re.match(msg_regex, message)
if m:
capture = int(m.group(1)) if m.groups() else None
return cls(capture=capture)
elif code == 403:
if code == 400:
return BadRequestError(message)
if code == 401:
return UnauthorizedError(message)
if code == 403:
return ForbiddenError(message)
elif code == 404:
if code == 404:
return NotFoundError(message)
elif code == 500:
if code == 500:
return ServerError(message)
return RPCError('{} (code {})'.format(message, code))

View File

@@ -1,51 +0,0 @@
from . import InvalidDCError
class FileMigrateError(InvalidDCError):
def __init__(self, **kwargs):
self.new_dc = kwargs['extra']
super(Exception, self).__init__(
self,
'The file to be accessed is currently stored in DC {}.'
.format(self.new_dc)
)
class PhoneMigrateError(InvalidDCError):
def __init__(self, **kwargs):
self.new_dc = kwargs['extra']
super(Exception, self).__init__(
self,
'The phone number a user is trying to use for authorization is '
'associated with DC {}.'
.format(self.new_dc)
)
class NetworkMigrateError(InvalidDCError):
def __init__(self, **kwargs):
self.new_dc = kwargs['extra']
super(Exception, self).__init__(
self,
'The source IP address is associated with DC {}.'
.format(self.new_dc)
)
class UserMigrateError(InvalidDCError):
def __init__(self, **kwargs):
self.new_dc = kwargs['extra']
super(Exception, self).__init__(
self,
'The user whose identity is being used to execute queries is '
'associated with DC {}.'
.format(self.new_dc)
)
rpc_errors_303_all = {
'FILE_MIGRATE_(\d+)': FileMigrateError,
'PHONE_MIGRATE_(\d+)': PhoneMigrateError,
'NETWORK_MIGRATE_(\d+)': NetworkMigrateError,
'USER_MIGRATE_(\d+)': UserMigrateError
}

View File

@@ -1,453 +0,0 @@
from . import BadRequestError
class ApiIdInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The api_id/api_hash combination is invalid.'
)
class BotMethodInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The API access for bot users is restricted. The method you '
'tried to invoke cannot be executed as a bot.'
)
class CdnMethodInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'This method cannot be invoked on a CDN server. Refer to '
'https://core.telegram.org/cdn#schema for available methods.'
)
class ChannelInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'Invalid channel object. Make sure to pass the right types,'
' for instance making sure that the request is designed for '
'channels or otherwise look for a different one more suited.'
)
class ChannelPrivateError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The channel specified is private and you lack permission to '
'access it. Another reason may be that you were banned from it.'
)
class ChatAdminRequiredError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'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).'
)
class ChatIdInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'Invalid object ID for a chat. Make sure to pass the right types,'
' for instance making sure that the request is designed for chats'
' (not channels/megagroups) or otherwise look for a different one'
' more suited.\nAn example working with a megagroup and'
' AddChatUserRequest, it will fail because megagroups are channels'
'. Use InviteToChannelRequest instead.'
)
class ConnectionLangPackInvalid(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The specified language pack is not valid. This is meant to be '
'used by official applications only so far, leave it empty.'
)
class ConnectionLayerInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The very first request must always be InvokeWithLayerRequest.'
)
class DcIdInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'This occurs when an authorization is tried to be exported for '
'the same data center one is currently connected to.'
)
class FieldNameEmptyError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The field with the name FIELD_NAME is missing.'
)
class FieldNameInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The field with the name FIELD_NAME is invalid.'
)
class FilePartsInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The number of file parts is invalid.'
)
class FilePartMissingError(BadRequestError):
def __init__(self, **kwargs):
self.which = kwargs['extra']
super(Exception, self).__init__(
self,
'Part {} of the file is missing from storage.'.format(self.which)
)
class FilePartInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The file part number is invalid.'
)
class FirstNameInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The first name is invalid.'
)
class InputMethodInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The invoked method does not exist anymore or has never existed.'
)
class InputRequestTooLongError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The input request was too long. This may be a bug in the library '
'as it can occur when serializing more bytes than it should (like'
'appending the vector constructor code at the end of a message).'
)
class LastNameInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The last name is invalid.'
)
class LimitInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'An invalid limit was provided. See '
'https://core.telegram.org/api/files#downloading-files'
)
class LocationInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The location given for a file was invalid. See '
'https://core.telegram.org/api/files#downloading-files'
)
class Md5ChecksumInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The MD5 check-sums do not match.'
)
class MessageEmptyError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'Empty or invalid UTF-8 message was sent.'
)
class MessageIdInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The specified message ID is invalid.'
)
class MessageTooLongError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'Message was too long. Current maximum length is 4096 UTF-8 '
'characters.'
)
class MessageNotModifiedError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'Content of the message was not modified.'
)
class MsgWaitFailedError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'A waiting call returned an error.'
)
class OffsetInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The given offset was invalid, it must be divisible by 1KB. '
'See https://core.telegram.org/api/files#downloading-files'
)
class PasswordHashInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The password (and thus its hash value) you entered is invalid.'
)
class PeerIdInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'An invalid Peer was used. Make sure to pass the right peer type.'
)
class PhoneCodeEmptyError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The phone code is missing.'
)
class PhoneCodeExpiredError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The confirmation code has expired.'
)
class PhoneCodeHashEmptyError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The phone code hash is missing.'
)
class PhoneCodeInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The phone code entered was invalid.'
)
class PhoneNumberBannedError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The used phone number has been banned from Telegram and cannot '
'be used anymore. Maybe check https://www.telegram.org/faq_spam.'
)
class PhoneNumberInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The phone number is invalid.'
)
class PhoneNumberOccupiedError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The phone number is already in use.'
)
class PhoneNumberUnoccupiedError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The phone number is not yet being used.'
)
class PhotoInvalidDimensionsError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The photo dimensions are invalid.'
)
class TypeConstructorInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The type constructor is invalid.'
)
class UsernameInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'Unacceptable username. Must match r"[a-zA-Z][\w\d]{4,31}".'
)
class UsernameNotModifiedError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The username is not different from the current username.'
)
class UsernameNotOccupiedError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The username is not in use by anyone else yet.'
)
class UsernameOccupiedError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The username is already taken.'
)
class UsersTooFewError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'Not enough users (to create a chat, for example).'
)
class UsersTooMuchError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The maximum number of users has been exceeded (to create a '
'chat, for example).'
)
class UserIdInvalidError(BadRequestError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'Invalid object ID for an user. Make sure to pass the right types,'
'for instance making sure that the request is designed for users'
'or otherwise look for a different one more suited.'
)
rpc_errors_400_all = {
'API_ID_INVALID': ApiIdInvalidError,
'BOT_METHOD_INVALID': BotMethodInvalidError,
'CDN_METHOD_INVALID': CdnMethodInvalidError,
'CHANNEL_INVALID': ChannelInvalidError,
'CHANNEL_PRIVATE': ChannelPrivateError,
'CHAT_ADMIN_REQUIRED': ChatAdminRequiredError,
'CHAT_ID_INVALID': ChatIdInvalidError,
'CONNECTION_LAYER_INVALID': ConnectionLayerInvalidError,
'DC_ID_INVALID': DcIdInvalidError,
'FIELD_NAME_EMPTY': FieldNameEmptyError,
'FIELD_NAME_INVALID': FieldNameInvalidError,
'FILE_PARTS_INVALID': FilePartsInvalidError,
'FILE_PART_(\d+)_MISSING': FilePartMissingError,
'FILE_PART_INVALID': FilePartInvalidError,
'FIRSTNAME_INVALID': FirstNameInvalidError,
'INPUT_METHOD_INVALID': InputMethodInvalidError,
'INPUT_REQUEST_TOO_LONG': InputRequestTooLongError,
'LASTNAME_INVALID': LastNameInvalidError,
'LIMIT_INVALID': LimitInvalidError,
'LOCATION_INVALID': LocationInvalidError,
'MD5_CHECKSUM_INVALID': Md5ChecksumInvalidError,
'MESSAGE_EMPTY': MessageEmptyError,
'MESSAGE_ID_INVALID': MessageIdInvalidError,
'MESSAGE_TOO_LONG': MessageTooLongError,
'MESSAGE_NOT_MODIFIED': MessageNotModifiedError,
'MSG_WAIT_FAILED': MsgWaitFailedError,
'OFFSET_INVALID': OffsetInvalidError,
'PASSWORD_HASH_INVALID': PasswordHashInvalidError,
'PEER_ID_INVALID': PeerIdInvalidError,
'PHONE_CODE_EMPTY': PhoneCodeEmptyError,
'PHONE_CODE_EXPIRED': PhoneCodeExpiredError,
'PHONE_CODE_HASH_EMPTY': PhoneCodeHashEmptyError,
'PHONE_CODE_INVALID': PhoneCodeInvalidError,
'PHONE_NUMBER_BANNED': PhoneNumberBannedError,
'PHONE_NUMBER_INVALID': PhoneNumberInvalidError,
'PHONE_NUMBER_OCCUPIED': PhoneNumberOccupiedError,
'PHONE_NUMBER_UNOCCUPIED': PhoneNumberUnoccupiedError,
'PHOTO_INVALID_DIMENSIONS': PhotoInvalidDimensionsError,
'TYPE_CONSTRUCTOR_INVALID': TypeConstructorInvalidError,
'USERNAME_INVALID': UsernameInvalidError,
'USERNAME_NOT_MODIFIED': UsernameNotModifiedError,
'USERNAME_NOT_OCCUPIED': UsernameNotOccupiedError,
'USERNAME_OCCUPIED': UsernameOccupiedError,
'USERS_TOO_FEW': UsersTooFewError,
'USERS_TOO_MUCH': UsersTooMuchError,
'USER_ID_INVALID': UserIdInvalidError,
}

View File

@@ -1,98 +0,0 @@
from . import UnauthorizedError
class ActiveUserRequiredError(UnauthorizedError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The method is only available to already activated users.'
)
class AuthKeyInvalidError(UnauthorizedError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The key is invalid.'
)
class AuthKeyPermEmptyError(UnauthorizedError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The method is unavailable for temporary authorization key, not '
'bound to permanent.'
)
class AuthKeyUnregisteredError(UnauthorizedError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The key is not registered in the system.'
)
class InviteHashExpiredError(UnauthorizedError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The chat the user tried to join has expired and is not valid '
'anymore.'
)
class SessionExpiredError(UnauthorizedError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The authorization has expired.'
)
class SessionPasswordNeededError(UnauthorizedError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'Two-steps verification is enabled and a password is required.'
)
class SessionRevokedError(UnauthorizedError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The authorization has been invalidated, because of the user '
'terminating all sessions.'
)
class UserAlreadyParticipantError(UnauthorizedError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The authenticated user is already a participant of the chat.'
)
class UserDeactivatedError(UnauthorizedError):
def __init__(self, **kwargs):
super(Exception, self).__init__(
self,
'The user has been deleted/deactivated.'
)
rpc_errors_401_all = {
'ACTIVE_USER_REQUIRED': ActiveUserRequiredError,
'AUTH_KEY_INVALID': AuthKeyInvalidError,
'AUTH_KEY_PERM_EMPTY': AuthKeyPermEmptyError,
'AUTH_KEY_UNREGISTERED': AuthKeyUnregisteredError,
'INVITE_HASH_EXPIRED': InviteHashExpiredError,
'SESSION_EXPIRED': SessionExpiredError,
'SESSION_PASSWORD_NEEDED': SessionPasswordNeededError,
'SESSION_REVOKED': SessionRevokedError,
'USER_ALREADY_PARTICIPANT': UserAlreadyParticipantError,
'USER_DEACTIVATED': UserDeactivatedError,
}

View File

@@ -1,16 +0,0 @@
from . import FloodError
class FloodWaitError(FloodError):
def __init__(self, **kwargs):
self.seconds = kwargs['extra']
super(Exception, self).__init__(
self,
'A wait of {} seconds is required.'
.format(self.seconds)
)
rpc_errors_420_all = {
'FLOOD_WAIT_(\d+)': FloodWaitError
}

View File

@@ -42,7 +42,7 @@ async def _do_authentication(connection):
req_pq_request = ReqPqRequest(
nonce=int.from_bytes(os.urandom(16), 'big', signed=True)
)
await sender.send(req_pq_request.to_bytes())
await sender.send(bytes(req_pq_request))
with BinaryReader(await sender.receive()) as reader:
req_pq_request.on_response(reader)
@@ -60,12 +60,12 @@ async def _do_authentication(connection):
p, q = rsa.get_byte_array(min(p, q)), rsa.get_byte_array(max(p, q))
new_nonce = int.from_bytes(os.urandom(32), 'little', signed=True)
pq_inner_data = PQInnerData(
pq_inner_data = bytes(PQInnerData(
pq=rsa.get_byte_array(pq), p=p, q=q,
nonce=res_pq.nonce,
server_nonce=res_pq.server_nonce,
new_nonce=new_nonce
).to_bytes()
))
# sha_digest + data + random_bytes
cipher_text, target_fingerprint = None, None
@@ -90,7 +90,7 @@ async def _do_authentication(connection):
public_key_fingerprint=target_fingerprint,
encrypted_data=cipher_text
)
await sender.send(req_dh_params.to_bytes())
await sender.send(bytes(req_dh_params))
# Step 2 response: DH Exchange
with BinaryReader(await sender.receive()) as reader:
@@ -138,12 +138,12 @@ async def _do_authentication(connection):
gab = pow(g_a, b, dh_prime)
# Prepare client DH Inner Data
client_dh_inner = ClientDHInnerData(
client_dh_inner = bytes(ClientDHInnerData(
nonce=res_pq.nonce,
server_nonce=res_pq.server_nonce,
retry_id=0, # TODO Actual retry ID
g_b=rsa.get_byte_array(gb)
).to_bytes()
))
client_dh_inner_hashed = sha1(client_dh_inner).digest() + client_dh_inner
@@ -156,7 +156,7 @@ async def _do_authentication(connection):
server_nonce=res_pq.server_nonce,
encrypted_data=client_dh_encrypted,
)
await sender.send(set_client_dh.to_bytes())
await sender.send(bytes(set_client_dh))
# Step 3 response: Complete DH Exchange
with BinaryReader(await sender.receive()) as reader:

View File

@@ -39,7 +39,7 @@ class MtProtoSender:
self._logger = logging.getLogger(__name__)
# Message IDs that need confirmation
self._need_confirmation = []
self._need_confirmation = set()
# Requests (as msg_id: Message) sent waiting to be received
self._pending_receive = {}
@@ -74,7 +74,7 @@ class MtProtoSender:
# Pack everything in the same container if we need to send AckRequests
if self._need_confirmation:
messages.append(
TLMessage(self.session, MsgsAck(self._need_confirmation))
TLMessage(self.session, MsgsAck(list(self._need_confirmation)))
)
self._need_confirmation.clear()
@@ -125,7 +125,7 @@ class MtProtoSender:
plain_text = \
struct.pack('<QQ', self.session.salt, self.session.id) \
+ message.to_bytes()
+ bytes(message)
msg_key = utils.calc_msg_key(plain_text)
key_id = struct.pack('<Q', self.session.auth_key.key_id)
@@ -174,7 +174,7 @@ class MtProtoSender:
"""
# TODO Check salt, session_id and sequence_number
self._need_confirmation.append(msg_id)
self._need_confirmation.add(msg_id)
code = reader.read_int(signed=False)
reader.seek(-4)
@@ -217,7 +217,7 @@ class MtProtoSender:
r = self._pop_request_of_type(msg_id, LogOutRequest)
if r:
r.result = True # Telegram won't send this value
r.confirm_received()
r.confirm_received.set()
self._logger.debug('Message ack confirmed', r)
return True
@@ -261,7 +261,7 @@ class MtProtoSender:
def _clear_all_pending(self):
for r in self._pending_receive.values():
r.confirm_received.set()
r.request.confirm_received.set()
self._pending_receive.clear()
async def _handle_pong(self, msg_id, sequence, reader):
@@ -303,6 +303,7 @@ class MtProtoSender:
self.session.salt = struct.unpack(
'<Q', struct.pack('<q', bad_salt.new_server_salt)
)[0]
self.session.save()
request = self._pop_request(bad_salt.bad_msg_id)
if request:
@@ -411,6 +412,11 @@ class MtProtoSender:
async def _handle_gzip_packed(self, msg_id, sequence, reader, state):
self._logger.debug('Handling gzip packed data')
with BinaryReader(GzipPacked.read(reader)) as compressed_reader:
# We are reentering process_msg, which seemingly the same msg_id
# to the self._need_confirmation set. Remove it from there first
# to avoid any future conflicts (i.e. if we "ignore" messages
# that we are already aware of, see 1a91c02 and old 63dfb1e)
self._need_confirmation -= {msg_id}
return await self._process_msg(msg_id, sequence, compressed_reader, state)
# endregion

View File

@@ -56,7 +56,7 @@ class TelegramBareClient:
"""
# Current TelegramClient version
__version__ = '0.15.2'
__version__ = '0.15.3'
# TODO Make this thread-safe, all connections share the same DC
_dc_options = None
@@ -124,7 +124,7 @@ class TelegramBareClient:
self._user_connected = False
# Save whether the user is authorized here (a.k.a. logged in)
self._authorized = False
self._authorized = None # None = We don't know yet
# Uploaded files cache so subsequent calls are instant
self._upload_cache = {}
@@ -198,12 +198,14 @@ class TelegramBareClient:
# another data center and this would raise UserMigrateError)
# to also assert whether the user is logged in or not.
self._user_connected = True
if _sync_updates and not _cdn:
if self._authorized is None and _sync_updates and not _cdn:
try:
await self.sync_updates()
self._set_connected_and_authorized()
except UnauthorizedError:
self._authorized = False
elif self._authorized:
self._set_connected_and_authorized()
return True
@@ -383,6 +385,8 @@ class TelegramBareClient:
# TODO Determine the sender to be used (main or a new connection)
sender = self._sender # .clone(), .connect()
# We're on the same connection so no need to pass update_state=None
# to avoid getting messages that we haven't acknowledged yet.
try:
for _ in range(retries):
@@ -426,10 +430,7 @@ class TelegramBareClient:
else:
while self._user_connected and not await self._reconnect():
sleep(0.1) # Retry forever until we can send the request
finally:
if sender != self._sender:
sender.disconnect()
return None
try:
raise next(x.rpc_error for x in requests if x.rpc_error)
@@ -673,13 +674,14 @@ class TelegramBareClient:
def add_update_handler(self, handler):
"""Adds an update handler (a function which takes a TLObject,
an update, as its parameter) and listens for updates"""
if not self.updates.get_workers:
warnings.warn("There are no update workers running, so adding an update handler will have no effect.")
if self.updates.workers is None:
warnings.warn(
"You have not setup any workers, so you won't receive updates."
" Pass update_workers=4 when creating the TelegramClient,"
" or set client.self.updates.workers = 4"
)
sync = not self.updates.handlers
self.updates.handlers.append(handler)
if sync:
self.sync_updates()
def remove_update_handler(self, handler):
self.updates.handlers.remove(handler)

View File

@@ -13,21 +13,21 @@ class GzipPacked(TLObject):
@staticmethod
def gzip_if_smaller(request):
"""Calls request.to_bytes(), and based on a certain threshold,
"""Calls bytes(request), and based on a certain threshold,
optionally gzips the resulting data. If the gzipped data is
smaller than the original byte array, this is returned instead.
Note that this only applies to content related requests.
"""
data = request.to_bytes()
data = bytes(request)
# TODO This threshold could be configurable
if request.content_related and len(data) > 512:
gzipped = GzipPacked(data).to_bytes()
gzipped = bytes(GzipPacked(data))
return gzipped if len(gzipped) < len(data) else data
else:
return data
def to_bytes(self):
def __bytes__(self):
# TODO Maybe compress level could be an option
return struct.pack('<I', GzipPacked.CONSTRUCTOR_ID) + \
TLObject.serialize_bytes(gzip.compress(self.data))

View File

@@ -11,10 +11,10 @@ class MessageContainer(TLObject):
self.content_related = False
self.messages = messages
def to_bytes(self):
def __bytes__(self):
return struct.pack(
'<Ii', MessageContainer.CONSTRUCTOR_ID, len(self.messages)
) + b''.join(m.to_bytes() for m in self.messages)
) + b''.join(bytes(m) for m in self.messages)
@staticmethod
def iter_read(reader):

View File

@@ -12,6 +12,6 @@ class TLMessage(TLObject):
self.seq_no = session.generate_sequence(request.content_related)
self.request = request
def to_bytes(self):
def __bytes__(self):
body = GzipPacked.gzip_if_smaller(self.request)
return struct.pack('<qii', self.msg_id, self.seq_no, len(body)) + body

View File

@@ -125,7 +125,7 @@ class TLObject:
def to_dict(self, recursive=True):
return {}
def to_bytes(self):
def __bytes__(self):
return b''
@staticmethod