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
69970b5b20
|
@ -112,6 +112,15 @@ as you wish. Remember to use the right types! To sum up:
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
This can further be simplified to:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
result = client(SendMessageRequest('username', 'Hello there!'))
|
||||||
|
# Or even
|
||||||
|
result = client(SendMessageRequest(PeerChannel(id), 'Hello there!'))
|
||||||
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Note that some requests have a "hash" parameter. This is **not**
|
Note that some requests have a "hash" parameter. This is **not**
|
||||||
|
|
|
@ -37,12 +37,24 @@ you're able to just do this:
|
||||||
# Using Peer/InputPeer (note that the API may return these)
|
# Using Peer/InputPeer (note that the API may return these)
|
||||||
# users, chats and channels may all have the same ID, so it's
|
# users, chats and channels may all have the same ID, so it's
|
||||||
# necessary to wrap (at least) chat and channels inside Peer.
|
# necessary to wrap (at least) chat and channels inside Peer.
|
||||||
|
#
|
||||||
|
# NOTICE how the IDs *must* be wrapped inside a Peer() so the
|
||||||
|
# library knows their type.
|
||||||
from telethon.tl.types import PeerUser, PeerChat, PeerChannel
|
from telethon.tl.types import PeerUser, PeerChat, PeerChannel
|
||||||
my_user = client.get_entity(PeerUser(some_id))
|
my_user = client.get_entity(PeerUser(some_id))
|
||||||
my_chat = client.get_entity(PeerChat(some_id))
|
my_chat = client.get_entity(PeerChat(some_id))
|
||||||
my_channel = client.get_entity(PeerChannel(some_id))
|
my_channel = 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!')``
|
||||||
|
|
|
@ -99,6 +99,10 @@ done! The event that will be passed always is of type ``XYZ.Event`` (for
|
||||||
instance, ``NewMessage.Event``), except for the ``Raw`` event which just
|
instance, ``NewMessage.Event``), except for the ``Raw`` event which just
|
||||||
passes the ``Update`` object.
|
passes the ``Update`` object.
|
||||||
|
|
||||||
|
Note that ``.reply()`` and ``.respond()`` are just wrappers around the
|
||||||
|
``client.send_message()`` method which supports the ``file=`` parameter.
|
||||||
|
This means you can reply with a photo if you do ``client.reply(file=photo)``.
|
||||||
|
|
||||||
You can put the same event on many handlers, and even different events on
|
You can put the same event on many handlers, and even different events on
|
||||||
the same handler. You can also have a handler work on only specific chats,
|
the same handler. You can also have a handler work on only specific chats,
|
||||||
for example:
|
for example:
|
||||||
|
|
|
@ -14,6 +14,71 @@ it can take advantage of new goodies!
|
||||||
.. contents:: List of All Versions
|
.. contents:: List of All Versions
|
||||||
|
|
||||||
|
|
||||||
|
Sessions overhaul (v0.18)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
*Published at 2018/03/04*
|
||||||
|
|
||||||
|
+-----------------------+
|
||||||
|
| Scheme layer used: 75 |
|
||||||
|
+-----------------------+
|
||||||
|
|
||||||
|
The ``Session``'s have been revisited thanks to the work of **@tulir** and
|
||||||
|
they now use an `ABC <https://docs.python.org/3/library/abc.html>`__ so you
|
||||||
|
can easily implement your own!
|
||||||
|
|
||||||
|
The default will still be a ``SQLiteSession``, but you might want to use
|
||||||
|
the new ``AlchemySessionContainer`` if you need. Refer to the section of
|
||||||
|
the documentation on :ref:`sessions` for more.
|
||||||
|
|
||||||
|
Breaking changes
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- ``events.MessageChanged`` doesn't exist anymore. Use the new
|
||||||
|
``events.MessageEdited`` and ``events.MessageDeleted`` instead.
|
||||||
|
|
||||||
|
Additions
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- The mentioned addition of new session types.
|
||||||
|
- You can omit the event type on ``client.add_event_handler`` to use ``Raw``.
|
||||||
|
- You can ``raise StopPropagation`` of events if you added several of them.
|
||||||
|
- ``.get_participants()`` can now get up to 90,000 members from groups with
|
||||||
|
100,000 if when ``aggressive=True``, "bypassing" Telegram's limit.
|
||||||
|
- You now can access ``NewMessage.Event.pattern_match``.
|
||||||
|
- Multiple captions are now supported when sending albums.
|
||||||
|
- ``client.send_message()`` has an optional ``file=`` parameter, so
|
||||||
|
you can do ``events.reply(file='/path/to/photo.jpg')`` and similar.
|
||||||
|
- Added ``.input_`` versions to ``events.ChatAction``.
|
||||||
|
- You can now access the public ``.client`` property on ``events``.
|
||||||
|
- New ``client.forward_messages``, with its own wrapper on ``events``,
|
||||||
|
called ``event.forward_to(...)``.
|
||||||
|
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- Silly bug regarding ``client.get_me(input_peer=True)``.
|
||||||
|
- ``client.send_voice_note()`` was missing some parameters.
|
||||||
|
- ``client.send_file()`` plays better with streams now.
|
||||||
|
- Incoming messages from bots weren't working with whitelists.
|
||||||
|
- Markdown's URL regex was not accepting newlines.
|
||||||
|
- Better attempt at joining background update threads.
|
||||||
|
- Use the right peer type when a marked integer ID is provided.
|
||||||
|
|
||||||
|
|
||||||
|
Internal changes
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Resolving ``events.Raw`` is now a no-op.
|
||||||
|
- Logging calls in the ``TcpClient`` to spot errors.
|
||||||
|
- ``events`` resolution is postponed until you are successfully connected,
|
||||||
|
so you can attach them before starting the client.
|
||||||
|
- When an entity is not found, it is searched in *all* dialogs. This might
|
||||||
|
not always be desirable but it's more comfortable for legitimate uses.
|
||||||
|
- Some non-persisting properties from the ``Session`` have been moved out.
|
||||||
|
|
||||||
|
|
||||||
Further easing library usage (v0.17.4)
|
Further easing library usage (v0.17.4)
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
|
|
6
setup.py
6
setup.py
|
@ -148,7 +148,11 @@ def main():
|
||||||
keywords='telegram api chat client library messaging mtproto',
|
keywords='telegram api chat client library messaging mtproto',
|
||||||
packages=find_packages(exclude=[
|
packages=find_packages(exclude=[
|
||||||
'telethon_generator', 'telethon_tests', 'run_tests.py',
|
'telethon_generator', 'telethon_tests', 'run_tests.py',
|
||||||
'try_telethon.py'
|
'try_telethon.py',
|
||||||
|
'telethon_generator/parser/__init__.py',
|
||||||
|
'telethon_generator/parser/source_builder.py',
|
||||||
|
'telethon_generator/parser/tl_object.py',
|
||||||
|
'telethon_generator/parser/tl_parser.py',
|
||||||
]),
|
]),
|
||||||
install_requires=['pyaes', 'rsa'],
|
install_requires=['pyaes', 'rsa'],
|
||||||
extras_require={
|
extras_require={
|
||||||
|
|
|
@ -140,6 +140,9 @@ class _EventCommon(abc.ABC):
|
||||||
)
|
)
|
||||||
return self._input_chat
|
return self._input_chat
|
||||||
|
|
||||||
|
def client(self):
|
||||||
|
return self._client
|
||||||
|
|
||||||
@property
|
@property
|
||||||
async def chat(self):
|
async def chat(self):
|
||||||
"""
|
"""
|
||||||
|
@ -316,10 +319,19 @@ class NewMessage(_EventBuilder):
|
||||||
Replies to the message (as a reply). This is a shorthand for
|
Replies to the message (as a reply). This is a shorthand for
|
||||||
``client.send_message(event.chat, ..., reply_to=event.message.id)``.
|
``client.send_message(event.chat, ..., reply_to=event.message.id)``.
|
||||||
"""
|
"""
|
||||||
return await self._client.send_message(await self.input_chat,
|
kwargs['reply_to'] = self.message.id
|
||||||
reply_to=self.message.id,
|
return await self._client.send_message(self.input_chat,
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
|
async def forward_to(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Forwards the message. This is a shorthand for
|
||||||
|
``client.forward_messages(entity, event.message, event.chat)``.
|
||||||
|
"""
|
||||||
|
kwargs['messages'] = [self.message.id]
|
||||||
|
kwargs['from_peer'] = self.input_chat
|
||||||
|
return await self._client.forward_messages(*args, **kwargs)
|
||||||
|
|
||||||
async def edit(self, *args, **kwargs):
|
async def edit(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Edits the message iff it's outgoing. This is a shorthand for
|
Edits the message iff it's outgoing. This is a shorthand for
|
||||||
|
@ -525,15 +537,19 @@ class ChatAction(_EventBuilder):
|
||||||
elif isinstance(action, types.MessageActionChannelCreate):
|
elif isinstance(action, types.MessageActionChannelCreate):
|
||||||
event = ChatAction.Event(msg.to_id,
|
event = ChatAction.Event(msg.to_id,
|
||||||
created=True,
|
created=True,
|
||||||
|
users=msg.from_id,
|
||||||
new_title=action.title)
|
new_title=action.title)
|
||||||
elif isinstance(action, types.MessageActionChatEditTitle):
|
elif isinstance(action, types.MessageActionChatEditTitle):
|
||||||
event = ChatAction.Event(msg.to_id,
|
event = ChatAction.Event(msg.to_id,
|
||||||
|
users=msg.from_id,
|
||||||
new_title=action.title)
|
new_title=action.title)
|
||||||
elif isinstance(action, types.MessageActionChatEditPhoto):
|
elif isinstance(action, types.MessageActionChatEditPhoto):
|
||||||
event = ChatAction.Event(msg.to_id,
|
event = ChatAction.Event(msg.to_id,
|
||||||
|
users=msg.from_id,
|
||||||
new_photo=action.photo)
|
new_photo=action.photo)
|
||||||
elif isinstance(action, types.MessageActionChatDeletePhoto):
|
elif isinstance(action, types.MessageActionChatDeletePhoto):
|
||||||
event = ChatAction.Event(msg.to_id,
|
event = ChatAction.Event(msg.to_id,
|
||||||
|
users=msg.from_id,
|
||||||
new_photo=True)
|
new_photo=True)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
@ -607,6 +623,7 @@ class ChatAction(_EventBuilder):
|
||||||
self.created = bool(created)
|
self.created = bool(created)
|
||||||
self._user_peers = users if isinstance(users, list) else [users]
|
self._user_peers = users if isinstance(users, list) else [users]
|
||||||
self._users = None
|
self._users = None
|
||||||
|
self._input_users = None
|
||||||
self.new_title = new_title
|
self.new_title = new_title
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -660,10 +677,16 @@ class ChatAction(_EventBuilder):
|
||||||
Might be ``None`` if the information can't be retrieved or
|
Might be ``None`` if the information can't be retrieved or
|
||||||
there is no user taking part.
|
there is no user taking part.
|
||||||
"""
|
"""
|
||||||
try:
|
if await self.users:
|
||||||
return next(await self.users)
|
return self._users[0]
|
||||||
except (StopIteration, TypeError):
|
|
||||||
return None
|
@property
|
||||||
|
async def input_user(self):
|
||||||
|
"""
|
||||||
|
Input version of the self.user property.
|
||||||
|
"""
|
||||||
|
if await self.input_users:
|
||||||
|
return self._input_users[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
async def users(self):
|
async def users(self):
|
||||||
|
@ -681,6 +704,22 @@ class ChatAction(_EventBuilder):
|
||||||
|
|
||||||
return self._users
|
return self._users
|
||||||
|
|
||||||
|
@property
|
||||||
|
async def input_users(self):
|
||||||
|
"""
|
||||||
|
Input version of the self.users property.
|
||||||
|
"""
|
||||||
|
if self._input_users is None and self._user_peers:
|
||||||
|
self._input_users = []
|
||||||
|
for peer in self._user_peers:
|
||||||
|
try:
|
||||||
|
self._input_users.append(
|
||||||
|
await self._client.get_input_entity(peer)
|
||||||
|
)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
return self._input_users
|
||||||
|
|
||||||
|
|
||||||
class UserUpdate(_EventBuilder):
|
class UserUpdate(_EventBuilder):
|
||||||
"""
|
"""
|
||||||
|
@ -829,21 +868,32 @@ class UserUpdate(_EventBuilder):
|
||||||
return self.chat
|
return self.chat
|
||||||
|
|
||||||
|
|
||||||
class MessageChanged(_EventBuilder):
|
class MessageEdited(NewMessage):
|
||||||
"""
|
"""
|
||||||
Represents a message changed (edited or deleted).
|
Event fired when a message has been edited.
|
||||||
"""
|
"""
|
||||||
def build(self, update):
|
def build(self, update):
|
||||||
if isinstance(update, (types.UpdateEditMessage,
|
if isinstance(update, (types.UpdateEditMessage,
|
||||||
types.UpdateEditChannelMessage)):
|
types.UpdateEditChannelMessage)):
|
||||||
event = MessageChanged.Event(edit_msg=update.message)
|
event = MessageEdited.Event(update.message)
|
||||||
elif isinstance(update, types.UpdateDeleteMessages):
|
else:
|
||||||
event = MessageChanged.Event(
|
return
|
||||||
|
|
||||||
|
return self._filter_event(event)
|
||||||
|
|
||||||
|
|
||||||
|
class MessageDeleted(_EventBuilder):
|
||||||
|
"""
|
||||||
|
Event fired when one or more messages are deleted.
|
||||||
|
"""
|
||||||
|
def build(self, update):
|
||||||
|
if isinstance(update, types.UpdateDeleteMessages):
|
||||||
|
event = MessageDeleted.Event(
|
||||||
deleted_ids=update.messages,
|
deleted_ids=update.messages,
|
||||||
peer=None
|
peer=None
|
||||||
)
|
)
|
||||||
elif isinstance(update, types.UpdateDeleteChannelMessages):
|
elif isinstance(update, types.UpdateDeleteChannelMessages):
|
||||||
event = MessageChanged.Event(
|
event = MessageDeleted.Event(
|
||||||
deleted_ids=update.messages,
|
deleted_ids=update.messages,
|
||||||
peer=types.PeerChannel(update.channel_id)
|
peer=types.PeerChannel(update.channel_id)
|
||||||
)
|
)
|
||||||
|
@ -852,33 +902,13 @@ class MessageChanged(_EventBuilder):
|
||||||
|
|
||||||
return self._filter_event(event)
|
return self._filter_event(event)
|
||||||
|
|
||||||
class Event(NewMessage.Event):
|
class Event(_EventCommon):
|
||||||
"""
|
def __init__(self, deleted_ids, peer):
|
||||||
Represents the event of an user status update (last seen, joined).
|
super().__init__(
|
||||||
|
types.Message((deleted_ids or [0])[0], peer, None, '')
|
||||||
Please note that the ``message`` member will be ``None`` if the
|
)
|
||||||
action was a deletion and not an edit.
|
self.deleted_id = None if not deleted_ids else deleted_ids[0]
|
||||||
|
self.deleted_ids = self.deleted_ids
|
||||||
Members:
|
|
||||||
edited (:obj:`bool`):
|
|
||||||
``True`` if the message was edited.
|
|
||||||
|
|
||||||
deleted (:obj:`bool`):
|
|
||||||
``True`` if the message IDs were deleted.
|
|
||||||
|
|
||||||
deleted_ids (:obj:`List[int]`):
|
|
||||||
A list containing the IDs of the messages that were deleted.
|
|
||||||
"""
|
|
||||||
def __init__(self, edit_msg=None, deleted_ids=None, peer=None):
|
|
||||||
if edit_msg is None:
|
|
||||||
msg = types.Message((deleted_ids or [0])[0], peer, None, '')
|
|
||||||
else:
|
|
||||||
msg = edit_msg
|
|
||||||
super().__init__(msg)
|
|
||||||
|
|
||||||
self.edited = bool(edit_msg)
|
|
||||||
self.deleted = bool(deleted_ids)
|
|
||||||
self.deleted_ids = deleted_ids or []
|
|
||||||
|
|
||||||
|
|
||||||
class StopPropagation(Exception):
|
class StopPropagation(Exception):
|
||||||
|
|
|
@ -125,7 +125,7 @@ class MemorySession(Session):
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def process_entities(self, tlo):
|
def process_entities(self, tlo):
|
||||||
self._entities += set(self._entities_to_rows(tlo))
|
self._entities |= set(self._entities_to_rows(tlo))
|
||||||
|
|
||||||
def get_entity_rows_by_phone(self, phone):
|
def get_entity_rows_by_phone(self, phone):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -56,7 +56,8 @@ from .tl.functions.messages import (
|
||||||
GetDialogsRequest, GetHistoryRequest, SendMediaRequest,
|
GetDialogsRequest, GetHistoryRequest, SendMediaRequest,
|
||||||
SendMessageRequest, GetChatsRequest, GetAllDraftsRequest,
|
SendMessageRequest, GetChatsRequest, GetAllDraftsRequest,
|
||||||
CheckChatInviteRequest, ReadMentionsRequest, SendMultiMediaRequest,
|
CheckChatInviteRequest, ReadMentionsRequest, SendMultiMediaRequest,
|
||||||
UploadMediaRequest, EditMessageRequest, GetFullChatRequest
|
UploadMediaRequest, EditMessageRequest, GetFullChatRequest,
|
||||||
|
ForwardMessagesRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
from .tl.functions import channels
|
from .tl.functions import channels
|
||||||
|
@ -665,8 +666,9 @@ class TelegramClient(TelegramBareClient):
|
||||||
|
|
||||||
return message, msg_entities
|
return message, msg_entities
|
||||||
|
|
||||||
async def send_message(self, entity, message, reply_to=None,
|
async def send_message(self, entity, message='', reply_to=None,
|
||||||
parse_mode='md', link_preview=True):
|
parse_mode='md', link_preview=True, file=None,
|
||||||
|
force_document=False):
|
||||||
"""
|
"""
|
||||||
Sends the given message to the specified entity (user/chat/channel).
|
Sends the given message to the specified entity (user/chat/channel).
|
||||||
|
|
||||||
|
@ -690,9 +692,25 @@ class TelegramClient(TelegramBareClient):
|
||||||
link_preview (:obj:`bool`, optional):
|
link_preview (:obj:`bool`, optional):
|
||||||
Should the link preview be shown?
|
Should the link preview be shown?
|
||||||
|
|
||||||
|
file (:obj:`file`, optional):
|
||||||
|
Sends a message with a file attached (e.g. a photo,
|
||||||
|
video, audio or document). The ``message`` may be empty.
|
||||||
|
|
||||||
|
force_document (:obj:`bool`, optional):
|
||||||
|
Whether to send the given file as a document or not.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
the sent message
|
the sent message
|
||||||
"""
|
"""
|
||||||
|
if file is not None:
|
||||||
|
return await self.send_file(
|
||||||
|
entity, file, caption=message, reply_to=reply_to,
|
||||||
|
parse_mode=parse_mode, force_document=force_document
|
||||||
|
)
|
||||||
|
elif not message:
|
||||||
|
raise ValueError(
|
||||||
|
'The message cannot be empty unless a file is provided'
|
||||||
|
)
|
||||||
|
|
||||||
entity = await self.get_input_entity(entity)
|
entity = await self.get_input_entity(entity)
|
||||||
if isinstance(message, Message):
|
if isinstance(message, Message):
|
||||||
|
@ -739,6 +757,58 @@ class TelegramClient(TelegramBareClient):
|
||||||
|
|
||||||
return self._get_response_message(request, result)
|
return self._get_response_message(request, result)
|
||||||
|
|
||||||
|
async def forward_messages(self, entity, messages, from_peer=None):
|
||||||
|
"""
|
||||||
|
Forwards the given message(s) to the specified entity.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity (:obj:`entity`):
|
||||||
|
To which entity the message(s) will be forwarded.
|
||||||
|
|
||||||
|
messages (:obj:`list` | :obj:`int` | :obj:`Message`):
|
||||||
|
The message(s) to forward, or their integer IDs.
|
||||||
|
|
||||||
|
from_peer (:obj:`entity`):
|
||||||
|
If the given messages are integer IDs and not instances
|
||||||
|
of the ``Message`` class, this *must* be specified in
|
||||||
|
order for the forward to work.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The forwarded messages.
|
||||||
|
"""
|
||||||
|
if not utils.is_list_like(messages):
|
||||||
|
messages = (messages,)
|
||||||
|
|
||||||
|
if not from_peer:
|
||||||
|
try:
|
||||||
|
# On private chats (to_id = PeerUser), if the message is
|
||||||
|
# not outgoing, we actually need to use "from_id" to get
|
||||||
|
# the conversation on which the message was sent.
|
||||||
|
from_peer = next(
|
||||||
|
m.from_id if not m.out and isinstance(m.to_id, PeerUser)
|
||||||
|
else m.to_id for m in messages if isinstance(m, Message)
|
||||||
|
)
|
||||||
|
except StopIteration:
|
||||||
|
raise ValueError(
|
||||||
|
'from_chat must be given if integer IDs are used'
|
||||||
|
)
|
||||||
|
|
||||||
|
req = ForwardMessagesRequest(
|
||||||
|
from_peer=from_peer,
|
||||||
|
id=[m if isinstance(m, int) else m.id for m in messages],
|
||||||
|
to_peer=entity
|
||||||
|
)
|
||||||
|
result = await self(req)
|
||||||
|
random_to_id = {}
|
||||||
|
id_to_message = {}
|
||||||
|
for update in result.updates:
|
||||||
|
if isinstance(update, UpdateMessageID):
|
||||||
|
random_to_id[update.random_id] = update.id
|
||||||
|
elif isinstance(update, UpdateNewMessage):
|
||||||
|
id_to_message[update.message.id] = update.message
|
||||||
|
|
||||||
|
return [id_to_message[random_to_id[rnd]] for rnd in req.random_id]
|
||||||
|
|
||||||
async def edit_message(self, entity, message_id, message=None,
|
async def edit_message(self, entity, message_id, message=None,
|
||||||
parse_mode='md', link_preview=True):
|
parse_mode='md', link_preview=True):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,6 +6,7 @@ import math
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import re
|
import re
|
||||||
import types
|
import types
|
||||||
|
from collections import UserList
|
||||||
from mimetypes import add_type, guess_extension
|
from mimetypes import add_type, guess_extension
|
||||||
|
|
||||||
from .tl.types import (
|
from .tl.types import (
|
||||||
|
@ -342,7 +343,8 @@ def is_list_like(obj):
|
||||||
enough. Things like open() are also iterable (and probably many
|
enough. Things like open() are also iterable (and probably many
|
||||||
other things), so just support the commonly known list-like objects.
|
other things), so just support the commonly known list-like objects.
|
||||||
"""
|
"""
|
||||||
return isinstance(obj, (list, tuple, set, dict, types.GeneratorType))
|
return isinstance(obj, (list, tuple, set, dict,
|
||||||
|
UserList, types.GeneratorType))
|
||||||
|
|
||||||
|
|
||||||
def parse_phone(phone):
|
def parse_phone(phone):
|
||||||
|
|
|
@ -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.17.4'
|
__version__ = '0.18'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user