mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-05 20:50:22 +03:00
Merge branch 'master' into asyncio
This commit is contained in:
commit
1eb418e1ab
|
@ -42,6 +42,9 @@ extensions = [
|
||||||
'custom_roles'
|
'custom_roles'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Change the default role so we can avoid prefixing everything with :obj:
|
||||||
|
default_role = "py:obj"
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
@ -157,7 +160,7 @@ latex_elements = {
|
||||||
# author, documentclass [howto, manual, or own class]).
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
(master_doc, 'Telethon.tex', 'Telethon Documentation',
|
(master_doc, 'Telethon.tex', 'Telethon Documentation',
|
||||||
'Jeff', 'manual'),
|
author, 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -163,36 +163,44 @@ The mentioned ``.start()`` method will handle this for you as well, but
|
||||||
you must set the ``password=`` parameter beforehand (it won't be asked).
|
you must set the ``password=`` parameter beforehand (it won't be asked).
|
||||||
|
|
||||||
If you don't have 2FA enabled, but you would like to do so through the library,
|
If you don't have 2FA enabled, but you would like to do so through the library,
|
||||||
take as example the following code snippet:
|
use ``client.edit_2fa()``.
|
||||||
|
Be sure to know what you're doing when using this function and
|
||||||
|
you won't run into any problems.
|
||||||
|
Take note that if you want to set only the email/hint and leave
|
||||||
|
the current password unchanged, you need to "redo" the 2fa.
|
||||||
|
|
||||||
|
See the examples below:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import os
|
from telethon.errors import EmailUnconfirmedError
|
||||||
from hashlib import sha256
|
|
||||||
from telethon.tl.functions import account
|
# Sets 2FA password for first time:
|
||||||
from telethon.tl.types.account import PasswordInputSettings
|
await client.edit_2fa(new_password='supersecurepassword')
|
||||||
|
|
||||||
new_salt = client(account.GetPasswordRequest()).new_salt
|
# Changes password:
|
||||||
salt = new_salt + os.urandom(8) # new random salt
|
await client.edit_2fa(current_password='supersecurepassword',
|
||||||
|
new_password='changedmymind')
|
||||||
pw = 'secret'.encode('utf-8') # type your new password here
|
|
||||||
hint = 'hint'
|
# Clears current password (i.e. removes 2FA):
|
||||||
|
await client.edit_2fa(current_password='changedmymind', new_password=None)
|
||||||
pw_salted = salt + pw + salt
|
|
||||||
pw_hash = sha256(pw_salted).digest()
|
# Sets new password with recovery email:
|
||||||
|
try:
|
||||||
result = await client(account.UpdatePasswordSettingsRequest(
|
await client.edit_2fa(new_password='memes and dreams',
|
||||||
current_password_hash=salt,
|
email='JohnSmith@example.com')
|
||||||
new_settings=PasswordInputSettings(
|
# Raises error (you need to check your email to complete 2FA setup.)
|
||||||
new_salt=salt,
|
except EmailUnconfirmedError:
|
||||||
new_password_hash=pw_hash,
|
# You can put email checking code here if desired.
|
||||||
hint=hint
|
pass
|
||||||
)
|
|
||||||
))
|
# Also take note that unless you remove 2FA or explicitly
|
||||||
|
# give email parameter again it will keep the last used setting
|
||||||
Thanks to `Issue 259 <https://github.com/LonamiWebs/Telethon/issues/259>`_
|
|
||||||
for the tip!
|
# Set hint after already setting password:
|
||||||
|
await client.edit_2fa(current_password='memes and dreams',
|
||||||
|
new_password='memes and dreams',
|
||||||
|
hint='It keeps you alive')
|
||||||
|
|
||||||
__ https://github.com/Anorov/PySocks#installation
|
__ https://github.com/Anorov/PySocks#installation
|
||||||
__ https://github.com/Anorov/PySocks#usage-1
|
__ https://github.com/Anorov/PySocks#usage-1
|
||||||
|
|
|
@ -32,7 +32,7 @@ you're able to just do this:
|
||||||
# Dialogs are the "conversations you have open".
|
# Dialogs are the "conversations you have open".
|
||||||
# This method returns a list of Dialog, which
|
# This method returns a list of Dialog, which
|
||||||
# has the .entity attribute and other information.
|
# has the .entity attribute and other information.
|
||||||
dialogs = await client.get_dialogs(limit=200)
|
dialogs = await client.get_dialogs()
|
||||||
|
|
||||||
# All of these work and do the same.
|
# All of these work and do the same.
|
||||||
lonami = await client.get_entity('lonami')
|
lonami = await client.get_entity('lonami')
|
||||||
|
@ -44,27 +44,17 @@ you're able to just do this:
|
||||||
contact = await client.get_entity('+34xxxxxxxxx')
|
contact = await client.get_entity('+34xxxxxxxxx')
|
||||||
friend = await client.get_entity(friend_id)
|
friend = await client.get_entity(friend_id)
|
||||||
|
|
||||||
# Using Peer/InputPeer (note that the API may return these)
|
# Getting entities through their ID (User, Chat or Channel)
|
||||||
# users, chats and channels may all have the same ID, so it's
|
entity = await client.get_entity(some_id)
|
||||||
# necessary to wrap (at least) chat and channels inside Peer.
|
|
||||||
#
|
# You can be more explicit about the type for said ID by wrapping
|
||||||
# NOTICE how the IDs *must* be wrapped inside a Peer() so the
|
# it inside a Peer instance. This is recommended but not necessary.
|
||||||
# library knows their type.
|
|
||||||
from telethon.tl.types import PeerUser, PeerChat, PeerChannel
|
from telethon.tl.types import PeerUser, PeerChat, PeerChannel
|
||||||
my_user = await client.get_entity(PeerUser(some_id))
|
my_user = await client.get_entity(PeerUser(some_id))
|
||||||
my_chat = await client.get_entity(PeerChat(some_id))
|
my_chat = await client.get_entity(PeerChat(some_id))
|
||||||
my_channel = await client.get_entity(PeerChannel(some_id))
|
my_channel = await client.get_entity(PeerChannel(some_id))
|
||||||
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
As it has been mentioned already, getting the entity of a channel
|
|
||||||
through e.g. ``client.get_entity(channel id)`` will **not** work.
|
|
||||||
You would use ``client.get_entity(types.PeerChannel(channel id))``.
|
|
||||||
Remember that supergroups are channels and normal groups are chats.
|
|
||||||
This is a common mistake!
|
|
||||||
|
|
||||||
|
|
||||||
All methods in the :ref:`telegram-client` call ``.get_input_entity()`` prior
|
All methods in the :ref:`telegram-client` call ``.get_input_entity()`` prior
|
||||||
to sending the requst to save you from the hassle of doing so manually.
|
to sending the requst to save you from the hassle of doing so manually.
|
||||||
That way, convenience calls such as ``client.send_message('lonami', 'hi!')``
|
That way, convenience calls such as ``client.send_message('lonami', 'hi!')``
|
||||||
|
|
|
@ -14,6 +14,36 @@ it can take advantage of new goodies!
|
||||||
.. contents:: List of All Versions
|
.. contents:: List of All Versions
|
||||||
|
|
||||||
|
|
||||||
|
Several bug fixes (v0.18.2)
|
||||||
|
===========================
|
||||||
|
|
||||||
|
*Published at 2018/03/27*
|
||||||
|
|
||||||
|
Just a few bug fixes before they become too many.
|
||||||
|
|
||||||
|
Additions
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- Getting an entity by its positive ID should be enough, regardless of their
|
||||||
|
type (whether it's an ``User``, a ``Chat`` or a ``Channel``). Although
|
||||||
|
wrapping them inside a ``Peer`` is still recommended, it's not necessary.
|
||||||
|
- New ``client.edit_2fa`` function to change your Two Factor Authentication
|
||||||
|
settings.
|
||||||
|
- ``.stringify()`` and string representation for custom ``Dialog/Draft``.
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- Some bug regarding ``.get_input_peer``.
|
||||||
|
- ``events.ChatAction`` wasn't picking up all the pins.
|
||||||
|
- ``force_document=True`` was being ignored for albums.
|
||||||
|
- Now you're able to send ``Photo`` and ``Document`` as files.
|
||||||
|
- Wrong access to a member on chat forbidden error for ``.get_participants``.
|
||||||
|
An empty list is returned instead.
|
||||||
|
- ``me/self`` check for ``.get[_input]_entity`` has been moved up so if
|
||||||
|
someone has "me" or "self" as their name they won't be retrieved.
|
||||||
|
|
||||||
|
|
||||||
Iterator methods (v0.18.1)
|
Iterator methods (v0.18.1)
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,9 @@ Working with Chats and Channels
|
||||||
Joining a chat or channel
|
Joining a chat or channel
|
||||||
*************************
|
*************************
|
||||||
|
|
||||||
Note that `Chat`__\ s are normal groups, and `Channel`__\ s are a
|
Note that :tl:`Chat` are normal groups, and :tl:`Channel` are a
|
||||||
special form of `Chat`__\ s,
|
special form of ``Chat``, which can also be super-groups if
|
||||||
which can also be super-groups if their ``megagroup`` member is
|
their ``megagroup`` member is ``True``.
|
||||||
``True``.
|
|
||||||
|
|
||||||
|
|
||||||
Joining a public channel
|
Joining a public channel
|
||||||
|
@ -101,6 +100,13 @@ __ https://lonamiwebs.github.io/Telethon/methods/messages/check_chat_invite.html
|
||||||
Retrieving all chat members (channels too)
|
Retrieving all chat members (channels too)
|
||||||
******************************************
|
******************************************
|
||||||
|
|
||||||
|
You can use
|
||||||
|
`client.get_participants <telethon.telegram_client.TelegramClient.get_participants>`
|
||||||
|
to retrieve the participants (click it to see the relevant parameters).
|
||||||
|
Most of the time you will just need ``client.get_participants(entity)``.
|
||||||
|
|
||||||
|
This is what said method is doing behind the scenes as an example.
|
||||||
|
|
||||||
In order to get all the members from a mega-group or channel, you need
|
In order to get all the members from a mega-group or channel, you need
|
||||||
to use `GetParticipantsRequest`__. As we can see it needs an
|
to use `GetParticipantsRequest`__. As we can see it needs an
|
||||||
`InputChannel`__, (passing the mega-group or channel you're going to
|
`InputChannel`__, (passing the mega-group or channel you're going to
|
||||||
|
@ -134,9 +140,10 @@ a fixed limit:
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
It is **not** possible to get more than 10,000 members from a
|
If you need more than 10,000 members from a group you should use the
|
||||||
group. It's a hard limit impossed by Telegram and there is
|
mentioned ``client.get_participants(..., aggressive=True)``. It will
|
||||||
nothing you can do about it. Refer to `issue 573`__ for more.
|
do some tricks behind the scenes to get as many entities as possible.
|
||||||
|
Refer to `issue 573`__ for more on this.
|
||||||
|
|
||||||
|
|
||||||
Note that ``GetParticipantsRequest`` returns `ChannelParticipants`__,
|
Note that ``GetParticipantsRequest`` returns `ChannelParticipants`__,
|
||||||
|
@ -147,8 +154,8 @@ __ https://lonamiwebs.github.io/Telethon/methods/channels/get_participants.html
|
||||||
__ https://lonamiwebs.github.io/Telethon/methods/channels/get_participants.html
|
__ https://lonamiwebs.github.io/Telethon/methods/channels/get_participants.html
|
||||||
__ https://lonamiwebs.github.io/Telethon/types/channel_participants_filter.html
|
__ https://lonamiwebs.github.io/Telethon/types/channel_participants_filter.html
|
||||||
__ https://lonamiwebs.github.io/Telethon/constructors/channel_participants_search.html
|
__ https://lonamiwebs.github.io/Telethon/constructors/channel_participants_search.html
|
||||||
__ https://lonamiwebs.github.io/Telethon/constructors/channels/channel_participants.html
|
|
||||||
__ https://github.com/LonamiWebs/Telethon/issues/573
|
__ https://github.com/LonamiWebs/Telethon/issues/573
|
||||||
|
__ https://lonamiwebs.github.io/Telethon/constructors/channels/channel_participants.html
|
||||||
|
|
||||||
|
|
||||||
Recent Actions
|
Recent Actions
|
||||||
|
|
|
@ -11,18 +11,27 @@ Working with messages
|
||||||
Forwarding messages
|
Forwarding messages
|
||||||
*******************
|
*******************
|
||||||
|
|
||||||
Note that ForwardMessageRequest_ (note it's Message, singular) will *not*
|
This request is available as a friendly method through
|
||||||
work if channels are involved. This is because channel (and megagroups) IDs
|
`client.forward_messages <telethon.telegram_client.TelegramClient.forward_messages>`,
|
||||||
are not unique, so you also need to know who the sender is (a parameter this
|
and can be used like shown below:
|
||||||
request doesn't have).
|
|
||||||
|
|
||||||
Either way, you are encouraged to use ForwardMessagesRequest_ (note it's
|
|
||||||
Message*s*, plural) *always*, since it is more powerful, as follows:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
# If you only have the message IDs
|
||||||
|
await client.forward_messages(
|
||||||
|
entity, # to which entity you are forwarding the messages
|
||||||
|
message_ids, # the IDs of the messages (or message) to forward
|
||||||
|
from_entity # who sent the messages?
|
||||||
|
)
|
||||||
|
|
||||||
|
# If you have ``Message`` objects
|
||||||
|
await client.forward_messages(
|
||||||
|
entity, # to which entity you are forwarding the messages
|
||||||
|
messages # the messages (or message) to forward
|
||||||
|
)
|
||||||
|
|
||||||
|
# You can also do it manually if you prefer
|
||||||
from telethon.tl.functions.messages import ForwardMessagesRequest
|
from telethon.tl.functions.messages import ForwardMessagesRequest
|
||||||
# note the s ^
|
|
||||||
|
|
||||||
messages = foo() # retrieve a few messages (or even one, in a list)
|
messages = foo() # retrieve a few messages (or even one, in a list)
|
||||||
from_entity = bar()
|
from_entity = bar()
|
||||||
|
@ -119,7 +128,6 @@ send yourself the very first sticker you have:
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
.. _ForwardMessageRequest: https://lonamiwebs.github.io/Telethon/methods/messages/forward_message.html
|
|
||||||
.. _ForwardMessagesRequest: https://lonamiwebs.github.io/Telethon/methods/messages/forward_messages.html
|
.. _ForwardMessagesRequest: https://lonamiwebs.github.io/Telethon/methods/messages/forward_messages.html
|
||||||
.. _SearchRequest: https://lonamiwebs.github.io/Telethon/methods/messages/search.html
|
.. _SearchRequest: https://lonamiwebs.github.io/Telethon/methods/messages/search.html
|
||||||
.. _issues: https://github.com/LonamiWebs/Telethon/issues/215
|
.. _issues: https://github.com/LonamiWebs/Telethon/issues/215
|
||||||
|
|
|
@ -78,6 +78,9 @@ def rpc_message_to_error(code, message, report_method=None):
|
||||||
if code == 404:
|
if code == 404:
|
||||||
return NotFoundError(message)
|
return NotFoundError(message)
|
||||||
|
|
||||||
|
if code == 406:
|
||||||
|
return AuthKeyError(message)
|
||||||
|
|
||||||
if code == 500:
|
if code == 500:
|
||||||
return ServerError(message)
|
return ServerError(message)
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,19 @@ class NotFoundError(RPCError):
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
|
class AuthKeyError(RPCError):
|
||||||
|
"""
|
||||||
|
Errors related to invalid authorization key, like
|
||||||
|
AUTH_KEY_DUPLICATED which can cause the connection to fail.
|
||||||
|
"""
|
||||||
|
code = 406
|
||||||
|
message = 'AUTH_KEY'
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super().__init__(message)
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
class FloodError(RPCError):
|
class FloodError(RPCError):
|
||||||
"""
|
"""
|
||||||
The maximum allowed number of attempts to invoke the given method
|
The maximum allowed number of attempts to invoke the given method
|
||||||
|
|
|
@ -46,11 +46,11 @@ class _EventBuilder(abc.ABC):
|
||||||
The common event builder, with builtin support to filter per chat.
|
The common event builder, with builtin support to filter per chat.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chats (:obj:`entity`, optional):
|
chats (`entity`, optional):
|
||||||
May be one or more entities (username/peer/etc.). By default,
|
May be one or more entities (username/peer/etc.). By default,
|
||||||
only matching chats will be handled.
|
only matching chats will be handled.
|
||||||
|
|
||||||
blacklist_chats (:obj:`bool`, optional):
|
blacklist_chats (`bool`, optional):
|
||||||
Whether to treat the chats as a blacklist instead of
|
Whether to treat the chats as a blacklist instead of
|
||||||
as a whitelist (default). This means that every chat
|
as a whitelist (default). This means that every chat
|
||||||
will be handled *except* those specified in ``chats``
|
will be handled *except* those specified in ``chats``
|
||||||
|
@ -118,11 +118,15 @@ class _EventCommon(abc.ABC):
|
||||||
try:
|
try:
|
||||||
if isinstance(chat, types.InputPeerChannel):
|
if isinstance(chat, types.InputPeerChannel):
|
||||||
result = await self._client(
|
result = await self._client(
|
||||||
functions.channels.GetMessagesRequest(chat, [msg_id])
|
functions.channels.GetMessagesRequest(chat, [
|
||||||
|
types.InputMessageID(msg_id)
|
||||||
|
])
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result = await self._client(
|
result = await self._client(
|
||||||
functions.messages.GetMessagesRequest([msg_id])
|
functions.messages.GetMessagesRequest([
|
||||||
|
types.InputMessageID(msg_id)
|
||||||
|
])
|
||||||
)
|
)
|
||||||
except RPCError:
|
except RPCError:
|
||||||
return None, None
|
return None, None
|
||||||
|
@ -228,15 +232,15 @@ class NewMessage(_EventBuilder):
|
||||||
Represents a new message event builder.
|
Represents a new message event builder.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
incoming (:obj:`bool`, optional):
|
incoming (`bool`, optional):
|
||||||
If set to ``True``, only **incoming** messages will be handled.
|
If set to ``True``, only **incoming** messages will be handled.
|
||||||
Mutually exclusive with ``outgoing`` (can only set one of either).
|
Mutually exclusive with ``outgoing`` (can only set one of either).
|
||||||
|
|
||||||
outgoing (:obj:`bool`, optional):
|
outgoing (`bool`, optional):
|
||||||
If set to ``True``, only **outgoing** messages will be handled.
|
If set to ``True``, only **outgoing** messages will be handled.
|
||||||
Mutually exclusive with ``incoming`` (can only set one of either).
|
Mutually exclusive with ``incoming`` (can only set one of either).
|
||||||
|
|
||||||
pattern (:obj:`str`, :obj:`callable`, :obj:`Pattern`, optional):
|
pattern (`str`, `callable`, `Pattern`, optional):
|
||||||
If set, only messages matching this pattern will be handled.
|
If set, only messages matching this pattern will be handled.
|
||||||
You can specify a regex-like string which will be matched
|
You can specify a regex-like string which will be matched
|
||||||
against the message, a callable function that returns ``True``
|
against the message, a callable function that returns ``True``
|
||||||
|
@ -300,7 +304,7 @@ class NewMessage(_EventBuilder):
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
event._entities = update.entities
|
event._entities = update._entities
|
||||||
return self._message_filter_event(event)
|
return self._message_filter_event(event)
|
||||||
|
|
||||||
def _message_filter_event(self, event):
|
def _message_filter_event(self, event):
|
||||||
|
@ -330,16 +334,16 @@ class NewMessage(_EventBuilder):
|
||||||
message (:tl:`Message`):
|
message (:tl:`Message`):
|
||||||
This is the original :tl:`Message` object.
|
This is the original :tl:`Message` object.
|
||||||
|
|
||||||
is_private (:obj:`bool`):
|
is_private (`bool`):
|
||||||
True if the message was sent as a private message.
|
True if the message was sent as a private message.
|
||||||
|
|
||||||
is_group (:obj:`bool`):
|
is_group (`bool`):
|
||||||
True if the message was sent on a group or megagroup.
|
True if the message was sent on a group or megagroup.
|
||||||
|
|
||||||
is_channel (:obj:`bool`):
|
is_channel (`bool`):
|
||||||
True if the message was sent on a megagroup or channel.
|
True if the message was sent on a megagroup or channel.
|
||||||
|
|
||||||
is_reply (:obj:`str`):
|
is_reply (`str`):
|
||||||
Whether the message is a reply to some other or not.
|
Whether the message is a reply to some other or not.
|
||||||
"""
|
"""
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
|
@ -501,11 +505,13 @@ class NewMessage(_EventBuilder):
|
||||||
if self._reply_message is None:
|
if self._reply_message is None:
|
||||||
if isinstance(await self.input_chat, types.InputPeerChannel):
|
if isinstance(await self.input_chat, types.InputPeerChannel):
|
||||||
r = await self._client(functions.channels.GetMessagesRequest(
|
r = await self._client(functions.channels.GetMessagesRequest(
|
||||||
await self.input_chat, [self.message.reply_to_msg_id]
|
await self.input_chat, [
|
||||||
|
types.InputMessageID(self.message.reply_to_msg_id)
|
||||||
|
]
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
r = await self._client(functions.messages.GetMessagesRequest(
|
r = await self._client(functions.messages.GetMessagesRequest(
|
||||||
[self.message.reply_to_msg_id]
|
[types.InputMessageID(self.message.reply_to_msg_id)]
|
||||||
))
|
))
|
||||||
if not isinstance(r, types.messages.MessagesNotModified):
|
if not isinstance(r, types.messages.MessagesNotModified):
|
||||||
self._reply_message = r.messages[0]
|
self._reply_message = r.messages[0]
|
||||||
|
@ -692,7 +698,7 @@ class ChatAction(_EventBuilder):
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
event._entities = update.entities
|
event._entities = update._entities
|
||||||
return self._filter_event(event)
|
return self._filter_event(event)
|
||||||
|
|
||||||
class Event(_EventCommon):
|
class Event(_EventCommon):
|
||||||
|
@ -700,35 +706,35 @@ class ChatAction(_EventBuilder):
|
||||||
Represents the event of a new chat action.
|
Represents the event of a new chat action.
|
||||||
|
|
||||||
Members:
|
Members:
|
||||||
new_pin (:obj:`bool`):
|
new_pin (`bool`):
|
||||||
``True`` if there is a new pin.
|
``True`` if there is a new pin.
|
||||||
|
|
||||||
new_photo (:obj:`bool`):
|
new_photo (`bool`):
|
||||||
``True`` if there's a new chat photo (or it was removed).
|
``True`` if there's a new chat photo (or it was removed).
|
||||||
|
|
||||||
photo (:tl:`Photo`, optional):
|
photo (:tl:`Photo`, optional):
|
||||||
The new photo (or ``None`` if it was removed).
|
The new photo (or ``None`` if it was removed).
|
||||||
|
|
||||||
|
|
||||||
user_added (:obj:`bool`):
|
user_added (`bool`):
|
||||||
``True`` if the user was added by some other.
|
``True`` if the user was added by some other.
|
||||||
|
|
||||||
user_joined (:obj:`bool`):
|
user_joined (`bool`):
|
||||||
``True`` if the user joined on their own.
|
``True`` if the user joined on their own.
|
||||||
|
|
||||||
user_left (:obj:`bool`):
|
user_left (`bool`):
|
||||||
``True`` if the user left on their own.
|
``True`` if the user left on their own.
|
||||||
|
|
||||||
user_kicked (:obj:`bool`):
|
user_kicked (`bool`):
|
||||||
``True`` if the user was kicked by some other.
|
``True`` if the user was kicked by some other.
|
||||||
|
|
||||||
created (:obj:`bool`, optional):
|
created (`bool`, optional):
|
||||||
``True`` if this chat was just created.
|
``True`` if this chat was just created.
|
||||||
|
|
||||||
new_title (:obj:`bool`, optional):
|
new_title (`bool`, optional):
|
||||||
The new title string for the chat, if applicable.
|
The new title string for the chat, if applicable.
|
||||||
|
|
||||||
unpin (:obj:`bool`):
|
unpin (`bool`):
|
||||||
``True`` if the existing pin gets unpinned.
|
``True`` if the existing pin gets unpinned.
|
||||||
"""
|
"""
|
||||||
def __init__(self, where, new_pin=None, new_photo=None,
|
def __init__(self, where, new_pin=None, new_photo=None,
|
||||||
|
@ -820,7 +826,9 @@ class ChatAction(_EventBuilder):
|
||||||
|
|
||||||
if isinstance(self._pinned_message, int) and await self.input_chat:
|
if isinstance(self._pinned_message, int) and await self.input_chat:
|
||||||
r = await self._client(functions.channels.GetMessagesRequest(
|
r = await self._client(functions.channels.GetMessagesRequest(
|
||||||
self._input_chat, [self._pinned_message]
|
self._input_chat, [
|
||||||
|
types.InputMessageID(self._pinned_message)
|
||||||
|
]
|
||||||
))
|
))
|
||||||
try:
|
try:
|
||||||
self._pinned_message = next(
|
self._pinned_message = next(
|
||||||
|
@ -941,7 +949,7 @@ class UserUpdate(_EventBuilder):
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
event._entities = update.entities
|
event._entities = update._entities
|
||||||
return self._filter_event(event)
|
return self._filter_event(event)
|
||||||
|
|
||||||
class Event(_EventCommon):
|
class Event(_EventCommon):
|
||||||
|
@ -949,62 +957,62 @@ class UserUpdate(_EventBuilder):
|
||||||
Represents the event of an user status update (last seen, joined).
|
Represents the event of an user status update (last seen, joined).
|
||||||
|
|
||||||
Members:
|
Members:
|
||||||
online (:obj:`bool`, optional):
|
online (`bool`, optional):
|
||||||
``True`` if the user is currently online, ``False`` otherwise.
|
``True`` if the user is currently online, ``False`` otherwise.
|
||||||
Might be ``None`` if this information is not present.
|
Might be ``None`` if this information is not present.
|
||||||
|
|
||||||
last_seen (:obj:`datetime`, optional):
|
last_seen (`datetime`, optional):
|
||||||
Exact date when the user was last seen if known.
|
Exact date when the user was last seen if known.
|
||||||
|
|
||||||
until (:obj:`datetime`, optional):
|
until (`datetime`, optional):
|
||||||
Until when will the user remain online.
|
Until when will the user remain online.
|
||||||
|
|
||||||
within_months (:obj:`bool`):
|
within_months (`bool`):
|
||||||
``True`` if the user was seen within 30 days.
|
``True`` if the user was seen within 30 days.
|
||||||
|
|
||||||
within_weeks (:obj:`bool`):
|
within_weeks (`bool`):
|
||||||
``True`` if the user was seen within 7 days.
|
``True`` if the user was seen within 7 days.
|
||||||
|
|
||||||
recently (:obj:`bool`):
|
recently (`bool`):
|
||||||
``True`` if the user was seen within a day.
|
``True`` if the user was seen within a day.
|
||||||
|
|
||||||
action (:tl:`SendMessageAction`, optional):
|
action (:tl:`SendMessageAction`, optional):
|
||||||
The "typing" action if any the user is performing if any.
|
The "typing" action if any the user is performing if any.
|
||||||
|
|
||||||
cancel (:obj:`bool`):
|
cancel (`bool`):
|
||||||
``True`` if the action was cancelling other actions.
|
``True`` if the action was cancelling other actions.
|
||||||
|
|
||||||
typing (:obj:`bool`):
|
typing (`bool`):
|
||||||
``True`` if the action is typing a message.
|
``True`` if the action is typing a message.
|
||||||
|
|
||||||
recording (:obj:`bool`):
|
recording (`bool`):
|
||||||
``True`` if the action is recording something.
|
``True`` if the action is recording something.
|
||||||
|
|
||||||
uploading (:obj:`bool`):
|
uploading (`bool`):
|
||||||
``True`` if the action is uploading something.
|
``True`` if the action is uploading something.
|
||||||
|
|
||||||
playing (:obj:`bool`):
|
playing (`bool`):
|
||||||
``True`` if the action is playing a game.
|
``True`` if the action is playing a game.
|
||||||
|
|
||||||
audio (:obj:`bool`):
|
audio (`bool`):
|
||||||
``True`` if what's being recorded/uploaded is an audio.
|
``True`` if what's being recorded/uploaded is an audio.
|
||||||
|
|
||||||
round (:obj:`bool`):
|
round (`bool`):
|
||||||
``True`` if what's being recorded/uploaded is a round video.
|
``True`` if what's being recorded/uploaded is a round video.
|
||||||
|
|
||||||
video (:obj:`bool`):
|
video (`bool`):
|
||||||
``True`` if what's being recorded/uploaded is an video.
|
``True`` if what's being recorded/uploaded is an video.
|
||||||
|
|
||||||
document (:obj:`bool`):
|
document (`bool`):
|
||||||
``True`` if what's being uploaded is document.
|
``True`` if what's being uploaded is document.
|
||||||
|
|
||||||
geo (:obj:`bool`):
|
geo (`bool`):
|
||||||
``True`` if what's being uploaded is a geo.
|
``True`` if what's being uploaded is a geo.
|
||||||
|
|
||||||
photo (:obj:`bool`):
|
photo (`bool`):
|
||||||
``True`` if what's being uploaded is a photo.
|
``True`` if what's being uploaded is a photo.
|
||||||
|
|
||||||
contact (:obj:`bool`):
|
contact (`bool`):
|
||||||
``True`` if what's being uploaded (selected) is a contact.
|
``True`` if what's being uploaded (selected) is a contact.
|
||||||
"""
|
"""
|
||||||
def __init__(self, user_id, status=None, typing=None):
|
def __init__(self, user_id, status=None, typing=None):
|
||||||
|
@ -1090,7 +1098,7 @@ class MessageEdited(NewMessage):
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
event._entities = update.entities
|
event._entities = update._entities
|
||||||
return self._message_filter_event(event)
|
return self._message_filter_event(event)
|
||||||
|
|
||||||
class Event(NewMessage.Event):
|
class Event(NewMessage.Event):
|
||||||
|
@ -1116,7 +1124,7 @@ class MessageDeleted(_EventBuilder):
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
event._entities = update.entities
|
event._entities = update._entities
|
||||||
return self._filter_event(event)
|
return self._filter_event(event)
|
||||||
|
|
||||||
class Event(_EventCommon):
|
class Event(_EventCommon):
|
||||||
|
@ -1128,6 +1136,140 @@ class MessageDeleted(_EventBuilder):
|
||||||
self.deleted_ids = deleted_ids
|
self.deleted_ids = deleted_ids
|
||||||
|
|
||||||
|
|
||||||
|
@_name_inner_event
|
||||||
|
class MessageRead(_EventBuilder):
|
||||||
|
"""
|
||||||
|
Event fired when one or more messages have been read.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
inbox (`bool`, optional):
|
||||||
|
If this argument is ``True``, then when you read someone else's
|
||||||
|
messages the event will be fired. By default (``False``) only
|
||||||
|
when messages you sent are read by someone else will fire it.
|
||||||
|
"""
|
||||||
|
def __init__(self, inbox=False, chats=None, blacklist_chats=None):
|
||||||
|
super().__init__(chats, blacklist_chats)
|
||||||
|
self.inbox = inbox
|
||||||
|
|
||||||
|
def build(self, update):
|
||||||
|
if isinstance(update, types.UpdateReadHistoryInbox):
|
||||||
|
event = MessageRead.Event(update.peer, update.max_id, False)
|
||||||
|
elif isinstance(update, types.UpdateReadHistoryOutbox):
|
||||||
|
event = MessageRead.Event(update.peer, update.max_id, True)
|
||||||
|
elif isinstance(update, types.UpdateReadChannelInbox):
|
||||||
|
event = MessageRead.Event(types.PeerChannel(update.channel_id),
|
||||||
|
update.max_id, False)
|
||||||
|
elif isinstance(update, types.UpdateReadChannelOutbox):
|
||||||
|
event = MessageRead.Event(types.PeerChannel(update.channel_id),
|
||||||
|
update.max_id, True)
|
||||||
|
elif isinstance(update, types.UpdateReadMessagesContents):
|
||||||
|
event = MessageRead.Event(message_ids=update.messages,
|
||||||
|
contents=True)
|
||||||
|
elif isinstance(update, types.UpdateChannelReadMessagesContents):
|
||||||
|
event = MessageRead.Event(types.PeerChannel(update.channel_id),
|
||||||
|
message_ids=update.messages,
|
||||||
|
contents=True)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.inbox == event.outbox:
|
||||||
|
return
|
||||||
|
|
||||||
|
event._entities = update._entities
|
||||||
|
return self._filter_event(event)
|
||||||
|
|
||||||
|
class Event(_EventCommon):
|
||||||
|
"""
|
||||||
|
Represents the event of one or more messages being read.
|
||||||
|
|
||||||
|
Members:
|
||||||
|
max_id (`int`):
|
||||||
|
Up to which message ID has been read. Every message
|
||||||
|
with an ID equal or lower to it have been read.
|
||||||
|
|
||||||
|
outbox (`bool`):
|
||||||
|
``True`` if someone else has read your messages.
|
||||||
|
|
||||||
|
contents (`bool`):
|
||||||
|
``True`` if what was read were the contents of a message.
|
||||||
|
This will be the case when e.g. you play a voice note.
|
||||||
|
It may only be set on ``inbox`` events.
|
||||||
|
"""
|
||||||
|
def __init__(self, peer=None, max_id=None, out=False, contents=False,
|
||||||
|
message_ids=None):
|
||||||
|
self.outbox = out
|
||||||
|
self.contents = contents
|
||||||
|
self._message_ids = message_ids or []
|
||||||
|
self._messages = None
|
||||||
|
self.max_id = max_id or max(message_ids or [], default=None)
|
||||||
|
super().__init__(peer, self.max_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inbox(self):
|
||||||
|
"""
|
||||||
|
``True`` if you have read someone else's messages.
|
||||||
|
"""
|
||||||
|
return not self.outbox
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message_ids(self):
|
||||||
|
"""
|
||||||
|
The IDs of the messages **which contents'** were read.
|
||||||
|
|
||||||
|
Use :meth:`is_read` if you need to check whether a message
|
||||||
|
was read instead checking if it's in here.
|
||||||
|
"""
|
||||||
|
return self._message_ids
|
||||||
|
|
||||||
|
@property
|
||||||
|
async def messages(self):
|
||||||
|
"""
|
||||||
|
The list of :tl:`Message` **which contents'** were read.
|
||||||
|
|
||||||
|
Use :meth:`is_read` if you need to check whether a message
|
||||||
|
was read instead checking if it's in here.
|
||||||
|
"""
|
||||||
|
if self._messages is None:
|
||||||
|
chat = self.input_chat
|
||||||
|
if not chat:
|
||||||
|
self._messages = []
|
||||||
|
elif isinstance(chat, types.InputPeerChannel):
|
||||||
|
ids = [types.InputMessageID(x) for x in self._message_ids]
|
||||||
|
self._messages =\
|
||||||
|
await self._client(functions.channels.GetMessagesRequest(
|
||||||
|
chat, ids
|
||||||
|
)).messages
|
||||||
|
else:
|
||||||
|
ids = [types.InputMessageID(x) for x in self._message_ids]
|
||||||
|
self._messages =\
|
||||||
|
await self._client(functions.messages.GetMessagesRequest(
|
||||||
|
ids
|
||||||
|
)).messages
|
||||||
|
|
||||||
|
return self._messages
|
||||||
|
|
||||||
|
def is_read(self, message):
|
||||||
|
"""
|
||||||
|
Returns ``True`` if the given message (or its ID) has been read.
|
||||||
|
|
||||||
|
If a list-like argument is provided, this method will return a
|
||||||
|
list of booleans indicating which messages have been read.
|
||||||
|
"""
|
||||||
|
if utils.is_list_like(message):
|
||||||
|
return [(m if isinstance(m, int) else m.id) <= self.max_id
|
||||||
|
for m in message]
|
||||||
|
else:
|
||||||
|
return (message if isinstance(message, int)
|
||||||
|
else message.id) <= self.max_id
|
||||||
|
|
||||||
|
def __contains__(self, message):
|
||||||
|
"""``True`` if the message(s) are read message."""
|
||||||
|
if utils.is_list_like(message):
|
||||||
|
return all(self.is_read(message))
|
||||||
|
else:
|
||||||
|
return self.is_read(message)
|
||||||
|
|
||||||
|
|
||||||
class StopPropagation(Exception):
|
class StopPropagation(Exception):
|
||||||
"""
|
"""
|
||||||
If this exception is raised in any of the handlers for a given event,
|
If this exception is raised in any of the handlers for a given event,
|
||||||
|
|
|
@ -479,11 +479,13 @@ class MtProtoSender:
|
||||||
reader.read_int(signed=False) # code
|
reader.read_int(signed=False) # code
|
||||||
request_id = reader.read_long()
|
request_id = reader.read_long()
|
||||||
inner_code = reader.read_int(signed=False)
|
inner_code = reader.read_int(signed=False)
|
||||||
|
reader.seek(-4)
|
||||||
|
|
||||||
__log__.debug('Received response for request with ID %d', request_id)
|
__log__.debug('Received response for request with ID %d', request_id)
|
||||||
request = self._pop_request(request_id)
|
request = self._pop_request(request_id)
|
||||||
|
|
||||||
if inner_code == 0x2144ca19: # RPC Error
|
if inner_code == 0x2144ca19: # RPC Error
|
||||||
|
reader.seek(4)
|
||||||
if self.session.report_errors and request:
|
if self.session.report_errors and request:
|
||||||
error = rpc_message_to_error(
|
error = rpc_message_to_error(
|
||||||
reader.read_int(), reader.tgread_string(),
|
reader.read_int(), reader.tgread_string(),
|
||||||
|
@ -505,12 +507,10 @@ class MtProtoSender:
|
||||||
return True # All contents were read okay
|
return True # All contents were read okay
|
||||||
|
|
||||||
elif request:
|
elif request:
|
||||||
if inner_code == 0x3072cfa1: # GZip packed
|
if inner_code == GzipPacked.CONSTRUCTOR_ID:
|
||||||
unpacked_data = gzip.decompress(reader.tgread_bytes())
|
with BinaryReader(GzipPacked.read(reader)) as compressed_reader:
|
||||||
with BinaryReader(unpacked_data) as compressed_reader:
|
|
||||||
request.on_response(compressed_reader)
|
request.on_response(compressed_reader)
|
||||||
else:
|
else:
|
||||||
reader.seek(-4)
|
|
||||||
request.on_response(reader)
|
request.on_response(reader)
|
||||||
|
|
||||||
self.session.process_entities(request.result)
|
self.session.process_entities(request.result)
|
||||||
|
@ -525,10 +525,17 @@ class MtProtoSender:
|
||||||
# session, it will be skipped by the handle_container().
|
# session, it will be skipped by the handle_container().
|
||||||
# For some reason this also seems to happen when downloading
|
# For some reason this also seems to happen when downloading
|
||||||
# photos, where the server responds with FileJpeg().
|
# photos, where the server responds with FileJpeg().
|
||||||
try:
|
def _try_read(r):
|
||||||
obj = reader.tgread_object()
|
try:
|
||||||
except Exception as e:
|
return r.tgread_object()
|
||||||
obj = '(failed to read: %s)' % e
|
except Exception as e:
|
||||||
|
return '(failed to read: {})'.format(e)
|
||||||
|
|
||||||
|
if inner_code == GzipPacked.CONSTRUCTOR_ID:
|
||||||
|
with BinaryReader(GzipPacked.read(reader)) as compressed_reader:
|
||||||
|
obj = _try_read(compressed_reader)
|
||||||
|
else:
|
||||||
|
obj = _try_read(reader)
|
||||||
|
|
||||||
__log__.warning(
|
__log__.warning(
|
||||||
'Lost request (ID %d) with code %s will be skipped, contents: %s',
|
'Lost request (ID %d) with code %s will be skipped, contents: %s',
|
||||||
|
|
|
@ -9,7 +9,7 @@ from .crypto import rsa
|
||||||
from .errors import (
|
from .errors import (
|
||||||
RPCError, BrokenAuthKeyError, ServerError, FloodWaitError,
|
RPCError, BrokenAuthKeyError, ServerError, FloodWaitError,
|
||||||
FloodTestPhoneWaitError, TypeNotFoundError, UnauthorizedError,
|
FloodTestPhoneWaitError, TypeNotFoundError, UnauthorizedError,
|
||||||
PhoneMigrateError, NetworkMigrateError, UserMigrateError
|
PhoneMigrateError, NetworkMigrateError, UserMigrateError, AuthKeyError
|
||||||
)
|
)
|
||||||
from .network import authenticator, MtProtoSender, Connection, ConnectionMode
|
from .network import authenticator, MtProtoSender, Connection, ConnectionMode
|
||||||
from .sessions import Session, SQLiteSession
|
from .sessions import Session, SQLiteSession
|
||||||
|
@ -217,6 +217,15 @@ class TelegramBareClient:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
return await self.connect(_sync_updates=_sync_updates)
|
return await self.connect(_sync_updates=_sync_updates)
|
||||||
|
|
||||||
|
except AuthKeyError as e:
|
||||||
|
# As of late March 2018 there were two AUTH_KEY_DUPLICATED
|
||||||
|
# reports. Retrying with a clean auth_key should fix this.
|
||||||
|
__log__.warning('Auth key error %s. Clearing it and retrying.', e)
|
||||||
|
self.disconnect()
|
||||||
|
self.session.auth_key = None
|
||||||
|
self.session.save()
|
||||||
|
return self.connect(_sync_updates=_sync_updates)
|
||||||
|
|
||||||
except (RPCError, ConnectionError) as e:
|
except (RPCError, ConnectionError) as e:
|
||||||
# Probably errors from the previous session, ignore them
|
# Probably errors from the previous session, ignore them
|
||||||
__log__.error('Connection failed due to %s', e)
|
__log__.error('Connection failed due to %s', e)
|
||||||
|
|
|
@ -38,12 +38,12 @@ from .errors import (
|
||||||
RPCError, UnauthorizedError, PhoneCodeEmptyError, PhoneCodeExpiredError,
|
RPCError, UnauthorizedError, PhoneCodeEmptyError, PhoneCodeExpiredError,
|
||||||
PhoneCodeHashEmptyError, PhoneCodeInvalidError, LocationInvalidError,
|
PhoneCodeHashEmptyError, PhoneCodeInvalidError, LocationInvalidError,
|
||||||
SessionPasswordNeededError, FileMigrateError, PhoneNumberUnoccupiedError,
|
SessionPasswordNeededError, FileMigrateError, PhoneNumberUnoccupiedError,
|
||||||
PhoneNumberOccupiedError
|
PhoneNumberOccupiedError, EmailUnconfirmedError, PasswordEmptyError
|
||||||
)
|
)
|
||||||
from .network import ConnectionMode
|
from .network import ConnectionMode
|
||||||
from .tl.custom import Draft, Dialog
|
from .tl.custom import Draft, Dialog
|
||||||
from .tl.functions.account import (
|
from .tl.functions.account import (
|
||||||
GetPasswordRequest
|
GetPasswordRequest, UpdatePasswordSettingsRequest
|
||||||
)
|
)
|
||||||
from .tl.functions.auth import (
|
from .tl.functions.auth import (
|
||||||
CheckPasswordRequest, LogOutRequest, SendCodeRequest, SignInRequest,
|
CheckPasswordRequest, LogOutRequest, SendCodeRequest, SignInRequest,
|
||||||
|
@ -83,9 +83,10 @@ from .tl.types import (
|
||||||
InputMessageEntityMentionName, DocumentAttributeVideo,
|
InputMessageEntityMentionName, DocumentAttributeVideo,
|
||||||
UpdateEditMessage, UpdateEditChannelMessage, UpdateShort, Updates,
|
UpdateEditMessage, UpdateEditChannelMessage, UpdateShort, Updates,
|
||||||
MessageMediaWebPage, ChannelParticipantsSearch, PhotoSize, PhotoCachedSize,
|
MessageMediaWebPage, ChannelParticipantsSearch, PhotoSize, PhotoCachedSize,
|
||||||
PhotoSizeEmpty, MessageService
|
PhotoSizeEmpty, MessageService, ChatParticipants
|
||||||
)
|
)
|
||||||
from .tl.types.messages import DialogsSlice
|
from .tl.types.messages import DialogsSlice
|
||||||
|
from .tl.types.account import PasswordInputSettings, NoPassword
|
||||||
from .extensions import markdown, html
|
from .extensions import markdown, html
|
||||||
|
|
||||||
__log__ = logging.getLogger(__name__)
|
__log__ = logging.getLogger(__name__)
|
||||||
|
@ -96,52 +97,51 @@ class TelegramClient(TelegramBareClient):
|
||||||
Initializes the Telegram client with the specified API ID and Hash.
|
Initializes the Telegram client with the specified API ID and Hash.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session (:obj:`str` | :obj:`telethon.sessions.abstract.Session`, \
|
session (`str` | `telethon.sessions.abstract.Session`, `None`):
|
||||||
:obj:`None`):
|
|
||||||
The file name of the session file to be used if a string is
|
The file name of the session file to be used if a string is
|
||||||
given (it may be a full path), or the Session instance to be
|
given (it may be a full path), or the Session instance to be
|
||||||
used otherwise. If it's ``None``, the session will not be saved,
|
used otherwise. If it's ``None``, the session will not be saved,
|
||||||
and you should call :meth:`.log_out()` when you're done.
|
and you should call :meth:`.log_out()` when you're done.
|
||||||
|
|
||||||
api_id (:obj:`int` | :obj:`str`):
|
api_id (`int` | `str`):
|
||||||
The API ID you obtained from https://my.telegram.org.
|
The API ID you obtained from https://my.telegram.org.
|
||||||
|
|
||||||
api_hash (:obj:`str`):
|
api_hash (`str`):
|
||||||
The API ID you obtained from https://my.telegram.org.
|
The API ID you obtained from https://my.telegram.org.
|
||||||
|
|
||||||
connection_mode (:obj:`ConnectionMode`, optional):
|
connection_mode (`ConnectionMode`, optional):
|
||||||
The connection mode to be used when creating a new connection
|
The connection mode to be used when creating a new connection
|
||||||
to the servers. Defaults to the ``TCP_FULL`` mode.
|
to the servers. Defaults to the ``TCP_FULL`` mode.
|
||||||
This will only affect how messages are sent over the network
|
This will only affect how messages are sent over the network
|
||||||
and how much processing is required before sending them.
|
and how much processing is required before sending them.
|
||||||
|
|
||||||
use_ipv6 (:obj:`bool`, optional):
|
use_ipv6 (`bool`, optional):
|
||||||
Whether to connect to the servers through IPv6 or not.
|
Whether to connect to the servers through IPv6 or not.
|
||||||
By default this is ``False`` as IPv6 support is not
|
By default this is ``False`` as IPv6 support is not
|
||||||
too widespread yet.
|
too widespread yet.
|
||||||
|
|
||||||
proxy (:obj:`tuple` | :obj:`dict`, optional):
|
proxy (`tuple` | `dict`, optional):
|
||||||
A tuple consisting of ``(socks.SOCKS5, 'host', port)``.
|
A tuple consisting of ``(socks.SOCKS5, 'host', port)``.
|
||||||
See https://github.com/Anorov/PySocks#usage-1 for more.
|
See https://github.com/Anorov/PySocks#usage-1 for more.
|
||||||
|
|
||||||
update_workers (:obj:`int`, optional):
|
update_workers (`int`, optional):
|
||||||
If specified, represents how many extra threads should
|
If specified, represents how many extra threads should
|
||||||
be spawned to handle incoming updates, and updates will
|
be spawned to handle incoming updates, and updates will
|
||||||
be kept in memory until they are processed. Note that
|
be kept in memory until they are processed. Note that
|
||||||
you must set this to at least ``0`` if you want to be
|
you must set this to at least ``0`` if you want to be
|
||||||
able to process updates through :meth:`updates.poll()`.
|
able to process updates through :meth:`updates.poll()`.
|
||||||
|
|
||||||
timeout (:obj:`int` | :obj:`float` | :obj:`timedelta`, optional):
|
timeout (`int` | `float` | `timedelta`, optional):
|
||||||
The timeout to be used when receiving responses from
|
The timeout to be used when receiving responses from
|
||||||
the network. Defaults to 5 seconds.
|
the network. Defaults to 5 seconds.
|
||||||
|
|
||||||
spawn_read_thread (:obj:`bool`, optional):
|
spawn_read_thread (`bool`, optional):
|
||||||
Whether to use an extra background thread or not. Defaults
|
Whether to use an extra background thread or not. Defaults
|
||||||
to ``True`` so receiving items from the network happens
|
to ``True`` so receiving items from the network happens
|
||||||
instantly, as soon as they arrive. Can still be disabled
|
instantly, as soon as they arrive. Can still be disabled
|
||||||
if you want to run the library without any additional thread.
|
if you want to run the library without any additional thread.
|
||||||
|
|
||||||
report_errors (:obj:`bool`, optional):
|
report_errors (`bool`, optional):
|
||||||
Whether to report RPC errors or not. Defaults to ``True``,
|
Whether to report RPC errors or not. Defaults to ``True``,
|
||||||
see :ref:`api-status` for more information.
|
see :ref:`api-status` for more information.
|
||||||
|
|
||||||
|
@ -204,10 +204,10 @@ class TelegramClient(TelegramBareClient):
|
||||||
Sends a code request to the specified phone number.
|
Sends a code request to the specified phone number.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
phone (:obj:`str` | :obj:`int`):
|
phone (`str` | `int`):
|
||||||
The phone to which the code will be sent.
|
The phone to which the code will be sent.
|
||||||
|
|
||||||
force_sms (:obj:`bool`, optional):
|
force_sms (`bool`, optional):
|
||||||
Whether to force sending as SMS.
|
Whether to force sending as SMS.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -247,36 +247,36 @@ class TelegramClient(TelegramBareClient):
|
||||||
(You are now logged in)
|
(You are now logged in)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
phone (:obj:`str` | :obj:`int` | :obj:`callable`):
|
phone (`str` | `int` | `callable`):
|
||||||
The phone (or callable without arguments to get it)
|
The phone (or callable without arguments to get it)
|
||||||
to which the code will be sent.
|
to which the code will be sent.
|
||||||
|
|
||||||
password (:obj:`callable`, optional):
|
password (`callable`, optional):
|
||||||
The password for 2 Factor Authentication (2FA).
|
The password for 2 Factor Authentication (2FA).
|
||||||
This is only required if it is enabled in your account.
|
This is only required if it is enabled in your account.
|
||||||
|
|
||||||
bot_token (:obj:`str`):
|
bot_token (`str`):
|
||||||
Bot Token obtained by `@BotFather <https://t.me/BotFather>`_
|
Bot Token obtained by `@BotFather <https://t.me/BotFather>`_
|
||||||
to log in as a bot. Cannot be specified with ``phone`` (only
|
to log in as a bot. Cannot be specified with ``phone`` (only
|
||||||
one of either allowed).
|
one of either allowed).
|
||||||
|
|
||||||
force_sms (:obj:`bool`, optional):
|
force_sms (`bool`, optional):
|
||||||
Whether to force sending the code request as SMS.
|
Whether to force sending the code request as SMS.
|
||||||
This only makes sense when signing in with a `phone`.
|
This only makes sense when signing in with a `phone`.
|
||||||
|
|
||||||
code_callback (:obj:`callable`, optional):
|
code_callback (`callable`, optional):
|
||||||
A callable that will be used to retrieve the Telegram
|
A callable that will be used to retrieve the Telegram
|
||||||
login code. Defaults to `input()`.
|
login code. Defaults to `input()`.
|
||||||
|
|
||||||
first_name (:obj:`str`, optional):
|
first_name (`str`, optional):
|
||||||
The first name to be used if signing up. This has no
|
The first name to be used if signing up. This has no
|
||||||
effect if the account already exists and you sign in.
|
effect if the account already exists and you sign in.
|
||||||
|
|
||||||
last_name (:obj:`str`, optional):
|
last_name (`str`, optional):
|
||||||
Similar to the first name, but for the last. Optional.
|
Similar to the first name, but for the last. Optional.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
This :obj:`TelegramClient`, so initialization
|
This `TelegramClient`, so initialization
|
||||||
can be chained with ``.start()``.
|
can be chained with ``.start()``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -367,26 +367,26 @@ class TelegramClient(TelegramBareClient):
|
||||||
or code that Telegram sent.
|
or code that Telegram sent.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
phone (:obj:`str` | :obj:`int`):
|
phone (`str` | `int`):
|
||||||
The phone to send the code to if no code was provided,
|
The phone to send the code to if no code was provided,
|
||||||
or to override the phone that was previously used with
|
or to override the phone that was previously used with
|
||||||
these requests.
|
these requests.
|
||||||
|
|
||||||
code (:obj:`str` | :obj:`int`):
|
code (`str` | `int`):
|
||||||
The code that Telegram sent. Note that if you have sent this
|
The code that Telegram sent. Note that if you have sent this
|
||||||
code through the application itself it will immediately
|
code through the application itself it will immediately
|
||||||
expire. If you want to send the code, obfuscate it somehow.
|
expire. If you want to send the code, obfuscate it somehow.
|
||||||
If you're not doing any of this you can ignore this note.
|
If you're not doing any of this you can ignore this note.
|
||||||
|
|
||||||
password (:obj:`str`):
|
password (`str`):
|
||||||
2FA password, should be used if a previous call raised
|
2FA password, should be used if a previous call raised
|
||||||
SessionPasswordNeededError.
|
SessionPasswordNeededError.
|
||||||
|
|
||||||
bot_token (:obj:`str`):
|
bot_token (`str`):
|
||||||
Used to sign in as a bot. Not all requests will be available.
|
Used to sign in as a bot. Not all requests will be available.
|
||||||
This should be the hash the @BotFather gave you.
|
This should be the hash the @BotFather gave you.
|
||||||
|
|
||||||
phone_code_hash (:obj:`str`):
|
phone_code_hash (`str`):
|
||||||
The hash returned by .send_code_request. This can be set to None
|
The hash returned by .send_code_request. This can be set to None
|
||||||
to use the last hash known.
|
to use the last hash known.
|
||||||
|
|
||||||
|
@ -443,13 +443,13 @@ class TelegramClient(TelegramBareClient):
|
||||||
You must call .send_code_request(phone) first.
|
You must call .send_code_request(phone) first.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
code (:obj:`str` | :obj:`int`):
|
code (`str` | `int`):
|
||||||
The code sent by Telegram
|
The code sent by Telegram
|
||||||
|
|
||||||
first_name (:obj:`str`):
|
first_name (`str`):
|
||||||
The first name to be used by the new account.
|
The first name to be used by the new account.
|
||||||
|
|
||||||
last_name (:obj:`str`, optional)
|
last_name (`str`, optional)
|
||||||
Optional last name.
|
Optional last name.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -495,7 +495,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
or None if the request fails (hence, not authenticated).
|
or None if the request fails (hence, not authenticated).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
input_peer (:obj:`bool`, optional):
|
input_peer (`bool`, optional):
|
||||||
Whether to return the :tl:`InputPeerUser` version or the normal
|
Whether to return the :tl:`InputPeerUser` version or the normal
|
||||||
:tl:`User`. This can be useful if you just need to know the ID
|
:tl:`User`. This can be useful if you just need to know the ID
|
||||||
of yourself.
|
of yourself.
|
||||||
|
@ -527,27 +527,27 @@ class TelegramClient(TelegramBareClient):
|
||||||
Dialogs are the open "chats" or conversations with other people.
|
Dialogs are the open "chats" or conversations with other people.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
limit (:obj:`int` | :obj:`None`):
|
limit (`int` | `None`):
|
||||||
How many dialogs to be retrieved as maximum. Can be set to
|
How many dialogs to be retrieved as maximum. Can be set to
|
||||||
``None`` to retrieve all dialogs. Note that this may take
|
``None`` to retrieve all dialogs. Note that this may take
|
||||||
whole minutes if you have hundreds of dialogs, as Telegram
|
whole minutes if you have hundreds of dialogs, as Telegram
|
||||||
will tell the library to slow down through a
|
will tell the library to slow down through a
|
||||||
``FloodWaitError``.
|
``FloodWaitError``.
|
||||||
|
|
||||||
offset_date (:obj:`datetime`, optional):
|
offset_date (`datetime`, optional):
|
||||||
The offset date to be used.
|
The offset date to be used.
|
||||||
|
|
||||||
offset_id (:obj:`int`, optional):
|
offset_id (`int`, optional):
|
||||||
The message ID to be used as an offset.
|
The message ID to be used as an offset.
|
||||||
|
|
||||||
offset_peer (:tl:`InputPeer`, optional):
|
offset_peer (:tl:`InputPeer`, optional):
|
||||||
The peer to be used as an offset.
|
The peer to be used as an offset.
|
||||||
|
|
||||||
_total (:obj:`list`, optional):
|
_total (`list`, optional):
|
||||||
A single-item list to pass the total parameter by reference.
|
A single-item list to pass the total parameter by reference.
|
||||||
|
|
||||||
Yields:
|
Yields:
|
||||||
Instances of :obj:`telethon.tl.custom.dialog.Dialog`.
|
Instances of `telethon.tl.custom.dialog.Dialog`.
|
||||||
"""
|
"""
|
||||||
limit = float('inf') if limit is None else int(limit)
|
limit = float('inf') if limit is None else int(limit)
|
||||||
if limit == 0:
|
if limit == 0:
|
||||||
|
@ -617,9 +617,9 @@ class TelegramClient(TelegramBareClient):
|
||||||
"""
|
"""
|
||||||
Iterator over all open draft messages.
|
Iterator over all open draft messages.
|
||||||
|
|
||||||
Instances of :obj:`telethon.tl.custom.draft.Draft` are yielded.
|
Instances of `telethon.tl.custom.draft.Draft` are yielded.
|
||||||
You can call :obj:`telethon.tl.custom.draft.Draft.set_message`
|
You can call `telethon.tl.custom.draft.Draft.set_message`
|
||||||
to change the message or :obj:`telethon.tl.custom.draft.Draft.delete`
|
to change the message or `telethon.tl.custom.draft.Draft.delete`
|
||||||
among other things.
|
among other things.
|
||||||
"""
|
"""
|
||||||
for update in (await self(GetAllDraftsRequest())).updates:
|
for update in (await self(GetAllDraftsRequest())).updates:
|
||||||
|
@ -710,33 +710,33 @@ class TelegramClient(TelegramBareClient):
|
||||||
Sends the given message to the specified entity (user/chat/channel).
|
Sends the given message to the specified entity (user/chat/channel).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (:obj:`entity`):
|
entity (`entity`):
|
||||||
To who will it be sent.
|
To who will it be sent.
|
||||||
|
|
||||||
message (:obj:`str` | :tl:`Message`):
|
message (`str` | :tl:`Message`):
|
||||||
The message to be sent, or another message object to resend.
|
The message to be sent, or another message object to resend.
|
||||||
|
|
||||||
reply_to (:obj:`int` | :tl:`Message`, optional):
|
reply_to (`int` | :tl:`Message`, optional):
|
||||||
Whether to reply to a message or not. If an integer is provided,
|
Whether to reply to a message or not. If an integer is provided,
|
||||||
it should be the ID of the message that it should reply to.
|
it should be the ID of the message that it should reply to.
|
||||||
|
|
||||||
parse_mode (:obj:`str`, optional):
|
parse_mode (`str`, optional):
|
||||||
Can be 'md' or 'markdown' for markdown-like parsing (default),
|
Can be 'md' or 'markdown' for markdown-like parsing (default),
|
||||||
or 'htm' or 'html' for HTML-like parsing. If ``None`` or any
|
or 'htm' or 'html' for HTML-like parsing. If ``None`` or any
|
||||||
other false-y value is provided, the message will be sent with
|
other false-y value is provided, the message will be sent with
|
||||||
no formatting.
|
no formatting.
|
||||||
|
|
||||||
link_preview (:obj:`bool`, optional):
|
link_preview (`bool`, optional):
|
||||||
Should the link preview be shown?
|
Should the link preview be shown?
|
||||||
|
|
||||||
file (:obj:`file`, optional):
|
file (`file`, optional):
|
||||||
Sends a message with a file attached (e.g. a photo,
|
Sends a message with a file attached (e.g. a photo,
|
||||||
video, audio or document). The ``message`` may be empty.
|
video, audio or document). The ``message`` may be empty.
|
||||||
|
|
||||||
force_document (:obj:`bool`, optional):
|
force_document (`bool`, optional):
|
||||||
Whether to send the given file as a document or not.
|
Whether to send the given file as a document or not.
|
||||||
|
|
||||||
clear_draft (:obj:`bool`, optional):
|
clear_draft (`bool`, optional):
|
||||||
Whether the existing draft should be cleared or not.
|
Whether the existing draft should be cleared or not.
|
||||||
Has no effect when sending a file.
|
Has no effect when sending a file.
|
||||||
|
|
||||||
|
@ -805,13 +805,13 @@ class TelegramClient(TelegramBareClient):
|
||||||
Forwards the given message(s) to the specified entity.
|
Forwards the given message(s) to the specified entity.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (:obj:`entity`):
|
entity (`entity`):
|
||||||
To which entity the message(s) will be forwarded.
|
To which entity the message(s) will be forwarded.
|
||||||
|
|
||||||
messages (:obj:`list` | :obj:`int` | :tl:`Message`):
|
messages (`list` | `int` | :tl:`Message`):
|
||||||
The message(s) to forward, or their integer IDs.
|
The message(s) to forward, or their integer IDs.
|
||||||
|
|
||||||
from_peer (:obj:`entity`):
|
from_peer (`entity`):
|
||||||
If the given messages are integer IDs and not instances
|
If the given messages are integer IDs and not instances
|
||||||
of the ``Message`` class, this *must* be specified in
|
of the ``Message`` class, this *must* be specified in
|
||||||
order for the forward to work.
|
order for the forward to work.
|
||||||
|
@ -858,22 +858,22 @@ class TelegramClient(TelegramBareClient):
|
||||||
Edits the given message ID (to change its contents or disable preview).
|
Edits the given message ID (to change its contents or disable preview).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (:obj:`entity`):
|
entity (`entity`):
|
||||||
From which chat to edit the message.
|
From which chat to edit the message.
|
||||||
|
|
||||||
message_id (:obj:`str`):
|
message_id (`str`):
|
||||||
The ID of the message (or ``Message`` itself) to be edited.
|
The ID of the message (or ``Message`` itself) to be edited.
|
||||||
|
|
||||||
message (:obj:`str`, optional):
|
message (`str`, optional):
|
||||||
The new text of the message.
|
The new text of the message.
|
||||||
|
|
||||||
parse_mode (:obj:`str`, optional):
|
parse_mode (`str`, optional):
|
||||||
Can be 'md' or 'markdown' for markdown-like parsing (default),
|
Can be 'md' or 'markdown' for markdown-like parsing (default),
|
||||||
or 'htm' or 'html' for HTML-like parsing. If ``None`` or any
|
or 'htm' or 'html' for HTML-like parsing. If ``None`` or any
|
||||||
other false-y value is provided, the message will be sent with
|
other false-y value is provided, the message will be sent with
|
||||||
no formatting.
|
no formatting.
|
||||||
|
|
||||||
link_preview (:obj:`bool`, optional):
|
link_preview (`bool`, optional):
|
||||||
Should the link preview be shown?
|
Should the link preview be shown?
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
@ -902,15 +902,15 @@ class TelegramClient(TelegramBareClient):
|
||||||
Deletes a message from a chat, optionally "for everyone".
|
Deletes a message from a chat, optionally "for everyone".
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (:obj:`entity`):
|
entity (`entity`):
|
||||||
From who the message will be deleted. This can actually
|
From who the message will be deleted. This can actually
|
||||||
be ``None`` for normal chats, but **must** be present
|
be ``None`` for normal chats, but **must** be present
|
||||||
for channels and megagroups.
|
for channels and megagroups.
|
||||||
|
|
||||||
message_ids (:obj:`list` | :obj:`int` | :tl:`Message`):
|
message_ids (`list` | `int` | :tl:`Message`):
|
||||||
The IDs (or ID) or messages to be deleted.
|
The IDs (or ID) or messages to be deleted.
|
||||||
|
|
||||||
revoke (:obj:`bool`, optional):
|
revoke (`bool`, optional):
|
||||||
Whether the message should be deleted for everyone or not.
|
Whether the message should be deleted for everyone or not.
|
||||||
By default it has the opposite behaviour of official clients,
|
By default it has the opposite behaviour of official clients,
|
||||||
and it will delete the message for everyone.
|
and it will delete the message for everyone.
|
||||||
|
@ -944,48 +944,48 @@ class TelegramClient(TelegramBareClient):
|
||||||
Iterator over the message history for the specified entity.
|
Iterator over the message history for the specified entity.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (:obj:`entity`):
|
entity (`entity`):
|
||||||
The entity from whom to retrieve the message history.
|
The entity from whom to retrieve the message history.
|
||||||
|
|
||||||
limit (:obj:`int` | :obj:`None`, optional):
|
limit (`int` | `None`, optional):
|
||||||
Number of messages to be retrieved. Due to limitations with
|
Number of messages to be retrieved. Due to limitations with
|
||||||
the API retrieving more than 3000 messages will take longer
|
the API retrieving more than 3000 messages will take longer
|
||||||
than half a minute (or even more based on previous calls).
|
than half a minute (or even more based on previous calls).
|
||||||
The limit may also be ``None``, which would eventually return
|
The limit may also be ``None``, which would eventually return
|
||||||
the whole history.
|
the whole history.
|
||||||
|
|
||||||
offset_date (:obj:`datetime`):
|
offset_date (`datetime`):
|
||||||
Offset date (messages *previous* to this date will be
|
Offset date (messages *previous* to this date will be
|
||||||
retrieved). Exclusive.
|
retrieved). Exclusive.
|
||||||
|
|
||||||
offset_id (:obj:`int`):
|
offset_id (`int`):
|
||||||
Offset message ID (only messages *previous* to the given
|
Offset message ID (only messages *previous* to the given
|
||||||
ID will be retrieved). Exclusive.
|
ID will be retrieved). Exclusive.
|
||||||
|
|
||||||
max_id (:obj:`int`):
|
max_id (`int`):
|
||||||
All the messages with a higher (newer) ID or equal to this will
|
All the messages with a higher (newer) ID or equal to this will
|
||||||
be excluded
|
be excluded
|
||||||
|
|
||||||
min_id (:obj:`int`):
|
min_id (`int`):
|
||||||
All the messages with a lower (older) ID or equal to this will
|
All the messages with a lower (older) ID or equal to this will
|
||||||
be excluded.
|
be excluded.
|
||||||
|
|
||||||
add_offset (:obj:`int`):
|
add_offset (`int`):
|
||||||
Additional message offset (all of the specified offsets +
|
Additional message offset (all of the specified offsets +
|
||||||
this offset = older messages).
|
this offset = older messages).
|
||||||
|
|
||||||
batch_size (:obj:`int`):
|
batch_size (`int`):
|
||||||
Messages will be returned in chunks of this size (100 is
|
Messages will be returned in chunks of this size (100 is
|
||||||
the maximum). While it makes no sense to modify this value,
|
the maximum). While it makes no sense to modify this value,
|
||||||
you are still free to do so.
|
you are still free to do so.
|
||||||
|
|
||||||
wait_time (:obj:`int`):
|
wait_time (`int`):
|
||||||
Wait time between different :tl:`GetHistoryRequest`. Use this
|
Wait time between different :tl:`GetHistoryRequest`. Use this
|
||||||
parameter to avoid hitting the ``FloodWaitError`` as needed.
|
parameter to avoid hitting the ``FloodWaitError`` as needed.
|
||||||
If left to ``None``, it will default to 1 second only if
|
If left to ``None``, it will default to 1 second only if
|
||||||
the limit is higher than 3000.
|
the limit is higher than 3000.
|
||||||
|
|
||||||
_total (:obj:`list`, optional):
|
_total (`list`, optional):
|
||||||
A single-item list to pass the total parameter by reference.
|
A single-item list to pass the total parameter by reference.
|
||||||
|
|
||||||
Yields:
|
Yields:
|
||||||
|
@ -1103,17 +1103,17 @@ class TelegramClient(TelegramBareClient):
|
||||||
read their messages, also known as the "double check").
|
read their messages, also known as the "double check").
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (:obj:`entity`):
|
entity (`entity`):
|
||||||
The chat where these messages are located.
|
The chat where these messages are located.
|
||||||
|
|
||||||
message (:obj:`list` | :tl:`Message`):
|
message (`list` | :tl:`Message`):
|
||||||
Either a list of messages or a single message.
|
Either a list of messages or a single message.
|
||||||
|
|
||||||
max_id (:obj:`int`):
|
max_id (`int`):
|
||||||
Overrides messages, until which message should the
|
Overrides messages, until which message should the
|
||||||
acknowledge should be sent.
|
acknowledge should be sent.
|
||||||
|
|
||||||
clear_mentions (:obj:`bool`):
|
clear_mentions (`bool`):
|
||||||
Whether the mention badge should be cleared (so that
|
Whether the mention badge should be cleared (so that
|
||||||
there are no more mentions) or not for the given entity.
|
there are no more mentions) or not for the given entity.
|
||||||
|
|
||||||
|
@ -1168,13 +1168,13 @@ class TelegramClient(TelegramBareClient):
|
||||||
Iterator over the participants belonging to the specified chat.
|
Iterator over the participants belonging to the specified chat.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (:obj:`entity`):
|
entity (`entity`):
|
||||||
The entity from which to retrieve the participants list.
|
The entity from which to retrieve the participants list.
|
||||||
|
|
||||||
limit (:obj:`int`):
|
limit (`int`):
|
||||||
Limits amount of participants fetched.
|
Limits amount of participants fetched.
|
||||||
|
|
||||||
search (:obj:`str`, optional):
|
search (`str`, optional):
|
||||||
Look for participants with this string in name/username.
|
Look for participants with this string in name/username.
|
||||||
|
|
||||||
filter (:tl:`ChannelParticipantsFilter`, optional):
|
filter (:tl:`ChannelParticipantsFilter`, optional):
|
||||||
|
@ -1182,7 +1182,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
Note that you might not have permissions for some filter.
|
Note that you might not have permissions for some filter.
|
||||||
This has no effect for normal chats or users.
|
This has no effect for normal chats or users.
|
||||||
|
|
||||||
aggressive (:obj:`bool`, optional):
|
aggressive (`bool`, optional):
|
||||||
Aggressively looks for all participants in the chat in
|
Aggressively looks for all participants in the chat in
|
||||||
order to get more than 10,000 members (a hard limit
|
order to get more than 10,000 members (a hard limit
|
||||||
imposed by Telegram). Note that this might take a long
|
imposed by Telegram). Note that this might take a long
|
||||||
|
@ -1192,7 +1192,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
This has no effect for groups or channels with less than
|
This has no effect for groups or channels with less than
|
||||||
10,000 members, or if a ``filter`` is given.
|
10,000 members, or if a ``filter`` is given.
|
||||||
|
|
||||||
_total (:obj:`list`, optional):
|
_total (`list`, optional):
|
||||||
A single-item list to pass the total parameter by reference.
|
A single-item list to pass the total parameter by reference.
|
||||||
|
|
||||||
Yields:
|
Yields:
|
||||||
|
@ -1282,6 +1282,11 @@ class TelegramClient(TelegramBareClient):
|
||||||
elif isinstance(entity, InputPeerChat):
|
elif isinstance(entity, InputPeerChat):
|
||||||
# TODO We *could* apply the `filter` here ourselves
|
# TODO We *could* apply the `filter` here ourselves
|
||||||
full = await self(GetFullChatRequest(entity.chat_id))
|
full = await self(GetFullChatRequest(entity.chat_id))
|
||||||
|
if not isinstance(full.full_chat.participants, ChatParticipants):
|
||||||
|
# ChatParticipantsForbidden won't have ``.participants``
|
||||||
|
_total[0] = 0
|
||||||
|
return
|
||||||
|
|
||||||
if _total:
|
if _total:
|
||||||
_total[0] = len(full.full_chat.participants.participants)
|
_total[0] = len(full.full_chat.participants.participants)
|
||||||
|
|
||||||
|
@ -1336,10 +1341,10 @@ class TelegramClient(TelegramBareClient):
|
||||||
Sends a file to the specified entity.
|
Sends a file to the specified entity.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (:obj:`entity`):
|
entity (`entity`):
|
||||||
Who will receive the file.
|
Who will receive the file.
|
||||||
|
|
||||||
file (:obj:`str` | :obj:`bytes` | :obj:`file` | :obj:`media`):
|
file (`str` | `bytes` | `file` | `media`):
|
||||||
The path of the file, byte array, or stream that will be sent.
|
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
|
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
|
or its type won't be inferred, and it will be sent as an
|
||||||
|
@ -1356,35 +1361,35 @@ class TelegramClient(TelegramBareClient):
|
||||||
sent as an album in the order in which they appear, sliced
|
sent as an album in the order in which they appear, sliced
|
||||||
in chunks of 10 if more than 10 are given.
|
in chunks of 10 if more than 10 are given.
|
||||||
|
|
||||||
caption (:obj:`str`, optional):
|
caption (`str`, optional):
|
||||||
Optional caption for the sent media message.
|
Optional caption for the sent media message.
|
||||||
|
|
||||||
force_document (:obj:`bool`, optional):
|
force_document (`bool`, optional):
|
||||||
If left to ``False`` and the file is a path that ends with
|
If left to ``False`` and the file is a path that ends with
|
||||||
the extension of an image file or a video file, it will be
|
the extension of an image file or a video file, it will be
|
||||||
sent as such. Otherwise always as a document.
|
sent as such. Otherwise always as a document.
|
||||||
|
|
||||||
progress_callback (:obj:`callable`, optional):
|
progress_callback (`callable`, optional):
|
||||||
A callback function accepting two parameters:
|
A callback function accepting two parameters:
|
||||||
``(sent bytes, total)``.
|
``(sent bytes, total)``.
|
||||||
|
|
||||||
reply_to (:obj:`int` | :tl:`Message`):
|
reply_to (`int` | :tl:`Message`):
|
||||||
Same as reply_to from .send_message().
|
Same as reply_to from .send_message().
|
||||||
|
|
||||||
attributes (:obj:`list`, optional):
|
attributes (`list`, optional):
|
||||||
Optional attributes that override the inferred ones, like
|
Optional attributes that override the inferred ones, like
|
||||||
:tl:`DocumentAttributeFilename` and so on.
|
:tl:`DocumentAttributeFilename` and so on.
|
||||||
|
|
||||||
thumb (:obj:`str` | :obj:`bytes` | :obj:`file`, optional):
|
thumb (`str` | `bytes` | `file`, optional):
|
||||||
Optional thumbnail (for videos).
|
Optional thumbnail (for videos).
|
||||||
|
|
||||||
allow_cache (:obj:`bool`, optional):
|
allow_cache (`bool`, optional):
|
||||||
Whether to allow using the cached version stored in the
|
Whether to allow using the cached version stored in the
|
||||||
database or not. Defaults to ``True`` to avoid re-uploads.
|
database or not. Defaults to ``True`` to avoid re-uploads.
|
||||||
Must be ``False`` if you wish to use different attributes
|
Must be ``False`` if you wish to use different attributes
|
||||||
or thumb than those that were used when the file was cached.
|
or thumb than those that were used when the file was cached.
|
||||||
|
|
||||||
parse_mode (:obj:`str`, optional):
|
parse_mode (`str`, optional):
|
||||||
The parse mode for the caption message.
|
The parse mode for the caption message.
|
||||||
|
|
||||||
Kwargs:
|
Kwargs:
|
||||||
|
@ -1624,7 +1629,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
will **not** upload the file to your own chat or any chat at all.
|
will **not** upload the file to your own chat or any chat at all.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file (:obj:`str` | :obj:`bytes` | :obj:`file`):
|
file (`str` | `bytes` | `file`):
|
||||||
The path of the file, byte array, or stream that will be sent.
|
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
|
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
|
or its type won't be inferred, and it will be sent as an
|
||||||
|
@ -1633,23 +1638,23 @@ class TelegramClient(TelegramBareClient):
|
||||||
Subsequent calls with the very same file will result in
|
Subsequent calls with the very same file will result in
|
||||||
immediate uploads, unless ``.clear_file_cache()`` is called.
|
immediate uploads, unless ``.clear_file_cache()`` is called.
|
||||||
|
|
||||||
part_size_kb (:obj:`int`, optional):
|
part_size_kb (`int`, optional):
|
||||||
Chunk size when uploading files. The larger, the less
|
Chunk size when uploading files. The larger, the less
|
||||||
requests will be made (up to 512KB maximum).
|
requests will be made (up to 512KB maximum).
|
||||||
|
|
||||||
file_name (:obj:`str`, optional):
|
file_name (`str`, optional):
|
||||||
The file name which will be used on the resulting InputFile.
|
The file name which will be used on the resulting InputFile.
|
||||||
If not specified, the name will be taken from the ``file``
|
If not specified, the name will be taken from the ``file``
|
||||||
and if this is not a ``str``, it will be ``"unnamed"``.
|
and if this is not a ``str``, it will be ``"unnamed"``.
|
||||||
|
|
||||||
use_cache (:obj:`type`, optional):
|
use_cache (`type`, optional):
|
||||||
The type of cache to use (currently either ``InputDocument``
|
The type of cache to use (currently either ``InputDocument``
|
||||||
or ``InputPhoto``). If present and the file is small enough
|
or ``InputPhoto``). If present and the file is small enough
|
||||||
to need the MD5, it will be checked against the database,
|
to need the MD5, it will be checked against the database,
|
||||||
and if a match is found, the upload won't be made. Instead,
|
and if a match is found, the upload won't be made. Instead,
|
||||||
an instance of type ``use_cache`` will be returned.
|
an instance of type ``use_cache`` will be returned.
|
||||||
|
|
||||||
progress_callback (:obj:`callable`, optional):
|
progress_callback (`callable`, optional):
|
||||||
A callback function accepting two parameters:
|
A callback function accepting two parameters:
|
||||||
``(sent bytes, total)``.
|
``(sent bytes, total)``.
|
||||||
|
|
||||||
|
@ -1752,14 +1757,14 @@ class TelegramClient(TelegramBareClient):
|
||||||
Downloads the profile photo of the given entity (user/chat/channel).
|
Downloads the profile photo of the given entity (user/chat/channel).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entity (:obj:`entity`):
|
entity (`entity`):
|
||||||
From who the photo will be downloaded.
|
From who the photo will be downloaded.
|
||||||
|
|
||||||
file (:obj:`str` | :obj:`file`, optional):
|
file (`str` | `file`, optional):
|
||||||
The output file path, directory, or stream-like object.
|
The output file path, directory, or stream-like object.
|
||||||
If the path exists and is a file, it will be overwritten.
|
If the path exists and is a file, it will be overwritten.
|
||||||
|
|
||||||
download_big (:obj:`bool`, optional):
|
download_big (`bool`, optional):
|
||||||
Whether to use the big version of the available photos.
|
Whether to use the big version of the available photos.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -1841,11 +1846,11 @@ class TelegramClient(TelegramBareClient):
|
||||||
message (:tl:`Message` | :tl:`Media`):
|
message (:tl:`Message` | :tl:`Media`):
|
||||||
The media or message containing the media that will be downloaded.
|
The media or message containing the media that will be downloaded.
|
||||||
|
|
||||||
file (:obj:`str` | :obj:`file`, optional):
|
file (`str` | `file`, optional):
|
||||||
The output file path, directory, or stream-like object.
|
The output file path, directory, or stream-like object.
|
||||||
If the path exists and is a file, it will be overwritten.
|
If the path exists and is a file, it will be overwritten.
|
||||||
|
|
||||||
progress_callback (:obj:`callable`, optional):
|
progress_callback (`callable`, optional):
|
||||||
A callback function accepting two parameters:
|
A callback function accepting two parameters:
|
||||||
``(received bytes, total)``.
|
``(received bytes, total)``.
|
||||||
|
|
||||||
|
@ -2066,19 +2071,19 @@ class TelegramClient(TelegramBareClient):
|
||||||
input_location (:tl:`InputFileLocation`):
|
input_location (:tl:`InputFileLocation`):
|
||||||
The file location from which the file will be downloaded.
|
The file location from which the file will be downloaded.
|
||||||
|
|
||||||
file (:obj:`str` | :obj:`file`):
|
file (`str` | `file`):
|
||||||
The output file path, directory, or stream-like object.
|
The output file path, directory, or stream-like object.
|
||||||
If the path exists and is a file, it will be overwritten.
|
If the path exists and is a file, it will be overwritten.
|
||||||
|
|
||||||
part_size_kb (:obj:`int`, optional):
|
part_size_kb (`int`, optional):
|
||||||
Chunk size when downloading files. The larger, the less
|
Chunk size when downloading files. The larger, the less
|
||||||
requests will be made (up to 512KB maximum).
|
requests will be made (up to 512KB maximum).
|
||||||
|
|
||||||
file_size (:obj:`int`, optional):
|
file_size (`int`, optional):
|
||||||
The file size that is about to be downloaded, if known.
|
The file size that is about to be downloaded, if known.
|
||||||
Only used if ``progress_callback`` is specified.
|
Only used if ``progress_callback`` is specified.
|
||||||
|
|
||||||
progress_callback (:obj:`callable`, optional):
|
progress_callback (`callable`, optional):
|
||||||
A callback function accepting two parameters:
|
A callback function accepting two parameters:
|
||||||
``(downloaded bytes, total)``. Note that the
|
``(downloaded bytes, total)``. Note that the
|
||||||
``total`` is the provided ``file_size``.
|
``total`` is the provided ``file_size``.
|
||||||
|
@ -2172,7 +2177,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
Decorator helper method around add_event_handler().
|
Decorator helper method around add_event_handler().
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (:obj:`_EventBuilder` | :obj:`type`):
|
event (`_EventBuilder` | `type`):
|
||||||
The event builder class or instance to be used,
|
The event builder class or instance to be used,
|
||||||
for instance ``events.NewMessage``.
|
for instance ``events.NewMessage``.
|
||||||
"""
|
"""
|
||||||
|
@ -2208,10 +2213,10 @@ class TelegramClient(TelegramBareClient):
|
||||||
Registers the given callback to be called on the specified event.
|
Registers the given callback to be called on the specified event.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
callback (:obj:`callable`):
|
callback (`callable`):
|
||||||
The callable function accepting one parameter to be used.
|
The callable function accepting one parameter to be used.
|
||||||
|
|
||||||
event (:obj:`_EventBuilder` | :obj:`type`, optional):
|
event (`_EventBuilder` | `type`, optional):
|
||||||
The event builder class or instance to be used,
|
The event builder class or instance to be used,
|
||||||
for instance ``events.NewMessage``.
|
for instance ``events.NewMessage``.
|
||||||
|
|
||||||
|
@ -2286,7 +2291,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
"""
|
"""
|
||||||
Turns the given entity into a valid Telegram user or chat.
|
Turns the given entity into a valid Telegram user or chat.
|
||||||
|
|
||||||
entity (:obj:`str` | :obj:`int` | :tl:`Peer` | :tl:`InputPeer`):
|
entity (`str` | `int` | :tl:`Peer` | :tl:`InputPeer`):
|
||||||
The entity (or iterable of entities) to be transformed.
|
The entity (or iterable of entities) to be transformed.
|
||||||
If it's a string which can be converted to an integer or starts
|
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.
|
with '+' it will be resolved as if it were a phone number.
|
||||||
|
@ -2402,7 +2407,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
use this kind of InputUser, InputChat and so on, so this is the
|
use this kind of InputUser, InputChat and so on, so this is the
|
||||||
most suitable call to make for those cases.
|
most suitable call to make for those cases.
|
||||||
|
|
||||||
entity (:obj:`str` | :obj:`int` | :tl:`Peer` | :tl:`InputPeer`):
|
entity (`str` | `int` | :tl:`Peer` | :tl:`InputPeer`):
|
||||||
The integer ID of an user or otherwise either of a
|
The integer ID of an user or otherwise either of a
|
||||||
:tl:`PeerUser`, :tl:`PeerChat` or :tl:`PeerChannel`, for
|
:tl:`PeerUser`, :tl:`PeerChat` or :tl:`PeerChannel`, for
|
||||||
which to get its ``Input*`` version.
|
which to get its ``Input*`` version.
|
||||||
|
@ -2414,6 +2419,9 @@ class TelegramClient(TelegramBareClient):
|
||||||
Returns:
|
Returns:
|
||||||
:tl:`InputPeerUser`, :tl:`InputPeerChat` or :tl:`InputPeerChannel`.
|
:tl:`InputPeerUser`, :tl:`InputPeerChat` or :tl:`InputPeerChannel`.
|
||||||
"""
|
"""
|
||||||
|
if peer in ('me', 'self'):
|
||||||
|
return InputPeerSelf()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# First try to get the entity from cache, otherwise figure it out
|
# First try to get the entity from cache, otherwise figure it out
|
||||||
return self.session.get_input_entity(peer)
|
return self.session.get_input_entity(peer)
|
||||||
|
@ -2421,8 +2429,6 @@ class TelegramClient(TelegramBareClient):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if isinstance(peer, str):
|
if isinstance(peer, str):
|
||||||
if peer in ('me', 'self'):
|
|
||||||
return InputPeerSelf()
|
|
||||||
return utils.get_input_peer(await self._get_entity_from_string(peer))
|
return utils.get_input_peer(await self._get_entity_from_string(peer))
|
||||||
|
|
||||||
original_peer = peer
|
original_peer = peer
|
||||||
|
@ -2459,4 +2465,75 @@ class TelegramClient(TelegramBareClient):
|
||||||
'Make sure you have encountered this peer before.'.format(peer)
|
'Make sure you have encountered this peer before.'.format(peer)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def edit_2fa(self, current_password=None, new_password=None, hint='',
|
||||||
|
email=None):
|
||||||
|
"""
|
||||||
|
Changes the 2FA settings of the logged in user, according to the
|
||||||
|
passed parameters. Take note of the parameter explanations.
|
||||||
|
|
||||||
|
Has no effect if both current and new password are omitted.
|
||||||
|
|
||||||
|
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. Raises ``EmailUnconfirmedError``
|
||||||
|
if value differs from current one, and has no effect if
|
||||||
|
``new_password`` is not set.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
``True`` if successful, ``False`` otherwise.
|
||||||
|
"""
|
||||||
|
if new_password is None and current_password is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
pass_result = await self(GetPasswordRequest())
|
||||||
|
if isinstance(pass_result, NoPassword) and current_password:
|
||||||
|
current_password = None
|
||||||
|
|
||||||
|
salt_random = os.urandom(8)
|
||||||
|
salt = pass_result.new_salt + salt_random
|
||||||
|
if not current_password:
|
||||||
|
current_password_hash = salt
|
||||||
|
else:
|
||||||
|
current_password = pass_result.current_salt +\
|
||||||
|
current_password.encode() + pass_result.current_salt
|
||||||
|
current_password_hash = hashlib.sha256(current_password).digest()
|
||||||
|
|
||||||
|
if new_password: # Setting new password
|
||||||
|
new_password = salt + new_password.encode('utf-8') + salt
|
||||||
|
new_password_hash = hashlib.sha256(new_password).digest()
|
||||||
|
new_settings = PasswordInputSettings(
|
||||||
|
new_salt=salt,
|
||||||
|
new_password_hash=new_password_hash,
|
||||||
|
hint=hint
|
||||||
|
)
|
||||||
|
if email: # If enabling 2FA or changing email
|
||||||
|
new_settings.email = email # TG counts empty string as None
|
||||||
|
return await self(UpdatePasswordSettingsRequest(
|
||||||
|
current_password_hash, new_settings=new_settings
|
||||||
|
))
|
||||||
|
else: # Removing existing password
|
||||||
|
return await self(UpdatePasswordSettingsRequest(
|
||||||
|
current_password_hash,
|
||||||
|
new_settings=PasswordInputSettings(
|
||||||
|
new_salt=bytes(),
|
||||||
|
new_password_hash=bytes(),
|
||||||
|
hint=hint
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from . import Draft
|
from . import Draft
|
||||||
|
from .. import TLObject
|
||||||
from ... import utils
|
from ... import utils
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ class Dialog:
|
||||||
dialog (:tl:`Dialog`):
|
dialog (:tl:`Dialog`):
|
||||||
The original ``Dialog`` instance.
|
The original ``Dialog`` instance.
|
||||||
|
|
||||||
pinned (:obj:`bool`):
|
pinned (`bool`):
|
||||||
Whether this dialog is pinned to the top or not.
|
Whether this dialog is pinned to the top or not.
|
||||||
|
|
||||||
message (:tl:`Message`):
|
message (:tl:`Message`):
|
||||||
|
@ -21,31 +22,31 @@ class Dialog:
|
||||||
will not be updated when new messages arrive, it's only set
|
will not be updated when new messages arrive, it's only set
|
||||||
on creation of the instance.
|
on creation of the instance.
|
||||||
|
|
||||||
date (:obj:`datetime`):
|
date (`datetime`):
|
||||||
The date of the last message sent on this dialog.
|
The date of the last message sent on this dialog.
|
||||||
|
|
||||||
entity (:obj:`entity`):
|
entity (`entity`):
|
||||||
The entity that belongs to this dialog (user, chat or channel).
|
The entity that belongs to this dialog (user, chat or channel).
|
||||||
|
|
||||||
input_entity (:tl:`InputPeer`):
|
input_entity (:tl:`InputPeer`):
|
||||||
Input version of the entity.
|
Input version of the entity.
|
||||||
|
|
||||||
id (:obj:`int`):
|
id (`int`):
|
||||||
The marked ID of the entity, which is guaranteed to be unique.
|
The marked ID of the entity, which is guaranteed to be unique.
|
||||||
|
|
||||||
name (:obj:`str`):
|
name (`str`):
|
||||||
Display name for this dialog. For chats and channels this is
|
Display name for this dialog. For chats and channels this is
|
||||||
their title, and for users it's "First-Name Last-Name".
|
their title, and for users it's "First-Name Last-Name".
|
||||||
|
|
||||||
unread_count (:obj:`int`):
|
unread_count (`int`):
|
||||||
How many messages are currently unread in this dialog. Note that
|
How many messages are currently unread in this dialog. Note that
|
||||||
this value won't update when new messages arrive.
|
this value won't update when new messages arrive.
|
||||||
|
|
||||||
unread_mentions_count (:obj:`int`):
|
unread_mentions_count (`int`):
|
||||||
How many mentions are currently unread in this dialog. Note that
|
How many mentions are currently unread in this dialog. Note that
|
||||||
this value won't update when new messages arrive.
|
this value won't update when new messages arrive.
|
||||||
|
|
||||||
draft (:obj:`telethon.tl.custom.draft.Draft`):
|
draft (`telethon.tl.custom.draft.Draft`):
|
||||||
The draft object in this dialog. It will not be ``None``,
|
The draft object in this dialog. It will not be ``None``,
|
||||||
so you can call ``draft.set_message(...)``.
|
so you can call ``draft.set_message(...)``.
|
||||||
"""
|
"""
|
||||||
|
@ -73,3 +74,19 @@ class Dialog:
|
||||||
``client.send_message(dialog.input_entity, *args, **kwargs)``.
|
``client.send_message(dialog.input_entity, *args, **kwargs)``.
|
||||||
"""
|
"""
|
||||||
return await self._client.send_message(self.input_entity, *args, **kwargs)
|
return await self._client.send_message(self.input_entity, *args, **kwargs)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'_': 'Dialog',
|
||||||
|
'name': self.name,
|
||||||
|
'date': self.date,
|
||||||
|
'draft': self.draft,
|
||||||
|
'message': self.message,
|
||||||
|
'entity': self.entity,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return TLObject.pretty_format(self.to_dict())
|
||||||
|
|
||||||
|
def stringify(self):
|
||||||
|
return TLObject.pretty_format(self.to_dict(), indent=0)
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from .. import TLObject
|
||||||
from ..functions.messages import SaveDraftRequest
|
from ..functions.messages import SaveDraftRequest
|
||||||
from ..types import UpdateDraftMessage, DraftMessage
|
from ..types import UpdateDraftMessage, DraftMessage
|
||||||
|
from ...errors import RPCError
|
||||||
from ...extensions import markdown
|
from ...extensions import markdown
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,13 +14,13 @@ class Draft:
|
||||||
instances of this class when calling :meth:`get_drafts()`.
|
instances of this class when calling :meth:`get_drafts()`.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
date (:obj:`datetime`):
|
date (`datetime`):
|
||||||
The date of the draft.
|
The date of the draft.
|
||||||
|
|
||||||
link_preview (:obj:`bool`):
|
link_preview (`bool`):
|
||||||
Whether the link preview is enabled or not.
|
Whether the link preview is enabled or not.
|
||||||
|
|
||||||
reply_to_msg_id (:obj:`int`):
|
reply_to_msg_id (`int`):
|
||||||
The message ID that the draft will reply to.
|
The message ID that the draft will reply to.
|
||||||
"""
|
"""
|
||||||
def __init__(self, client, peer, draft):
|
def __init__(self, client, peer, draft):
|
||||||
|
@ -142,3 +144,24 @@ class Draft:
|
||||||
Deletes this draft, and returns ``True`` on success.
|
Deletes this draft, and returns ``True`` on success.
|
||||||
"""
|
"""
|
||||||
return await self.set_message(text='')
|
return await self.set_message(text='')
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
try:
|
||||||
|
entity = self.entity
|
||||||
|
except RPCError as e:
|
||||||
|
entity = e
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_': 'Draft',
|
||||||
|
'text': self.text,
|
||||||
|
'entity': entity,
|
||||||
|
'date': self.date,
|
||||||
|
'link_preview': self.link_preview,
|
||||||
|
'reply_to_msg_id': self.reply_to_msg_id
|
||||||
|
}
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return TLObject.pretty_format(self.to_dict())
|
||||||
|
|
||||||
|
def stringify(self):
|
||||||
|
return TLObject.pretty_format(self.to_dict(), indent=0)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import logging
|
|
||||||
import pickle
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections import deque
|
import itertools
|
||||||
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from . import utils
|
||||||
from .tl import types as tl
|
from .tl import types as tl
|
||||||
|
|
||||||
__log__ = logging.getLogger(__name__)
|
__log__ = logging.getLogger(__name__)
|
||||||
|
@ -42,14 +42,20 @@ class UpdateState:
|
||||||
# After running the script for over an hour and receiving over
|
# After running the script for over an hour and receiving over
|
||||||
# 1000 updates, the only duplicates received were users going
|
# 1000 updates, the only duplicates received were users going
|
||||||
# online or offline. We can trust the server until new reports.
|
# online or offline. We can trust the server until new reports.
|
||||||
|
# This should only be used as read-only.
|
||||||
if isinstance(update, tl.UpdateShort):
|
if isinstance(update, tl.UpdateShort):
|
||||||
|
update.update._entities = {}
|
||||||
self.handle_update(update.update)
|
self.handle_update(update.update)
|
||||||
# Expand "Updates" into "Update", and pass these to callbacks.
|
# Expand "Updates" into "Update", and pass these to callbacks.
|
||||||
# Since .users and .chats have already been processed, we
|
# Since .users and .chats have already been processed, we
|
||||||
# don't need to care about those either.
|
# don't need to care about those either.
|
||||||
elif isinstance(update, (tl.Updates, tl.UpdatesCombined)):
|
elif isinstance(update, (tl.Updates, tl.UpdatesCombined)):
|
||||||
|
entities = {utils.get_peer_id(x): x for x in
|
||||||
|
itertools.chain(update.users, update.chats)}
|
||||||
for u in update.updates:
|
for u in update.updates:
|
||||||
|
u._entities = entities
|
||||||
self.handle_update(u)
|
self.handle_update(u)
|
||||||
# TODO Handle "tl.UpdatesTooLong"
|
# TODO Handle "tl.UpdatesTooLong"
|
||||||
else:
|
else:
|
||||||
|
update._entities = {}
|
||||||
self.handle_update(update)
|
self.handle_update(update)
|
||||||
|
|
|
@ -261,12 +261,22 @@ def get_input_media(media, is_photo=False):
|
||||||
ttl_seconds=media.ttl_seconds
|
ttl_seconds=media.ttl_seconds
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(media, (Photo, photos.Photo, PhotoEmpty)):
|
||||||
|
return InputMediaPhoto(
|
||||||
|
id=get_input_photo(media)
|
||||||
|
)
|
||||||
|
|
||||||
if isinstance(media, MessageMediaDocument):
|
if isinstance(media, MessageMediaDocument):
|
||||||
return InputMediaDocument(
|
return InputMediaDocument(
|
||||||
id=get_input_document(media.document),
|
id=get_input_document(media.document),
|
||||||
ttl_seconds=media.ttl_seconds
|
ttl_seconds=media.ttl_seconds
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(media, (Document, DocumentEmpty)):
|
||||||
|
return InputMediaDocument(
|
||||||
|
id=get_input_document(media)
|
||||||
|
)
|
||||||
|
|
||||||
if isinstance(media, FileLocation):
|
if isinstance(media, FileLocation):
|
||||||
if is_photo:
|
if is_photo:
|
||||||
return InputMediaUploadedPhoto(file=media)
|
return InputMediaUploadedPhoto(file=media)
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# Versions should comply with PEP440.
|
# Versions should comply with PEP440.
|
||||||
# This line is parsed in setup.py:
|
# This line is parsed in setup.py:
|
||||||
__version__ = '0.18.1'
|
__version__ = '0.18.2'
|
||||||
|
|
|
@ -11,6 +11,7 @@ known_base_classes = {
|
||||||
401: 'UnauthorizedError',
|
401: 'UnauthorizedError',
|
||||||
403: 'ForbiddenError',
|
403: 'ForbiddenError',
|
||||||
404: 'NotFoundError',
|
404: 'NotFoundError',
|
||||||
|
406: 'AuthKeyError',
|
||||||
420: 'FloodError',
|
420: 'FloodError',
|
||||||
500: 'ServerError',
|
500: 'ServerError',
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ class SourceBuilder:
|
||||||
"""
|
"""
|
||||||
self.write(' ' * (self.current_indent * self.indent_size))
|
self.write(' ' * (self.current_indent * self.indent_size))
|
||||||
|
|
||||||
def write(self, string):
|
def write(self, string, *args, **kwargs):
|
||||||
"""Writes a string into the source code,
|
"""Writes a string into the source code,
|
||||||
applying indentation if required
|
applying indentation if required
|
||||||
"""
|
"""
|
||||||
|
@ -26,13 +26,16 @@ class SourceBuilder:
|
||||||
if string.strip():
|
if string.strip():
|
||||||
self.indent()
|
self.indent()
|
||||||
|
|
||||||
self.out_stream.write(string)
|
if args or kwargs:
|
||||||
|
self.out_stream.write(string.format(*args, **kwargs))
|
||||||
|
else:
|
||||||
|
self.out_stream.write(string)
|
||||||
|
|
||||||
def writeln(self, string=''):
|
def writeln(self, string='', *args, **kwargs):
|
||||||
"""Writes a string into the source code _and_ appends a new line,
|
"""Writes a string into the source code _and_ appends a new line,
|
||||||
applying indentation if required
|
applying indentation if required
|
||||||
"""
|
"""
|
||||||
self.write(string + '\n')
|
self.write(string + '\n', *args, **kwargs)
|
||||||
self.on_new_line = True
|
self.on_new_line = True
|
||||||
|
|
||||||
# If we're writing a block, increment indent for the next time
|
# If we're writing a block, increment indent for the next time
|
||||||
|
|
|
@ -556,7 +556,7 @@ accountDaysTTL#b8d0afdf days:int = AccountDaysTTL;
|
||||||
documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute;
|
documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute;
|
||||||
documentAttributeAnimated#11b58939 = DocumentAttribute;
|
documentAttributeAnimated#11b58939 = DocumentAttribute;
|
||||||
documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute;
|
documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute;
|
||||||
documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute;
|
documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true supports_streaming:flags.1?true duration:int w:int h:int = DocumentAttribute;
|
||||||
documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
|
documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
|
||||||
documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
|
documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
|
||||||
documentAttributeHasStickers#9801d2f7 = DocumentAttribute;
|
documentAttributeHasStickers#9801d2f7 = DocumentAttribute;
|
||||||
|
@ -938,7 +938,15 @@ recentMeUrlStickerSet#bc0a57dc url:string set:StickerSetCovered = RecentMeUrl;
|
||||||
|
|
||||||
help.recentMeUrls#e0310d7 urls:Vector<RecentMeUrl> chats:Vector<Chat> users:Vector<User> = help.RecentMeUrls;
|
help.recentMeUrls#e0310d7 urls:Vector<RecentMeUrl> chats:Vector<Chat> users:Vector<User> = help.RecentMeUrls;
|
||||||
|
|
||||||
inputSingleMedia#31bc3d25 media:InputMedia flags:# random_id:long message:string entities:flags.0?Vector<MessageEntity> = InputSingleMedia;
|
inputSingleMedia#1cc6e91f flags:# media:InputMedia random_id:long message:string entities:flags.0?Vector<MessageEntity> = InputSingleMedia;
|
||||||
|
|
||||||
|
webAuthorization#cac943f2 hash:long bot_id:int domain:string browser:string platform:string date_created:int date_active:int ip:string region:string = WebAuthorization;
|
||||||
|
|
||||||
|
account.webAuthorizations#ed56c9fc authorizations:Vector<WebAuthorization> users:Vector<User> = account.WebAuthorizations;
|
||||||
|
|
||||||
|
inputMessageID#a676a322 id:int = InputMessage;
|
||||||
|
inputMessageReplyTo#bad88395 id:int = InputMessage;
|
||||||
|
inputMessagePinned#86872538 = InputMessage;
|
||||||
|
|
||||||
---functions---
|
---functions---
|
||||||
|
|
||||||
|
@ -993,6 +1001,9 @@ account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings
|
||||||
account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode;
|
account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode;
|
||||||
account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool;
|
account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool;
|
||||||
account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPassword;
|
account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPassword;
|
||||||
|
account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
|
||||||
|
account.resetWebAuthorization#2d01b9ef hash:long = Bool;
|
||||||
|
account.resetWebAuthorizations#682d2594 = Bool;
|
||||||
|
|
||||||
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
||||||
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
||||||
|
@ -1013,7 +1024,7 @@ contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.
|
||||||
contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool;
|
contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool;
|
||||||
contacts.resetSaved#879537f1 = Bool;
|
contacts.resetSaved#879537f1 = Bool;
|
||||||
|
|
||||||
messages.getMessages#4222fa74 id:Vector<int> = messages.Messages;
|
messages.getMessages#63c66506 id:Vector<InputMessage> = messages.Messages;
|
||||||
messages.getDialogs#191ba9c5 flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int = messages.Dialogs;
|
messages.getDialogs#191ba9c5 flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int = messages.Dialogs;
|
||||||
messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
|
messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
|
||||||
messages.search#39e9ea0 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
|
messages.search#39e9ea0 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
|
||||||
|
@ -1141,7 +1152,7 @@ channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
|
||||||
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
|
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
|
||||||
channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory;
|
channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory;
|
||||||
channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector<int> = Bool;
|
channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector<int> = Bool;
|
||||||
channels.getMessages#93d7b347 channel:InputChannel id:Vector<int> = messages.Messages;
|
channels.getMessages#ad8c9a23 channel:InputChannel id:Vector<InputMessage> = messages.Messages;
|
||||||
channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants;
|
channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants;
|
||||||
channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant;
|
channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant;
|
||||||
channels.getChannels#a7f6bbb id:Vector<InputChannel> = messages.Chats;
|
channels.getChannels#a7f6bbb id:Vector<InputChannel> = messages.Chats;
|
||||||
|
|
|
@ -24,9 +24,11 @@ class TLGenerator:
|
||||||
self.output_dir = output_dir
|
self.output_dir = output_dir
|
||||||
|
|
||||||
def _get_file(self, *paths):
|
def _get_file(self, *paths):
|
||||||
|
"""Wrapper around ``os.path.join()`` with output as first path."""
|
||||||
return os.path.join(self.output_dir, *paths)
|
return os.path.join(self.output_dir, *paths)
|
||||||
|
|
||||||
def _rm_if_exists(self, filename):
|
def _rm_if_exists(self, filename):
|
||||||
|
"""Recursively deletes the given filename if it exists."""
|
||||||
file = self._get_file(filename)
|
file = self._get_file(filename)
|
||||||
if os.path.exists(file):
|
if os.path.exists(file):
|
||||||
if os.path.isdir(file):
|
if os.path.isdir(file):
|
||||||
|
@ -35,19 +37,21 @@ class TLGenerator:
|
||||||
os.remove(file)
|
os.remove(file)
|
||||||
|
|
||||||
def tlobjects_exist(self):
|
def tlobjects_exist(self):
|
||||||
"""Determines whether the TLObjects were previously
|
"""
|
||||||
generated (hence exist) or not
|
Determines whether the TLObjects were previously
|
||||||
|
generated (hence exist) or not.
|
||||||
"""
|
"""
|
||||||
return os.path.isfile(self._get_file('all_tlobjects.py'))
|
return os.path.isfile(self._get_file('all_tlobjects.py'))
|
||||||
|
|
||||||
def clean_tlobjects(self):
|
def clean_tlobjects(self):
|
||||||
"""Cleans the automatically generated TLObjects from disk"""
|
"""Cleans the automatically generated TLObjects from disk."""
|
||||||
for name in ('functions', 'types', 'all_tlobjects.py'):
|
for name in ('functions', 'types', 'all_tlobjects.py'):
|
||||||
self._rm_if_exists(name)
|
self._rm_if_exists(name)
|
||||||
|
|
||||||
def generate_tlobjects(self, scheme_file, import_depth):
|
def generate_tlobjects(self, scheme_file, import_depth):
|
||||||
"""Generates all the TLObjects from scheme.tl to
|
"""
|
||||||
tl/functions and tl/types
|
Generates all the TLObjects from the ``scheme_file`` to
|
||||||
|
``tl/functions`` and ``tl/types``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# First ensure that the required parent directories exist
|
# First ensure that the required parent directories exist
|
||||||
|
@ -85,42 +89,33 @@ class TLGenerator:
|
||||||
# Step 4: Once all the objects have been generated,
|
# Step 4: Once all the objects have been generated,
|
||||||
# we can now group them in a single file
|
# we can now group them in a single file
|
||||||
filename = os.path.join(self._get_file('all_tlobjects.py'))
|
filename = os.path.join(self._get_file('all_tlobjects.py'))
|
||||||
with open(filename, 'w', encoding='utf-8') as file:
|
with open(filename, 'w', encoding='utf-8') as file,\
|
||||||
with SourceBuilder(file) as builder:
|
SourceBuilder(file) as builder:
|
||||||
builder.writeln(AUTO_GEN_NOTICE)
|
builder.writeln(AUTO_GEN_NOTICE)
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
|
|
||||||
builder.writeln('from . import types, functions')
|
builder.writeln('from . import types, functions')
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
|
|
||||||
# Create a constant variable to indicate which layer this is
|
# Create a constant variable to indicate which layer this is
|
||||||
builder.writeln('LAYER = {}'.format(
|
builder.writeln('LAYER = {}', TLParser.find_layer(scheme_file))
|
||||||
TLParser.find_layer(scheme_file))
|
builder.writeln()
|
||||||
)
|
|
||||||
builder.writeln()
|
|
||||||
|
|
||||||
# Then create the dictionary containing constructor_id: class
|
# Then create the dictionary containing constructor_id: class
|
||||||
builder.writeln('tlobjects = {')
|
builder.writeln('tlobjects = {')
|
||||||
builder.current_indent += 1
|
builder.current_indent += 1
|
||||||
|
|
||||||
# Fill the dictionary (0x1a2b3c4f: tl.full.type.path.Class)
|
# Fill the dictionary (0x1a2b3c4f: tl.full.type.path.Class)
|
||||||
for tlobject in tlobjects:
|
for tlobject in tlobjects:
|
||||||
constructor = hex(tlobject.id)
|
builder.write('{:#010x}: ', tlobject.id)
|
||||||
if len(constructor) != 10:
|
builder.write('functions' if tlobject.is_function else 'types')
|
||||||
# Make it a nice length 10 so it fits well
|
if tlobject.namespace:
|
||||||
constructor = '0x' + constructor[2:].zfill(8)
|
builder.write('.' + tlobject.namespace)
|
||||||
|
|
||||||
builder.write('{}: '.format(constructor))
|
builder.writeln('.{},', tlobject.class_name())
|
||||||
builder.write(
|
|
||||||
'functions' if tlobject.is_function else 'types')
|
|
||||||
|
|
||||||
if tlobject.namespace:
|
builder.current_indent -= 1
|
||||||
builder.write('.' + tlobject.namespace)
|
builder.writeln('}')
|
||||||
|
|
||||||
builder.writeln('.{},'.format(tlobject.class_name()))
|
|
||||||
|
|
||||||
builder.current_indent -= 1
|
|
||||||
builder.writeln('}')
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _write_init_py(out_dir, depth, namespace_tlobjects, type_constructors):
|
def _write_init_py(out_dir, depth, namespace_tlobjects, type_constructors):
|
||||||
|
@ -136,16 +131,17 @@ class TLGenerator:
|
||||||
# so they all can be serialized and sent, however, only the
|
# so they all can be serialized and sent, however, only the
|
||||||
# functions are "content_related".
|
# functions are "content_related".
|
||||||
builder.writeln(
|
builder.writeln(
|
||||||
'from {}.tl.tlobject import TLObject'.format('.' * depth)
|
'from {}.tl.tlobject import TLObject', '.' * depth
|
||||||
)
|
)
|
||||||
builder.writeln('from typing import Optional, List, Union, TYPE_CHECKING')
|
builder.writeln('from typing import Optional, List, '
|
||||||
|
'Union, TYPE_CHECKING')
|
||||||
|
|
||||||
# Add the relative imports to the namespaces,
|
# Add the relative imports to the namespaces,
|
||||||
# unless we already are in a namespace.
|
# unless we already are in a namespace.
|
||||||
if not ns:
|
if not ns:
|
||||||
builder.writeln('from . import {}'.format(', '.join(
|
builder.writeln('from . import {}', ', '.join(
|
||||||
x for x in namespace_tlobjects.keys() if x
|
x for x in namespace_tlobjects.keys() if x
|
||||||
)))
|
))
|
||||||
|
|
||||||
# Import 'os' for those needing access to 'os.urandom()'
|
# Import 'os' for those needing access to 'os.urandom()'
|
||||||
# Currently only 'random_id' needs 'os' to be imported,
|
# Currently only 'random_id' needs 'os' to be imported,
|
||||||
|
@ -204,18 +200,18 @@ class TLGenerator:
|
||||||
if name == 'date':
|
if name == 'date':
|
||||||
imports['datetime'] = ['datetime']
|
imports['datetime'] = ['datetime']
|
||||||
continue
|
continue
|
||||||
elif not import_space in imports:
|
elif import_space not in imports:
|
||||||
imports[import_space] = set()
|
imports[import_space] = set()
|
||||||
imports[import_space].add('Type{}'.format(name))
|
imports[import_space].add('Type{}'.format(name))
|
||||||
|
|
||||||
# Add imports required for type checking.
|
# Add imports required for type checking
|
||||||
builder.writeln('if TYPE_CHECKING:')
|
if imports:
|
||||||
for namespace, names in imports.items():
|
builder.writeln('if TYPE_CHECKING:')
|
||||||
builder.writeln('from {} import {}'.format(
|
for namespace, names in imports.items():
|
||||||
namespace, ', '.join(names)))
|
builder.writeln('from {} import {}',
|
||||||
else:
|
namespace, ', '.join(names))
|
||||||
builder.writeln('pass')
|
|
||||||
builder.end_block()
|
builder.end_block()
|
||||||
|
|
||||||
# Generate the class for every TLObject
|
# Generate the class for every TLObject
|
||||||
for t in tlobjects:
|
for t in tlobjects:
|
||||||
|
@ -229,25 +225,24 @@ class TLGenerator:
|
||||||
for line in type_defs:
|
for line in type_defs:
|
||||||
builder.writeln(line)
|
builder.writeln(line)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _write_source_code(tlobject, builder, depth, type_constructors):
|
def _write_source_code(tlobject, builder, depth, type_constructors):
|
||||||
"""Writes the source code corresponding to the given TLObject
|
"""
|
||||||
by making use of the 'builder' SourceBuilder.
|
Writes the source code corresponding to the given TLObject
|
||||||
|
by making use of the ``builder`` `SourceBuilder`.
|
||||||
|
|
||||||
Additional information such as file path depth and
|
Additional information such as file path depth and
|
||||||
the Type: [Constructors] must be given for proper
|
the ``Type: [Constructors]`` must be given for proper
|
||||||
importing and documentation strings.
|
importing and documentation strings.
|
||||||
"""
|
"""
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
builder.writeln('class {}(TLObject):'.format(tlobject.class_name()))
|
builder.writeln('class {}(TLObject):', tlobject.class_name())
|
||||||
|
|
||||||
# Class-level variable to store its Telegram's constructor ID
|
# Class-level variable to store its Telegram's constructor ID
|
||||||
builder.writeln('CONSTRUCTOR_ID = {}'.format(hex(tlobject.id)))
|
builder.writeln('CONSTRUCTOR_ID = {:#x}', tlobject.id)
|
||||||
builder.writeln('SUBCLASS_OF_ID = {}'.format(
|
builder.writeln('SUBCLASS_OF_ID = {:#x}',
|
||||||
hex(crc32(tlobject.result.encode('ascii'))))
|
crc32(tlobject.result.encode('ascii')))
|
||||||
)
|
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
|
|
||||||
# Flag arguments must go last
|
# Flag arguments must go last
|
||||||
|
@ -265,9 +260,7 @@ class TLGenerator:
|
||||||
|
|
||||||
# Write the __init__ function
|
# Write the __init__ function
|
||||||
if args:
|
if args:
|
||||||
builder.writeln(
|
builder.writeln('def __init__(self, {}):', ', '.join(args))
|
||||||
'def __init__(self, {}):'.format(', '.join(args))
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
builder.writeln('def __init__(self):')
|
builder.writeln('def __init__(self):')
|
||||||
|
|
||||||
|
@ -286,30 +279,27 @@ class TLGenerator:
|
||||||
builder.writeln('"""')
|
builder.writeln('"""')
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if not arg.flag_indicator:
|
if not arg.flag_indicator:
|
||||||
builder.writeln(':param {} {}:'.format(
|
builder.writeln(':param {} {}:',
|
||||||
arg.doc_type_hint(), arg.name
|
arg.doc_type_hint(), arg.name)
|
||||||
))
|
|
||||||
builder.current_indent -= 1 # It will auto-indent (':')
|
builder.current_indent -= 1 # It will auto-indent (':')
|
||||||
|
|
||||||
# We also want to know what type this request returns
|
# We also want to know what type this request returns
|
||||||
# or to which type this constructor belongs to
|
# or to which type this constructor belongs to
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
if tlobject.is_function:
|
if tlobject.is_function:
|
||||||
builder.write(':returns {}: '.format(tlobject.result))
|
builder.write(':returns {}: ', tlobject.result)
|
||||||
else:
|
else:
|
||||||
builder.write('Constructor for {}: '.format(tlobject.result))
|
builder.write('Constructor for {}: ', tlobject.result)
|
||||||
|
|
||||||
constructors = type_constructors[tlobject.result]
|
constructors = type_constructors[tlobject.result]
|
||||||
if not constructors:
|
if not constructors:
|
||||||
builder.writeln('This type has no constructors.')
|
builder.writeln('This type has no constructors.')
|
||||||
elif len(constructors) == 1:
|
elif len(constructors) == 1:
|
||||||
builder.writeln('Instance of {}.'.format(
|
builder.writeln('Instance of {}.',
|
||||||
constructors[0].class_name()
|
constructors[0].class_name())
|
||||||
))
|
|
||||||
else:
|
else:
|
||||||
builder.writeln('Instance of either {}.'.format(
|
builder.writeln('Instance of either {}.', ', '.join(
|
||||||
', '.join(c.class_name() for c in constructors)
|
c.class_name() for c in constructors))
|
||||||
))
|
|
||||||
|
|
||||||
builder.writeln('"""')
|
builder.writeln('"""')
|
||||||
|
|
||||||
|
@ -327,8 +317,8 @@ class TLGenerator:
|
||||||
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if not arg.can_be_inferred:
|
if not arg.can_be_inferred:
|
||||||
builder.writeln('self.{0} = {0} # type: {1}'.format(
|
builder.writeln('self.{0} = {0} # type: {1}',
|
||||||
arg.name, arg.python_type_hint()))
|
arg.name, arg.python_type_hint())
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Currently the only argument that can be
|
# Currently the only argument that can be
|
||||||
|
@ -350,7 +340,7 @@ class TLGenerator:
|
||||||
|
|
||||||
builder.writeln(
|
builder.writeln(
|
||||||
"self.random_id = random_id if random_id "
|
"self.random_id = random_id if random_id "
|
||||||
"is not None else {}".format(code)
|
"is not None else {}", code
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Cannot infer a value for ', arg)
|
raise ValueError('Cannot infer a value for ', arg)
|
||||||
|
@ -374,27 +364,27 @@ class TLGenerator:
|
||||||
base_types = ('string', 'bytes', 'int', 'long', 'int128',
|
base_types = ('string', 'bytes', 'int', 'long', 'int128',
|
||||||
'int256', 'double', 'Bool', 'true', 'date')
|
'int256', 'double', 'Bool', 'true', 'date')
|
||||||
|
|
||||||
builder.write("'_': '{}'".format(tlobject.class_name()))
|
builder.write("'_': '{}'", tlobject.class_name())
|
||||||
for arg in args:
|
for arg in args:
|
||||||
builder.writeln(',')
|
builder.writeln(',')
|
||||||
builder.write("'{}': ".format(arg.name))
|
builder.write("'{}': ", arg.name)
|
||||||
if arg.type in base_types:
|
if arg.type in base_types:
|
||||||
if arg.is_vector:
|
if arg.is_vector:
|
||||||
builder.write('[] if self.{0} is None else self.{0}[:]'
|
builder.write('[] if self.{0} is None else self.{0}[:]',
|
||||||
.format(arg.name))
|
arg.name)
|
||||||
else:
|
else:
|
||||||
builder.write('self.{}'.format(arg.name))
|
builder.write('self.{}', arg.name)
|
||||||
else:
|
else:
|
||||||
if arg.is_vector:
|
if arg.is_vector:
|
||||||
builder.write(
|
builder.write(
|
||||||
'[] if self.{0} is None else [None '
|
'[] if self.{0} is None else [None '
|
||||||
'if x is None else x.to_dict() for x in self.{0}]'
|
'if x is None else x.to_dict() for x in self.{0}]',
|
||||||
.format(arg.name)
|
arg.name
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
builder.write(
|
builder.write(
|
||||||
'None if self.{0} is None else self.{0}.to_dict()'
|
'None if self.{0} is None else self.{0}.to_dict()',
|
||||||
.format(arg.name)
|
arg.name
|
||||||
)
|
)
|
||||||
|
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
|
@ -421,17 +411,16 @@ class TLGenerator:
|
||||||
.format(a.name) for a in ra)
|
.format(a.name) for a in ra)
|
||||||
builder.writeln(
|
builder.writeln(
|
||||||
"assert ({}) or ({}), '{} parameters must all "
|
"assert ({}) or ({}), '{} parameters must all "
|
||||||
"be False-y (like None) or all me True-y'".format(
|
"be False-y (like None) or all me True-y'",
|
||||||
' and '.join(cnd1), ' and '.join(cnd2),
|
' and '.join(cnd1), ' and '.join(cnd2),
|
||||||
', '.join(a.name for a in ra)
|
', '.join(a.name for a in ra)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
builder.writeln("return b''.join((")
|
builder.writeln("return b''.join((")
|
||||||
builder.current_indent += 1
|
builder.current_indent += 1
|
||||||
|
|
||||||
# First constructor code, we already know its bytes
|
# First constructor code, we already know its bytes
|
||||||
builder.writeln('{},'.format(repr(struct.pack('<I', tlobject.id))))
|
builder.writeln('{},', repr(struct.pack('<I', tlobject.id)))
|
||||||
|
|
||||||
for arg in tlobject.args:
|
for arg in tlobject.args:
|
||||||
if TLGenerator.write_to_bytes(builder, arg, tlobject.args):
|
if TLGenerator.write_to_bytes(builder, arg, tlobject.args):
|
||||||
|
@ -449,12 +438,14 @@ class TLGenerator:
|
||||||
builder, arg, tlobject.args, name='_' + arg.name
|
builder, arg, tlobject.args, name='_' + arg.name
|
||||||
)
|
)
|
||||||
|
|
||||||
builder.writeln('return {}({})'.format(
|
builder.writeln(
|
||||||
tlobject.class_name(), ', '.join(
|
'return {}({})',
|
||||||
|
tlobject.class_name(),
|
||||||
|
', '.join(
|
||||||
'{0}=_{0}'.format(a.name) for a in tlobject.sorted_args()
|
'{0}=_{0}'.format(a.name) for a in tlobject.sorted_args()
|
||||||
if not a.flag_indicator and not a.generic_definition
|
if not a.flag_indicator and not a.generic_definition
|
||||||
)
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
# Only requests can have a different response that's not their
|
# Only requests can have a different response that's not their
|
||||||
# serialized body, that is, we'll be setting their .result.
|
# serialized body, that is, we'll be setting their .result.
|
||||||
|
@ -482,13 +473,13 @@ class TLGenerator:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _write_self_assign(builder, arg, get_input_code):
|
def _write_self_assign(builder, arg, get_input_code):
|
||||||
"""Writes self.arg = input.format(self.arg), considering vectors"""
|
"""Writes self.arg = input.format(self.arg), considering vectors."""
|
||||||
if arg.is_vector:
|
if arg.is_vector:
|
||||||
builder.write('self.{0} = [{1} for _x in self.{0}]'
|
builder.write('self.{0} = [{1} for _x in self.{0}]',
|
||||||
.format(arg.name, get_input_code.format('_x')))
|
arg.name, get_input_code.format('_x'))
|
||||||
else:
|
else:
|
||||||
builder.write('self.{} = {}'.format(
|
builder.write('self.{} = {}',
|
||||||
arg.name, get_input_code.format('self.' + arg.name)))
|
arg.name, get_input_code.format('self.' + arg.name))
|
||||||
|
|
||||||
builder.writeln(
|
builder.writeln(
|
||||||
' if self.{} else None'.format(arg.name) if arg.is_flag else ''
|
' if self.{} else None'.format(arg.name) if arg.is_flag else ''
|
||||||
|
@ -536,17 +527,17 @@ class TLGenerator:
|
||||||
# so we need an extra join here. Note that empty vector flags
|
# so we need an extra join here. Note that empty vector flags
|
||||||
# should NOT be sent either!
|
# should NOT be sent either!
|
||||||
builder.write("b'' if {0} is None or {0} is False "
|
builder.write("b'' if {0} is None or {0} is False "
|
||||||
"else b''.join((".format(name))
|
"else b''.join((", name)
|
||||||
else:
|
else:
|
||||||
builder.write("b'' if {0} is None or {0} is False "
|
builder.write("b'' if {0} is None or {0} is False "
|
||||||
"else (".format(name))
|
"else (", name)
|
||||||
|
|
||||||
if arg.is_vector:
|
if arg.is_vector:
|
||||||
if arg.use_vector_id:
|
if arg.use_vector_id:
|
||||||
# vector code, unsigned 0x1cb5c415 as little endian
|
# vector code, unsigned 0x1cb5c415 as little endian
|
||||||
builder.write(r"b'\x15\xc4\xb5\x1c',")
|
builder.write(r"b'\x15\xc4\xb5\x1c',")
|
||||||
|
|
||||||
builder.write("struct.pack('<i', len({})),".format(name))
|
builder.write("struct.pack('<i', len({})),", name)
|
||||||
|
|
||||||
# Cannot unpack the values for the outer tuple through *[(
|
# Cannot unpack the values for the outer tuple through *[(
|
||||||
# since that's a Python >3.5 feature, so add another join.
|
# since that's a Python >3.5 feature, so add another join.
|
||||||
|
@ -560,7 +551,7 @@ class TLGenerator:
|
||||||
arg.is_vector = True
|
arg.is_vector = True
|
||||||
arg.is_flag = old_flag
|
arg.is_flag = old_flag
|
||||||
|
|
||||||
builder.write(' for x in {})'.format(name))
|
builder.write(' for x in {})', name)
|
||||||
|
|
||||||
elif arg.flag_indicator:
|
elif arg.flag_indicator:
|
||||||
# Calculate the flags with those items which are not None
|
# Calculate the flags with those items which are not None
|
||||||
|
@ -579,41 +570,39 @@ class TLGenerator:
|
||||||
|
|
||||||
elif 'int' == arg.type:
|
elif 'int' == arg.type:
|
||||||
# struct.pack is around 4 times faster than int.to_bytes
|
# struct.pack is around 4 times faster than int.to_bytes
|
||||||
builder.write("struct.pack('<i', {})".format(name))
|
builder.write("struct.pack('<i', {})", name)
|
||||||
|
|
||||||
elif 'long' == arg.type:
|
elif 'long' == arg.type:
|
||||||
builder.write("struct.pack('<q', {})".format(name))
|
builder.write("struct.pack('<q', {})", name)
|
||||||
|
|
||||||
elif 'int128' == arg.type:
|
elif 'int128' == arg.type:
|
||||||
builder.write("{}.to_bytes(16, 'little', signed=True)".format(name))
|
builder.write("{}.to_bytes(16, 'little', signed=True)", name)
|
||||||
|
|
||||||
elif 'int256' == arg.type:
|
elif 'int256' == arg.type:
|
||||||
builder.write("{}.to_bytes(32, 'little', signed=True)".format(name))
|
builder.write("{}.to_bytes(32, 'little', signed=True)", name)
|
||||||
|
|
||||||
elif 'double' == arg.type:
|
elif 'double' == arg.type:
|
||||||
builder.write("struct.pack('<d', {})".format(name))
|
builder.write("struct.pack('<d', {})", name)
|
||||||
|
|
||||||
elif 'string' == arg.type:
|
elif 'string' == arg.type:
|
||||||
builder.write('TLObject.serialize_bytes({})'.format(name))
|
builder.write('TLObject.serialize_bytes({})', name)
|
||||||
|
|
||||||
elif 'Bool' == arg.type:
|
elif 'Bool' == arg.type:
|
||||||
# 0x997275b5 if boolean else 0xbc799737
|
# 0x997275b5 if boolean else 0xbc799737
|
||||||
builder.write(
|
builder.write(r"b'\xb5ur\x99' if {} else b'7\x97y\xbc'", name)
|
||||||
r"b'\xb5ur\x99' if {} else b'7\x97y\xbc'".format(name)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif 'true' == arg.type:
|
elif 'true' == arg.type:
|
||||||
pass # These are actually NOT written! Only used for flags
|
pass # These are actually NOT written! Only used for flags
|
||||||
|
|
||||||
elif 'bytes' == arg.type:
|
elif 'bytes' == arg.type:
|
||||||
builder.write('TLObject.serialize_bytes({})'.format(name))
|
builder.write('TLObject.serialize_bytes({})', name)
|
||||||
|
|
||||||
elif 'date' == arg.type: # Custom format
|
elif 'date' == arg.type: # Custom format
|
||||||
builder.write('TLObject.serialize_datetime({})'.format(name))
|
builder.write('TLObject.serialize_datetime({})', name)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Else it may be a custom type
|
# Else it may be a custom type
|
||||||
builder.write('bytes({})'.format(name))
|
builder.write('bytes({})', name)
|
||||||
|
|
||||||
if arg.is_flag:
|
if arg.is_flag:
|
||||||
builder.write(')')
|
builder.write(')')
|
||||||
|
@ -646,15 +635,12 @@ class TLGenerator:
|
||||||
# Treat 'true' flags as a special case, since they're true if
|
# Treat 'true' flags as a special case, since they're true if
|
||||||
# they're set, and nothing else needs to actually be read.
|
# they're set, and nothing else needs to actually be read.
|
||||||
if 'true' == arg.type:
|
if 'true' == arg.type:
|
||||||
builder.writeln(
|
builder.writeln('{} = bool(flags & {})',
|
||||||
'{} = bool(flags & {})'.format(name, 1 << arg.flag_index)
|
name, 1 << arg.flag_index)
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
was_flag = True
|
was_flag = True
|
||||||
builder.writeln('if flags & {}:'.format(
|
builder.writeln('if flags & {}:', 1 << arg.flag_index)
|
||||||
1 << arg.flag_index
|
|
||||||
))
|
|
||||||
# Temporary disable .is_flag not to enter this if
|
# Temporary disable .is_flag not to enter this if
|
||||||
# again when calling the method recursively
|
# again when calling the method recursively
|
||||||
arg.is_flag = False
|
arg.is_flag = False
|
||||||
|
@ -664,12 +650,12 @@ class TLGenerator:
|
||||||
# We have to read the vector's constructor ID
|
# We have to read the vector's constructor ID
|
||||||
builder.writeln("reader.read_int()")
|
builder.writeln("reader.read_int()")
|
||||||
|
|
||||||
builder.writeln('{} = []'.format(name))
|
builder.writeln('{} = []', name)
|
||||||
builder.writeln('for _ in range(reader.read_int()):')
|
builder.writeln('for _ in range(reader.read_int()):')
|
||||||
# Temporary disable .is_vector, not to enter this if again
|
# Temporary disable .is_vector, not to enter this if again
|
||||||
arg.is_vector = False
|
arg.is_vector = False
|
||||||
TLGenerator.write_read_code(builder, arg, args, name='_x')
|
TLGenerator.write_read_code(builder, arg, args, name='_x')
|
||||||
builder.writeln('{}.append(_x)'.format(name))
|
builder.writeln('{}.append(_x)', name)
|
||||||
arg.is_vector = True
|
arg.is_vector = True
|
||||||
|
|
||||||
elif arg.flag_indicator:
|
elif arg.flag_indicator:
|
||||||
|
@ -678,44 +664,40 @@ class TLGenerator:
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
|
|
||||||
elif 'int' == arg.type:
|
elif 'int' == arg.type:
|
||||||
builder.writeln('{} = reader.read_int()'.format(name))
|
builder.writeln('{} = reader.read_int()', name)
|
||||||
|
|
||||||
elif 'long' == arg.type:
|
elif 'long' == arg.type:
|
||||||
builder.writeln('{} = reader.read_long()'.format(name))
|
builder.writeln('{} = reader.read_long()', name)
|
||||||
|
|
||||||
elif 'int128' == arg.type:
|
elif 'int128' == arg.type:
|
||||||
builder.writeln(
|
builder.writeln('{} = reader.read_large_int(bits=128)', name)
|
||||||
'{} = reader.read_large_int(bits=128)'.format(name)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif 'int256' == arg.type:
|
elif 'int256' == arg.type:
|
||||||
builder.writeln(
|
builder.writeln('{} = reader.read_large_int(bits=256)', name)
|
||||||
'{} = reader.read_large_int(bits=256)'.format(name)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif 'double' == arg.type:
|
elif 'double' == arg.type:
|
||||||
builder.writeln('{} = reader.read_double()'.format(name))
|
builder.writeln('{} = reader.read_double()', name)
|
||||||
|
|
||||||
elif 'string' == arg.type:
|
elif 'string' == arg.type:
|
||||||
builder.writeln('{} = reader.tgread_string()'.format(name))
|
builder.writeln('{} = reader.tgread_string()', name)
|
||||||
|
|
||||||
elif 'Bool' == arg.type:
|
elif 'Bool' == arg.type:
|
||||||
builder.writeln('{} = reader.tgread_bool()'.format(name))
|
builder.writeln('{} = reader.tgread_bool()', name)
|
||||||
|
|
||||||
elif 'true' == arg.type:
|
elif 'true' == arg.type:
|
||||||
# Arbitrary not-None value, don't actually read "true" flags
|
# Arbitrary not-None value, don't actually read "true" flags
|
||||||
builder.writeln('{} = True'.format(name))
|
builder.writeln('{} = True', name)
|
||||||
|
|
||||||
elif 'bytes' == arg.type:
|
elif 'bytes' == arg.type:
|
||||||
builder.writeln('{} = reader.tgread_bytes()'.format(name))
|
builder.writeln('{} = reader.tgread_bytes()', name)
|
||||||
|
|
||||||
elif 'date' == arg.type: # Custom format
|
elif 'date' == arg.type: # Custom format
|
||||||
builder.writeln('{} = reader.tgread_date()'.format(name))
|
builder.writeln('{} = reader.tgread_date()', name)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Else it may be a custom type
|
# Else it may be a custom type
|
||||||
if not arg.skip_constructor_id:
|
if not arg.skip_constructor_id:
|
||||||
builder.writeln('{} = reader.tgread_object()'.format(name))
|
builder.writeln('{} = reader.tgread_object()', name)
|
||||||
else:
|
else:
|
||||||
# Import the correct type inline to avoid cyclic imports.
|
# Import the correct type inline to avoid cyclic imports.
|
||||||
# There may be better solutions so that we can just access
|
# There may be better solutions so that we can just access
|
||||||
|
@ -732,10 +714,9 @@ class TLGenerator:
|
||||||
# file with the same namespace, but since it does no harm
|
# file with the same namespace, but since it does no harm
|
||||||
# and we don't have information about such thing in the
|
# and we don't have information about such thing in the
|
||||||
# method we just ignore that case.
|
# method we just ignore that case.
|
||||||
builder.writeln('from {} import {}'.format(ns, class_name))
|
builder.writeln('from {} import {}', ns, class_name)
|
||||||
builder.writeln('{} = {}.from_reader(reader)'.format(
|
builder.writeln('{} = {}.from_reader(reader)',
|
||||||
name, class_name
|
name, class_name)
|
||||||
))
|
|
||||||
|
|
||||||
# End vector and flag blocks if required (if we opened them before)
|
# End vector and flag blocks if required (if we opened them before)
|
||||||
if arg.is_vector:
|
if arg.is_vector:
|
||||||
|
@ -744,7 +725,7 @@ class TLGenerator:
|
||||||
if was_flag:
|
if was_flag:
|
||||||
builder.current_indent -= 1
|
builder.current_indent -= 1
|
||||||
builder.writeln('else:')
|
builder.writeln('else:')
|
||||||
builder.writeln('{} = None'.format(name))
|
builder.writeln('{} = None', name)
|
||||||
builder.current_indent -= 1
|
builder.current_indent -= 1
|
||||||
# Restore .is_flag
|
# Restore .is_flag
|
||||||
arg.is_flag = True
|
arg.is_flag = True
|
||||||
|
|
Loading…
Reference in New Issue
Block a user