mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-03 19:50:15 +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 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)
|
||||
# users, chats and channels may all have the same ID, so it's
|
||||
# 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
|
||||
my_user = client.get_entity(PeerUser(some_id))
|
||||
my_chat = client.get_entity(PeerChat(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
|
||||
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!')``
|
||||
|
|
|
@ -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
|
||||
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
|
||||
the same handler. You can also have a handler work on only specific chats,
|
||||
for example:
|
||||
|
|
|
@ -14,6 +14,71 @@ it can take advantage of new goodies!
|
|||
.. 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)
|
||||
======================================
|
||||
|
||||
|
|
6
setup.py
6
setup.py
|
@ -148,7 +148,11 @@ def main():
|
|||
keywords='telegram api chat client library messaging mtproto',
|
||||
packages=find_packages(exclude=[
|
||||
'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'],
|
||||
extras_require={
|
||||
|
|
|
@ -140,6 +140,9 @@ class _EventCommon(abc.ABC):
|
|||
)
|
||||
return self._input_chat
|
||||
|
||||
def client(self):
|
||||
return self._client
|
||||
|
||||
@property
|
||||
async def chat(self):
|
||||
"""
|
||||
|
@ -316,10 +319,19 @@ class NewMessage(_EventBuilder):
|
|||
Replies to the message (as a reply). This is a shorthand for
|
||||
``client.send_message(event.chat, ..., reply_to=event.message.id)``.
|
||||
"""
|
||||
return await self._client.send_message(await self.input_chat,
|
||||
reply_to=self.message.id,
|
||||
kwargs['reply_to'] = self.message.id
|
||||
return await self._client.send_message(self.input_chat,
|
||||
*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):
|
||||
"""
|
||||
Edits the message iff it's outgoing. This is a shorthand for
|
||||
|
@ -525,15 +537,19 @@ class ChatAction(_EventBuilder):
|
|||
elif isinstance(action, types.MessageActionChannelCreate):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
created=True,
|
||||
users=msg.from_id,
|
||||
new_title=action.title)
|
||||
elif isinstance(action, types.MessageActionChatEditTitle):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
users=msg.from_id,
|
||||
new_title=action.title)
|
||||
elif isinstance(action, types.MessageActionChatEditPhoto):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
users=msg.from_id,
|
||||
new_photo=action.photo)
|
||||
elif isinstance(action, types.MessageActionChatDeletePhoto):
|
||||
event = ChatAction.Event(msg.to_id,
|
||||
users=msg.from_id,
|
||||
new_photo=True)
|
||||
else:
|
||||
return
|
||||
|
@ -607,6 +623,7 @@ class ChatAction(_EventBuilder):
|
|||
self.created = bool(created)
|
||||
self._user_peers = users if isinstance(users, list) else [users]
|
||||
self._users = None
|
||||
self._input_users = None
|
||||
self.new_title = new_title
|
||||
|
||||
@property
|
||||
|
@ -660,10 +677,16 @@ class ChatAction(_EventBuilder):
|
|||
Might be ``None`` if the information can't be retrieved or
|
||||
there is no user taking part.
|
||||
"""
|
||||
try:
|
||||
return next(await self.users)
|
||||
except (StopIteration, TypeError):
|
||||
return None
|
||||
if await self.users:
|
||||
return self._users[0]
|
||||
|
||||
@property
|
||||
async def input_user(self):
|
||||
"""
|
||||
Input version of the self.user property.
|
||||
"""
|
||||
if await self.input_users:
|
||||
return self._input_users[0]
|
||||
|
||||
@property
|
||||
async def users(self):
|
||||
|
@ -681,6 +704,22 @@ class ChatAction(_EventBuilder):
|
|||
|
||||
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):
|
||||
"""
|
||||
|
@ -829,21 +868,32 @@ class UserUpdate(_EventBuilder):
|
|||
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):
|
||||
if isinstance(update, (types.UpdateEditMessage,
|
||||
types.UpdateEditChannelMessage)):
|
||||
event = MessageChanged.Event(edit_msg=update.message)
|
||||
elif isinstance(update, types.UpdateDeleteMessages):
|
||||
event = MessageChanged.Event(
|
||||
event = MessageEdited.Event(update.message)
|
||||
else:
|
||||
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,
|
||||
peer=None
|
||||
)
|
||||
elif isinstance(update, types.UpdateDeleteChannelMessages):
|
||||
event = MessageChanged.Event(
|
||||
event = MessageDeleted.Event(
|
||||
deleted_ids=update.messages,
|
||||
peer=types.PeerChannel(update.channel_id)
|
||||
)
|
||||
|
@ -852,33 +902,13 @@ class MessageChanged(_EventBuilder):
|
|||
|
||||
return self._filter_event(event)
|
||||
|
||||
class Event(NewMessage.Event):
|
||||
"""
|
||||
Represents the event of an user status update (last seen, joined).
|
||||
|
||||
Please note that the ``message`` member will be ``None`` if the
|
||||
action was a deletion and not an edit.
|
||||
|
||||
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 Event(_EventCommon):
|
||||
def __init__(self, deleted_ids, peer):
|
||||
super().__init__(
|
||||
types.Message((deleted_ids or [0])[0], peer, None, '')
|
||||
)
|
||||
self.deleted_id = None if not deleted_ids else deleted_ids[0]
|
||||
self.deleted_ids = self.deleted_ids
|
||||
|
||||
|
||||
class StopPropagation(Exception):
|
||||
|
|
|
@ -125,7 +125,7 @@ class MemorySession(Session):
|
|||
return rows
|
||||
|
||||
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):
|
||||
try:
|
||||
|
|
|
@ -56,7 +56,8 @@ from .tl.functions.messages import (
|
|||
GetDialogsRequest, GetHistoryRequest, SendMediaRequest,
|
||||
SendMessageRequest, GetChatsRequest, GetAllDraftsRequest,
|
||||
CheckChatInviteRequest, ReadMentionsRequest, SendMultiMediaRequest,
|
||||
UploadMediaRequest, EditMessageRequest, GetFullChatRequest
|
||||
UploadMediaRequest, EditMessageRequest, GetFullChatRequest,
|
||||
ForwardMessagesRequest
|
||||
)
|
||||
|
||||
from .tl.functions import channels
|
||||
|
@ -665,8 +666,9 @@ class TelegramClient(TelegramBareClient):
|
|||
|
||||
return message, msg_entities
|
||||
|
||||
async def send_message(self, entity, message, reply_to=None,
|
||||
parse_mode='md', link_preview=True):
|
||||
async def send_message(self, entity, message='', reply_to=None,
|
||||
parse_mode='md', link_preview=True, file=None,
|
||||
force_document=False):
|
||||
"""
|
||||
Sends the given message to the specified entity (user/chat/channel).
|
||||
|
||||
|
@ -690,9 +692,25 @@ class TelegramClient(TelegramBareClient):
|
|||
link_preview (:obj:`bool`, optional):
|
||||
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:
|
||||
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)
|
||||
if isinstance(message, Message):
|
||||
|
@ -739,6 +757,58 @@ class TelegramClient(TelegramBareClient):
|
|||
|
||||
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,
|
||||
parse_mode='md', link_preview=True):
|
||||
"""
|
||||
|
|
|
@ -6,6 +6,7 @@ import math
|
|||
import mimetypes
|
||||
import re
|
||||
import types
|
||||
from collections import UserList
|
||||
from mimetypes import add_type, guess_extension
|
||||
|
||||
from .tl.types import (
|
||||
|
@ -342,7 +343,8 @@ def is_list_like(obj):
|
|||
enough. Things like open() are also iterable (and probably many
|
||||
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):
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# Versions should comply with PEP440.
|
||||
# This line is parsed in setup.py:
|
||||
__version__ = '0.17.4'
|
||||
__version__ = '0.18'
|
||||
|
|
Loading…
Reference in New Issue
Block a user