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
06af73ed65
|
@ -65,7 +65,7 @@ if you're new with ``asyncio``.
|
||||||
await client.send_file('username', '/home/myself/Pictures/holidays.jpg')
|
await client.send_file('username', '/home/myself/Pictures/holidays.jpg')
|
||||||
|
|
||||||
await client.download_profile_photo('me')
|
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])
|
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
|
or even entire :tl:`User`, :tl:`Chat` and :tl:`Channel` objects and even
|
||||||
phone numbers from people you have in your contacts.
|
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
|
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
|
# Note that you can use 'me' or 'self' to message yourself
|
||||||
await client.send_message('username', 'Hello World from Telethon!')
|
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')
|
await client.send_file('username', '/home/myself/Pictures/holidays.jpg')
|
||||||
|
|
||||||
# The utils package has some goodies, like .get_display_name()
|
# The utils package has some goodies, like .get_display_name()
|
||||||
|
@ -83,15 +88,16 @@ a single line.
|
||||||
Available methods
|
Available methods
|
||||||
*****************
|
*****************
|
||||||
|
|
||||||
This page lists all the "handy" methods available for you to use in the
|
The :ref:`reference <telethon-package>` lists all the "handy" methods
|
||||||
``TelegramClient`` class. These are simply wrappers around the "raw"
|
available for you to use in the ``TelegramClient`` class. These are simply
|
||||||
Telegram API, making it much more manageable and easier to work with.
|
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,
|
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_
|
and don't be afraid to read the source code of the InteractiveTelegramClient_
|
||||||
or even the TelegramClient_ itself to learn how it works.
|
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
|
.. _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
|
.. _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*,
|
You can query an inline bot, such as `@VoteBot`__ (note, *query*,
|
||||||
not *interact* with a voting message), by making use of the
|
not *interact* with a voting message), by making use of the
|
||||||
`GetInlineBotResultsRequest`__ request:
|
:tl:`GetInlineBotResultsRequest` request:
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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
|
And you can select any of their results by using
|
||||||
`SendInlineBotResultRequest`__:
|
:tl:`SendInlineBotResultRequest`:
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -58,7 +58,4 @@ show it visually (button rows, and buttons within each row, each with
|
||||||
its own data).
|
its own data).
|
||||||
|
|
||||||
__ https://t.me/vote
|
__ 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
|
__ 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
|
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
|
.. 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`__.
|
For more on channels, check the `channels namespace`__.
|
||||||
|
|
||||||
|
|
||||||
|
__ https://lonamiwebs.github.io/Telethon/methods/channels/index.html
|
||||||
|
|
||||||
|
|
||||||
Joining a private chat or channel
|
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
|
enough information to join! The part after the
|
||||||
``https://t.me/joinchat/``, this is, ``AAAAAFFszQPyPEZ7wgxLtd`` on this
|
``https://t.me/joinchat/``, this is, ``AAAAAFFszQPyPEZ7wgxLtd`` on this
|
||||||
example, is the ``hash`` of the chat or channel. Now you can use
|
example, is the ``hash`` of the chat or channel. Now you can use
|
||||||
`ImportChatInviteRequest`__ as follows:
|
:tl:`ImportChatInviteRequest` as follows:
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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,
|
If you don't want to add yourself, maybe because you're already in,
|
||||||
you can always add someone else with the `AddChatUserRequest`__, which
|
you can always add someone else with the :tl:`AddChatUserRequest`, which
|
||||||
use is very straightforward, or `InviteToChannelRequest`__ for channels:
|
use is very straightforward, or :tl:`InviteToChannelRequest` for channels:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# For normal chats
|
# For normal chats
|
||||||
from telethon.tl.functions.messages import AddChatUserRequest
|
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(
|
await client(AddChatUserRequest(
|
||||||
chat_id,
|
chat_id,
|
||||||
user_to_add,
|
user_to_add,
|
||||||
fwd_limit=10 # Allow the user to see the 10 last messages
|
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
|
from telethon.tl.functions.channels import InviteToChannelRequest
|
||||||
|
|
||||||
await client(InviteToChannelRequest(
|
await client(InviteToChannelRequest(
|
||||||
|
@ -78,24 +83,13 @@ use is very straightforward, or `InviteToChannelRequest`__ for channels:
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Checking a link without joining
|
Checking a link without joining
|
||||||
*******************************
|
*******************************
|
||||||
|
|
||||||
If you don't need to join but rather check whether it's a group or a
|
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.
|
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)
|
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.
|
This is what said method is doing behind the scenes as an example.
|
||||||
|
|
||||||
In order to get all the members from a mega-group or channel, you need
|
In order to get all the members from a mega-group or channel, you need
|
||||||
to use `GetParticipantsRequest`__. As we can see it needs an
|
to use :tl:`GetParticipantsRequest`. As we can see it needs an
|
||||||
`InputChannel`__, (passing the mega-group or channel you're going to
|
:tl:`InputChannel`, (passing the mega-group or channel you're going to
|
||||||
use will work), and a mandatory `ChannelParticipantsFilter`__. The
|
use will work), and a mandatory :tl:`ChannelParticipantsFilter`. The
|
||||||
closest thing to "no filter" is to simply use
|
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
|
If we want to get *all* the members, we need to use a moving offset and
|
||||||
a fixed limit:
|
a fixed limit:
|
||||||
|
@ -146,34 +140,28 @@ a fixed limit:
|
||||||
Refer to `issue 573`__ for more on this.
|
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
|
which may have more information you need (like the role of the
|
||||||
participants, total count of members, etc.)
|
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://github.com/LonamiWebs/Telethon/issues/573
|
||||||
__ https://lonamiwebs.github.io/Telethon/constructors/channels/channel_participants.html
|
|
||||||
|
|
||||||
|
|
||||||
Recent Actions
|
Recent Actions
|
||||||
**************
|
**************
|
||||||
|
|
||||||
"Recent actions" is simply the name official applications have given to
|
"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
|
you'll get AdminLogResults.events in return which in turn has the final
|
||||||
`.action`__.
|
`.action`__.
|
||||||
|
|
||||||
__ https://lonamiwebs.github.io/Telethon/methods/channels/get_admin_log.html
|
|
||||||
__ https://lonamiwebs.github.io/Telethon/types/channel_admin_log_event_action.html
|
__ https://lonamiwebs.github.io/Telethon/types/channel_admin_log_event_action.html
|
||||||
|
|
||||||
|
|
||||||
Admin Permissions
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -209,16 +197,81 @@ Giving or revoking admin permissions can be done with the `EditAdminRequest`__:
|
||||||
# User will now be able to change group info, delete other people's
|
# User will now be able to change group info, delete other people's
|
||||||
# messages and pin messages.
|
# 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/Kyle2142
|
||||||
__ https://github.com/LonamiWebs/Telethon/issues/490
|
__ https://github.com/LonamiWebs/Telethon/issues/490
|
||||||
__ https://lonamiwebs.github.io/Telethon/constructors/channel_admin_rights.html
|
__ 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
|
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
|
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
|
.. 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/305
|
||||||
__ https://github.com/LonamiWebs/Telethon/issues/409
|
__ https://github.com/LonamiWebs/Telethon/issues/409
|
||||||
__ https://github.com/LonamiWebs/Telethon/issues/447
|
__ 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
|
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:
|
into issues_. A valid example would be:
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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
|
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,
|
could think to specify "no user", won't work because this parameter is a flag,
|
||||||
and it being unspecified has a different meaning.
|
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
|
.. _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/working-with-messages
|
||||||
extra/examples/chats-and-channels
|
extra/examples/chats-and-channels
|
||||||
extra/examples/bots
|
extra/examples/bots
|
||||||
|
extra/examples/projects-using-telethon
|
||||||
|
|
||||||
|
|
||||||
.. _Troubleshooting:
|
.. _Troubleshooting:
|
||||||
|
|
|
@ -7,3 +7,50 @@ telethon\.events package
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
: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 mimetypes import guess_type
|
||||||
|
|
||||||
from .crypto import CdnDecrypter
|
from .crypto import CdnDecrypter
|
||||||
|
from .tl import TLObject
|
||||||
from .tl.custom import InputSizedFile
|
from .tl.custom import InputSizedFile
|
||||||
from .tl.functions.upload import (
|
from .tl.functions.upload import (
|
||||||
SaveBigFilePartRequest, SaveFilePartRequest, GetFileRequest
|
SaveBigFilePartRequest, SaveFilePartRequest, GetFileRequest
|
||||||
|
@ -38,8 +39,7 @@ from .errors import (
|
||||||
RPCError, UnauthorizedError, PhoneCodeEmptyError, PhoneCodeExpiredError,
|
RPCError, UnauthorizedError, PhoneCodeEmptyError, PhoneCodeExpiredError,
|
||||||
PhoneCodeHashEmptyError, PhoneCodeInvalidError, LocationInvalidError,
|
PhoneCodeHashEmptyError, PhoneCodeInvalidError, LocationInvalidError,
|
||||||
SessionPasswordNeededError, FileMigrateError, PhoneNumberUnoccupiedError,
|
SessionPasswordNeededError, FileMigrateError, PhoneNumberUnoccupiedError,
|
||||||
PhoneNumberOccupiedError, EmailUnconfirmedError, PasswordEmptyError,
|
PhoneNumberOccupiedError, UsernameNotOccupiedError
|
||||||
UsernameNotOccupiedError
|
|
||||||
)
|
)
|
||||||
from .network import ConnectionMode
|
from .network import ConnectionMode
|
||||||
from .tl.custom import Draft, Dialog
|
from .tl.custom import Draft, Dialog
|
||||||
|
@ -84,7 +84,8 @@ from .tl.types import (
|
||||||
InputMessageEntityMentionName, DocumentAttributeVideo,
|
InputMessageEntityMentionName, DocumentAttributeVideo,
|
||||||
UpdateEditMessage, UpdateEditChannelMessage, UpdateShort, Updates,
|
UpdateEditMessage, UpdateEditChannelMessage, UpdateShort, Updates,
|
||||||
MessageMediaWebPage, ChannelParticipantsSearch, PhotoSize, PhotoCachedSize,
|
MessageMediaWebPage, ChannelParticipantsSearch, PhotoSize, PhotoCachedSize,
|
||||||
PhotoSizeEmpty, MessageService, ChatParticipants
|
PhotoSizeEmpty, MessageService, ChatParticipants,
|
||||||
|
ChannelParticipantsBanned, ChannelParticipantsKicked
|
||||||
)
|
)
|
||||||
from .tl.types.messages import DialogsSlice
|
from .tl.types.messages import DialogsSlice
|
||||||
from .tl.types.account import PasswordInputSettings, NoPassword
|
from .tl.types.account import PasswordInputSettings, NoPassword
|
||||||
|
@ -354,7 +355,14 @@ class TelegramClient(TelegramBareClient):
|
||||||
me = await self.sign_in(phone=phone, password=password)
|
me = await self.sign_in(phone=phone, password=password)
|
||||||
|
|
||||||
# We won't reach here if any step failed (exit by exception)
|
# 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()
|
await self._check_events_pending_resolve()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -712,6 +720,13 @@ class TelegramClient(TelegramBareClient):
|
||||||
"""
|
"""
|
||||||
Sends the given message to the specified entity (user/chat/channel).
|
Sends the given message to the specified entity (user/chat/channel).
|
||||||
|
|
||||||
|
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:
|
Args:
|
||||||
entity (`entity`):
|
entity (`entity`):
|
||||||
To who will it be sent.
|
To who will it be sent.
|
||||||
|
@ -820,9 +835,11 @@ class TelegramClient(TelegramBareClient):
|
||||||
order for the forward to work.
|
order for the forward to work.
|
||||||
|
|
||||||
Returns:
|
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,)
|
messages = (messages,)
|
||||||
|
|
||||||
if not from_peer:
|
if not from_peer:
|
||||||
|
@ -853,7 +870,8 @@ class TelegramClient(TelegramBareClient):
|
||||||
elif isinstance(update, (UpdateNewMessage, UpdateNewChannelMessage)):
|
elif isinstance(update, (UpdateNewMessage, UpdateNewChannelMessage)):
|
||||||
id_to_message[update.message.id] = update.message
|
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,
|
async def edit_message(self, entity, message_id, message=None,
|
||||||
parse_mode='md', link_preview=True):
|
parse_mode='md', link_preview=True):
|
||||||
|
@ -1205,7 +1223,12 @@ class TelegramClient(TelegramBareClient):
|
||||||
or :tl:`ChatParticipants` for normal chats.
|
or :tl:`ChatParticipants` for normal chats.
|
||||||
"""
|
"""
|
||||||
if isinstance(filter, type):
|
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)
|
entity = await self.get_input_entity(entity)
|
||||||
if search and (filter or not isinstance(entity, InputPeerChannel)):
|
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.
|
it will be used to determine metadata from audio and video files.
|
||||||
|
|
||||||
Returns:
|
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
|
# First check if the user passed an iterable, in which case
|
||||||
# we may want to send as an album if all are photo files.
|
# 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):
|
if event and not isinstance(event, type):
|
||||||
event = type(event)
|
event = type(event)
|
||||||
|
|
||||||
for i, ec in enumerate(self._event_builders):
|
i = len(self._event_builders)
|
||||||
ev, cb = ec
|
while i:
|
||||||
|
i -= 1
|
||||||
|
ev, cb = self._event_builders[i]
|
||||||
if cb == callback and (not event or isinstance(ev, event)):
|
if cb == callback and (not event or isinstance(ev, event)):
|
||||||
del self._event_builders[i]
|
del self._event_builders[i]
|
||||||
found += 1
|
found += 1
|
||||||
|
@ -2313,13 +2339,11 @@ class TelegramClient(TelegramBareClient):
|
||||||
error will be raised.
|
error will be raised.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:tl:`User`, :tl:`Chat` or :tl:`Channel` corresponding to the input
|
:tl:`User`, :tl:`Chat` or :tl:`Channel` corresponding to the
|
||||||
entity.
|
input entity. A list will be returned if more than one was given.
|
||||||
"""
|
"""
|
||||||
if utils.is_list_like(entity):
|
single = not utils.is_list_like(entity)
|
||||||
single = False
|
if single:
|
||||||
else:
|
|
||||||
single = True
|
|
||||||
entity = (entity,)
|
entity = (entity,)
|
||||||
|
|
||||||
# Group input entities by string (resolve username),
|
# Group input entities by string (resolve username),
|
||||||
|
@ -2443,18 +2467,12 @@ class TelegramClient(TelegramBareClient):
|
||||||
if isinstance(peer, str):
|
if isinstance(peer, str):
|
||||||
return utils.get_input_peer(await self._get_entity_from_string(peer))
|
return utils.get_input_peer(await self._get_entity_from_string(peer))
|
||||||
|
|
||||||
original_peer = peer
|
if not isinstance(peer, int) and (not isinstance(peer, TLObject)
|
||||||
if not isinstance(peer, int):
|
or peer.SUBCLASS_OF_ID != 0x2d45687):
|
||||||
try:
|
# Try casting the object into an input peer. Might TypeError.
|
||||||
if peer.SUBCLASS_OF_ID != 0x2d45687: # crc32(b'Peer')
|
# Don't do it if a not-found ID was given (instead ValueError).
|
||||||
return utils.get_input_peer(peer)
|
# Also ignore Peer (0x2d45687 == crc32(b'Peer'))'s, lacking hash.
|
||||||
except (AttributeError, TypeError):
|
return utils.get_input_peer(peer)
|
||||||
peer = None
|
|
||||||
|
|
||||||
if not peer:
|
|
||||||
raise TypeError(
|
|
||||||
'Cannot turn "{}" into an input entity.'.format(original_peer)
|
|
||||||
)
|
|
||||||
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Could not find the input entity corresponding to "{}". '
|
'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')
|
if entity.SUBCLASS_OF_ID == 0xc91c90b6: # crc32(b'InputPeer')
|
||||||
return entity
|
return entity
|
||||||
except AttributeError:
|
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 isinstance(entity, User):
|
||||||
if entity.is_self and allow_self:
|
if entity.is_self and allow_self:
|
||||||
|
@ -105,7 +109,6 @@ def get_input_peer(entity, allow_self=True):
|
||||||
if isinstance(entity, (Channel, ChannelForbidden)):
|
if isinstance(entity, (Channel, ChannelForbidden)):
|
||||||
return InputPeerChannel(entity.id, entity.access_hash or 0)
|
return InputPeerChannel(entity.id, entity.access_hash or 0)
|
||||||
|
|
||||||
# Less common cases
|
|
||||||
if isinstance(entity, InputUser):
|
if isinstance(entity, InputUser):
|
||||||
return InputPeerUser(entity.user_id, entity.access_hash)
|
return InputPeerUser(entity.user_id, entity.access_hash)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user