mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-02 11:10:18 +03:00
Merge branch 'master' into asyncio
This commit is contained in:
commit
06af73ed65
|
@ -65,7 +65,7 @@ if you're new with ``asyncio``.
|
|||
await client.send_file('username', '/home/myself/Pictures/holidays.jpg')
|
||||
|
||||
await client.download_profile_photo('me')
|
||||
messages = await client.get_message_history('username')
|
||||
messages = await client.get_messages('username')
|
||||
await client.download_media(messages[0])
|
||||
|
||||
|
||||
|
|
|
@ -20,6 +20,13 @@ in response to certain methods, such as :tl:`GetUsersRequest`.
|
|||
or even entire :tl:`User`, :tl:`Chat` and :tl:`Channel` objects and even
|
||||
phone numbers from people you have in your contacts.
|
||||
|
||||
To "encounter" an ID, you would have to "find it" like you would in the
|
||||
normal app. If the peer is in your dialogs, you would need to
|
||||
`client.get_dialogs() <telethon.telegram_client.TelegramClient.get_dialogs>`.
|
||||
If the peer is someone in a group, you would similarly
|
||||
`client.get_participants(group) <telethon.telegram_client.TelegramClient.get_participants>`.
|
||||
|
||||
|
||||
Getting entities
|
||||
****************
|
||||
|
||||
|
|
|
@ -58,6 +58,11 @@ Many other common methods for quick scripts are also available:
|
|||
# Note that you can use 'me' or 'self' to message yourself
|
||||
await client.send_message('username', 'Hello World from Telethon!')
|
||||
|
||||
# .send_message's parse mode defaults to markdown, so you
|
||||
# can use **bold**, __italics__, [links](https://example.com), `code`,
|
||||
# and even [mentions](@username)/[mentions](tg://user?id=123456789)
|
||||
await client.send_message('username', '**Using** __markdown__ `too`!')
|
||||
|
||||
await client.send_file('username', '/home/myself/Pictures/holidays.jpg')
|
||||
|
||||
# The utils package has some goodies, like .get_display_name()
|
||||
|
@ -83,15 +88,16 @@ a single line.
|
|||
Available methods
|
||||
*****************
|
||||
|
||||
This page lists all the "handy" methods available for you to use in the
|
||||
``TelegramClient`` class. These are simply wrappers around the "raw"
|
||||
Telegram API, making it much more manageable and easier to work with.
|
||||
The :ref:`reference <telethon-package>` lists all the "handy" methods
|
||||
available for you to use in the ``TelegramClient`` class. These are simply
|
||||
wrappers around the "raw" Telegram API, making it much more manageable and
|
||||
easier to work with.
|
||||
|
||||
Please refer to :ref:`accessing-the-full-api` if these aren't enough,
|
||||
and don't be afraid to read the source code of the InteractiveTelegramClient_
|
||||
or even the TelegramClient_ itself to learn how it works.
|
||||
|
||||
To see the methods available in the client, see :ref:`telethon-package`.
|
||||
See the mentioned :ref:`telethon-package` to find the available methods.
|
||||
|
||||
.. _InteractiveTelegramClient: https://github.com/LonamiWebs/Telethon/blob/master/telethon_examples/interactive_telegram_client.py
|
||||
.. _TelegramClient: https://github.com/LonamiWebs/Telethon/blob/master/telethon/telegram_client.py
|
||||
|
|
|
@ -13,7 +13,7 @@ Talking to Inline Bots
|
|||
|
||||
You can query an inline bot, such as `@VoteBot`__ (note, *query*,
|
||||
not *interact* with a voting message), by making use of the
|
||||
`GetInlineBotResultsRequest`__ request:
|
||||
:tl:`GetInlineBotResultsRequest` request:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -24,7 +24,7 @@ not *interact* with a voting message), by making use of the
|
|||
))
|
||||
|
||||
And you can select any of their results by using
|
||||
`SendInlineBotResultRequest`__:
|
||||
:tl:`SendInlineBotResultRequest`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -41,7 +41,7 @@ Talking to Bots with special reply markup
|
|||
*****************************************
|
||||
|
||||
To interact with a message that has a special reply markup, such as
|
||||
`@VoteBot`__ polls, you would use `GetBotCallbackAnswerRequest`__:
|
||||
`@VoteBot`__ polls, you would use :tl:`GetBotCallbackAnswerRequest`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -58,7 +58,4 @@ show it visually (button rows, and buttons within each row, each with
|
|||
its own data).
|
||||
|
||||
__ https://t.me/vote
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/messages/get_inline_bot_results.html
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/messages/send_inline_bot_result.html
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/messages/get_bot_callback_answer.html
|
||||
__ https://t.me/vote
|
||||
|
|
|
@ -20,7 +20,7 @@ Joining a public channel
|
|||
************************
|
||||
|
||||
Once you have the :ref:`entity <entities>` of the channel you want to join
|
||||
to, you can make use of the `JoinChannelRequest`__ to join such channel:
|
||||
to, you can make use of the :tl:`JoinChannelRequest` to join such channel:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -35,6 +35,9 @@ to, you can make use of the `JoinChannelRequest`__ to join such channel:
|
|||
For more on channels, check the `channels namespace`__.
|
||||
|
||||
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/channels/index.html
|
||||
|
||||
|
||||
Joining a private chat or channel
|
||||
*********************************
|
||||
|
||||
|
@ -43,7 +46,7 @@ If all you have is a link like this one:
|
|||
enough information to join! The part after the
|
||||
``https://t.me/joinchat/``, this is, ``AAAAAFFszQPyPEZ7wgxLtd`` on this
|
||||
example, is the ``hash`` of the chat or channel. Now you can use
|
||||
`ImportChatInviteRequest`__ as follows:
|
||||
:tl:`ImportChatInviteRequest` as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -55,21 +58,23 @@ Adding someone else to such chat or channel
|
|||
*******************************************
|
||||
|
||||
If you don't want to add yourself, maybe because you're already in,
|
||||
you can always add someone else with the `AddChatUserRequest`__, which
|
||||
use is very straightforward, or `InviteToChannelRequest`__ for channels:
|
||||
you can always add someone else with the :tl:`AddChatUserRequest`, which
|
||||
use is very straightforward, or :tl:`InviteToChannelRequest` for channels:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# For normal chats
|
||||
from telethon.tl.functions.messages import AddChatUserRequest
|
||||
|
||||
# Note that ``user_to_add`` is NOT the name of the parameter.
|
||||
# It's the user you want to add (``user_id=user_to_add``).
|
||||
await client(AddChatUserRequest(
|
||||
chat_id,
|
||||
user_to_add,
|
||||
fwd_limit=10 # Allow the user to see the 10 last messages
|
||||
))
|
||||
|
||||
# For channels
|
||||
# For channels (which includes megagroups)
|
||||
from telethon.tl.functions.channels import InviteToChannelRequest
|
||||
|
||||
await client(InviteToChannelRequest(
|
||||
|
@ -78,24 +83,13 @@ use is very straightforward, or `InviteToChannelRequest`__ for channels:
|
|||
))
|
||||
|
||||
|
||||
|
||||
Checking a link without joining
|
||||
*******************************
|
||||
|
||||
If you don't need to join but rather check whether it's a group or a
|
||||
channel, you can use the `CheckChatInviteRequest`__, which takes in
|
||||
channel, you can use the :tl:`CheckChatInviteRequest`, which takes in
|
||||
the hash of said channel or group.
|
||||
|
||||
__ https://lonamiwebs.github.io/Telethon/constructors/chat.html
|
||||
__ https://lonamiwebs.github.io/Telethon/constructors/channel.html
|
||||
__ https://lonamiwebs.github.io/Telethon/types/chat.html
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/channels/join_channel.html
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/channels/index.html
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/messages/import_chat_invite.html
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/messages/add_chat_user.html
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/channels/invite_to_channel.html
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/messages/check_chat_invite.html
|
||||
|
||||
|
||||
Retrieving all chat members (channels too)
|
||||
******************************************
|
||||
|
@ -108,11 +102,11 @@ 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
|
||||
to use `GetParticipantsRequest`__. As we can see it needs an
|
||||
`InputChannel`__, (passing the mega-group or channel you're going to
|
||||
use will work), and a mandatory `ChannelParticipantsFilter`__. The
|
||||
to use :tl:`GetParticipantsRequest`. As we can see it needs an
|
||||
:tl:`InputChannel`, (passing the mega-group or channel you're going to
|
||||
use will work), and a mandatory :tl:`ChannelParticipantsFilter`. The
|
||||
closest thing to "no filter" is to simply use
|
||||
`ChannelParticipantsSearch`__ with an empty ``'q'`` string.
|
||||
:tl:`ChannelParticipantsSearch` with an empty ``'q'`` string.
|
||||
|
||||
If we want to get *all* the members, we need to use a moving offset and
|
||||
a fixed limit:
|
||||
|
@ -146,34 +140,28 @@ a fixed limit:
|
|||
Refer to `issue 573`__ for more on this.
|
||||
|
||||
|
||||
Note that ``GetParticipantsRequest`` returns `ChannelParticipants`__,
|
||||
Note that :tl:`GetParticipantsRequest` returns :tl:`ChannelParticipants`,
|
||||
which may have more information you need (like the role of the
|
||||
participants, total count of members, etc.)
|
||||
|
||||
__ 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/constructors/channel_participants_search.html
|
||||
__ https://github.com/LonamiWebs/Telethon/issues/573
|
||||
__ https://lonamiwebs.github.io/Telethon/constructors/channels/channel_participants.html
|
||||
|
||||
|
||||
Recent Actions
|
||||
**************
|
||||
|
||||
"Recent actions" is simply the name official applications have given to
|
||||
the "admin log". Simply use `GetAdminLogRequest`__ for that, and
|
||||
the "admin log". Simply use :tl:`GetAdminLogRequest` for that, and
|
||||
you'll get AdminLogResults.events in return which in turn has the final
|
||||
`.action`__.
|
||||
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/channels/get_admin_log.html
|
||||
__ https://lonamiwebs.github.io/Telethon/types/channel_admin_log_event_action.html
|
||||
|
||||
|
||||
Admin Permissions
|
||||
*****************
|
||||
|
||||
Giving or revoking admin permissions can be done with the `EditAdminRequest`__:
|
||||
Giving or revoking admin permissions can be done with the :tl:`EditAdminRequest`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -208,17 +196,82 @@ Giving or revoking admin permissions can be done with the `EditAdminRequest`__:
|
|||
|
||||
# User will now be able to change group info, delete other people's
|
||||
# messages and pin messages.
|
||||
|
||||
| Thanks to `@Kyle2142`__ for `pointing out`__ that you **cannot** set all
|
||||
| parameters to ``True`` to give a user full permissions, as not all
|
||||
| permissions are related to both broadcast channels/megagroups.
|
||||
|
|
||||
| E.g. trying to set ``post_messages=True`` in a megagroup will raise an
|
||||
| error. It is recommended to always use keyword arguments, and to set only
|
||||
| the permissions the user needs. If you don't need to change a permission,
|
||||
| it can be omitted (full list `here`__).
|
||||
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/channels/edit_admin.html
|
||||
|
||||
.. note::
|
||||
|
||||
Thanks to `@Kyle2142`__ for `pointing out`__ that you **cannot** set all
|
||||
parameters to ``True`` to give a user full permissions, as not all
|
||||
permissions are related to both broadcast channels/megagroups.
|
||||
|
||||
E.g. trying to set ``post_messages=True`` in a megagroup will raise an
|
||||
error. It is recommended to always use keyword arguments, and to set only
|
||||
the permissions the user needs. If you don't need to change a permission,
|
||||
it can be omitted (full list `here`__).
|
||||
|
||||
|
||||
Restricting Users
|
||||
*****************
|
||||
|
||||
Similar to how you give or revoke admin permissions, you can edit the
|
||||
banned rights of an user through :tl:`EditAdminRequest` and its parameter
|
||||
:tl:`ChannelBannedRights`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.tl.functions.channels import EditBannedRequest
|
||||
from telethon.tl.types import ChannelBannedRights
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Restricting an user for 7 days, only allowing view/send messages.
|
||||
#
|
||||
# Note that it's "reversed". You must set to ``True`` the permissions
|
||||
# you want to REMOVE, and leave as ``None`` those you want to KEEP.
|
||||
rights = ChannelBannedRights(
|
||||
until_date=datetime.now() + timedelta(days=7),
|
||||
view_messages=None,
|
||||
send_messages=None,
|
||||
send_media=True,
|
||||
send_stickers=True,
|
||||
send_gifs=True,
|
||||
send_games=True,
|
||||
send_inline=True,
|
||||
embed_links=True
|
||||
)
|
||||
|
||||
# The above is equivalent to
|
||||
rights = ChannelBannedRights(
|
||||
until_date=datetime.now() + timedelta(days=7),
|
||||
send_media=True,
|
||||
send_stickers=True,
|
||||
send_gifs=True,
|
||||
send_games=True,
|
||||
send_inline=True,
|
||||
embed_links=True
|
||||
)
|
||||
|
||||
await client(EditBannedRequest(channel, user, rights))
|
||||
|
||||
|
||||
Kicking a member
|
||||
****************
|
||||
|
||||
Telegram doesn't actually have a request to kick an user from a group.
|
||||
Instead, you need to restrict them so they can't see messages. Any date
|
||||
is enough:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.tl.functions.channels import EditBannedRequest
|
||||
from telethon.tl.types import ChannelBannedRights
|
||||
|
||||
await client(EditBannedRequest(channel, user, ChannelBannedRights(
|
||||
until_date=None,
|
||||
view_messages=True
|
||||
)))
|
||||
|
||||
|
||||
__ https://github.com/Kyle2142
|
||||
__ https://github.com/LonamiWebs/Telethon/issues/490
|
||||
__ https://lonamiwebs.github.io/Telethon/constructors/channel_admin_rights.html
|
||||
|
@ -229,7 +282,7 @@ Increasing View Count in a Channel
|
|||
|
||||
It has been asked `quite`__ `a few`__ `times`__ (really, `many`__), and
|
||||
while I don't understand why so many people ask this, the solution is to
|
||||
use `GetMessagesViewsRequest`__, setting ``increment=True``:
|
||||
use :tl:`GetMessagesViewsRequest`, setting ``increment=True``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -253,4 +306,3 @@ __ https://github.com/LonamiWebs/Telethon/issues/233
|
|||
__ https://github.com/LonamiWebs/Telethon/issues/305
|
||||
__ https://github.com/LonamiWebs/Telethon/issues/409
|
||||
__ https://github.com/LonamiWebs/Telethon/issues/447
|
||||
__ https://lonamiwebs.github.io/Telethon/methods/messages/get_messages_views.html
|
||||
|
|
41
readthedocs/extra/examples/projects-using-telethon.rst
Normal file
41
readthedocs/extra/examples/projects-using-telethon.rst
Normal file
|
@ -0,0 +1,41 @@
|
|||
=======================
|
||||
Projects using Telethon
|
||||
=======================
|
||||
|
||||
This page lists some real world examples showcasing what can be built with
|
||||
the library.
|
||||
|
||||
.. note::
|
||||
|
||||
Do you have a project that uses the library or know of any that's not
|
||||
listed here? Feel free to leave a comment at
|
||||
`issue 744 <https://github.com/LonamiWebs/Telethon/issues/744>`_
|
||||
so it can be included in the next revision of the documentation!
|
||||
|
||||
|
||||
telegram-export
|
||||
***************
|
||||
|
||||
`Link <https://github.com/expectocode/telegram-export>`_ /
|
||||
`Author's website <https://github.com/expectocode>`_
|
||||
|
||||
A tool to download Telegram data (users, chats, messages, and media)
|
||||
into a database (and display the saved data).
|
||||
|
||||
|
||||
mautrix-telegram
|
||||
****************
|
||||
|
||||
`Link <https://github.com/tulir/mautrix-telegram>`_ /
|
||||
`Author's website <https://maunium.net/>`_
|
||||
|
||||
A Matrix-Telegram hybrid puppeting/relaybot bridge.
|
||||
|
||||
|
||||
TelegramTUI
|
||||
***********
|
||||
|
||||
`Link <https://github.com/bad-day/TelegramTUI>`_ /
|
||||
`Author's website <https://github.com/bad-day>`_
|
||||
|
||||
A Telegram client on your terminal.
|
|
@ -51,7 +51,7 @@ too, if that's all you have.
|
|||
Searching Messages
|
||||
*******************
|
||||
|
||||
Messages are searched through the obvious SearchRequest_, but you may run
|
||||
Messages are searched through the obvious :tl:`SearchRequest`, but you may run
|
||||
into issues_. A valid example would be:
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -75,7 +75,7 @@ into issues_. A valid example would be:
|
|||
))
|
||||
|
||||
It's important to note that the optional parameter ``from_id`` could have
|
||||
been omitted (defaulting to ``None``). Changing it to InputUserEmpty_, as one
|
||||
been omitted (defaulting to ``None``). Changing it to :tl:`InputUserEmpty`, as one
|
||||
could think to specify "no user", won't work because this parameter is a flag,
|
||||
and it being unspecified has a different meaning.
|
||||
|
||||
|
@ -128,7 +128,4 @@ send yourself the very first sticker you have:
|
|||
))
|
||||
|
||||
|
||||
.. _ForwardMessagesRequest: https://lonamiwebs.github.io/Telethon/methods/messages/forward_messages.html
|
||||
.. _SearchRequest: https://lonamiwebs.github.io/Telethon/methods/messages/search.html
|
||||
.. _issues: https://github.com/LonamiWebs/Telethon/issues/215
|
||||
.. _InputUserEmpty: https://lonamiwebs.github.io/Telethon/constructors/input_user_empty.html
|
||||
|
|
|
@ -74,6 +74,7 @@ heavy job for you, so you can focus on developing an application.
|
|||
extra/examples/working-with-messages
|
||||
extra/examples/chats-and-channels
|
||||
extra/examples/bots
|
||||
extra/examples/projects-using-telethon
|
||||
|
||||
|
||||
.. _Troubleshooting:
|
||||
|
|
|
@ -7,3 +7,50 @@ telethon\.events package
|
|||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Every event (builder) subclasses `telethon.events.common.EventBuilder`,
|
||||
so all the methods in it can be used from any event builder/event instance.
|
||||
|
||||
.. automodule:: telethon.events.common
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Below all the event types are listed:
|
||||
|
||||
.. automodule:: telethon.events.newmessage
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
.. automodule:: telethon.events.chataction
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
.. automodule:: telethon.events.userupdate
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
.. automodule:: telethon.events.messageedited
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
.. automodule:: telethon.events.messagedeleted
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
.. automodule:: telethon.events.messageread
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
File diff suppressed because it is too large
Load Diff
329
telethon/events/chataction.py
Normal file
329
telethon/events/chataction.py
Normal file
|
@ -0,0 +1,329 @@
|
|||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .. import utils
|
||||
from ..tl import types, functions
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class ChatAction(EventBuilder):
|
||||
"""
|
||||
Represents an action in a chat (such as user joined, left, or new pin).
|
||||
"""
|
||||
def build(self, update):
|
||||
if isinstance(update, types.UpdateChannelPinnedMessage) and update.id == 0:
|
||||
# Telegram does not always send
|
||||
# UpdateChannelPinnedMessage for new pins
|
||||
# but always for unpin, with update.id = 0
|
||||
event = ChatAction.Event(types.PeerChannel(update.channel_id),
|
||||
unpin=True)
|
||||
|
||||
elif isinstance(update, types.UpdateChatParticipantAdd):
|
||||
event = ChatAction.Event(types.PeerChat(update.chat_id),
|
||||
added_by=update.inviter_id or True,
|
||||
users=update.user_id)
|
||||
|
||||
elif isinstance(update, types.UpdateChatParticipantDelete):
|
||||
event = ChatAction.Event(types.PeerChat(update.chat_id),
|
||||
kicked_by=True,
|
||||
users=update.user_id)
|
||||
|
||||
elif (isinstance(update, (
|
||||
types.UpdateNewMessage, types.UpdateNewChannelMessage))
|
||||
and isinstance(update.message, types.MessageService)):
|
||||
msg = update.message
|
||||
action = update.message.action
|
||||
if isinstance(action, types.MessageActionChatJoinedByLink):
|
||||
event = ChatAction.Event(msg,
|
||||
added_by=True,
|
||||
users=msg.from_id)
|
||||
elif isinstance(action, types.MessageActionChatAddUser):
|
||||
event = ChatAction.Event(msg,
|
||||
added_by=msg.from_id or True,
|
||||
users=action.users)
|
||||
elif isinstance(action, types.MessageActionChatDeleteUser):
|
||||
event = ChatAction.Event(msg,
|
||||
kicked_by=msg.from_id or True,
|
||||
users=action.user_id)
|
||||
elif isinstance(action, types.MessageActionChatCreate):
|
||||
event = ChatAction.Event(msg,
|
||||
users=action.users,
|
||||
created=True,
|
||||
new_title=action.title)
|
||||
elif isinstance(action, types.MessageActionChannelCreate):
|
||||
event = ChatAction.Event(msg,
|
||||
created=True,
|
||||
users=msg.from_id,
|
||||
new_title=action.title)
|
||||
elif isinstance(action, types.MessageActionChatEditTitle):
|
||||
event = ChatAction.Event(msg,
|
||||
users=msg.from_id,
|
||||
new_title=action.title)
|
||||
elif isinstance(action, types.MessageActionChatEditPhoto):
|
||||
event = ChatAction.Event(msg,
|
||||
users=msg.from_id,
|
||||
new_photo=action.photo)
|
||||
elif isinstance(action, types.MessageActionChatDeletePhoto):
|
||||
event = ChatAction.Event(msg,
|
||||
users=msg.from_id,
|
||||
new_photo=True)
|
||||
elif isinstance(action, types.MessageActionPinMessage):
|
||||
# Telegram always sends this service message for new pins
|
||||
event = ChatAction.Event(msg,
|
||||
users=msg.from_id,
|
||||
new_pin=msg.reply_to_msg_id)
|
||||
else:
|
||||
return
|
||||
else:
|
||||
return
|
||||
|
||||
event._entities = update._entities
|
||||
return self._filter_event(event)
|
||||
|
||||
class Event(EventCommon):
|
||||
"""
|
||||
Represents the event of a new chat action.
|
||||
|
||||
Members:
|
||||
action_message (`MessageAction <https://lonamiwebs.github.io/Telethon/types/message_action.html>`_):
|
||||
The message invoked by this Chat Action.
|
||||
|
||||
new_pin (`bool`):
|
||||
``True`` if there is a new pin.
|
||||
|
||||
new_photo (`bool`):
|
||||
``True`` if there's a new chat photo (or it was removed).
|
||||
|
||||
photo (:tl:`Photo`, optional):
|
||||
The new photo (or ``None`` if it was removed).
|
||||
|
||||
|
||||
user_added (`bool`):
|
||||
``True`` if the user was added by some other.
|
||||
|
||||
user_joined (`bool`):
|
||||
``True`` if the user joined on their own.
|
||||
|
||||
user_left (`bool`):
|
||||
``True`` if the user left on their own.
|
||||
|
||||
user_kicked (`bool`):
|
||||
``True`` if the user was kicked by some other.
|
||||
|
||||
created (`bool`, optional):
|
||||
``True`` if this chat was just created.
|
||||
|
||||
new_title (`bool`, optional):
|
||||
The new title string for the chat, if applicable.
|
||||
|
||||
unpin (`bool`):
|
||||
``True`` if the existing pin gets unpinned.
|
||||
"""
|
||||
def __init__(self, where, new_pin=None, new_photo=None,
|
||||
added_by=None, kicked_by=None, created=None,
|
||||
users=None, new_title=None, unpin=None):
|
||||
if isinstance(where, types.MessageService):
|
||||
self.action_message = where
|
||||
where = where.to_id
|
||||
else:
|
||||
self.action_message = None
|
||||
|
||||
super().__init__(chat_peer=where, msg_id=new_pin)
|
||||
|
||||
self.new_pin = isinstance(new_pin, int)
|
||||
self._pinned_message = new_pin
|
||||
|
||||
self.new_photo = new_photo is not None
|
||||
self.photo = \
|
||||
new_photo if isinstance(new_photo, types.Photo) else None
|
||||
|
||||
self._added_by = None
|
||||
self._kicked_by = None
|
||||
self.user_added, self.user_joined, self.user_left,\
|
||||
self.user_kicked, self.unpin = (False, False, False, False, False)
|
||||
|
||||
if added_by is True:
|
||||
self.user_joined = True
|
||||
elif added_by:
|
||||
self.user_added = True
|
||||
self._added_by = added_by
|
||||
|
||||
if kicked_by is True:
|
||||
self.user_left = True
|
||||
elif kicked_by:
|
||||
self.user_kicked = True
|
||||
self._kicked_by = kicked_by
|
||||
|
||||
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
|
||||
self.unpin = unpin
|
||||
|
||||
async def respond(self, *args, **kwargs):
|
||||
"""
|
||||
Responds to the chat action message (not as a reply).
|
||||
Shorthand for ``client.send_message(event.chat, ...)``.
|
||||
"""
|
||||
return await self._client.send_message(self.input_chat, *args, **kwargs)
|
||||
|
||||
async def reply(self, *args, **kwargs):
|
||||
"""
|
||||
Replies to the chat action message (as a reply). Shorthand for
|
||||
``client.send_message(event.chat, ..., reply_to=event.message.id)``.
|
||||
|
||||
Has the same effect as ``.respond()`` if there is no message.
|
||||
"""
|
||||
if not self.action_message:
|
||||
return self.respond(*args, **kwargs)
|
||||
|
||||
kwargs['reply_to'] = self.action_message.id
|
||||
return await self._client.send_message(self.input_chat, *args, **kwargs)
|
||||
|
||||
async def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Deletes the chat action message. You're responsible for checking
|
||||
whether you have the permission to do so, or to except the error
|
||||
otherwise. This is a shorthand for
|
||||
``client.delete_messages(event.chat, event.message, ...)``.
|
||||
|
||||
Does nothing if no message action triggered this event.
|
||||
"""
|
||||
if self.action_message:
|
||||
return await self._client.delete_messages(self.input_chat,
|
||||
[self.action_message],
|
||||
*args, **kwargs)
|
||||
|
||||
@property
|
||||
async def pinned_message(self):
|
||||
"""
|
||||
If ``new_pin`` is ``True``, this returns the (:tl:`Message`)
|
||||
object that was pinned.
|
||||
"""
|
||||
if self._pinned_message == 0:
|
||||
return None
|
||||
|
||||
if isinstance(self._pinned_message, int) and self.input_chat:
|
||||
r = await self._client(functions.channels.GetMessagesRequest(
|
||||
self._input_chat, [
|
||||
types.InputMessageID(self._pinned_message)
|
||||
]
|
||||
))
|
||||
try:
|
||||
self._pinned_message = next(
|
||||
x for x in r.messages
|
||||
if isinstance(x, types.Message)
|
||||
and x.id == self._pinned_message
|
||||
)
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
if isinstance(self._pinned_message, types.Message):
|
||||
return self._pinned_message
|
||||
|
||||
@property
|
||||
async def added_by(self):
|
||||
"""
|
||||
The user who added ``users``, if applicable (``None`` otherwise).
|
||||
"""
|
||||
if self._added_by and not isinstance(self._added_by, types.User):
|
||||
self._added_by =\
|
||||
self._entities.get(utils.get_peer_id(self._added_by))
|
||||
|
||||
if not self._added_by:
|
||||
self._added_by = await self._client.get_entity(self._added_by)
|
||||
|
||||
return self._added_by
|
||||
|
||||
@property
|
||||
async def kicked_by(self):
|
||||
"""
|
||||
The user who kicked ``users``, if applicable (``None`` otherwise).
|
||||
"""
|
||||
if self._kicked_by and not isinstance(self._kicked_by, types.User):
|
||||
self._kicked_by =\
|
||||
self._entities.get(utils.get_peer_id(self._kicked_by))
|
||||
|
||||
if not self._kicked_by:
|
||||
self._kicked_by = await self._client.get_entity(self._kicked_by)
|
||||
|
||||
return self._kicked_by
|
||||
|
||||
@property
|
||||
async def user(self):
|
||||
"""
|
||||
The first user that takes part in this action (e.g. joined).
|
||||
|
||||
Might be ``None`` if the information can't be retrieved or
|
||||
there is no user taking part.
|
||||
"""
|
||||
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 user_id(self):
|
||||
"""
|
||||
Returns the marked signed ID of the first user, if any.
|
||||
"""
|
||||
if await self.input_users:
|
||||
return utils.get_peer_id(self._input_users[0])
|
||||
|
||||
@property
|
||||
async def users(self):
|
||||
"""
|
||||
A list of users that take part in this action (e.g. joined).
|
||||
|
||||
Might be empty if the information can't be retrieved or there
|
||||
are no users taking part.
|
||||
"""
|
||||
if not self._user_peers:
|
||||
return []
|
||||
|
||||
if self._users is None:
|
||||
have, missing = [], []
|
||||
for peer in self._user_peers:
|
||||
user = self._entities.get(utils.get_peer_id(peer))
|
||||
if user:
|
||||
have.append(user)
|
||||
else:
|
||||
missing.append(peer)
|
||||
|
||||
try:
|
||||
missing = await self._client.get_entity(missing)
|
||||
except (TypeError, ValueError):
|
||||
missing = []
|
||||
|
||||
self._users = have + missing
|
||||
|
||||
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
|
||||
|
||||
@property
|
||||
async def user_ids(self):
|
||||
"""
|
||||
Returns the marked signed ID of the users, if any.
|
||||
"""
|
||||
if await self.input_users:
|
||||
return [utils.get_peer_id(u) for u in self._input_users]
|
232
telethon/events/common.py
Normal file
232
telethon/events/common.py
Normal file
|
@ -0,0 +1,232 @@
|
|||
import abc
|
||||
import datetime
|
||||
import itertools
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from .. import utils
|
||||
from ..errors import RPCError
|
||||
from ..extensions import markdown
|
||||
from ..tl import TLObject, types, functions
|
||||
|
||||
|
||||
async def _into_id_set(client, chats):
|
||||
"""Helper util to turn the input chat or chats into a set of IDs."""
|
||||
if chats is None:
|
||||
return None
|
||||
|
||||
if not utils.is_list_like(chats):
|
||||
chats = (chats,)
|
||||
|
||||
result = set()
|
||||
for chat in chats:
|
||||
if isinstance(chat, int):
|
||||
if chat < 0:
|
||||
result.add(chat) # Explicitly marked IDs are negative
|
||||
else:
|
||||
result.update({ # Support all valid types of peers
|
||||
utils.get_peer_id(types.PeerUser(chat)),
|
||||
utils.get_peer_id(types.PeerChat(chat)),
|
||||
utils.get_peer_id(types.PeerChannel(chat)),
|
||||
})
|
||||
elif isinstance(chat, TLObject) and chat.SUBCLASS_OF_ID == 0x2d45687:
|
||||
# 0x2d45687 == crc32(b'Peer')
|
||||
result.add(utils.get_peer_id(chat))
|
||||
else:
|
||||
chat = await client.get_input_entity(chat)
|
||||
if isinstance(chat, types.InputPeerSelf):
|
||||
chat = await client.get_me(input_peer=True)
|
||||
result.add(utils.get_peer_id(chat))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class EventBuilder(abc.ABC):
|
||||
"""
|
||||
The common event builder, with builtin support to filter per chat.
|
||||
|
||||
Args:
|
||||
chats (`entity`, optional):
|
||||
May be one or more entities (username/peer/etc.). By default,
|
||||
only matching chats will be handled.
|
||||
|
||||
blacklist_chats (`bool`, optional):
|
||||
Whether to treat the chats as a blacklist instead of
|
||||
as a whitelist (default). This means that every chat
|
||||
will be handled *except* those specified in ``chats``
|
||||
which will be ignored if ``blacklist_chats=True``.
|
||||
"""
|
||||
def __init__(self, chats=None, blacklist_chats=False):
|
||||
self.chats = chats
|
||||
self.blacklist_chats = blacklist_chats
|
||||
self._self_id = None
|
||||
|
||||
@abc.abstractmethod
|
||||
def build(self, update):
|
||||
"""Builds an event for the given update if possible, or returns None"""
|
||||
|
||||
async def resolve(self, client):
|
||||
"""Helper method to allow event builders to be resolved before usage"""
|
||||
self.chats = await _into_id_set(client, self.chats)
|
||||
self._self_id = await client.get_me(input_peer=True).user_id
|
||||
|
||||
def _filter_event(self, event):
|
||||
"""
|
||||
If the ID of ``event._chat_peer`` isn't in the chats set (or it is
|
||||
but the set is a blacklist) returns ``None``, otherwise the event.
|
||||
"""
|
||||
if self.chats is not None:
|
||||
inside = utils.get_peer_id(event._chat_peer) in self.chats
|
||||
if inside == self.blacklist_chats:
|
||||
# If this chat matches but it's a blacklist ignore.
|
||||
# If it doesn't match but it's a whitelist ignore.
|
||||
return None
|
||||
return event
|
||||
|
||||
|
||||
class EventCommon(abc.ABC):
|
||||
"""Intermediate class with common things to all events"""
|
||||
_event_name = 'Event'
|
||||
|
||||
def __init__(self, chat_peer=None, msg_id=None, broadcast=False):
|
||||
self._entities = {}
|
||||
self._client = None
|
||||
self._chat_peer = chat_peer
|
||||
self._message_id = msg_id
|
||||
self._input_chat = None
|
||||
self._chat = None
|
||||
|
||||
self.pattern_match = None
|
||||
|
||||
self.is_private = isinstance(chat_peer, types.PeerUser)
|
||||
self.is_group = (
|
||||
isinstance(chat_peer, (types.PeerChat, types.PeerChannel))
|
||||
and not broadcast
|
||||
)
|
||||
self.is_channel = isinstance(chat_peer, types.PeerChannel)
|
||||
|
||||
async def _get_entity(self, msg_id, entity_id, chat=None):
|
||||
"""
|
||||
Helper function to call :tl:`GetMessages` on the give msg_id and
|
||||
return the input entity whose ID is the given entity ID.
|
||||
|
||||
If ``chat`` is present it must be an :tl:`InputPeer`.
|
||||
|
||||
Returns a tuple of ``(entity, input_peer)`` if it was found, or
|
||||
a tuple of ``(None, None)`` if it couldn't be.
|
||||
"""
|
||||
try:
|
||||
if isinstance(chat, types.InputPeerChannel):
|
||||
result = await self._client(
|
||||
functions.channels.GetMessagesRequest(chat, [
|
||||
types.InputMessageID(msg_id)
|
||||
])
|
||||
)
|
||||
else:
|
||||
result = await self._client(
|
||||
functions.messages.GetMessagesRequest([
|
||||
types.InputMessageID(msg_id)
|
||||
])
|
||||
)
|
||||
except RPCError:
|
||||
return None, None
|
||||
|
||||
entity = {
|
||||
utils.get_peer_id(x): x for x in itertools.chain(
|
||||
getattr(result, 'chats', []),
|
||||
getattr(result, 'users', []))
|
||||
}.get(entity_id)
|
||||
if entity:
|
||||
return entity, utils.get_input_peer(entity)
|
||||
else:
|
||||
return None, None
|
||||
|
||||
@property
|
||||
async def input_chat(self):
|
||||
"""
|
||||
The (:tl:`InputPeer`) (group, megagroup or channel) on which
|
||||
the event occurred. This doesn't have the title or anything,
|
||||
but is useful if you don't need those to avoid further
|
||||
requests.
|
||||
|
||||
Note that this might be ``None`` if the library can't find it.
|
||||
"""
|
||||
|
||||
if self._input_chat is None and self._chat_peer is not None:
|
||||
try:
|
||||
self._input_chat = await self._client.get_input_entity(
|
||||
self._chat_peer
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
# The library hasn't seen this chat, get the message
|
||||
if not isinstance(self._chat_peer, types.PeerChannel):
|
||||
# TODO For channels, getDifference? Maybe looking
|
||||
# in the dialogs (which is already done) is enough.
|
||||
if self._message_id is not None:
|
||||
self._chat, self._input_chat = await self._get_entity(
|
||||
self._message_id,
|
||||
utils.get_peer_id(self._chat_peer)
|
||||
)
|
||||
return self._input_chat
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
return self._client
|
||||
|
||||
@property
|
||||
async def chat(self):
|
||||
"""
|
||||
The (:tl:`User` | :tl:`Chat` | :tl:`Channel`, optional) on which
|
||||
the event occurred. This property may make an API call the first time
|
||||
to get the most up to date version of the chat (mostly when the event
|
||||
doesn't belong to a channel), so keep that in mind.
|
||||
"""
|
||||
if not await self.input_chat:
|
||||
return None
|
||||
|
||||
if self._chat is None:
|
||||
self._chat = self._entities.get(utils.get_peer_id(self._input_chat))
|
||||
|
||||
if self._chat is None:
|
||||
self._chat = await self._client.get_entity(self._input_chat)
|
||||
|
||||
return self._chat
|
||||
|
||||
@property
|
||||
def chat_id(self):
|
||||
"""
|
||||
Returns the marked integer ID of the chat, if any.
|
||||
"""
|
||||
if self._chat_peer:
|
||||
return utils.get_peer_id(self._chat_peer)
|
||||
|
||||
def __str__(self):
|
||||
return TLObject.pretty_format(self.to_dict())
|
||||
|
||||
def stringify(self):
|
||||
return TLObject.pretty_format(self.to_dict(), indent=0)
|
||||
|
||||
def to_dict(self):
|
||||
d = {k: v for k, v in self.__dict__.items() if k[0] != '_'}
|
||||
d['_'] = self._event_name
|
||||
return d
|
||||
|
||||
|
||||
class Raw(EventBuilder):
|
||||
"""
|
||||
Represents a raw event. The event is the update itself.
|
||||
"""
|
||||
async def resolve(self, client):
|
||||
pass
|
||||
|
||||
def build(self, update):
|
||||
return update
|
||||
|
||||
|
||||
def name_inner_event(cls):
|
||||
"""Decorator to rename cls.Event 'Event' as 'cls.Event'"""
|
||||
if hasattr(cls, 'Event'):
|
||||
cls.Event._event_name = '{}.Event'.format(cls.__name__)
|
||||
else:
|
||||
warnings.warn('Class {} does not have a inner Event'.format(cls))
|
||||
return cls
|
33
telethon/events/messagedeleted.py
Normal file
33
telethon/events/messagedeleted.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from ..tl import types
|
||||
|
||||
|
||||
@name_inner_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 = MessageDeleted.Event(
|
||||
deleted_ids=update.messages,
|
||||
peer=types.PeerChannel(update.channel_id)
|
||||
)
|
||||
else:
|
||||
return
|
||||
|
||||
event._entities = update._entities
|
||||
return self._filter_event(event)
|
||||
|
||||
class Event(EventCommon):
|
||||
def __init__(self, deleted_ids, peer):
|
||||
super().__init__(
|
||||
chat_peer=peer, msg_id=(deleted_ids or [0])[0]
|
||||
)
|
||||
self.deleted_id = None if not deleted_ids else deleted_ids[0]
|
||||
self.deleted_ids = deleted_ids
|
22
telethon/events/messageedited.py
Normal file
22
telethon/events/messageedited.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from .common import name_inner_event
|
||||
from .newmessage import NewMessage
|
||||
from ..tl import types
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class MessageEdited(NewMessage):
|
||||
"""
|
||||
Event fired when a message has been edited.
|
||||
"""
|
||||
def build(self, update):
|
||||
if isinstance(update, (types.UpdateEditMessage,
|
||||
types.UpdateEditChannelMessage)):
|
||||
event = MessageEdited.Event(update.message)
|
||||
else:
|
||||
return
|
||||
|
||||
event._entities = update._entities
|
||||
return self._message_filter_event(event)
|
||||
|
||||
class Event(NewMessage.Event):
|
||||
pass # Required if we want a different name for it
|
137
telethon/events/messageread.py
Normal file
137
telethon/events/messageread.py
Normal file
|
@ -0,0 +1,137 @@
|
|||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from .. import utils
|
||||
from ..tl import types, functions
|
||||
|
||||
|
||||
@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)
|
419
telethon/events/newmessage.py
Normal file
419
telethon/events/newmessage.py
Normal file
|
@ -0,0 +1,419 @@
|
|||
import abc
|
||||
import datetime
|
||||
import itertools
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from .. import utils
|
||||
from ..errors import RPCError
|
||||
from ..extensions import markdown
|
||||
from ..tl import TLObject, types, functions
|
||||
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class NewMessage(EventBuilder):
|
||||
"""
|
||||
Represents a new message event builder.
|
||||
|
||||
Args:
|
||||
incoming (`bool`, optional):
|
||||
If set to ``True``, only **incoming** messages will be handled.
|
||||
Mutually exclusive with ``outgoing`` (can only set one of either).
|
||||
|
||||
outgoing (`bool`, optional):
|
||||
If set to ``True``, only **outgoing** messages will be handled.
|
||||
Mutually exclusive with ``incoming`` (can only set one of either).
|
||||
|
||||
pattern (`str`, `callable`, `Pattern`, optional):
|
||||
If set, only messages matching this pattern will be handled.
|
||||
You can specify a regex-like string which will be matched
|
||||
against the message, a callable function that returns ``True``
|
||||
if a message is acceptable, or a compiled regex pattern.
|
||||
"""
|
||||
def __init__(self, incoming=None, outgoing=None,
|
||||
chats=None, blacklist_chats=False, pattern=None):
|
||||
if incoming and outgoing:
|
||||
raise ValueError('Can only set either incoming or outgoing')
|
||||
|
||||
super().__init__(chats=chats, blacklist_chats=blacklist_chats)
|
||||
self.incoming = incoming
|
||||
self.outgoing = outgoing
|
||||
if isinstance(pattern, str):
|
||||
self.pattern = re.compile(pattern).match
|
||||
elif not pattern or callable(pattern):
|
||||
self.pattern = pattern
|
||||
elif hasattr(pattern, 'match') and callable(pattern.match):
|
||||
self.pattern = pattern.match
|
||||
else:
|
||||
raise TypeError('Invalid pattern type given')
|
||||
|
||||
def build(self, update):
|
||||
if isinstance(update,
|
||||
(types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
||||
if not isinstance(update.message, types.Message):
|
||||
return # We don't care about MessageService's here
|
||||
event = NewMessage.Event(update.message)
|
||||
elif isinstance(update, types.UpdateShortMessage):
|
||||
event = NewMessage.Event(types.Message(
|
||||
out=update.out,
|
||||
mentioned=update.mentioned,
|
||||
media_unread=update.media_unread,
|
||||
silent=update.silent,
|
||||
id=update.id,
|
||||
to_id=types.PeerUser(update.user_id),
|
||||
from_id=self._self_id if update.out else update.user_id,
|
||||
message=update.message,
|
||||
date=update.date,
|
||||
fwd_from=update.fwd_from,
|
||||
via_bot_id=update.via_bot_id,
|
||||
reply_to_msg_id=update.reply_to_msg_id,
|
||||
entities=update.entities
|
||||
))
|
||||
elif isinstance(update, types.UpdateShortChatMessage):
|
||||
event = NewMessage.Event(types.Message(
|
||||
out=update.out,
|
||||
mentioned=update.mentioned,
|
||||
media_unread=update.media_unread,
|
||||
silent=update.silent,
|
||||
id=update.id,
|
||||
from_id=update.from_id,
|
||||
to_id=types.PeerChat(update.chat_id),
|
||||
message=update.message,
|
||||
date=update.date,
|
||||
fwd_from=update.fwd_from,
|
||||
via_bot_id=update.via_bot_id,
|
||||
reply_to_msg_id=update.reply_to_msg_id,
|
||||
entities=update.entities
|
||||
))
|
||||
else:
|
||||
return
|
||||
|
||||
event._entities = update._entities
|
||||
return self._message_filter_event(event)
|
||||
|
||||
def _message_filter_event(self, event):
|
||||
# Short-circuit if we let pass all events
|
||||
if all(x is None for x in (self.incoming, self.outgoing, self.chats,
|
||||
self.pattern)):
|
||||
return event
|
||||
|
||||
if self.incoming and event.message.out:
|
||||
return
|
||||
if self.outgoing and not event.message.out:
|
||||
return
|
||||
|
||||
if self.pattern:
|
||||
match = self.pattern(event.message.message or '')
|
||||
if not match:
|
||||
return
|
||||
event.pattern_match = match
|
||||
|
||||
return self._filter_event(event)
|
||||
|
||||
class Event(EventCommon):
|
||||
"""
|
||||
Represents the event of a new message.
|
||||
|
||||
Members:
|
||||
message (:tl:`Message`):
|
||||
This is the original :tl:`Message` object.
|
||||
|
||||
is_private (`bool`):
|
||||
True if the message was sent as a private message.
|
||||
|
||||
is_group (`bool`):
|
||||
True if the message was sent on a group or megagroup.
|
||||
|
||||
is_channel (`bool`):
|
||||
True if the message was sent on a megagroup or channel.
|
||||
|
||||
is_reply (`str`):
|
||||
Whether the message is a reply to some other or not.
|
||||
"""
|
||||
def __init__(self, message):
|
||||
if not message.out and isinstance(message.to_id, types.PeerUser):
|
||||
# Incoming message (e.g. from a bot) has to_id=us, and
|
||||
# from_id=bot (the actual "chat" from an user's perspective).
|
||||
chat_peer = types.PeerUser(message.from_id)
|
||||
else:
|
||||
chat_peer = message.to_id
|
||||
|
||||
super().__init__(chat_peer=chat_peer,
|
||||
msg_id=message.id, broadcast=bool(message.post))
|
||||
|
||||
self.message = message
|
||||
self._text = None
|
||||
|
||||
self._input_sender = None
|
||||
self._sender = None
|
||||
|
||||
self.is_reply = bool(message.reply_to_msg_id)
|
||||
self._reply_message = None
|
||||
|
||||
async def respond(self, *args, **kwargs):
|
||||
"""
|
||||
Responds to the message (not as a reply). This is a shorthand for
|
||||
``client.send_message(event.chat, ...)``.
|
||||
"""
|
||||
return await self._client.send_message(self.input_chat, *args, **kwargs)
|
||||
|
||||
async def reply(self, *args, **kwargs):
|
||||
"""
|
||||
Replies to the message (as a reply). This is a shorthand for
|
||||
``client.send_message(event.chat, ..., reply_to=event.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
|
||||
``client.edit_message(event.chat, event.message, ...)``.
|
||||
|
||||
Returns ``None`` if the message was incoming,
|
||||
or the edited message otherwise.
|
||||
"""
|
||||
if self.message.fwd_from:
|
||||
return None
|
||||
if not self.message.out:
|
||||
if not isinstance(self.message.to_id, types.PeerUser):
|
||||
return None
|
||||
me = await self._client.get_me(input_peer=True)
|
||||
if self.message.to_id.user_id != me.user_id:
|
||||
return None
|
||||
|
||||
return await self._client.edit_message(self.input_chat,
|
||||
self.message,
|
||||
*args, **kwargs)
|
||||
|
||||
async def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Deletes the message. You're responsible for checking whether you
|
||||
have the permission to do so, or to except the error otherwise.
|
||||
This is a shorthand for
|
||||
``client.delete_messages(event.chat, event.message, ...)``.
|
||||
"""
|
||||
return await self._client.delete_messages(self.input_chat,
|
||||
[self.message],
|
||||
*args, **kwargs)
|
||||
|
||||
@property
|
||||
async def input_sender(self):
|
||||
"""
|
||||
This (:tl:`InputPeer`) is the input version of the user who
|
||||
sent the message. Similarly to ``input_chat``, this doesn't have
|
||||
things like username or similar, but still useful in some cases.
|
||||
|
||||
Note that this might not be available if the library can't
|
||||
find the input chat, or if the message a broadcast on a channel.
|
||||
"""
|
||||
if self._input_sender is None:
|
||||
if self.is_channel and not self.is_group:
|
||||
return None
|
||||
|
||||
try:
|
||||
self._input_sender = await self._client.get_input_entity(
|
||||
self.message.from_id
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
# We can rely on self.input_chat for this
|
||||
self._sender, self._input_sender = self._get_entity(
|
||||
self.message.id,
|
||||
self.message.from_id,
|
||||
chat=self.input_chat
|
||||
)
|
||||
|
||||
return self._input_sender
|
||||
|
||||
@property
|
||||
async def sender(self):
|
||||
"""
|
||||
This (:tl:`User`) may make an API call the first time to get
|
||||
the most up to date version of the sender (mostly when the event
|
||||
doesn't belong to a channel), so keep that in mind.
|
||||
|
||||
``input_sender`` needs to be available (often the case).
|
||||
"""
|
||||
if not await self.input_sender:
|
||||
return None
|
||||
|
||||
if self._sender is None:
|
||||
self._sender = \
|
||||
self._entities.get(utils.get_peer_id(self._input_sender))
|
||||
|
||||
if self._sender is None:
|
||||
self._sender = await self._client.get_entity(self._input_sender)
|
||||
|
||||
return self._sender
|
||||
|
||||
@property
|
||||
def sender_id(self):
|
||||
"""
|
||||
Returns the marked sender integer ID, if present.
|
||||
"""
|
||||
if self.input_sender:
|
||||
return utils.get_peer_id(self._input_sender)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
The message text, markdown-formatted.
|
||||
"""
|
||||
if self._text is None:
|
||||
if not self.message.entities:
|
||||
return self.message.message
|
||||
self._text = markdown.unparse(self.message.message,
|
||||
self.message.entities or [])
|
||||
return self._text
|
||||
|
||||
@property
|
||||
def raw_text(self):
|
||||
"""
|
||||
The raw message text, ignoring any formatting.
|
||||
"""
|
||||
return self.message.message
|
||||
|
||||
@property
|
||||
async def reply_message(self):
|
||||
"""
|
||||
This optional :tl:`Message` will make an API call the first
|
||||
time to get the full :tl:`Message` object that one was replying to,
|
||||
so use with care as there is no caching besides local caching yet.
|
||||
"""
|
||||
if not self.message.reply_to_msg_id:
|
||||
return None
|
||||
|
||||
if self._reply_message is None:
|
||||
if isinstance(self.input_chat, types.InputPeerChannel):
|
||||
r = await self._client(functions.channels.GetMessagesRequest(
|
||||
self.input_chat, [
|
||||
types.InputMessageID(self.message.reply_to_msg_id)
|
||||
]
|
||||
))
|
||||
else:
|
||||
r = await self._client(functions.messages.GetMessagesRequest(
|
||||
[types.InputMessageID(self.message.reply_to_msg_id)]
|
||||
))
|
||||
if not isinstance(r, types.messages.MessagesNotModified):
|
||||
self._reply_message = r.messages[0]
|
||||
|
||||
return self._reply_message
|
||||
|
||||
@property
|
||||
def forward(self):
|
||||
"""
|
||||
The unmodified :tl:`MessageFwdHeader`, if present..
|
||||
"""
|
||||
return self.message.fwd_from
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
"""
|
||||
The unmodified :tl:`MessageMedia`, if present.
|
||||
"""
|
||||
return self.message.media
|
||||
|
||||
@property
|
||||
def photo(self):
|
||||
"""
|
||||
If the message media is a photo,
|
||||
this returns the :tl:`Photo` object.
|
||||
"""
|
||||
if isinstance(self.message.media, types.MessageMediaPhoto):
|
||||
photo = self.message.media.photo
|
||||
if isinstance(photo, types.Photo):
|
||||
return photo
|
||||
|
||||
@property
|
||||
def document(self):
|
||||
"""
|
||||
If the message media is a document,
|
||||
this returns the :tl:`Document` object.
|
||||
"""
|
||||
if isinstance(self.message.media, types.MessageMediaDocument):
|
||||
doc = self.message.media.document
|
||||
if isinstance(doc, types.Document):
|
||||
return doc
|
||||
|
||||
def _document_by_attribute(self, kind, condition=None):
|
||||
"""
|
||||
Helper method to return the document only if it has an attribute
|
||||
that's an instance of the given kind, and passes the condition.
|
||||
"""
|
||||
doc = self.document
|
||||
if doc:
|
||||
for attr in doc.attributes:
|
||||
if isinstance(attr, kind):
|
||||
if not condition or condition(doc):
|
||||
return doc
|
||||
|
||||
@property
|
||||
def audio(self):
|
||||
"""
|
||||
If the message media is a document with an Audio attribute,
|
||||
this returns the :tl:`Document` object.
|
||||
"""
|
||||
return self._document_by_attribute(types.DocumentAttributeAudio,
|
||||
lambda attr: not attr.voice)
|
||||
|
||||
@property
|
||||
def voice(self):
|
||||
"""
|
||||
If the message media is a document with a Voice attribute,
|
||||
this returns the :tl:`Document` object.
|
||||
"""
|
||||
return self._document_by_attribute(types.DocumentAttributeAudio,
|
||||
lambda attr: attr.voice)
|
||||
|
||||
@property
|
||||
def video(self):
|
||||
"""
|
||||
If the message media is a document with a Video attribute,
|
||||
this returns the :tl:`Document` object.
|
||||
"""
|
||||
return self._document_by_attribute(types.DocumentAttributeVideo)
|
||||
|
||||
@property
|
||||
def video_note(self):
|
||||
"""
|
||||
If the message media is a document with a Video attribute,
|
||||
this returns the :tl:`Document` object.
|
||||
"""
|
||||
return self._document_by_attribute(types.DocumentAttributeVideo,
|
||||
lambda attr: attr.round_message)
|
||||
|
||||
@property
|
||||
def gif(self):
|
||||
"""
|
||||
If the message media is a document with an Animated attribute,
|
||||
this returns the :tl:`Document` object.
|
||||
"""
|
||||
return self._document_by_attribute(types.DocumentAttributeAnimated)
|
||||
|
||||
@property
|
||||
def sticker(self):
|
||||
"""
|
||||
If the message media is a document with a Sticker attribute,
|
||||
this returns the :tl:`Document` object.
|
||||
"""
|
||||
return self._document_by_attribute(types.DocumentAttributeSticker)
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
"""
|
||||
Whether the message is outgoing (i.e. you sent it from
|
||||
another session) or incoming (i.e. someone else sent it).
|
||||
"""
|
||||
return self.message.out
|
163
telethon/events/userupdate.py
Normal file
163
telethon/events/userupdate.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
import datetime
|
||||
|
||||
from .common import EventBuilder, EventCommon, name_inner_event
|
||||
from ..tl import types
|
||||
|
||||
|
||||
@name_inner_event
|
||||
class UserUpdate(EventBuilder):
|
||||
"""
|
||||
Represents an user update (gone online, offline, joined Telegram).
|
||||
"""
|
||||
def build(self, update):
|
||||
if isinstance(update, types.UpdateUserStatus):
|
||||
event = UserUpdate.Event(update.user_id,
|
||||
status=update.status)
|
||||
else:
|
||||
return
|
||||
|
||||
event._entities = update._entities
|
||||
return self._filter_event(event)
|
||||
|
||||
class Event(EventCommon):
|
||||
"""
|
||||
Represents the event of an user status update (last seen, joined).
|
||||
|
||||
Members:
|
||||
online (`bool`, optional):
|
||||
``True`` if the user is currently online, ``False`` otherwise.
|
||||
Might be ``None`` if this information is not present.
|
||||
|
||||
last_seen (`datetime`, optional):
|
||||
Exact date when the user was last seen if known.
|
||||
|
||||
until (`datetime`, optional):
|
||||
Until when will the user remain online.
|
||||
|
||||
within_months (`bool`):
|
||||
``True`` if the user was seen within 30 days.
|
||||
|
||||
within_weeks (`bool`):
|
||||
``True`` if the user was seen within 7 days.
|
||||
|
||||
recently (`bool`):
|
||||
``True`` if the user was seen within a day.
|
||||
|
||||
action (:tl:`SendMessageAction`, optional):
|
||||
The "typing" action if any the user is performing if any.
|
||||
|
||||
cancel (`bool`):
|
||||
``True`` if the action was cancelling other actions.
|
||||
|
||||
typing (`bool`):
|
||||
``True`` if the action is typing a message.
|
||||
|
||||
recording (`bool`):
|
||||
``True`` if the action is recording something.
|
||||
|
||||
uploading (`bool`):
|
||||
``True`` if the action is uploading something.
|
||||
|
||||
playing (`bool`):
|
||||
``True`` if the action is playing a game.
|
||||
|
||||
audio (`bool`):
|
||||
``True`` if what's being recorded/uploaded is an audio.
|
||||
|
||||
round (`bool`):
|
||||
``True`` if what's being recorded/uploaded is a round video.
|
||||
|
||||
video (`bool`):
|
||||
``True`` if what's being recorded/uploaded is an video.
|
||||
|
||||
document (`bool`):
|
||||
``True`` if what's being uploaded is document.
|
||||
|
||||
geo (`bool`):
|
||||
``True`` if what's being uploaded is a geo.
|
||||
|
||||
photo (`bool`):
|
||||
``True`` if what's being uploaded is a photo.
|
||||
|
||||
contact (`bool`):
|
||||
``True`` if what's being uploaded (selected) is a contact.
|
||||
"""
|
||||
def __init__(self, user_id, status=None, typing=None):
|
||||
super().__init__(types.PeerUser(user_id))
|
||||
|
||||
self.online = None if status is None else \
|
||||
isinstance(status, types.UserStatusOnline)
|
||||
|
||||
self.last_seen = status.was_online if \
|
||||
isinstance(status, types.UserStatusOffline) else None
|
||||
|
||||
self.until = status.expires if \
|
||||
isinstance(status, types.UserStatusOnline) else None
|
||||
|
||||
if self.last_seen:
|
||||
diff = datetime.datetime.now() - self.last_seen
|
||||
if diff < datetime.timedelta(days=30):
|
||||
self.within_months = True
|
||||
if diff < datetime.timedelta(days=7):
|
||||
self.within_weeks = True
|
||||
if diff < datetime.timedelta(days=1):
|
||||
self.recently = True
|
||||
else:
|
||||
self.within_months = self.within_weeks = self.recently = False
|
||||
if isinstance(status, (types.UserStatusOnline,
|
||||
types.UserStatusRecently)):
|
||||
self.within_months = self.within_weeks = True
|
||||
self.recently = True
|
||||
elif isinstance(status, types.UserStatusLastWeek):
|
||||
self.within_months = self.within_weeks = True
|
||||
elif isinstance(status, types.UserStatusLastMonth):
|
||||
self.within_months = True
|
||||
|
||||
self.action = typing
|
||||
if typing:
|
||||
self.cancel = self.typing = self.recording = self.uploading = \
|
||||
self.playing = False
|
||||
self.audio = self.round = self.video = self.document = \
|
||||
self.geo = self.photo = self.contact = False
|
||||
|
||||
if isinstance(typing, types.SendMessageCancelAction):
|
||||
self.cancel = True
|
||||
elif isinstance(typing, types.SendMessageTypingAction):
|
||||
self.typing = True
|
||||
elif isinstance(typing, types.SendMessageGamePlayAction):
|
||||
self.playing = True
|
||||
elif isinstance(typing, types.SendMessageGeoLocationAction):
|
||||
self.geo = True
|
||||
elif isinstance(typing, types.SendMessageRecordAudioAction):
|
||||
self.recording = self.audio = True
|
||||
elif isinstance(typing, types.SendMessageRecordRoundAction):
|
||||
self.recording = self.round = True
|
||||
elif isinstance(typing, types.SendMessageRecordVideoAction):
|
||||
self.recording = self.video = True
|
||||
elif isinstance(typing, types.SendMessageChooseContactAction):
|
||||
self.uploading = self.contact = True
|
||||
elif isinstance(typing, types.SendMessageUploadAudioAction):
|
||||
self.uploading = self.audio = True
|
||||
elif isinstance(typing, types.SendMessageUploadDocumentAction):
|
||||
self.uploading = self.document = True
|
||||
elif isinstance(typing, types.SendMessageUploadPhotoAction):
|
||||
self.uploading = self.photo = True
|
||||
elif isinstance(typing, types.SendMessageUploadRoundAction):
|
||||
self.uploading = self.round = True
|
||||
elif isinstance(typing, types.SendMessageUploadVideoAction):
|
||||
self.uploading = self.video = True
|
||||
|
||||
@property
|
||||
async def user(self):
|
||||
"""Alias around the chat (conversation)."""
|
||||
return await self.chat
|
||||
|
||||
@property
|
||||
async def input_user(self):
|
||||
"""Alias around the input chat."""
|
||||
return await self.input_chat
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
"""Alias around `chat_id`."""
|
||||
return self.chat_id
|
|
@ -14,6 +14,7 @@ from io import BytesIO
|
|||
from mimetypes import guess_type
|
||||
|
||||
from .crypto import CdnDecrypter
|
||||
from .tl import TLObject
|
||||
from .tl.custom import InputSizedFile
|
||||
from .tl.functions.upload import (
|
||||
SaveBigFilePartRequest, SaveFilePartRequest, GetFileRequest
|
||||
|
@ -38,8 +39,7 @@ from .errors import (
|
|||
RPCError, UnauthorizedError, PhoneCodeEmptyError, PhoneCodeExpiredError,
|
||||
PhoneCodeHashEmptyError, PhoneCodeInvalidError, LocationInvalidError,
|
||||
SessionPasswordNeededError, FileMigrateError, PhoneNumberUnoccupiedError,
|
||||
PhoneNumberOccupiedError, EmailUnconfirmedError, PasswordEmptyError,
|
||||
UsernameNotOccupiedError
|
||||
PhoneNumberOccupiedError, UsernameNotOccupiedError
|
||||
)
|
||||
from .network import ConnectionMode
|
||||
from .tl.custom import Draft, Dialog
|
||||
|
@ -84,7 +84,8 @@ from .tl.types import (
|
|||
InputMessageEntityMentionName, DocumentAttributeVideo,
|
||||
UpdateEditMessage, UpdateEditChannelMessage, UpdateShort, Updates,
|
||||
MessageMediaWebPage, ChannelParticipantsSearch, PhotoSize, PhotoCachedSize,
|
||||
PhotoSizeEmpty, MessageService, ChatParticipants
|
||||
PhotoSizeEmpty, MessageService, ChatParticipants,
|
||||
ChannelParticipantsBanned, ChannelParticipantsKicked
|
||||
)
|
||||
from .tl.types.messages import DialogsSlice
|
||||
from .tl.types.account import PasswordInputSettings, NoPassword
|
||||
|
@ -354,7 +355,14 @@ class TelegramClient(TelegramBareClient):
|
|||
me = await self.sign_in(phone=phone, password=password)
|
||||
|
||||
# We won't reach here if any step failed (exit by exception)
|
||||
print('Signed in successfully as', utils.get_display_name(me))
|
||||
signed, name = 'Signed in successfully as', utils.get_display_name(me)
|
||||
try:
|
||||
print(signed, name)
|
||||
except UnicodeEncodeError:
|
||||
# Some terminals don't support certain characters
|
||||
print(signed, name.encode('utf-8', errors='ignore')
|
||||
.decode('ascii', errors='ignore'))
|
||||
|
||||
await self._check_events_pending_resolve()
|
||||
return self
|
||||
|
||||
|
@ -712,6 +720,13 @@ class TelegramClient(TelegramBareClient):
|
|||
"""
|
||||
Sends the given message to the specified entity (user/chat/channel).
|
||||
|
||||
The default parse mode is the same as the official applications
|
||||
(a custom flavour of markdown). ``**bold**, `code` or __italic__``
|
||||
are available. In addition you can send ``[links](https://example.com)``
|
||||
and ``[mentions](@username)`` (or using IDs like in the Bot API:
|
||||
``[mention](tg://user?id=123456789)``) and ``pre`` blocks with three
|
||||
backticks.
|
||||
|
||||
Args:
|
||||
entity (`entity`):
|
||||
To who will it be sent.
|
||||
|
@ -820,9 +835,11 @@ class TelegramClient(TelegramBareClient):
|
|||
order for the forward to work.
|
||||
|
||||
Returns:
|
||||
The list of forwarded :tl:`Message`.
|
||||
The list of forwarded :tl:`Message`, or a single one if a list
|
||||
wasn't provided as input.
|
||||
"""
|
||||
if not utils.is_list_like(messages):
|
||||
single = not utils.is_list_like(messages)
|
||||
if single:
|
||||
messages = (messages,)
|
||||
|
||||
if not from_peer:
|
||||
|
@ -853,7 +870,8 @@ class TelegramClient(TelegramBareClient):
|
|||
elif isinstance(update, (UpdateNewMessage, UpdateNewChannelMessage)):
|
||||
id_to_message[update.message.id] = update.message
|
||||
|
||||
return [id_to_message[random_to_id[rnd]] for rnd in req.random_id]
|
||||
result = [id_to_message[random_to_id[rnd]] for rnd in req.random_id]
|
||||
return result[0] if single else result
|
||||
|
||||
async def edit_message(self, entity, message_id, message=None,
|
||||
parse_mode='md', link_preview=True):
|
||||
|
@ -1205,7 +1223,12 @@ class TelegramClient(TelegramBareClient):
|
|||
or :tl:`ChatParticipants` for normal chats.
|
||||
"""
|
||||
if isinstance(filter, type):
|
||||
filter = filter()
|
||||
if filter in (ChannelParticipantsBanned, ChannelParticipantsKicked,
|
||||
ChannelParticipantsSearch):
|
||||
# These require a `q` parameter (support types for convenience)
|
||||
filter = filter('')
|
||||
else:
|
||||
filter = filter()
|
||||
|
||||
entity = await self.get_input_entity(entity)
|
||||
if search and (filter or not isinstance(entity, InputPeerChannel)):
|
||||
|
@ -1404,7 +1427,8 @@ class TelegramClient(TelegramBareClient):
|
|||
it will be used to determine metadata from audio and video files.
|
||||
|
||||
Returns:
|
||||
The :tl:`Message` (or messages) containing the sent file.
|
||||
The :tl:`Message` (or messages) containing the sent file,
|
||||
or messages if a list of them was passed.
|
||||
"""
|
||||
# First check if the user passed an iterable, in which case
|
||||
# we may want to send as an album if all are photo files.
|
||||
|
@ -2255,8 +2279,10 @@ class TelegramClient(TelegramBareClient):
|
|||
if event and not isinstance(event, type):
|
||||
event = type(event)
|
||||
|
||||
for i, ec in enumerate(self._event_builders):
|
||||
ev, cb = ec
|
||||
i = len(self._event_builders)
|
||||
while i:
|
||||
i -= 1
|
||||
ev, cb = self._event_builders[i]
|
||||
if cb == callback and (not event or isinstance(ev, event)):
|
||||
del self._event_builders[i]
|
||||
found += 1
|
||||
|
@ -2313,13 +2339,11 @@ class TelegramClient(TelegramBareClient):
|
|||
error will be raised.
|
||||
|
||||
Returns:
|
||||
:tl:`User`, :tl:`Chat` or :tl:`Channel` corresponding to the input
|
||||
entity.
|
||||
:tl:`User`, :tl:`Chat` or :tl:`Channel` corresponding to the
|
||||
input entity. A list will be returned if more than one was given.
|
||||
"""
|
||||
if utils.is_list_like(entity):
|
||||
single = False
|
||||
else:
|
||||
single = True
|
||||
single = not utils.is_list_like(entity)
|
||||
if single:
|
||||
entity = (entity,)
|
||||
|
||||
# Group input entities by string (resolve username),
|
||||
|
@ -2443,18 +2467,12 @@ class TelegramClient(TelegramBareClient):
|
|||
if isinstance(peer, str):
|
||||
return utils.get_input_peer(await self._get_entity_from_string(peer))
|
||||
|
||||
original_peer = peer
|
||||
if not isinstance(peer, int):
|
||||
try:
|
||||
if peer.SUBCLASS_OF_ID != 0x2d45687: # crc32(b'Peer')
|
||||
return utils.get_input_peer(peer)
|
||||
except (AttributeError, TypeError):
|
||||
peer = None
|
||||
|
||||
if not peer:
|
||||
raise TypeError(
|
||||
'Cannot turn "{}" into an input entity.'.format(original_peer)
|
||||
)
|
||||
if not isinstance(peer, int) and (not isinstance(peer, TLObject)
|
||||
or peer.SUBCLASS_OF_ID != 0x2d45687):
|
||||
# Try casting the object into an input peer. Might TypeError.
|
||||
# Don't do it if a not-found ID was given (instead ValueError).
|
||||
# Also ignore Peer (0x2d45687 == crc32(b'Peer'))'s, lacking hash.
|
||||
return utils.get_input_peer(peer)
|
||||
|
||||
raise ValueError(
|
||||
'Could not find the input entity corresponding to "{}". '
|
||||
|
|
|
@ -91,7 +91,11 @@ def get_input_peer(entity, allow_self=True):
|
|||
if entity.SUBCLASS_OF_ID == 0xc91c90b6: # crc32(b'InputPeer')
|
||||
return entity
|
||||
except AttributeError:
|
||||
_raise_cast_fail(entity, 'InputPeer')
|
||||
if hasattr(entity, 'input_entity'):
|
||||
# e.g. custom.Dialog (can't cyclic import)
|
||||
return entity.input_entity
|
||||
else:
|
||||
_raise_cast_fail(entity, 'InputPeer')
|
||||
|
||||
if isinstance(entity, User):
|
||||
if entity.is_self and allow_self:
|
||||
|
@ -105,7 +109,6 @@ def get_input_peer(entity, allow_self=True):
|
|||
if isinstance(entity, (Channel, ChannelForbidden)):
|
||||
return InputPeerChannel(entity.id, entity.access_hash or 0)
|
||||
|
||||
# Less common cases
|
||||
if isinstance(entity, InputUser):
|
||||
return InputPeerUser(entity.user_id, entity.access_hash)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user