Merge branch 'master' into asyncio

This commit is contained in:
Lonami Exo
2017-10-29 20:08:32 +01:00
18 changed files with 550 additions and 355 deletions

View File

@@ -81,6 +81,9 @@ class MtProtoSender:
message = messages[0]
else:
message = TLMessage(self.session, MessageContainer(messages))
# On bad_msg_salt errors, Telegram will reply with the ID of
# the container and not the requests it contains, so in case
# this happens we need to know to which container they belong.
for m in messages:
m.container_msg_id = message.msg_id
@@ -261,7 +264,12 @@ class MtProtoSender:
return self._pending_receive.pop(msg_id).request
def _pop_requests_of_container(self, container_msg_id):
msgs = [msg for msg in self._pending_receive.values() if msg.container_msg_id == container_msg_id]
"""Pops the pending requests (plural) from self._pending_receive if
they were sent on a container that matches container_msg_id.
"""
msgs = [msg for msg in self._pending_receive.values()
if msg.container_msg_id == container_msg_id]
requests = [msg.request for msg in msgs]
for msg in msgs:
self._pending_receive.pop(msg.msg_id, None)
@@ -273,14 +281,17 @@ class MtProtoSender:
self._pending_receive.clear()
async def _resend_request(self, msg_id):
"""Re-sends the request that belongs to a certain msg_id. This may
also be the msg_id of a container if they were sent in one.
"""
request = self._pop_request(msg_id)
if request:
self._logger.debug('requests is about to resend')
self._logger.debug('Resending request')
await self.send(request)
return
requests = self._pop_requests_of_container(msg_id)
if requests:
self._logger.debug('container of requests is about to resend')
self._logger.debug('Resending container of requests')
await self.send(*requests)
async def _handle_pong(self, msg_id, sequence, reader):
@@ -324,6 +335,8 @@ class MtProtoSender:
)[0]
self.session.save()
# "the bad_server_salt response is received with the
# correct salt, and the message is to be re-sent with it"
await self._resend_request(bad_salt.bad_msg_id)
return True

View File

@@ -30,6 +30,7 @@ from .tl.functions.upload import (
GetFileRequest, SaveBigFilePartRequest, SaveFilePartRequest
)
from .tl.types import InputFile, InputFileBig
from .tl.types.auth import ExportedAuthorization
from .tl.types.upload import FileCdnRedirect
from .update_state import UpdateState
from .utils import get_appropriated_part_size
@@ -59,7 +60,7 @@ class TelegramBareClient:
__version__ = '0.15.3'
# TODO Make this thread-safe, all connections share the same DC
_dc_options = None
_config = None # Server configuration (with .dc_options)
# region Initialization
@@ -148,7 +149,7 @@ class TelegramBareClient:
# region Connecting
async def connect(self, _exported_auth=None, _sync_updates=True, _cdn=False):
async def connect(self, _sync_updates=True):
"""Connects to the Telegram servers, executing authentication if
required. Note that authenticating to the Telegram servers is
not the same as authenticating the desired user itself, which
@@ -156,60 +157,21 @@ class TelegramBareClient:
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
second thread will be started if necessary. Note that this
will FAIL if the client is not connected to the user's
native data center, raising a "UserMigrateError", and
calling .disconnect() in the process.
If '_cdn' is False, methods that are not allowed on such data
centers won't be invoked.
"""
try:
await 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 = \
await authenticator.do_authentication(self._sender.connection)
except BrokenAuthKeyError:
self._user_connected = False
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:
await self._init_connection(ImportAuthorizationRequest(
_exported_auth.id, _exported_auth.bytes
))
elif not _cdn:
TelegramBareClient._dc_options = \
(await self._init_connection(GetConfigRequest())).dc_options
elif _exported_auth is not None:
await self(ImportAuthorizationRequest(
_exported_auth.id, _exported_auth.bytes
))
if TelegramBareClient._dc_options is None and not _cdn:
TelegramBareClient._dc_options = \
(await self(GetConfigRequest())).dc_options
# Connection was successful! Try syncing the update state
# UNLESS '_sync_updates' is False (we probably are in
# another data center and this would raise UserMigrateError)
# to also assert whether the user is logged in or not.
self._user_connected = True
if self._authorized is None and _sync_updates and not _cdn:
if self._authorized is None and _sync_updates:
try:
await self.sync_updates()
self._set_connected_and_authorized()
@@ -224,11 +186,7 @@ class TelegramBareClient:
# This is fine, probably layer migration
self._logger.debug('Found invalid item, probably migrating', e)
self.disconnect()
return await self.connect(
_exported_auth=_exported_auth,
_sync_updates=_sync_updates,
_cdn=_cdn
)
return await self.connect(_sync_updates=_sync_updates)
except (RPCError, ConnectionError) as error:
# Probably errors from the previous session, ignore them
@@ -241,8 +199,9 @@ class TelegramBareClient:
def is_connected(self):
return self._sender.is_connected()
async def _init_connection(self, query=None):
result = await self(InvokeWithLayerRequest(LAYER, InitConnectionRequest(
def _wrap_init_connection(self, query):
"""Wraps query around InvokeWithLayerRequest(InitConnectionRequest())"""
return InvokeWithLayerRequest(LAYER, InitConnectionRequest(
api_id=self.api_id,
device_model=self.session.device_model,
system_version=self.session.system_version,
@@ -251,10 +210,7 @@ class TelegramBareClient:
system_lang_code=self.session.system_lang_code,
lang_pack='', # "langPacks are for official apps only"
query=query
)))
self.session.layer = LAYER
self.session.save()
return result
))
def disconnect(self):
"""Disconnects from the Telegram server"""
@@ -286,13 +242,18 @@ class TelegramBareClient:
finally:
self._reconnect_lock.release()
else:
self.disconnect()
self.session.auth_key = None # Force creating new auth_key
# Since we're reconnecting possibly due to a UserMigrateError,
# we need to first know the Data Centers we can connect to. Do
# that before disconnecting.
dc = await 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
# 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.disconnect()
return await self.connect()
# endregion
@@ -301,11 +262,8 @@ class TelegramBareClient:
async def _get_dc(self, dc_id, ipv6=False, cdn=False):
"""Gets the Data Center (DC) associated to 'dc_id'"""
if TelegramBareClient._dc_options is None:
raise ConnectionError(
'Cannot determine the required data center IP address. '
'Stabilise a successful initial connection first.'
)
if not TelegramBareClient._config:
TelegramBareClient._config = await self(GetConfigRequest())
try:
if cdn:
@@ -314,15 +272,15 @@ class TelegramBareClient:
rsa.add_key(pk.public_key)
return next(
dc for dc in TelegramBareClient._dc_options if dc.id == dc_id
and bool(dc.ipv6) == ipv6 and bool(dc.cdn) == cdn
dc for dc in TelegramBareClient._config.dc_options
if dc.id == dc_id and bool(dc.ipv6) == ipv6 and bool(dc.cdn) == cdn
)
except StopIteration:
if not cdn:
raise
# New configuration, perhaps a new CDN was added?
TelegramBareClient._dc_options = await (self(GetConfigRequest())).dc_options
TelegramBareClient._config = await self(GetConfigRequest())
return await self._get_dc(dc_id, ipv6=ipv6, cdn=cdn)
async def _get_exported_client(self, dc_id):
@@ -363,7 +321,14 @@ class TelegramBareClient:
timeout=self._sender.connection.get_timeout(),
loop=self._loop
)
await client.connect(_exported_auth=export_auth, _sync_updates=False)
await client.connect(_sync_updates=False)
if isinstance(export_auth, ExportedAuthorization):
await 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
return client
@@ -386,9 +351,10 @@ class TelegramBareClient:
# This will make use of the new RSA keys for this specific CDN.
#
# This relies on the fact that TelegramBareClient._dc_options is
# static and it won't be called from this DC (it would fail).
await client.connect(_cdn=True) # Avoid invoking non-CDN methods
# We won't be calling GetConfigRequest because it's only called
# when needed by ._get_dc, and also it's static so it's likely
# set already. Avoid invoking non-CDN methods by not syncing updates.
await client.connect(_sync_updates=False)
client._authorized = self._authorized
return client
@@ -423,11 +389,33 @@ class TelegramBareClient:
invoke = __call__
async def _invoke(self, call_receive, retry, *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:
# Ensure that we start with no previous errors (i.e. resending)
for x in requests:
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 = \
await 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 = await self(
self._wrap_init_connection(GetConfigRequest())
)
await self._sender.send(*requests)
if not call_receive:
@@ -440,6 +428,13 @@ class TelegramBareClient:
while not all(x.confirm_received.is_set() for x in requests):
await self._sender.receive(update_state=self.updates)
except BrokenAuthKeyError:
self._logger.error('Broken auth key, a new one will be generated')
self.session.auth_key = None
except TimeoutError:
pass # We will just retry
except ConnectionResetError:
if not self._user_connected or self._reconnect_lock.locked():
# Only attempt reconnecting if the user called connect and not
@@ -453,6 +448,12 @@ class TelegramBareClient:
await asyncio.sleep(retry + 1, loop=self._loop)
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:
raise next(x.rpc_error for x in requests if x.rpc_error)
except StopIteration:
@@ -728,7 +729,8 @@ class TelegramBareClient:
if need_reconnect:
need_reconnect = False
while self._user_connected and not await self._reconnect():
await asyncio.sleep(0.1, loop=self._loop) # Retry forever, this is instant messaging
# Retry forever, this is instant messaging
await asyncio.sleep(0.1, loop=self._loop)
await self._sender.receive(update_state=self.updates)
except TimeoutError:
@@ -748,7 +750,8 @@ class TelegramBareClient:
try:
import socks
if isinstance(error, (
socks.GeneralProxyError, socks.ProxyConnectionError
socks.GeneralProxyError,
socks.ProxyConnectionError
)):
# This is a known error, and it's not related to
# Telegram but rather to the proxy. Disconnect and
@@ -764,6 +767,7 @@ class TelegramBareClient:
# add a little sleep to avoid the CPU usage going mad.
await asyncio.sleep(0.1, loop=self._loop)
break
self._recv_loop = None
# endregion

View File

@@ -51,7 +51,6 @@ from .tl.types import (
PeerUser, InputPeerUser, InputPeerChat, InputPeerChannel)
from .tl.types.messages import DialogsSlice
class TelegramClient(TelegramBareClient):
"""Full featured TelegramClient meant to extend the basic functionality"""
@@ -103,7 +102,11 @@ class TelegramClient(TelegramBareClient):
# region Authorization requests
async def send_code_request(self, phone):
"""Sends a code request to the specified phone number"""
"""Sends a code request to the specified phone number.
:param str | int phone: The phone to which the code will be sent.
:return auth.SentCode: Information about the result of the request.
"""
phone = EntityDatabase.parse_phone(phone) or self._phone
result = await self(SendCodeRequest(phone, self.api_id, self.api_hash))
self._phone = phone
@@ -112,26 +115,27 @@ class TelegramClient(TelegramBareClient):
async def sign_in(self, phone=None, code=None,
password=None, bot_token=None, phone_code_hash=None):
"""Completes the sign in process with the phone number + code pair.
"""
Starts or completes the sign in process with the given phone number
or code that Telegram sent.
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 SessionPasswordNeededError was raised.
:param str | int phone:
The phone to send the code to if no code was provided, or to
override the phone that was previously used with these requests.
:param str | int code:
The code that Telegram sent.
:param str password:
2FA password, should be used if a previous call raised
SessionPasswordNeededError.
:param str bot_token:
Used to sign in as a bot. Not all requests will be available.
This should be the hash the @BotFather gave you.
:param str phone_code_hash:
The hash returned by .send_code_request. This can be set to None
to use the last hash known.
If you're calling .sign_in() on two completely different clients
(for example, through an API that creates a new client per phone),
you must first call .sign_in(phone) to receive the code, and then
with the result such method results, call
.sign_in(phone, code, phone_code_hash=result.phone_code_hash).
If this is done on the same client, the client will fill said values
for you.
To login as a bot, only `bot_token` should be provided.
This should equal to the bot access hash provided by
https://t.me/BotFather during your bot creation.
If the login succeeds, the logged in user is returned.
:return auth.SentCode | User:
The signed in user, or the information about .send_code_request().
"""
if phone and not code:
@@ -175,7 +179,15 @@ class TelegramClient(TelegramBareClient):
return result.user
async def sign_up(self, code, first_name, last_name=''):
"""Signs up to Telegram. Make sure you sent a code request first!"""
"""
Signs up to Telegram if you don't have an account yet.
You must call .send_code_request(phone) first.
:param str | int code: The code sent by Telegram
:param str first_name: The first name to be used by the new account.
:param str last_name: Optional last name.
:return User: The new created user.
"""
result = await self(SignUpRequest(
phone_number=self._phone,
phone_code_hash=self._phone_code_hash,
@@ -188,8 +200,10 @@ class TelegramClient(TelegramBareClient):
return result.user
async def log_out(self):
"""Logs out and deletes the current session.
Returns True if everything went okay."""
"""Logs out Telegram and deletes the current *.session file.
:return bool: True if the operation was successful.
"""
try:
await self(LogOutRequest())
except RPCError:
@@ -201,8 +215,12 @@ class TelegramClient(TelegramBareClient):
return True
async def get_me(self):
"""Gets "me" (the self user) which is currently authenticated,
or None if the request fails (hence, not authenticated)."""
"""
Gets "me" (the self user) which is currently authenticated,
or None if the request fails (hence, not authenticated).
:return User: Your own user.
"""
try:
return (await self(GetUsersRequest([InputUserSelf()])))[0]
except UnauthorizedError:
@@ -217,15 +235,21 @@ class TelegramClient(TelegramBareClient):
offset_date=None,
offset_id=0,
offset_peer=InputPeerEmpty()):
"""Returns a tuple of lists ([dialogs], [entities])
with at least 'limit' items each unless all dialogs were consumed.
"""
Gets N "dialogs" (open "chats" or conversations with other people).
If `limit` is None, all dialogs will be retrieved (from the given
offset) will be retrieved.
The `entities` represent the user, chat or channel
corresponding to that dialog. If it's an integer, not
all dialogs may be retrieved at once.
:param limit:
How many dialogs to be retrieved as maximum. Can be set to None
to retrieve all dialogs. Note that this may take whole minutes
if you have hundreds of dialogs, as Telegram will tell the library
to slow down through a FloodWaitError.
:param offset_date:
The offset date to be used.
:param offset_id:
The message ID to be used as an offset.
:param offset_peer:
The peer to be used as an offset.
:return: A tuple of lists ([dialogs], [entities]).
"""
if limit is None:
limit = float('inf')
@@ -284,8 +308,9 @@ class TelegramClient(TelegramBareClient):
"""
Gets all open draft messages.
Returns a list of custom `Draft` objects that are easy to work with: You can call
`draft.set_message('text')` to change the message, or delete it through `draft.delete()`.
Returns a list of custom `Draft` objects that are easy to work with:
You can call `draft.set_message('text')` to change the message,
or delete it through `draft.delete()`.
:return List[telethon.tl.custom.Draft]: A list of open drafts
"""
@@ -300,11 +325,14 @@ class TelegramClient(TelegramBareClient):
message,
reply_to=None,
link_preview=True):
"""Sends a message to the given entity (or input peer)
and returns the sent message as a Telegram object.
"""
Sends the given message to the specified entity (user/chat/channel).
If 'reply_to' is set to either a message or a message ID,
the sent message will be replying to such message.
:param str | int | User | Chat | Channel entity: To who will it be sent.
:param str message: The message to be sent.
:param int | Message reply_to: Whether to reply to a message or not.
:param link_preview: Should the link preview be shown?
:return Message: the sent message
"""
entity = await self.get_input_entity(entity)
request = SendMessageRequest(
@@ -348,11 +376,11 @@ class TelegramClient(TelegramBareClient):
Deletes a message from a chat, optionally "for everyone" with argument
`revoke` set to `True`.
The `revoke` argument has no effect for Channels and Supergroups,
The `revoke` argument has no effect for Channels and Megagroups,
where it inherently behaves as being `True`.
Note: The `entity` argument can be `None` for normal chats, but it's
mandatory to delete messages from Channels and Supergroups. It is also
mandatory to delete messages from Channels and Megagroups. It is also
possible to supply a chat_id which will be automatically resolved to
the right type of InputPeer.
@@ -397,9 +425,6 @@ class TelegramClient(TelegramBareClient):
:return: A tuple containing total message count and two more lists ([messages], [senders]).
Note that the sender can be null if it was not found!
The entity may be a phone or an username at the expense of
some performance loss.
"""
result = await self(GetHistoryRequest(
peer=await self.get_input_entity(entity),
@@ -429,16 +454,15 @@ class TelegramClient(TelegramBareClient):
return total_messages, result.messages, entities
async def send_read_acknowledge(self, entity, messages=None, max_id=None):
"""Sends a "read acknowledge" (i.e., notifying the given peer that we've
read their messages, also known as the "double check").
"""
Sends a "read acknowledge" (i.e., notifying the given peer that we've
read their messages, also known as the "double check").
Either a list of messages (or a single message) can be given,
or the maximum message ID (until which message we want to send the read acknowledge).
Returns an AffectedMessages TLObject
The entity may be a phone or an username at the expense of
some performance loss.
:param entity: The chat where these messages are located.
:param messages: Either a list of messages or a single message.
:param max_id: Overrides messages, until which message should the
acknowledge should be sent.
:return:
"""
if max_id is None:
if not messages:
@@ -480,36 +504,36 @@ class TelegramClient(TelegramBareClient):
reply_to=None,
attributes=None,
**kwargs):
"""Sends a file to the specified entity.
The file may either be a path, a byte array, or a stream.
Note that if a byte array or a stream is given, a filename
or its type won't be inferred, and it will be sent as an
"unnamed application/octet-stream".
"""
Sends a file to the specified entity.
An optional caption can also be specified for said file.
If "force_document" is False, the file will be sent as a photo
if it's recognised to have a common image format (e.g. .png, .jpg).
Otherwise, the file will always be sent as an uncompressed document.
Subsequent calls with the very same file will result in
immediate uploads, unless .clear_file_cache() is called.
If "progress_callback" is not None, it should be a function that
takes two parameters, (bytes_uploaded, total_bytes).
The "reply_to" parameter works exactly as the one on .send_message.
If "attributes" is set to be a list of DocumentAttribute's, these
will override the automatically inferred ones (so that you can
modify the file name of the file sent for instance).
:param entity:
Who will receive the file.
:param file:
The path of the file, byte array, or stream that will be sent.
Note that if a byte array or a stream is given, a filename
or its type won't be inferred, and it will be sent as an
"unnamed application/octet-stream".
Subsequent calls with the very same file will result in
immediate uploads, unless .clear_file_cache() is called.
:param caption:
Optional caption for the sent media message.
:param force_document:
If left to False and the file is a path that ends with .png, .jpg
and such, the file will be sent as a photo. Otherwise always as
a document.
:param progress_callback:
A callback function accepting two parameters: (sent bytes, total)
:param reply_to:
Same as reply_to from .send_message().
:param attributes:
Optional attributes that override the inferred ones, like
DocumentAttributeFilename and so on.
:param kwargs:
If "is_voice_note" in kwargs, despite its value, and the file is
sent as a document, it will be sent as a voice note.
The entity may be a phone or an username at the expense of
some performance loss.
:return:
"""
as_photo = False
if isinstance(file, str):
@@ -600,21 +624,19 @@ class TelegramClient(TelegramBareClient):
# region Downloading media requests
async def download_profile_photo(self, entity, file=None, download_big=True):
"""Downloads the profile photo for an user or a chat (channels too).
Returns None if no photo was provided, or if it was Empty.
"""
Downloads the profile photo of the given entity (user/chat/channel).
If an entity itself (an user, chat or channel) is given, the photo
to be downloaded will be downloaded automatically.
On success, the file path is returned since it may differ from
the one provided.
The specified output file can either be a file path, a directory,
or a stream-like object. If the path exists and is a file, it will
be overwritten.
The entity may be a phone or an username at the expense of
some performance loss.
:param entity:
From who the photo will be downloaded.
:param file:
The output file path, directory, or stream-like object.
If the path exists and is a file, it will be overwritten.
:param download_big:
Whether to use the big version of the available photos.
:return:
None if no photo was provided, or if it was Empty. On success
the file path is returned since it may differ from the one given.
"""
possible_names = []
if not isinstance(entity, TLObject) or type(entity).SUBCLASS_OF_ID in (
@@ -669,21 +691,16 @@ class TelegramClient(TelegramBareClient):
return file
async def download_media(self, message, file=None, progress_callback=None):
"""Downloads the media from a specified Message (it can also be
the message.media) into the desired file (a stream or str),
optionally finding its extension automatically.
The specified output file can either be a file path, a directory,
or a stream-like object. If the path exists and is a file, it will
be overwritten.
If the operation succeeds, the path will be returned (since
the extension may have been added automatically). Otherwise,
None is returned.
The progress_callback should be a callback function which takes
two parameters, uploaded size and total file size (both in bytes).
This will be called every time a part is downloaded
"""
Downloads the given media, or the media from a specified Message.
:param message:
The media or message containing the media that will be downloaded.
:param file:
The output file path, directory, or stream-like object.
If the path exists and is a file, it will be overwritten.
:param progress_callback:
A callback function accepting two parameters: (recv bytes, total)
:return:
"""
# TODO This won't work for messageService
if isinstance(message, Message):
@@ -781,14 +798,14 @@ class TelegramClient(TelegramBareClient):
f = file
try:
# Remove these pesky characters
first_name = first_name.replace(';', '')
last_name = (last_name or '').replace(';', '')
f.write('BEGIN:VCARD\n')
f.write('VERSION:4.0\n')
f.write('N:{};{};;;\n'.format(
first_name, last_name if last_name else '')
)
f.write('FN:{}\n'.format(' '.join((first_name, last_name))))
f.write('TEL;TYPE=cell;VALUE=uri:tel:+{}\n'.format(
phone_number))
f.write('N:{};{};;;\n'.format(first_name, last_name))
f.write('FN:{} {}\n'.format(first_name, last_name))
f.write('TEL;TYPE=cell;VALUE=uri:tel:+{}\n'.format(phone_number))
f.write('END:VCARD\n')
finally:
# Only close the stream if we opened it
@@ -862,20 +879,24 @@ class TelegramClient(TelegramBareClient):
# region Small utilities to make users' life easier
async def get_entity(self, entity):
"""Turns an entity into a valid Telegram user or chat.
If "entity" is a string which can be converted to an integer,
or if it starts with '+' it will be resolved as if it
were a phone number.
"""
Turns the given entity into a valid Telegram user or chat.
If "entity" is a string and doesn't start with '+', or
it starts with '@', it will be resolved from the username.
If no exact match is returned, an error will be raised.
:param entity:
The entity to be transformed.
If it's a string which can be converted to an integer or starts
with '+' it will be resolved as if it were a phone number.
If "entity" is an integer or a "Peer", its information will
be returned through a call to self.get_input_peer(entity).
If it doesn't start with '+' or starts with a '@' it will be
be resolved from the username. If no exact match is returned,
an error will be raised.
If the entity is neither, and it's not a TLObject, an
error will be raised.
If the entity is an integer or a Peer, its information will be
returned through a call to self.get_input_peer(entity).
If the entity is neither, and it's not a TLObject, an
error will be raised.
:return:
"""
try:
return self.session.entities[entity]
@@ -929,14 +950,23 @@ class TelegramClient(TelegramBareClient):
)
async def get_input_entity(self, peer):
"""Gets the input entity given its PeerUser, PeerChat, PeerChannel.
If no Peer class is used, peer is assumed to be the integer ID
of an User.
"""
Turns the given peer into its input entity version. Most requests
use this kind of InputUser, InputChat and so on, so this is the
most suitable call to make for those cases.
If this Peer hasn't been seen before by the library, all dialogs
will loaded, and their entities saved to the session file.
:param peer:
The integer ID of an user or otherwise either of a
PeerUser, PeerChat or PeerChannel, for which to get its
Input* version.
If even after it's not found, a ValueError is raised.
If this Peer hasn't been seen before by the library, the top
dialogs will be loaded and their entities saved to the session
file (unless this feature was disabled explicitly).
If in the end the access hash required for the peer was not found,
a ValueError will be raised.
:return:
"""
try:
# First try to get the entity from cache, otherwise figure it out
@@ -987,4 +1017,4 @@ class TelegramClient(TelegramBareClient):
'Make sure you have encountered this peer before.'.format(peer)
)
# endregion
# endregion

View File

@@ -1,11 +1,11 @@
import re
from .. import utils
from ..tl import TLObject
from ..tl.types import (
User, Chat, Channel, PeerUser, PeerChat, PeerChannel,
InputPeerUser, InputPeerChat, InputPeerChannel
)
from .. import utils # Keep this line the last to maybe fix #357
class EntityDatabase:

View File

@@ -13,7 +13,6 @@ class TLMessage(TLObject):
self.seq_no = session.generate_sequence(request.content_related)
self.request = request
self.container_msg_id = None
logging.getLogger(__name__).debug(self)
def to_dict(self, recursive=True):
return {

View File

@@ -91,8 +91,11 @@ class TLObject:
@staticmethod
def serialize_bytes(data):
"""Write bytes by using Telegram guidelines"""
if isinstance(data, str):
data = data.encode('utf-8')
if not isinstance(data, bytes):
if isinstance(data, str):
data = data.encode('utf-8')
else:
raise ValueError('bytes or str expected, not', type(data))
r = []
if len(data) < 254:

View File

@@ -142,7 +142,10 @@ def get_input_user(entity):
else:
return InputUser(entity.id, entity.access_hash)
if isinstance(entity, UserEmpty):
if isinstance(entity, InputPeerSelf):
return InputUserSelf()
if isinstance(entity, (UserEmpty, InputPeerEmpty)):
return InputUserEmpty()
if isinstance(entity, UserFull):