mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
Все подряд
This commit is contained in:
14
.venv2/Lib/site-packages/telethon/tl/custom/__init__.py
Normal file
14
.venv2/Lib/site-packages/telethon/tl/custom/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from .adminlogevent import AdminLogEvent
|
||||
from .draft import Draft
|
||||
from .dialog import Dialog
|
||||
from .inputsizedfile import InputSizedFile
|
||||
from .messagebutton import MessageButton
|
||||
from .forward import Forward
|
||||
from .message import Message
|
||||
from .button import Button
|
||||
from .inlinebuilder import InlineBuilder
|
||||
from .inlineresult import InlineResult
|
||||
from .inlineresults import InlineResults
|
||||
from .conversation import Conversation
|
||||
from .qrlogin import QRLogin
|
||||
from .participantpermissions import ParticipantPermissions
|
475
.venv2/Lib/site-packages/telethon/tl/custom/adminlogevent.py
Normal file
475
.venv2/Lib/site-packages/telethon/tl/custom/adminlogevent.py
Normal file
@@ -0,0 +1,475 @@
|
||||
from ...tl import types
|
||||
from ...utils import get_input_peer
|
||||
|
||||
|
||||
class AdminLogEvent:
|
||||
"""
|
||||
Represents a more friendly interface for admin log events.
|
||||
|
||||
Members:
|
||||
original (:tl:`ChannelAdminLogEvent`):
|
||||
The original :tl:`ChannelAdminLogEvent`.
|
||||
|
||||
entities (`dict`):
|
||||
A dictionary mapping user IDs to :tl:`User`.
|
||||
|
||||
When `old` and `new` are :tl:`ChannelParticipant`, you can
|
||||
use this dictionary to map the ``user_id``, ``kicked_by``,
|
||||
``inviter_id`` and ``promoted_by`` IDs to their :tl:`User`.
|
||||
|
||||
user (:tl:`User`):
|
||||
The user that caused this action (``entities[original.user_id]``).
|
||||
|
||||
input_user (:tl:`InputPeerUser`):
|
||||
Input variant of `user`.
|
||||
"""
|
||||
def __init__(self, original, entities):
|
||||
self.original = original
|
||||
self.entities = entities
|
||||
self.user = entities[original.user_id]
|
||||
self.input_user = get_input_peer(self.user)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
The ID of this event.
|
||||
"""
|
||||
return self.original.id
|
||||
|
||||
@property
|
||||
def date(self):
|
||||
"""
|
||||
The date when this event occurred.
|
||||
"""
|
||||
return self.original.date
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
"""
|
||||
The ID of the user that triggered this event.
|
||||
"""
|
||||
return self.original.user_id
|
||||
|
||||
@property
|
||||
def action(self):
|
||||
"""
|
||||
The original :tl:`ChannelAdminLogEventAction`.
|
||||
"""
|
||||
return self.original.action
|
||||
|
||||
@property
|
||||
def old(self):
|
||||
"""
|
||||
The old value from the event.
|
||||
"""
|
||||
ori = self.original.action
|
||||
if isinstance(ori, (
|
||||
types.ChannelAdminLogEventActionChangeAbout,
|
||||
types.ChannelAdminLogEventActionChangeTitle,
|
||||
types.ChannelAdminLogEventActionChangeUsername,
|
||||
types.ChannelAdminLogEventActionChangeLocation,
|
||||
types.ChannelAdminLogEventActionChangeHistoryTTL,
|
||||
)):
|
||||
return ori.prev_value
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionChangePhoto):
|
||||
return ori.prev_photo
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionChangeStickerSet):
|
||||
return ori.prev_stickerset
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionEditMessage):
|
||||
return ori.prev_message
|
||||
elif isinstance(ori, (
|
||||
types.ChannelAdminLogEventActionParticipantToggleAdmin,
|
||||
types.ChannelAdminLogEventActionParticipantToggleBan
|
||||
)):
|
||||
return ori.prev_participant
|
||||
elif isinstance(ori, (
|
||||
types.ChannelAdminLogEventActionToggleInvites,
|
||||
types.ChannelAdminLogEventActionTogglePreHistoryHidden,
|
||||
types.ChannelAdminLogEventActionToggleSignatures
|
||||
)):
|
||||
return not ori.new_value
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionDeleteMessage):
|
||||
return ori.message
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionDefaultBannedRights):
|
||||
return ori.prev_banned_rights
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionDiscardGroupCall):
|
||||
return ori.call
|
||||
elif isinstance(ori, (
|
||||
types.ChannelAdminLogEventActionExportedInviteDelete,
|
||||
types.ChannelAdminLogEventActionExportedInviteRevoke,
|
||||
types.ChannelAdminLogEventActionParticipantJoinByInvite,
|
||||
)):
|
||||
return ori.invite
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionExportedInviteEdit):
|
||||
return ori.prev_invite
|
||||
|
||||
@property
|
||||
def new(self):
|
||||
"""
|
||||
The new value present in the event.
|
||||
"""
|
||||
ori = self.original.action
|
||||
if isinstance(ori, (
|
||||
types.ChannelAdminLogEventActionChangeAbout,
|
||||
types.ChannelAdminLogEventActionChangeTitle,
|
||||
types.ChannelAdminLogEventActionChangeUsername,
|
||||
types.ChannelAdminLogEventActionToggleInvites,
|
||||
types.ChannelAdminLogEventActionTogglePreHistoryHidden,
|
||||
types.ChannelAdminLogEventActionToggleSignatures,
|
||||
types.ChannelAdminLogEventActionChangeLocation,
|
||||
types.ChannelAdminLogEventActionChangeHistoryTTL,
|
||||
)):
|
||||
return ori.new_value
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionChangePhoto):
|
||||
return ori.new_photo
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionChangeStickerSet):
|
||||
return ori.new_stickerset
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionEditMessage):
|
||||
return ori.new_message
|
||||
elif isinstance(ori, (
|
||||
types.ChannelAdminLogEventActionParticipantToggleAdmin,
|
||||
types.ChannelAdminLogEventActionParticipantToggleBan
|
||||
)):
|
||||
return ori.new_participant
|
||||
elif isinstance(ori, (
|
||||
types.ChannelAdminLogEventActionParticipantInvite,
|
||||
types.ChannelAdminLogEventActionParticipantVolume,
|
||||
)):
|
||||
return ori.participant
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionDefaultBannedRights):
|
||||
return ori.new_banned_rights
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionStopPoll):
|
||||
return ori.message
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionStartGroupCall):
|
||||
return ori.call
|
||||
elif isinstance(ori, (
|
||||
types.ChannelAdminLogEventActionParticipantMute,
|
||||
types.ChannelAdminLogEventActionParticipantUnmute,
|
||||
)):
|
||||
return ori.participant
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionToggleGroupCallSetting):
|
||||
return ori.join_muted
|
||||
elif isinstance(ori, types.ChannelAdminLogEventActionExportedInviteEdit):
|
||||
return ori.new_invite
|
||||
|
||||
@property
|
||||
def changed_about(self):
|
||||
"""
|
||||
Whether the channel's about was changed or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as `str`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionChangeAbout)
|
||||
|
||||
@property
|
||||
def changed_title(self):
|
||||
"""
|
||||
Whether the channel's title was changed or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as `str`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionChangeTitle)
|
||||
|
||||
@property
|
||||
def changed_username(self):
|
||||
"""
|
||||
Whether the channel's username was changed or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as `str`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionChangeUsername)
|
||||
|
||||
@property
|
||||
def changed_photo(self):
|
||||
"""
|
||||
Whether the channel's photo was changed or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as :tl:`Photo`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionChangePhoto)
|
||||
|
||||
@property
|
||||
def changed_sticker_set(self):
|
||||
"""
|
||||
Whether the channel's sticker set was changed or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as :tl:`InputStickerSet`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionChangeStickerSet)
|
||||
|
||||
@property
|
||||
def changed_message(self):
|
||||
"""
|
||||
Whether a message in this channel was edited or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as
|
||||
`Message <telethon.tl.custom.message.Message>`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionEditMessage)
|
||||
|
||||
@property
|
||||
def deleted_message(self):
|
||||
"""
|
||||
Whether a message in this channel was deleted or not.
|
||||
|
||||
If `True`, `old` will be present as
|
||||
`Message <telethon.tl.custom.message.Message>`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionDeleteMessage)
|
||||
|
||||
@property
|
||||
def changed_admin(self):
|
||||
"""
|
||||
Whether the permissions for an admin in this channel
|
||||
changed or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as
|
||||
:tl:`ChannelParticipant`.
|
||||
"""
|
||||
return isinstance(
|
||||
self.original.action,
|
||||
types.ChannelAdminLogEventActionParticipantToggleAdmin)
|
||||
|
||||
@property
|
||||
def changed_restrictions(self):
|
||||
"""
|
||||
Whether a message in this channel was edited or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as
|
||||
:tl:`ChannelParticipant`.
|
||||
"""
|
||||
return isinstance(
|
||||
self.original.action,
|
||||
types.ChannelAdminLogEventActionParticipantToggleBan)
|
||||
|
||||
@property
|
||||
def changed_invites(self):
|
||||
"""
|
||||
Whether the invites in the channel were toggled or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as `bool`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionToggleInvites)
|
||||
|
||||
@property
|
||||
def changed_location(self):
|
||||
"""
|
||||
Whether the location setting of the channel has changed or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as :tl:`ChannelLocation`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionChangeLocation)
|
||||
|
||||
@property
|
||||
def joined(self):
|
||||
"""
|
||||
Whether `user` joined through the channel's
|
||||
public username or not.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionParticipantJoin)
|
||||
|
||||
@property
|
||||
def joined_invite(self):
|
||||
"""
|
||||
Whether a new user joined through an invite
|
||||
link to the channel or not.
|
||||
|
||||
If `True`, `new` will be present as
|
||||
:tl:`ChannelParticipant`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionParticipantInvite)
|
||||
|
||||
@property
|
||||
def left(self):
|
||||
"""
|
||||
Whether `user` left the channel or not.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionParticipantLeave)
|
||||
|
||||
@property
|
||||
def changed_hide_history(self):
|
||||
"""
|
||||
Whether hiding the previous message history for new members
|
||||
in the channel was toggled or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as `bool`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionTogglePreHistoryHidden)
|
||||
|
||||
@property
|
||||
def changed_signatures(self):
|
||||
"""
|
||||
Whether the message signatures in the channel were toggled
|
||||
or not.
|
||||
|
||||
If `True`, `old` and `new` will be present as `bool`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionToggleSignatures)
|
||||
|
||||
@property
|
||||
def changed_pin(self):
|
||||
"""
|
||||
Whether a new message in this channel was pinned or not.
|
||||
|
||||
If `True`, `new` will be present as
|
||||
`Message <telethon.tl.custom.message.Message>`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionUpdatePinned)
|
||||
|
||||
@property
|
||||
def changed_default_banned_rights(self):
|
||||
"""
|
||||
Whether the default banned rights were changed or not.
|
||||
|
||||
If `True`, `old` and `new` will
|
||||
be present as :tl:`ChatBannedRights`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionDefaultBannedRights)
|
||||
|
||||
@property
|
||||
def stopped_poll(self):
|
||||
"""
|
||||
Whether a poll was stopped or not.
|
||||
|
||||
If `True`, `new` will be present as
|
||||
`Message <telethon.tl.custom.message.Message>`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionStopPoll)
|
||||
|
||||
@property
|
||||
def started_group_call(self):
|
||||
"""
|
||||
Whether a group call was started or not.
|
||||
|
||||
If `True`, `new` will be present as :tl:`InputGroupCall`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionStartGroupCall)
|
||||
|
||||
@property
|
||||
def discarded_group_call(self):
|
||||
"""
|
||||
Whether a group call was started or not.
|
||||
|
||||
If `True`, `old` will be present as :tl:`InputGroupCall`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionDiscardGroupCall)
|
||||
|
||||
@property
|
||||
def user_muted(self):
|
||||
"""
|
||||
Whether a participant was muted in the ongoing group call or not.
|
||||
|
||||
If `True`, `new` will be present as :tl:`GroupCallParticipant`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionParticipantMute)
|
||||
|
||||
@property
|
||||
def user_unmutted(self):
|
||||
"""
|
||||
Whether a participant was unmuted from the ongoing group call or not.
|
||||
|
||||
If `True`, `new` will be present as :tl:`GroupCallParticipant`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionParticipantUnmute)
|
||||
|
||||
@property
|
||||
def changed_call_settings(self):
|
||||
"""
|
||||
Whether the group call settings were changed or not.
|
||||
|
||||
If `True`, `new` will be `True` if new users are muted on join.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionToggleGroupCallSetting)
|
||||
|
||||
@property
|
||||
def changed_history_ttl(self):
|
||||
"""
|
||||
Whether the Time To Live of the message history has changed.
|
||||
|
||||
Messages sent after this change will have a ``ttl_period`` in seconds
|
||||
indicating how long they should live for before being auto-deleted.
|
||||
|
||||
If `True`, `old` will be the old TTL, and `new` the new TTL, in seconds.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionChangeHistoryTTL)
|
||||
|
||||
@property
|
||||
def deleted_exported_invite(self):
|
||||
"""
|
||||
Whether the exported chat invite has been deleted.
|
||||
|
||||
If `True`, `old` will be the deleted :tl:`ExportedChatInvite`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionExportedInviteDelete)
|
||||
|
||||
@property
|
||||
def edited_exported_invite(self):
|
||||
"""
|
||||
Whether the exported chat invite has been edited.
|
||||
|
||||
If `True`, `old` and `new` will be the old and new
|
||||
:tl:`ExportedChatInvite`, respectively.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionExportedInviteEdit)
|
||||
|
||||
@property
|
||||
def revoked_exported_invite(self):
|
||||
"""
|
||||
Whether the exported chat invite has been revoked.
|
||||
|
||||
If `True`, `old` will be the revoked :tl:`ExportedChatInvite`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionExportedInviteRevoke)
|
||||
|
||||
@property
|
||||
def joined_by_invite(self):
|
||||
"""
|
||||
Whether a new participant has joined with the use of an invite link.
|
||||
|
||||
If `True`, `old` will be pre-existing (old) :tl:`ExportedChatInvite`
|
||||
used to join.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionParticipantJoinByInvite)
|
||||
|
||||
@property
|
||||
def changed_user_volume(self):
|
||||
"""
|
||||
Whether a participant's volume in a call has been changed.
|
||||
|
||||
If `True`, `new` will be the updated :tl:`GroupCallParticipant`.
|
||||
"""
|
||||
return isinstance(self.original.action,
|
||||
types.ChannelAdminLogEventActionParticipantVolume)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.original)
|
||||
|
||||
def stringify(self):
|
||||
return self.original.stringify()
|
309
.venv2/Lib/site-packages/telethon/tl/custom/button.py
Normal file
309
.venv2/Lib/site-packages/telethon/tl/custom/button.py
Normal file
@@ -0,0 +1,309 @@
|
||||
from .. import types
|
||||
from ... import utils
|
||||
|
||||
|
||||
class Button:
|
||||
"""
|
||||
.. note::
|
||||
|
||||
This class is used to **define** reply markups, e.g. when
|
||||
sending a message or replying to events. When you access
|
||||
`Message.buttons <telethon.tl.custom.message.Message.buttons>`
|
||||
they are actually `MessageButton
|
||||
<telethon.tl.custom.messagebutton.MessageButton>`,
|
||||
so you might want to refer to that class instead.
|
||||
|
||||
Helper class to allow defining ``reply_markup`` when
|
||||
sending a message with inline or keyboard buttons.
|
||||
|
||||
You should make use of the defined class methods to create button
|
||||
instances instead making them yourself (i.e. don't do ``Button(...)``
|
||||
but instead use methods line `Button.inline(...) <inline>` etc.
|
||||
|
||||
You can use `inline`, `switch_inline`, `url`, `auth`, `buy` and `game`
|
||||
together to create inline buttons (under the message).
|
||||
|
||||
You can use `text`, `request_location`, `request_phone` and `request_poll`
|
||||
together to create a reply markup (replaces the user keyboard).
|
||||
You can also configure the aspect of the reply with these.
|
||||
The latest message with a reply markup will be the one shown to the user
|
||||
(messages contain the buttons, not the chat itself).
|
||||
|
||||
You **cannot** mix the two type of buttons together,
|
||||
and it will error if you try to do so.
|
||||
|
||||
The text for all buttons may be at most 142 characters.
|
||||
If more characters are given, Telegram will cut the text
|
||||
to 128 characters and add the ellipsis (…) character as
|
||||
the 129.
|
||||
"""
|
||||
def __init__(self, button, *, resize, single_use, selective):
|
||||
self.button = button
|
||||
self.resize = resize
|
||||
self.single_use = single_use
|
||||
self.selective = selective
|
||||
|
||||
@staticmethod
|
||||
def _is_inline(button):
|
||||
"""
|
||||
Returns `True` if the button belongs to an inline keyboard.
|
||||
"""
|
||||
return isinstance(button, (
|
||||
types.KeyboardButtonBuy,
|
||||
types.KeyboardButtonCallback,
|
||||
types.KeyboardButtonGame,
|
||||
types.KeyboardButtonSwitchInline,
|
||||
types.KeyboardButtonUrl,
|
||||
types.InputKeyboardButtonUrlAuth,
|
||||
types.KeyboardButtonWebView,
|
||||
))
|
||||
|
||||
@staticmethod
|
||||
def inline(text, data=None):
|
||||
"""
|
||||
Creates a new inline button with some payload data in it.
|
||||
|
||||
If `data` is omitted, the given `text` will be used as `data`.
|
||||
In any case `data` should be either `bytes` or `str`.
|
||||
|
||||
Note that the given `data` must be less or equal to 64 bytes.
|
||||
If more than 64 bytes are passed as data, ``ValueError`` is raised.
|
||||
If you need to store more than 64 bytes, consider saving the real
|
||||
data in a database and a reference to that data inside the button.
|
||||
|
||||
When the user clicks this button, `events.CallbackQuery
|
||||
<telethon.events.callbackquery.CallbackQuery>` will trigger with the
|
||||
same data that the button contained, so that you can determine which
|
||||
button was pressed.
|
||||
"""
|
||||
if not data:
|
||||
data = text.encode('utf-8')
|
||||
elif not isinstance(data, (bytes, bytearray, memoryview)):
|
||||
data = str(data).encode('utf-8')
|
||||
|
||||
if len(data) > 64:
|
||||
raise ValueError('Too many bytes for the data')
|
||||
|
||||
return types.KeyboardButtonCallback(text, data)
|
||||
|
||||
@staticmethod
|
||||
def switch_inline(text, query='', same_peer=False):
|
||||
"""
|
||||
Creates a new inline button to switch to inline query.
|
||||
|
||||
If `query` is given, it will be the default text to be used
|
||||
when making the inline query.
|
||||
|
||||
If ``same_peer is True`` the inline query will directly be
|
||||
set under the currently opened chat. Otherwise, the user will
|
||||
have to select a different dialog to make the query.
|
||||
|
||||
When the user clicks this button, after a chat is selected, their
|
||||
input field will be filled with the username of your bot followed
|
||||
by the query text, ready to make inline queries.
|
||||
"""
|
||||
return types.KeyboardButtonSwitchInline(text, query, same_peer)
|
||||
|
||||
@staticmethod
|
||||
def url(text, url=None):
|
||||
"""
|
||||
Creates a new inline button to open the desired URL on click.
|
||||
|
||||
If no `url` is given, the `text` will be used as said URL instead.
|
||||
|
||||
You cannot detect that the user clicked this button directly.
|
||||
|
||||
When the user clicks this button, a confirmation box will be shown
|
||||
to the user asking whether they want to open the displayed URL unless
|
||||
the domain is trusted, and once confirmed the URL will open in their
|
||||
device.
|
||||
"""
|
||||
return types.KeyboardButtonUrl(text, url or text)
|
||||
|
||||
@staticmethod
|
||||
def auth(text, url=None, *, bot=None, write_access=False, fwd_text=None):
|
||||
"""
|
||||
Creates a new inline button to authorize the user at the given URL.
|
||||
|
||||
You should set the `url` to be on the same domain as the one configured
|
||||
for the desired `bot` via `@BotFather <https://t.me/BotFather>`_ using
|
||||
the ``/setdomain`` command.
|
||||
|
||||
For more information about letting the user login via Telegram to
|
||||
a certain domain, see https://core.telegram.org/widgets/login.
|
||||
|
||||
If no `url` is specified, it will default to `text`.
|
||||
|
||||
Args:
|
||||
bot (`hints.EntityLike`):
|
||||
The bot that requires this authorization. By default, this
|
||||
is the bot that is currently logged in (itself), although
|
||||
you may pass a different input peer.
|
||||
|
||||
.. note::
|
||||
|
||||
For now, you cannot use ID or username for this argument.
|
||||
If you want to use a different bot than the one currently
|
||||
logged in, you must manually use `client.get_input_entity()
|
||||
<telethon.client.users.UserMethods.get_input_entity>`.
|
||||
|
||||
write_access (`bool`):
|
||||
Whether write access is required or not.
|
||||
This is `False` by default (read-only access).
|
||||
|
||||
fwd_text (`str`):
|
||||
The new text to show in the button if the message is
|
||||
forwarded. By default, the button text will be the same.
|
||||
|
||||
When the user clicks this button, a confirmation box will be shown
|
||||
to the user asking whether they want to login to the specified domain.
|
||||
"""
|
||||
return types.InputKeyboardButtonUrlAuth(
|
||||
text=text,
|
||||
url=url or text,
|
||||
bot=utils.get_input_user(bot or types.InputUserSelf()),
|
||||
request_write_access=write_access,
|
||||
fwd_text=fwd_text
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def text(cls, text, *, resize=None, single_use=None, selective=None):
|
||||
"""
|
||||
Creates a new keyboard button with the given text.
|
||||
|
||||
Args:
|
||||
resize (`bool`):
|
||||
If present, the entire keyboard will be reconfigured to
|
||||
be resized and be smaller if there are not many buttons.
|
||||
|
||||
single_use (`bool`):
|
||||
If present, the entire keyboard will be reconfigured to
|
||||
be usable only once before it hides itself.
|
||||
|
||||
selective (`bool`):
|
||||
If present, the entire keyboard will be reconfigured to
|
||||
be "selective". The keyboard will be shown only to specific
|
||||
users. It will target users that are @mentioned in the text
|
||||
of the message or to the sender of the message you reply to.
|
||||
|
||||
When the user clicks this button, a text message with the same text
|
||||
as the button will be sent, and can be handled with `events.NewMessage
|
||||
<telethon.events.newmessage.NewMessage>`. You cannot distinguish
|
||||
between a button press and the user typing and sending exactly the
|
||||
same text on their own.
|
||||
"""
|
||||
return cls(types.KeyboardButton(text),
|
||||
resize=resize, single_use=single_use, selective=selective)
|
||||
|
||||
@classmethod
|
||||
def request_location(cls, text, *,
|
||||
resize=None, single_use=None, selective=None):
|
||||
"""
|
||||
Creates a new keyboard button to request the user's location on click.
|
||||
|
||||
``resize``, ``single_use`` and ``selective`` are documented in `text`.
|
||||
|
||||
When the user clicks this button, a confirmation box will be shown
|
||||
to the user asking whether they want to share their location with the
|
||||
bot, and if confirmed a message with geo media will be sent.
|
||||
"""
|
||||
return cls(types.KeyboardButtonRequestGeoLocation(text),
|
||||
resize=resize, single_use=single_use, selective=selective)
|
||||
|
||||
@classmethod
|
||||
def request_phone(cls, text, *,
|
||||
resize=None, single_use=None, selective=None):
|
||||
"""
|
||||
Creates a new keyboard button to request the user's phone on click.
|
||||
|
||||
``resize``, ``single_use`` and ``selective`` are documented in `text`.
|
||||
|
||||
When the user clicks this button, a confirmation box will be shown
|
||||
to the user asking whether they want to share their phone with the
|
||||
bot, and if confirmed a message with contact media will be sent.
|
||||
"""
|
||||
return cls(types.KeyboardButtonRequestPhone(text),
|
||||
resize=resize, single_use=single_use, selective=selective)
|
||||
|
||||
@classmethod
|
||||
def request_poll(cls, text, *, force_quiz=False,
|
||||
resize=None, single_use=None, selective=None):
|
||||
"""
|
||||
Creates a new keyboard button to request the user to create a poll.
|
||||
|
||||
If `force_quiz` is `False`, the user will be allowed to choose whether
|
||||
they want their poll to be a quiz or not. Otherwise, the user will be
|
||||
forced to create a quiz when creating the poll.
|
||||
|
||||
If a poll is a quiz, there will be only one answer that is valid, and
|
||||
the votes cannot be retracted. Otherwise, users can vote and retract
|
||||
the vote, and the pol might be multiple choice.
|
||||
|
||||
``resize``, ``single_use`` and ``selective`` are documented in `text`.
|
||||
|
||||
When the user clicks this button, a screen letting the user create a
|
||||
poll will be shown, and if they do create one, the poll will be sent.
|
||||
"""
|
||||
return cls(types.KeyboardButtonRequestPoll(text, quiz=force_quiz),
|
||||
resize=resize, single_use=single_use, selective=selective)
|
||||
|
||||
@staticmethod
|
||||
def clear(selective=None):
|
||||
"""
|
||||
Clears all keyboard buttons after sending a message with this markup.
|
||||
When used, no other button should be present or it will be ignored.
|
||||
|
||||
``selective`` is as documented in `text`.
|
||||
|
||||
"""
|
||||
return types.ReplyKeyboardHide(selective=selective)
|
||||
|
||||
@staticmethod
|
||||
def force_reply(single_use=None, selective=None, placeholder=None):
|
||||
"""
|
||||
Forces a reply to the message with this markup. If used,
|
||||
no other button should be present or it will be ignored.
|
||||
|
||||
``single_use`` and ``selective`` are as documented in `text`.
|
||||
|
||||
Args:
|
||||
placeholder (str):
|
||||
text to show the user at typing place of message.
|
||||
|
||||
If the placeholder is too long, Telegram applications will
|
||||
crop the text (for example, to 64 characters and adding an
|
||||
ellipsis (…) character as the 65th).
|
||||
"""
|
||||
return types.ReplyKeyboardForceReply(
|
||||
single_use=single_use,
|
||||
selective=selective,
|
||||
placeholder=placeholder)
|
||||
|
||||
@staticmethod
|
||||
def buy(text):
|
||||
"""
|
||||
Creates a new inline button to buy a product.
|
||||
|
||||
This can only be used when sending files of type
|
||||
:tl:`InputMediaInvoice`, and must be the first button.
|
||||
|
||||
If the button is not specified, Telegram will automatically
|
||||
add the button to the message. See the
|
||||
`Payments API <https://core.telegram.org/api/payments>`__
|
||||
documentation for more information.
|
||||
"""
|
||||
return types.KeyboardButtonBuy(text)
|
||||
|
||||
@staticmethod
|
||||
def game(text):
|
||||
"""
|
||||
Creates a new inline button to start playing a game.
|
||||
|
||||
This should be used when sending files of type
|
||||
:tl:`InputMediaGame`, and must be the first button.
|
||||
|
||||
See the
|
||||
`Games <https://core.telegram.org/api/bots/games>`__
|
||||
documentation for more information on using games.
|
||||
"""
|
||||
return types.KeyboardButtonGame(text)
|
150
.venv2/Lib/site-packages/telethon/tl/custom/chatgetter.py
Normal file
150
.venv2/Lib/site-packages/telethon/tl/custom/chatgetter.py
Normal file
@@ -0,0 +1,150 @@
|
||||
import abc
|
||||
|
||||
from ... import errors, utils
|
||||
from ...tl import types
|
||||
|
||||
|
||||
class ChatGetter(abc.ABC):
|
||||
"""
|
||||
Helper base class that introduces the `chat`, `input_chat`
|
||||
and `chat_id` properties and `get_chat` and `get_input_chat`
|
||||
methods.
|
||||
"""
|
||||
def __init__(self, chat_peer=None, *, input_chat=None, chat=None, broadcast=None):
|
||||
self._chat_peer = chat_peer
|
||||
self._input_chat = input_chat
|
||||
self._chat = chat
|
||||
self._broadcast = broadcast
|
||||
self._client = None
|
||||
|
||||
@property
|
||||
def chat(self):
|
||||
"""
|
||||
Returns the :tl:`User`, :tl:`Chat` or :tl:`Channel` where this object
|
||||
belongs to. It may be `None` if Telegram didn't send the chat.
|
||||
|
||||
If you only need the ID, use `chat_id` instead.
|
||||
|
||||
If you need to call a method which needs
|
||||
this chat, use `input_chat` instead.
|
||||
|
||||
If you're using `telethon.events`, use `get_chat()` instead.
|
||||
"""
|
||||
return self._chat
|
||||
|
||||
async def get_chat(self):
|
||||
"""
|
||||
Returns `chat`, but will make an API call to find the
|
||||
chat unless it's already cached.
|
||||
|
||||
If you only need the ID, use `chat_id` instead.
|
||||
|
||||
If you need to call a method which needs
|
||||
this chat, use `get_input_chat()` instead.
|
||||
"""
|
||||
# See `get_sender` for information about 'min'.
|
||||
if (self._chat is None or getattr(self._chat, 'min', None))\
|
||||
and await self.get_input_chat():
|
||||
try:
|
||||
self._chat =\
|
||||
await self._client.get_entity(self._input_chat)
|
||||
except ValueError:
|
||||
await self._refetch_chat()
|
||||
return self._chat
|
||||
|
||||
@property
|
||||
def input_chat(self):
|
||||
"""
|
||||
This :tl:`InputPeer` is the input version of the chat where the
|
||||
message was sent. Similarly to `input_sender
|
||||
<telethon.tl.custom.sendergetter.SenderGetter.input_sender>`, 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 doesn't
|
||||
have enough information available.
|
||||
"""
|
||||
if self._input_chat is None and self._chat_peer and self._client:
|
||||
try:
|
||||
self._input_chat = self._client._mb_entity_cache.get(
|
||||
utils.get_peer_id(self._chat_peer, add_mark=False))._as_input_peer()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return self._input_chat
|
||||
|
||||
async def get_input_chat(self):
|
||||
"""
|
||||
Returns `input_chat`, but will make an API call to find the
|
||||
input chat unless it's already cached.
|
||||
"""
|
||||
if self.input_chat is None and self.chat_id and self._client:
|
||||
try:
|
||||
# The chat may be recent, look in dialogs
|
||||
target = self.chat_id
|
||||
async for d in self._client.iter_dialogs(100):
|
||||
if d.id == target:
|
||||
self._chat = d.entity
|
||||
self._input_chat = d.input_entity
|
||||
break
|
||||
except errors.RPCError:
|
||||
pass
|
||||
|
||||
return self._input_chat
|
||||
|
||||
@property
|
||||
def chat_id(self):
|
||||
"""
|
||||
Returns the marked chat integer ID. Note that this value **will
|
||||
be different** from ``peer_id`` for incoming private messages, since
|
||||
the chat *to* which the messages go is to your own person, but
|
||||
the *chat* itself is with the one who sent the message.
|
||||
|
||||
TL;DR; this gets the ID that you expect.
|
||||
|
||||
If there is a chat in the object, `chat_id` will *always* be set,
|
||||
which is why you should use it instead of `chat.id <chat>`.
|
||||
"""
|
||||
return utils.get_peer_id(self._chat_peer) if self._chat_peer else None
|
||||
|
||||
@property
|
||||
def is_private(self):
|
||||
"""
|
||||
`True` if the message was sent as a private message.
|
||||
|
||||
Returns `None` if there isn't enough information
|
||||
(e.g. on `events.MessageDeleted <telethon.events.messagedeleted.MessageDeleted>`).
|
||||
"""
|
||||
return isinstance(self._chat_peer, types.PeerUser) if self._chat_peer else None
|
||||
|
||||
@property
|
||||
def is_group(self):
|
||||
"""
|
||||
True if the message was sent on a group or megagroup.
|
||||
|
||||
Returns `None` if there isn't enough information
|
||||
(e.g. on `events.MessageDeleted <telethon.events.messagedeleted.MessageDeleted>`).
|
||||
"""
|
||||
# TODO Cache could tell us more in the future
|
||||
if self._broadcast is None and hasattr(self.chat, 'broadcast'):
|
||||
self._broadcast = bool(self.chat.broadcast)
|
||||
|
||||
if isinstance(self._chat_peer, types.PeerChannel):
|
||||
if self._broadcast is None:
|
||||
return None
|
||||
else:
|
||||
return not self._broadcast
|
||||
|
||||
return isinstance(self._chat_peer, types.PeerChat)
|
||||
|
||||
@property
|
||||
def is_channel(self):
|
||||
"""`True` if the message was sent on a megagroup or channel."""
|
||||
# The only case where chat peer could be none is in MessageDeleted,
|
||||
# however those always have the peer in channels.
|
||||
return isinstance(self._chat_peer, types.PeerChannel)
|
||||
|
||||
async def _refetch_chat(self):
|
||||
"""
|
||||
Re-fetches chat information through other means.
|
||||
"""
|
529
.venv2/Lib/site-packages/telethon/tl/custom/conversation.py
Normal file
529
.venv2/Lib/site-packages/telethon/tl/custom/conversation.py
Normal file
@@ -0,0 +1,529 @@
|
||||
import asyncio
|
||||
import functools
|
||||
import inspect
|
||||
import itertools
|
||||
import time
|
||||
|
||||
from .chatgetter import ChatGetter
|
||||
from ... import helpers, utils, errors
|
||||
|
||||
# Sometimes the edits arrive very fast (within the same second).
|
||||
# In that case we add a small delta so that the age is older, for
|
||||
# comparision purposes. This value is enough for up to 1000 messages.
|
||||
_EDIT_COLLISION_DELTA = 0.001
|
||||
|
||||
|
||||
def _checks_cancelled(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if self._cancelled:
|
||||
raise asyncio.CancelledError('The conversation was cancelled before')
|
||||
|
||||
return f(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
class Conversation(ChatGetter):
|
||||
"""
|
||||
Represents a conversation inside an specific chat.
|
||||
|
||||
A conversation keeps track of new messages since it was
|
||||
created until its exit and easily lets you query the
|
||||
current state.
|
||||
|
||||
If you need a conversation across two or more chats,
|
||||
you should use two conversations and synchronize them
|
||||
as you better see fit.
|
||||
"""
|
||||
_id_counter = 0
|
||||
_custom_counter = 0
|
||||
|
||||
def __init__(self, client, input_chat,
|
||||
*, timeout, total_timeout, max_messages,
|
||||
exclusive, replies_are_responses):
|
||||
# This call resets the client
|
||||
ChatGetter.__init__(self, input_chat=input_chat)
|
||||
|
||||
self._id = Conversation._id_counter
|
||||
Conversation._id_counter += 1
|
||||
|
||||
self._client = client
|
||||
self._timeout = timeout
|
||||
self._total_timeout = total_timeout
|
||||
self._total_due = None
|
||||
|
||||
self._outgoing = set()
|
||||
self._last_outgoing = 0
|
||||
self._incoming = []
|
||||
self._last_incoming = 0
|
||||
self._max_incoming = max_messages
|
||||
self._last_read = None
|
||||
self._custom = {}
|
||||
|
||||
self._pending_responses = {}
|
||||
self._pending_replies = {}
|
||||
self._pending_edits = {}
|
||||
self._pending_reads = {}
|
||||
|
||||
self._exclusive = exclusive
|
||||
self._cancelled = False
|
||||
|
||||
# The user is able to expect two responses for the same message.
|
||||
# {desired message ID: next incoming index}
|
||||
self._response_indices = {}
|
||||
if replies_are_responses:
|
||||
self._reply_indices = self._response_indices
|
||||
else:
|
||||
self._reply_indices = {}
|
||||
|
||||
self._edit_dates = {}
|
||||
|
||||
@_checks_cancelled
|
||||
async def send_message(self, *args, **kwargs):
|
||||
"""
|
||||
Sends a message in the context of this conversation. Shorthand
|
||||
for `telethon.client.messages.MessageMethods.send_message` with
|
||||
``entity`` already set.
|
||||
"""
|
||||
sent = await self._client.send_message(
|
||||
self._input_chat, *args, **kwargs)
|
||||
|
||||
# Albums will be lists, so handle that
|
||||
ms = sent if isinstance(sent, list) else (sent,)
|
||||
self._outgoing.update(m.id for m in ms)
|
||||
self._last_outgoing = ms[-1].id
|
||||
return sent
|
||||
|
||||
@_checks_cancelled
|
||||
async def send_file(self, *args, **kwargs):
|
||||
"""
|
||||
Sends a file in the context of this conversation. Shorthand
|
||||
for `telethon.client.uploads.UploadMethods.send_file` with
|
||||
``entity`` already set.
|
||||
"""
|
||||
sent = await self._client.send_file(
|
||||
self._input_chat, *args, **kwargs)
|
||||
|
||||
# Albums will be lists, so handle that
|
||||
ms = sent if isinstance(sent, list) else (sent,)
|
||||
self._outgoing.update(m.id for m in ms)
|
||||
self._last_outgoing = ms[-1].id
|
||||
return sent
|
||||
|
||||
@_checks_cancelled
|
||||
def mark_read(self, message=None):
|
||||
"""
|
||||
Marks as read the latest received message if ``message is None``.
|
||||
Otherwise, marks as read until the given message (or message ID).
|
||||
|
||||
This is equivalent to calling `client.send_read_acknowledge
|
||||
<telethon.client.messages.MessageMethods.send_read_acknowledge>`.
|
||||
"""
|
||||
if message is None:
|
||||
if self._incoming:
|
||||
message = self._incoming[-1].id
|
||||
else:
|
||||
message = 0
|
||||
elif not isinstance(message, int):
|
||||
message = message.id
|
||||
|
||||
return self._client.send_read_acknowledge(
|
||||
self._input_chat, max_id=message)
|
||||
|
||||
def get_response(self, message=None, *, timeout=None):
|
||||
"""
|
||||
Gets the next message that responds to a previous one. This is
|
||||
the method you need most of the time, along with `get_edit`.
|
||||
|
||||
Args:
|
||||
message (`Message <telethon.tl.custom.message.Message>` | `int`, optional):
|
||||
The message (or the message ID) for which a response
|
||||
is expected. By default this is the last sent message.
|
||||
|
||||
timeout (`int` | `float`, optional):
|
||||
If present, this `timeout` (in seconds) will override the
|
||||
per-action timeout defined for the conversation.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async with client.conversation(...) as conv:
|
||||
await conv.send_message('Hey, what is your name?')
|
||||
|
||||
response = await conv.get_response()
|
||||
name = response.text
|
||||
|
||||
await conv.send_message('Nice to meet you, {}!'.format(name))
|
||||
"""
|
||||
return self._get_message(
|
||||
message, self._response_indices, self._pending_responses, timeout,
|
||||
lambda x, y: True
|
||||
)
|
||||
|
||||
def get_reply(self, message=None, *, timeout=None):
|
||||
"""
|
||||
Gets the next message that explicitly replies to a previous one.
|
||||
"""
|
||||
return self._get_message(
|
||||
message, self._reply_indices, self._pending_replies, timeout,
|
||||
lambda x, y: x.reply_to and x.reply_to.reply_to_msg_id == y
|
||||
)
|
||||
|
||||
def _get_message(
|
||||
self, target_message, indices, pending, timeout, condition):
|
||||
"""
|
||||
Gets the next desired message under the desired condition.
|
||||
|
||||
Args:
|
||||
target_message (`object`):
|
||||
The target message for which we want to find another
|
||||
response that applies based on `condition`.
|
||||
|
||||
indices (`dict`):
|
||||
This dictionary remembers the last ID chosen for the
|
||||
input `target_message`.
|
||||
|
||||
pending (`dict`):
|
||||
This dictionary remembers {msg_id: Future} to be set
|
||||
once `condition` is met.
|
||||
|
||||
timeout (`int`):
|
||||
The timeout (in seconds) override to use for this operation.
|
||||
|
||||
condition (`callable`):
|
||||
The condition callable that checks if an incoming
|
||||
message is a valid response.
|
||||
"""
|
||||
start_time = time.time()
|
||||
target_id = self._get_message_id(target_message)
|
||||
|
||||
# If there is no last-chosen ID, make sure to pick one *after*
|
||||
# the input message, since we don't want responses back in time
|
||||
if target_id not in indices:
|
||||
for i, incoming in enumerate(self._incoming):
|
||||
if incoming.id > target_id:
|
||||
indices[target_id] = i
|
||||
break
|
||||
else:
|
||||
indices[target_id] = len(self._incoming)
|
||||
|
||||
# We will always return a future from here, even if the result
|
||||
# can be set immediately. Otherwise, needing to await only
|
||||
# sometimes is an annoying edge case (i.e. we would return
|
||||
# a `Message` but `get_response()` always `await`'s).
|
||||
future = self._client.loop.create_future()
|
||||
|
||||
# If there are enough responses saved return the next one
|
||||
last_idx = indices[target_id]
|
||||
if last_idx < len(self._incoming):
|
||||
incoming = self._incoming[last_idx]
|
||||
if condition(incoming, target_id):
|
||||
indices[target_id] += 1
|
||||
future.set_result(incoming)
|
||||
return future
|
||||
|
||||
# Otherwise the next incoming response will be the one to use
|
||||
#
|
||||
# Note how we fill "pending" before giving control back to the
|
||||
# event loop through "await". We want to register it as soon as
|
||||
# possible, since any other task switch may arrive with the result.
|
||||
pending[target_id] = future
|
||||
return self._get_result(future, start_time, timeout, pending, target_id)
|
||||
|
||||
def get_edit(self, message=None, *, timeout=None):
|
||||
"""
|
||||
Awaits for an edit after the last message to arrive.
|
||||
The arguments are the same as those for `get_response`.
|
||||
"""
|
||||
start_time = time.time()
|
||||
target_id = self._get_message_id(message)
|
||||
|
||||
target_date = self._edit_dates.get(target_id, 0)
|
||||
earliest_edit = min(
|
||||
(x for x in self._incoming
|
||||
if x.edit_date
|
||||
and x.id > target_id
|
||||
and x.edit_date.timestamp() > target_date
|
||||
),
|
||||
key=lambda x: x.edit_date.timestamp(),
|
||||
default=None
|
||||
)
|
||||
|
||||
future = self._client.loop.create_future()
|
||||
if earliest_edit and earliest_edit.edit_date.timestamp() > target_date:
|
||||
self._edit_dates[target_id] = earliest_edit.edit_date.timestamp()
|
||||
future.set_result(earliest_edit)
|
||||
return future # we should always return something we can await
|
||||
|
||||
# Otherwise the next incoming response will be the one to use
|
||||
self._pending_edits[target_id] = future
|
||||
return self._get_result(future, start_time, timeout, self._pending_edits, target_id)
|
||||
|
||||
def wait_read(self, message=None, *, timeout=None):
|
||||
"""
|
||||
Awaits for the sent message to be marked as read. Note that
|
||||
receiving a response doesn't imply the message was read, and
|
||||
this action will also trigger even without a response.
|
||||
"""
|
||||
start_time = time.time()
|
||||
future = self._client.loop.create_future()
|
||||
target_id = self._get_message_id(message)
|
||||
|
||||
if self._last_read is None:
|
||||
self._last_read = target_id - 1
|
||||
|
||||
if self._last_read >= target_id:
|
||||
return
|
||||
|
||||
self._pending_reads[target_id] = future
|
||||
return self._get_result(future, start_time, timeout, self._pending_reads, target_id)
|
||||
|
||||
async def wait_event(self, event, *, timeout=None):
|
||||
"""
|
||||
Waits for a custom event to occur. Timeouts still apply.
|
||||
|
||||
.. note::
|
||||
|
||||
**Only use this if there isn't another method available!**
|
||||
For example, don't use `wait_event` for new messages,
|
||||
since `get_response` already exists, etc.
|
||||
|
||||
Unless you're certain that your code will run fast enough,
|
||||
generally you should get a "handle" of this special coroutine
|
||||
before acting. In this example you will see how to wait for a user
|
||||
to join a group with proper use of `wait_event`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import TelegramClient, events
|
||||
|
||||
client = TelegramClient(...)
|
||||
group_id = ...
|
||||
|
||||
async def main():
|
||||
# Could also get the user id from an event; this is just an example
|
||||
user_id = ...
|
||||
|
||||
async with client.conversation(user_id) as conv:
|
||||
# Get a handle to the future event we'll wait for
|
||||
handle = conv.wait_event(events.ChatAction(
|
||||
group_id,
|
||||
func=lambda e: e.user_joined and e.user_id == user_id
|
||||
))
|
||||
|
||||
# Perform whatever action in between
|
||||
await conv.send_message('Please join this group before speaking to me!')
|
||||
|
||||
# Wait for the event we registered above to fire
|
||||
event = await handle
|
||||
|
||||
# Continue with the conversation
|
||||
await conv.send_message('Thanks!')
|
||||
|
||||
This way your event can be registered before acting,
|
||||
since the response may arrive before your event was
|
||||
registered. It depends on your use case since this
|
||||
also means the event can arrive before you send
|
||||
a previous action.
|
||||
"""
|
||||
start_time = time.time()
|
||||
if isinstance(event, type):
|
||||
event = event()
|
||||
|
||||
await event.resolve(self._client)
|
||||
|
||||
counter = Conversation._custom_counter
|
||||
Conversation._custom_counter += 1
|
||||
|
||||
future = self._client.loop.create_future()
|
||||
self._custom[counter] = (event, future)
|
||||
try:
|
||||
return await self._get_result(future, start_time, timeout, self._custom, counter)
|
||||
finally:
|
||||
# Need to remove it from the dict if it times out, else we may
|
||||
# try and fail to set the result later (#1618).
|
||||
self._custom.pop(counter, None)
|
||||
|
||||
async def _check_custom(self, built):
|
||||
for key, (ev, fut) in list(self._custom.items()):
|
||||
ev_type = type(ev)
|
||||
inst = built[ev_type]
|
||||
|
||||
if inst:
|
||||
filter = ev.filter(inst)
|
||||
if inspect.isawaitable(filter):
|
||||
filter = await filter
|
||||
|
||||
if filter:
|
||||
fut.set_result(inst)
|
||||
del self._custom[key]
|
||||
|
||||
def _on_new_message(self, response):
|
||||
response = response.message
|
||||
if response.chat_id != self.chat_id or response.out:
|
||||
return
|
||||
|
||||
if len(self._incoming) == self._max_incoming:
|
||||
self._cancel_all(ValueError('Too many incoming messages'))
|
||||
return
|
||||
|
||||
self._incoming.append(response)
|
||||
|
||||
# Most of the time, these dictionaries will contain just one item
|
||||
# TODO In fact, why not make it be that way? Force one item only.
|
||||
# How often will people want to wait for two responses at
|
||||
# the same time? It's impossible, first one will arrive
|
||||
# and then another, so they can do that.
|
||||
for msg_id, future in list(self._pending_responses.items()):
|
||||
self._response_indices[msg_id] = len(self._incoming)
|
||||
future.set_result(response)
|
||||
del self._pending_responses[msg_id]
|
||||
|
||||
for msg_id, future in list(self._pending_replies.items()):
|
||||
if response.reply_to and msg_id == response.reply_to.reply_to_msg_id:
|
||||
self._reply_indices[msg_id] = len(self._incoming)
|
||||
future.set_result(response)
|
||||
del self._pending_replies[msg_id]
|
||||
|
||||
def _on_edit(self, message):
|
||||
message = message.message
|
||||
if message.chat_id != self.chat_id or message.out:
|
||||
return
|
||||
|
||||
# We have to update our incoming messages with the new edit date
|
||||
for i, m in enumerate(self._incoming):
|
||||
if m.id == message.id:
|
||||
self._incoming[i] = message
|
||||
break
|
||||
|
||||
for msg_id, future in list(self._pending_edits.items()):
|
||||
if msg_id < message.id:
|
||||
edit_ts = message.edit_date.timestamp()
|
||||
|
||||
# We compare <= because edit_ts resolution is always to
|
||||
# seconds, but we may have increased _edit_dates before.
|
||||
# Since the dates are ever growing this is not a problem.
|
||||
if edit_ts <= self._edit_dates.get(msg_id, 0):
|
||||
self._edit_dates[msg_id] += _EDIT_COLLISION_DELTA
|
||||
else:
|
||||
self._edit_dates[msg_id] = message.edit_date.timestamp()
|
||||
|
||||
future.set_result(message)
|
||||
del self._pending_edits[msg_id]
|
||||
|
||||
def _on_read(self, event):
|
||||
if event.chat_id != self.chat_id or event.inbox:
|
||||
return
|
||||
|
||||
self._last_read = event.max_id
|
||||
|
||||
for msg_id, pending in list(self._pending_reads.items()):
|
||||
if msg_id >= self._last_read:
|
||||
pending.set_result(True)
|
||||
del self._pending_reads[msg_id]
|
||||
|
||||
def _get_message_id(self, message):
|
||||
if message is not None: # 0 is valid but false-y, check for None
|
||||
return message if isinstance(message, int) else message.id
|
||||
elif self._last_outgoing:
|
||||
return self._last_outgoing
|
||||
else:
|
||||
raise ValueError('No message was sent previously')
|
||||
|
||||
@_checks_cancelled
|
||||
def _get_result(self, future, start_time, timeout, pending, target_id):
|
||||
due = self._total_due
|
||||
if timeout is None:
|
||||
timeout = self._timeout
|
||||
|
||||
if timeout is not None:
|
||||
due = min(due, start_time + timeout)
|
||||
|
||||
# NOTE: We can't try/finally to pop from pending here because
|
||||
# the event loop needs to get back to us, but it might
|
||||
# dispatch another update before, and in that case a
|
||||
# response could be set twice. So responses must be
|
||||
# cleared when their futures are set to a result.
|
||||
return asyncio.wait_for(
|
||||
future,
|
||||
timeout=None if due == float('inf') else due - time.time()
|
||||
)
|
||||
|
||||
def _cancel_all(self, exception=None):
|
||||
self._cancelled = True
|
||||
for pending in itertools.chain(
|
||||
self._pending_responses.values(),
|
||||
self._pending_replies.values(),
|
||||
self._pending_edits.values()):
|
||||
if exception:
|
||||
pending.set_exception(exception)
|
||||
else:
|
||||
pending.cancel()
|
||||
|
||||
for _, fut in self._custom.values():
|
||||
if exception:
|
||||
fut.set_exception(exception)
|
||||
else:
|
||||
fut.cancel()
|
||||
|
||||
async def __aenter__(self):
|
||||
self._input_chat = \
|
||||
await self._client.get_input_entity(self._input_chat)
|
||||
|
||||
self._chat_peer = utils.get_peer(self._input_chat)
|
||||
|
||||
# Make sure we're the only conversation in this chat if it's exclusive
|
||||
chat_id = utils.get_peer_id(self._chat_peer)
|
||||
conv_set = self._client._conversations[chat_id]
|
||||
if self._exclusive and conv_set:
|
||||
raise errors.AlreadyInConversationError()
|
||||
|
||||
conv_set.add(self)
|
||||
self._cancelled = False
|
||||
|
||||
self._last_outgoing = 0
|
||||
self._last_incoming = 0
|
||||
for d in (
|
||||
self._outgoing, self._incoming,
|
||||
self._pending_responses, self._pending_replies,
|
||||
self._pending_edits, self._response_indices,
|
||||
self._reply_indices, self._edit_dates, self._custom):
|
||||
d.clear()
|
||||
|
||||
if self._total_timeout:
|
||||
self._total_due = time.time() + self._total_timeout
|
||||
else:
|
||||
self._total_due = float('inf')
|
||||
|
||||
return self
|
||||
|
||||
def cancel(self):
|
||||
"""
|
||||
Cancels the current conversation. Pending responses and subsequent
|
||||
calls to get a response will raise ``asyncio.CancelledError``.
|
||||
|
||||
This method is synchronous and should not be awaited.
|
||||
"""
|
||||
self._cancel_all()
|
||||
|
||||
async def cancel_all(self):
|
||||
"""
|
||||
Calls `cancel` on *all* conversations in this chat.
|
||||
|
||||
Note that you should ``await`` this method, since it's meant to be
|
||||
used outside of a context manager, and it needs to resolve the chat.
|
||||
"""
|
||||
chat_id = await self._client.get_peer_id(self._input_chat)
|
||||
for conv in self._client._conversations[chat_id]:
|
||||
conv.cancel()
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
chat_id = utils.get_peer_id(self._chat_peer)
|
||||
conv_set = self._client._conversations[chat_id]
|
||||
conv_set.discard(self)
|
||||
if not conv_set:
|
||||
del self._client._conversations[chat_id]
|
||||
|
||||
self._cancel_all()
|
||||
|
||||
__enter__ = helpers._sync_enter
|
||||
__exit__ = helpers._sync_exit
|
161
.venv2/Lib/site-packages/telethon/tl/custom/dialog.py
Normal file
161
.venv2/Lib/site-packages/telethon/tl/custom/dialog.py
Normal file
@@ -0,0 +1,161 @@
|
||||
from . import Draft
|
||||
from .. import TLObject, types, functions
|
||||
from ... import utils
|
||||
|
||||
|
||||
class Dialog:
|
||||
"""
|
||||
Custom class that encapsulates a dialog (an open "conversation" with
|
||||
someone, a group or a channel) providing an abstraction to easily
|
||||
access the input version/normal entity/message etc. The library will
|
||||
return instances of this class when calling :meth:`.get_dialogs()`.
|
||||
|
||||
Args:
|
||||
dialog (:tl:`Dialog`):
|
||||
The original ``Dialog`` instance.
|
||||
|
||||
pinned (`bool`):
|
||||
Whether this dialog is pinned to the top or not.
|
||||
|
||||
folder_id (`folder_id`):
|
||||
The folder ID that this dialog belongs to.
|
||||
|
||||
archived (`bool`):
|
||||
Whether this dialog is archived or not (``folder_id is None``).
|
||||
|
||||
message (`Message <telethon.tl.custom.message.Message>`):
|
||||
The last message sent on this dialog. Note that this member
|
||||
will not be updated when new messages arrive, it's only set
|
||||
on creation of the instance.
|
||||
|
||||
date (`datetime`):
|
||||
The date of the last message sent on this dialog.
|
||||
|
||||
entity (`entity`):
|
||||
The entity that belongs to this dialog (user, chat or channel).
|
||||
|
||||
input_entity (:tl:`InputPeer`):
|
||||
Input version of the entity.
|
||||
|
||||
id (`int`):
|
||||
The marked ID of the entity, which is guaranteed to be unique.
|
||||
|
||||
name (`str`):
|
||||
Display name for this dialog. For chats and channels this is
|
||||
their title, and for users it's "First-Name Last-Name".
|
||||
|
||||
title (`str`):
|
||||
Alias for `name`.
|
||||
|
||||
unread_count (`int`):
|
||||
How many messages are currently unread in this dialog. Note that
|
||||
this value won't update when new messages arrive.
|
||||
|
||||
unread_mentions_count (`int`):
|
||||
How many mentions are currently unread in this dialog. Note that
|
||||
this value won't update when new messages arrive.
|
||||
|
||||
draft (`Draft <telethon.tl.custom.draft.Draft>`):
|
||||
The draft object in this dialog. It will not be `None`,
|
||||
so you can call ``draft.set_message(...)``.
|
||||
|
||||
is_user (`bool`):
|
||||
`True` if the `entity` is a :tl:`User`.
|
||||
|
||||
is_group (`bool`):
|
||||
`True` if the `entity` is a :tl:`Chat`
|
||||
or a :tl:`Channel` megagroup.
|
||||
|
||||
is_channel (`bool`):
|
||||
`True` if the `entity` is a :tl:`Channel`.
|
||||
"""
|
||||
def __init__(self, client, dialog, entities, message):
|
||||
# Both entities and messages being dicts {ID: item}
|
||||
self._client = client
|
||||
self.dialog = dialog
|
||||
self.pinned = bool(dialog.pinned)
|
||||
self.folder_id = dialog.folder_id
|
||||
self.archived = dialog.folder_id is not None
|
||||
self.message = message
|
||||
self.date = getattr(self.message, 'date', None)
|
||||
|
||||
self.entity = entities[utils.get_peer_id(dialog.peer)]
|
||||
self.input_entity = utils.get_input_peer(self.entity)
|
||||
self.id = utils.get_peer_id(self.entity) # ^ May be InputPeerSelf()
|
||||
self.name = self.title = utils.get_display_name(self.entity)
|
||||
|
||||
self.unread_count = dialog.unread_count
|
||||
self.unread_mentions_count = dialog.unread_mentions_count
|
||||
|
||||
self.draft = Draft(client, self.entity, self.dialog.draft)
|
||||
|
||||
self.is_user = isinstance(self.entity, types.User)
|
||||
self.is_group = (
|
||||
isinstance(self.entity, (types.Chat, types.ChatForbidden)) or
|
||||
(isinstance(self.entity, types.Channel) and self.entity.megagroup)
|
||||
)
|
||||
self.is_channel = isinstance(self.entity, types.Channel)
|
||||
|
||||
async def send_message(self, *args, **kwargs):
|
||||
"""
|
||||
Sends a message to this dialog. This is just a wrapper around
|
||||
``client.send_message(dialog.input_entity, *args, **kwargs)``.
|
||||
"""
|
||||
return await self._client.send_message(
|
||||
self.input_entity, *args, **kwargs)
|
||||
|
||||
async def delete(self, revoke=False):
|
||||
"""
|
||||
Deletes the dialog from your dialog list. If you own the
|
||||
channel this won't destroy it, only delete it from the list.
|
||||
|
||||
Shorthand for `telethon.client.dialogs.DialogMethods.delete_dialog`
|
||||
with ``entity`` already set.
|
||||
"""
|
||||
# Pass the entire entity so the method can determine whether
|
||||
# the `Chat` is deactivated (in which case we don't kick ourselves,
|
||||
# or it would raise `PEER_ID_INVALID`).
|
||||
await self._client.delete_dialog(self.entity, revoke=revoke)
|
||||
|
||||
async def archive(self, folder=1):
|
||||
"""
|
||||
Archives (or un-archives) this dialog.
|
||||
|
||||
Args:
|
||||
folder (`int`, optional):
|
||||
The folder to which the dialog should be archived to.
|
||||
|
||||
If you want to "un-archive" it, use ``folder=0``.
|
||||
|
||||
Returns:
|
||||
The :tl:`Updates` object that the request produces.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Archiving
|
||||
dialog.archive()
|
||||
|
||||
# Un-archiving
|
||||
dialog.archive(0)
|
||||
"""
|
||||
return await self._client(functions.folders.EditPeerFoldersRequest([
|
||||
types.InputFolderPeer(self.input_entity, folder_id=folder)
|
||||
]))
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'_': 'Dialog',
|
||||
'name': self.name,
|
||||
'date': self.date,
|
||||
'draft': self.draft,
|
||||
'message': self.message,
|
||||
'entity': self.entity,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return TLObject.pretty_format(self.to_dict())
|
||||
|
||||
def stringify(self):
|
||||
return TLObject.pretty_format(self.to_dict(), indent=0)
|
191
.venv2/Lib/site-packages/telethon/tl/custom/draft.py
Normal file
191
.venv2/Lib/site-packages/telethon/tl/custom/draft.py
Normal file
@@ -0,0 +1,191 @@
|
||||
import datetime
|
||||
|
||||
from .. import TLObject, types
|
||||
from ..functions.messages import SaveDraftRequest
|
||||
from ..types import DraftMessage
|
||||
from ...errors import RPCError
|
||||
from ...extensions import markdown
|
||||
from ...utils import get_input_peer, get_peer, get_peer_id
|
||||
|
||||
|
||||
class Draft:
|
||||
"""
|
||||
Custom class that encapsulates a draft on the Telegram servers, providing
|
||||
an abstraction to change the message conveniently. The library will return
|
||||
instances of this class when calling :meth:`get_drafts()`.
|
||||
|
||||
Args:
|
||||
date (`datetime`):
|
||||
The date of the draft.
|
||||
|
||||
link_preview (`bool`):
|
||||
Whether the link preview is enabled or not.
|
||||
|
||||
reply_to_msg_id (`int`):
|
||||
The message ID that the draft will reply to.
|
||||
"""
|
||||
def __init__(self, client, entity, draft):
|
||||
self._client = client
|
||||
self._peer = get_peer(entity)
|
||||
self._entity = entity
|
||||
self._input_entity = get_input_peer(entity) if entity else None
|
||||
|
||||
if not draft or not isinstance(draft, DraftMessage):
|
||||
draft = DraftMessage('', None, None, None, None)
|
||||
|
||||
self._text = markdown.unparse(draft.message, draft.entities)
|
||||
self._raw_text = draft.message
|
||||
self.date = draft.date
|
||||
self.link_preview = not draft.no_webpage
|
||||
self.reply_to_msg_id = draft.reply_to.reply_to_msg_id if isinstance(draft.reply_to, types.InputReplyToMessage) else None
|
||||
|
||||
@property
|
||||
def entity(self):
|
||||
"""
|
||||
The entity that belongs to this dialog (user, chat or channel).
|
||||
"""
|
||||
return self._entity
|
||||
|
||||
@property
|
||||
def input_entity(self):
|
||||
"""
|
||||
Input version of the entity.
|
||||
"""
|
||||
if not self._input_entity:
|
||||
try:
|
||||
self._input_entity = self._client._mb_entity_cache.get(
|
||||
get_peer_id(self._peer, add_mark=False))._as_input_peer()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return self._input_entity
|
||||
|
||||
async def get_entity(self):
|
||||
"""
|
||||
Returns `entity` but will make an API call if necessary.
|
||||
"""
|
||||
if not self.entity and await self.get_input_entity():
|
||||
try:
|
||||
self._entity =\
|
||||
await self._client.get_entity(self._input_entity)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return self._entity
|
||||
|
||||
async def get_input_entity(self):
|
||||
"""
|
||||
Returns `input_entity` but will make an API call if necessary.
|
||||
"""
|
||||
# We don't actually have an API call we can make yet
|
||||
# to get more info, but keep this method for consistency.
|
||||
return self.input_entity
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
The markdown text contained in the draft. It will be
|
||||
empty if there is no text (and hence no draft is set).
|
||||
"""
|
||||
return self._text
|
||||
|
||||
@property
|
||||
def raw_text(self):
|
||||
"""
|
||||
The raw (text without formatting) contained in the draft.
|
||||
It will be empty if there is no text (thus draft not set).
|
||||
"""
|
||||
return self._raw_text
|
||||
|
||||
@property
|
||||
def is_empty(self):
|
||||
"""
|
||||
Convenience bool to determine if the draft is empty or not.
|
||||
"""
|
||||
return not self._text
|
||||
|
||||
async def set_message(
|
||||
self, text=None, reply_to=0, parse_mode=(),
|
||||
link_preview=None):
|
||||
"""
|
||||
Changes the draft message on the Telegram servers. The changes are
|
||||
reflected in this object.
|
||||
|
||||
:param str text: New text of the draft.
|
||||
Preserved if left as None.
|
||||
|
||||
:param int reply_to: Message ID to reply to.
|
||||
Preserved if left as 0, erased if set to None.
|
||||
|
||||
:param bool link_preview: Whether to attach a web page preview.
|
||||
Preserved if left as None.
|
||||
|
||||
:param str parse_mode: The parse mode to be used for the text.
|
||||
:return bool: `True` on success.
|
||||
"""
|
||||
if text is None:
|
||||
text = self._text
|
||||
|
||||
if reply_to == 0:
|
||||
reply_to = self.reply_to_msg_id
|
||||
|
||||
if link_preview is None:
|
||||
link_preview = self.link_preview
|
||||
|
||||
raw_text, entities =\
|
||||
await self._client._parse_message_text(text, parse_mode)
|
||||
|
||||
result = await self._client(SaveDraftRequest(
|
||||
peer=self._peer,
|
||||
message=raw_text,
|
||||
no_webpage=not link_preview,
|
||||
reply_to=None if reply_to is None else types.InputReplyToMessage(reply_to),
|
||||
entities=entities
|
||||
))
|
||||
|
||||
if result:
|
||||
self._text = text
|
||||
self._raw_text = raw_text
|
||||
self.link_preview = link_preview
|
||||
self.reply_to_msg_id = reply_to
|
||||
self.date = datetime.datetime.now(tz=datetime.timezone.utc)
|
||||
|
||||
return result
|
||||
|
||||
async def send(self, clear=True, parse_mode=()):
|
||||
"""
|
||||
Sends the contents of this draft to the dialog. This is just a
|
||||
wrapper around ``send_message(dialog.input_entity, *args, **kwargs)``.
|
||||
"""
|
||||
await self._client.send_message(
|
||||
self._peer, self.text, reply_to=self.reply_to_msg_id,
|
||||
link_preview=self.link_preview, parse_mode=parse_mode,
|
||||
clear_draft=clear
|
||||
)
|
||||
|
||||
async def delete(self):
|
||||
"""
|
||||
Deletes this draft, and returns `True` on success.
|
||||
"""
|
||||
return await self.set_message(text='')
|
||||
|
||||
def to_dict(self):
|
||||
try:
|
||||
entity = self.entity
|
||||
except RPCError as e:
|
||||
entity = e
|
||||
|
||||
return {
|
||||
'_': 'Draft',
|
||||
'text': self.text,
|
||||
'entity': entity,
|
||||
'date': self.date,
|
||||
'link_preview': self.link_preview,
|
||||
'reply_to_msg_id': self.reply_to_msg_id
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return TLObject.pretty_format(self.to_dict())
|
||||
|
||||
def stringify(self):
|
||||
return TLObject.pretty_format(self.to_dict(), indent=0)
|
146
.venv2/Lib/site-packages/telethon/tl/custom/file.py
Normal file
146
.venv2/Lib/site-packages/telethon/tl/custom/file.py
Normal file
@@ -0,0 +1,146 @@
|
||||
import mimetypes
|
||||
import os
|
||||
|
||||
from ... import utils
|
||||
from ...tl import types
|
||||
|
||||
|
||||
class File:
|
||||
"""
|
||||
Convenience class over media like photos or documents, which
|
||||
supports accessing the attributes in a more convenient way.
|
||||
|
||||
If any of the attributes are not present in the current media,
|
||||
the properties will be `None`.
|
||||
|
||||
The original media is available through the ``media`` attribute.
|
||||
"""
|
||||
def __init__(self, media):
|
||||
self.media = media
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
The old bot-API style ``file_id`` representing this file.
|
||||
|
||||
.. warning::
|
||||
|
||||
This feature has not been maintained for a long time and
|
||||
may not work. It will be removed in future versions.
|
||||
|
||||
.. note::
|
||||
|
||||
This file ID may not work under user accounts,
|
||||
but should still be usable by bot accounts.
|
||||
|
||||
You can, however, still use it to identify
|
||||
a file in for example a database.
|
||||
"""
|
||||
return utils.pack_bot_file_id(self.media)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
The file name of this document.
|
||||
"""
|
||||
return self._from_attr(types.DocumentAttributeFilename, 'file_name')
|
||||
|
||||
@property
|
||||
def ext(self):
|
||||
"""
|
||||
The extension from the mime type of this file.
|
||||
|
||||
If the mime type is unknown, the extension
|
||||
from the file name (if any) will be used.
|
||||
"""
|
||||
return (
|
||||
mimetypes.guess_extension(self.mime_type)
|
||||
or os.path.splitext(self.name or '')[-1]
|
||||
or None
|
||||
)
|
||||
|
||||
@property
|
||||
def mime_type(self):
|
||||
"""
|
||||
The mime-type of this file.
|
||||
"""
|
||||
if isinstance(self.media, types.Photo):
|
||||
return 'image/jpeg'
|
||||
elif isinstance(self.media, types.Document):
|
||||
return self.media.mime_type
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
"""
|
||||
The width in pixels of this media if it's a photo or a video.
|
||||
"""
|
||||
if isinstance(self.media, types.Photo):
|
||||
return max(getattr(s, 'w', 0) for s in self.media.sizes)
|
||||
|
||||
return self._from_attr((
|
||||
types.DocumentAttributeImageSize, types.DocumentAttributeVideo), 'w')
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
"""
|
||||
The height in pixels of this media if it's a photo or a video.
|
||||
"""
|
||||
if isinstance(self.media, types.Photo):
|
||||
return max(getattr(s, 'h', 0) for s in self.media.sizes)
|
||||
|
||||
return self._from_attr((
|
||||
types.DocumentAttributeImageSize, types.DocumentAttributeVideo), 'h')
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
"""
|
||||
The duration in seconds of the audio or video.
|
||||
"""
|
||||
return self._from_attr((
|
||||
types.DocumentAttributeAudio, types.DocumentAttributeVideo), 'duration')
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
"""
|
||||
The title of the song.
|
||||
"""
|
||||
return self._from_attr(types.DocumentAttributeAudio, 'title')
|
||||
|
||||
@property
|
||||
def performer(self):
|
||||
"""
|
||||
The performer of the song.
|
||||
"""
|
||||
return self._from_attr(types.DocumentAttributeAudio, 'performer')
|
||||
|
||||
@property
|
||||
def emoji(self):
|
||||
"""
|
||||
A string with all emoji that represent the current sticker.
|
||||
"""
|
||||
return self._from_attr(types.DocumentAttributeSticker, 'alt')
|
||||
|
||||
@property
|
||||
def sticker_set(self):
|
||||
"""
|
||||
The :tl:`InputStickerSet` to which the sticker file belongs.
|
||||
"""
|
||||
return self._from_attr(types.DocumentAttributeSticker, 'stickerset')
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""
|
||||
The size in bytes of this file.
|
||||
|
||||
For photos, this is the heaviest thumbnail, as it often repressents the largest dimensions.
|
||||
"""
|
||||
if isinstance(self.media, types.Photo):
|
||||
return max(filter(None, map(utils._photo_size_byte_count, self.media.sizes)), default=None)
|
||||
elif isinstance(self.media, types.Document):
|
||||
return self.media.size
|
||||
|
||||
def _from_attr(self, cls, field):
|
||||
if isinstance(self.media, types.Document):
|
||||
for attr in self.media.attributes:
|
||||
if isinstance(attr, cls):
|
||||
return getattr(attr, field, None)
|
51
.venv2/Lib/site-packages/telethon/tl/custom/forward.py
Normal file
51
.venv2/Lib/site-packages/telethon/tl/custom/forward.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from .chatgetter import ChatGetter
|
||||
from .sendergetter import SenderGetter
|
||||
from ... import utils, helpers
|
||||
from ...tl import types
|
||||
|
||||
|
||||
class Forward(ChatGetter, SenderGetter):
|
||||
"""
|
||||
Custom class that encapsulates a :tl:`MessageFwdHeader` providing an
|
||||
abstraction to easily access information like the original sender.
|
||||
|
||||
Remember that this class implements `ChatGetter
|
||||
<telethon.tl.custom.chatgetter.ChatGetter>` and `SenderGetter
|
||||
<telethon.tl.custom.sendergetter.SenderGetter>` which means you
|
||||
have access to all their sender and chat properties and methods.
|
||||
|
||||
Attributes:
|
||||
|
||||
original_fwd (:tl:`MessageFwdHeader`):
|
||||
The original :tl:`MessageFwdHeader` instance.
|
||||
|
||||
Any other attribute:
|
||||
Attributes not described here are the same as those available
|
||||
in the original :tl:`MessageFwdHeader`.
|
||||
"""
|
||||
def __init__(self, client, original, entities):
|
||||
# Copy all the fields, not reference! It would cause memory cycles:
|
||||
# self.original_fwd.original_fwd.original_fwd.original_fwd
|
||||
# ...would be valid if we referenced.
|
||||
self.__dict__.update(original.__dict__)
|
||||
self.original_fwd = original
|
||||
|
||||
sender_id = sender = input_sender = peer = chat = input_chat = None
|
||||
if original.from_id:
|
||||
ty = helpers._entity_type(original.from_id)
|
||||
if ty == helpers._EntityType.USER:
|
||||
sender_id = utils.get_peer_id(original.from_id)
|
||||
sender, input_sender = utils._get_entity_pair(
|
||||
sender_id, entities, client._mb_entity_cache)
|
||||
|
||||
elif ty in (helpers._EntityType.CHAT, helpers._EntityType.CHANNEL):
|
||||
peer = original.from_id
|
||||
chat, input_chat = utils._get_entity_pair(
|
||||
utils.get_peer_id(peer), entities, client._mb_entity_cache)
|
||||
|
||||
# This call resets the client
|
||||
ChatGetter.__init__(self, peer, chat=chat, input_chat=input_chat)
|
||||
SenderGetter.__init__(self, sender_id, sender=sender, input_sender=input_sender)
|
||||
self._client = client
|
||||
|
||||
# TODO We could reload the message
|
450
.venv2/Lib/site-packages/telethon/tl/custom/inlinebuilder.py
Normal file
450
.venv2/Lib/site-packages/telethon/tl/custom/inlinebuilder.py
Normal file
@@ -0,0 +1,450 @@
|
||||
import hashlib
|
||||
|
||||
from .. import functions, types
|
||||
from ... import utils
|
||||
|
||||
_TYPE_TO_MIMES = {
|
||||
'gif': ['image/gif'], # 'video/mp4' too, but that's used for video
|
||||
'article': ['text/html'],
|
||||
'audio': ['audio/mpeg'],
|
||||
'contact': [],
|
||||
'file': ['application/pdf', 'application/zip'], # actually any
|
||||
'geo': [],
|
||||
'photo': ['image/jpeg'],
|
||||
'sticker': ['image/webp', 'application/x-tgsticker'],
|
||||
'venue': [],
|
||||
'video': ['video/mp4'], # tdlib includes text/html for some reason
|
||||
'voice': ['audio/ogg'],
|
||||
}
|
||||
|
||||
|
||||
class InlineBuilder:
|
||||
"""
|
||||
Helper class to allow defining `InlineQuery
|
||||
<telethon.events.inlinequery.InlineQuery>` ``results``.
|
||||
|
||||
Common arguments to all methods are
|
||||
explained here to avoid repetition:
|
||||
|
||||
text (`str`, optional):
|
||||
If present, the user will send a text
|
||||
message with this text upon being clicked.
|
||||
|
||||
link_preview (`bool`, optional):
|
||||
Whether to show a link preview in the sent
|
||||
text message or not.
|
||||
|
||||
geo (:tl:`InputGeoPoint`, :tl:`GeoPoint`, :tl:`InputMediaVenue`, :tl:`MessageMediaVenue`, optional):
|
||||
If present, it may either be a geo point or a venue.
|
||||
|
||||
period (int, optional):
|
||||
The period in seconds to be used for geo points.
|
||||
|
||||
contact (:tl:`InputMediaContact`, :tl:`MessageMediaContact`, optional):
|
||||
If present, it must be the contact information to send.
|
||||
|
||||
game (`bool`, optional):
|
||||
May be `True` to indicate that the game will be sent.
|
||||
|
||||
buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, :tl:`KeyboardButton`, optional):
|
||||
Same as ``buttons`` for `client.send_message()
|
||||
<telethon.client.messages.MessageMethods.send_message>`.
|
||||
|
||||
parse_mode (`str`, optional):
|
||||
Same as ``parse_mode`` for `client.send_message()
|
||||
<telethon.client.messageparse.MessageParseMethods.parse_mode>`.
|
||||
|
||||
id (`str`, optional):
|
||||
The string ID to use for this result. If not present, it
|
||||
will be the SHA256 hexadecimal digest of converting the
|
||||
created :tl:`InputBotInlineResult` with empty ID to ``bytes()``,
|
||||
so that the ID will be deterministic for the same input.
|
||||
|
||||
.. note::
|
||||
|
||||
If two inputs are exactly the same, their IDs will be the same
|
||||
too. If you send two articles with the same ID, it will raise
|
||||
``ResultIdDuplicateError``. Consider giving them an explicit
|
||||
ID if you need to send two results that are the same.
|
||||
"""
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
# noinspection PyIncorrectDocstring
|
||||
async def article(
|
||||
self, title, description=None,
|
||||
*, url=None, thumb=None, content=None,
|
||||
id=None, text=None, parse_mode=(), link_preview=True,
|
||||
geo=None, period=60, contact=None, game=False, buttons=None
|
||||
):
|
||||
"""
|
||||
Creates new inline result of article type.
|
||||
|
||||
Args:
|
||||
title (`str`):
|
||||
The title to be shown for this result.
|
||||
|
||||
description (`str`, optional):
|
||||
Further explanation of what this result means.
|
||||
|
||||
url (`str`, optional):
|
||||
The URL to be shown for this result.
|
||||
|
||||
thumb (:tl:`InputWebDocument`, optional):
|
||||
The thumbnail to be shown for this result.
|
||||
For now it has to be a :tl:`InputWebDocument` if present.
|
||||
|
||||
content (:tl:`InputWebDocument`, optional):
|
||||
The content to be shown for this result.
|
||||
For now it has to be a :tl:`InputWebDocument` if present.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
results = [
|
||||
# Option with title and description sending a message.
|
||||
builder.article(
|
||||
title='First option',
|
||||
description='This is the first option',
|
||||
text='Text sent after clicking this option',
|
||||
),
|
||||
# Option with title URL to be opened when clicked.
|
||||
builder.article(
|
||||
title='Second option',
|
||||
url='https://example.com',
|
||||
text='Text sent if the user clicks the option and not the URL',
|
||||
),
|
||||
# Sending a message with buttons.
|
||||
# You can use a list or a list of lists to include more buttons.
|
||||
builder.article(
|
||||
title='Third option',
|
||||
text='Text sent with buttons below',
|
||||
buttons=Button.url('https://example.com'),
|
||||
),
|
||||
]
|
||||
"""
|
||||
# TODO Does 'article' work always?
|
||||
# article, photo, gif, mpeg4_gif, video, audio,
|
||||
# voice, document, location, venue, contact, game
|
||||
result = types.InputBotInlineResult(
|
||||
id=id or '',
|
||||
type='article',
|
||||
send_message=await self._message(
|
||||
text=text, parse_mode=parse_mode, link_preview=link_preview,
|
||||
geo=geo, period=period,
|
||||
contact=contact,
|
||||
game=game,
|
||||
buttons=buttons
|
||||
),
|
||||
title=title,
|
||||
description=description,
|
||||
url=url,
|
||||
thumb=thumb,
|
||||
content=content
|
||||
)
|
||||
if id is None:
|
||||
result.id = hashlib.sha256(bytes(result)).hexdigest()
|
||||
|
||||
return result
|
||||
|
||||
# noinspection PyIncorrectDocstring
|
||||
async def photo(
|
||||
self, file, *, id=None, include_media=True,
|
||||
text=None, parse_mode=(), link_preview=True,
|
||||
geo=None, period=60, contact=None, game=False, buttons=None
|
||||
):
|
||||
"""
|
||||
Creates a new inline result of photo type.
|
||||
|
||||
Args:
|
||||
include_media (`bool`, optional):
|
||||
Whether the photo file used to display the result should be
|
||||
included in the message itself or not. By default, the photo
|
||||
is included, and the text parameter alters the caption.
|
||||
|
||||
file (`obj`, optional):
|
||||
Same as ``file`` for `client.send_file()
|
||||
<telethon.client.uploads.UploadMethods.send_file>`.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
results = [
|
||||
# Sending just the photo when the user selects it.
|
||||
builder.photo('/path/to/photo.jpg'),
|
||||
|
||||
# Including a caption with some in-memory photo.
|
||||
photo_bytesio = ...
|
||||
builder.photo(
|
||||
photo_bytesio,
|
||||
text='This will be the caption of the sent photo',
|
||||
),
|
||||
|
||||
# Sending just the message without including the photo.
|
||||
builder.photo(
|
||||
photo,
|
||||
text='This will be a normal text message',
|
||||
include_media=False,
|
||||
),
|
||||
]
|
||||
"""
|
||||
try:
|
||||
fh = utils.get_input_photo(file)
|
||||
except TypeError:
|
||||
_, media, _ = await self._client._file_to_media(
|
||||
file, allow_cache=True, as_image=True
|
||||
)
|
||||
if isinstance(media, types.InputPhoto):
|
||||
fh = media
|
||||
else:
|
||||
r = await self._client(functions.messages.UploadMediaRequest(
|
||||
types.InputPeerSelf(), media=media
|
||||
))
|
||||
fh = utils.get_input_photo(r.photo)
|
||||
|
||||
result = types.InputBotInlineResultPhoto(
|
||||
id=id or '',
|
||||
type='photo',
|
||||
photo=fh,
|
||||
send_message=await self._message(
|
||||
text=text or '',
|
||||
parse_mode=parse_mode,
|
||||
link_preview=link_preview,
|
||||
media=include_media,
|
||||
geo=geo,
|
||||
period=period,
|
||||
contact=contact,
|
||||
game=game,
|
||||
buttons=buttons
|
||||
)
|
||||
)
|
||||
if id is None:
|
||||
result.id = hashlib.sha256(bytes(result)).hexdigest()
|
||||
|
||||
return result
|
||||
|
||||
# noinspection PyIncorrectDocstring
|
||||
async def document(
|
||||
self, file, title=None, *, description=None, type=None,
|
||||
mime_type=None, attributes=None, force_document=False,
|
||||
voice_note=False, video_note=False, use_cache=True, id=None,
|
||||
text=None, parse_mode=(), link_preview=True,
|
||||
geo=None, period=60, contact=None, game=False, buttons=None,
|
||||
include_media=True
|
||||
):
|
||||
"""
|
||||
Creates a new inline result of document type.
|
||||
|
||||
`use_cache`, `mime_type`, `attributes`, `force_document`,
|
||||
`voice_note` and `video_note` are described in `client.send_file
|
||||
<telethon.client.uploads.UploadMethods.send_file>`.
|
||||
|
||||
Args:
|
||||
file (`obj`):
|
||||
Same as ``file`` for `client.send_file()
|
||||
<telethon.client.uploads.UploadMethods.send_file>`.
|
||||
|
||||
title (`str`, optional):
|
||||
The title to be shown for this result.
|
||||
|
||||
description (`str`, optional):
|
||||
Further explanation of what this result means.
|
||||
|
||||
type (`str`, optional):
|
||||
The type of the document. May be one of: article, audio,
|
||||
contact, file, geo, gif, photo, sticker, venue, video, voice.
|
||||
It will be automatically set if ``mime_type`` is specified,
|
||||
and default to ``'file'`` if no matching mime type is found.
|
||||
you may need to pass ``attributes`` in order to use ``type``
|
||||
effectively.
|
||||
|
||||
attributes (`list`, optional):
|
||||
Optional attributes that override the inferred ones, like
|
||||
:tl:`DocumentAttributeFilename` and so on.
|
||||
|
||||
include_media (`bool`, optional):
|
||||
Whether the document file used to display the result should be
|
||||
included in the message itself or not. By default, the document
|
||||
is included, and the text parameter alters the caption.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
results = [
|
||||
# Sending just the file when the user selects it.
|
||||
builder.document('/path/to/file.pdf'),
|
||||
|
||||
# Including a caption with some in-memory file.
|
||||
file_bytesio = ...
|
||||
builder.document(
|
||||
file_bytesio,
|
||||
text='This will be the caption of the sent file',
|
||||
),
|
||||
|
||||
# Sending just the message without including the file.
|
||||
builder.document(
|
||||
photo,
|
||||
text='This will be a normal text message',
|
||||
include_media=False,
|
||||
),
|
||||
]
|
||||
"""
|
||||
if type is None:
|
||||
if voice_note:
|
||||
type = 'voice'
|
||||
elif mime_type:
|
||||
for ty, mimes in _TYPE_TO_MIMES.items():
|
||||
for mime in mimes:
|
||||
if mime_type == mime:
|
||||
type = ty
|
||||
break
|
||||
|
||||
if type is None:
|
||||
type = 'file'
|
||||
|
||||
try:
|
||||
fh = utils.get_input_document(file)
|
||||
except TypeError:
|
||||
_, media, _ = await self._client._file_to_media(
|
||||
file,
|
||||
mime_type=mime_type,
|
||||
attributes=attributes,
|
||||
force_document=force_document,
|
||||
voice_note=voice_note,
|
||||
video_note=video_note,
|
||||
allow_cache=use_cache
|
||||
)
|
||||
if isinstance(media, types.InputDocument):
|
||||
fh = media
|
||||
else:
|
||||
r = await self._client(functions.messages.UploadMediaRequest(
|
||||
types.InputPeerSelf(), media=media
|
||||
))
|
||||
fh = utils.get_input_document(r.document)
|
||||
|
||||
result = types.InputBotInlineResultDocument(
|
||||
id=id or '',
|
||||
type=type,
|
||||
document=fh,
|
||||
send_message=await self._message(
|
||||
# Empty string for text if there's media but text is None.
|
||||
# We may want to display a document but send text; however
|
||||
# default to sending the media (without text, i.e. stickers).
|
||||
text=text or '',
|
||||
parse_mode=parse_mode,
|
||||
link_preview=link_preview,
|
||||
media=include_media,
|
||||
geo=geo,
|
||||
period=period,
|
||||
contact=contact,
|
||||
game=game,
|
||||
buttons=buttons
|
||||
),
|
||||
title=title,
|
||||
description=description
|
||||
)
|
||||
if id is None:
|
||||
result.id = hashlib.sha256(bytes(result)).hexdigest()
|
||||
|
||||
return result
|
||||
|
||||
# noinspection PyIncorrectDocstring
|
||||
async def game(
|
||||
self, short_name, *, id=None,
|
||||
text=None, parse_mode=(), link_preview=True,
|
||||
geo=None, period=60, contact=None, game=False, buttons=None
|
||||
):
|
||||
"""
|
||||
Creates a new inline result of game type.
|
||||
|
||||
Args:
|
||||
short_name (`str`):
|
||||
The short name of the game to use.
|
||||
"""
|
||||
result = types.InputBotInlineResultGame(
|
||||
id=id or '',
|
||||
short_name=short_name,
|
||||
send_message=await self._message(
|
||||
text=text, parse_mode=parse_mode, link_preview=link_preview,
|
||||
geo=geo, period=period,
|
||||
contact=contact,
|
||||
game=game,
|
||||
buttons=buttons
|
||||
)
|
||||
)
|
||||
if id is None:
|
||||
result.id = hashlib.sha256(bytes(result)).hexdigest()
|
||||
|
||||
return result
|
||||
|
||||
async def _message(
|
||||
self, *,
|
||||
text=None, parse_mode=(), link_preview=True, media=False,
|
||||
geo=None, period=60, contact=None, game=False, buttons=None
|
||||
):
|
||||
# Empty strings are valid but false-y; if they're empty use dummy '\0'
|
||||
args = ('\0' if text == '' else text, geo, contact, game)
|
||||
if sum(1 for x in args if x is not None and x is not False) != 1:
|
||||
raise ValueError(
|
||||
'Must set exactly one of text, geo, contact or game (set {})'
|
||||
.format(', '.join(x[0] for x in zip(
|
||||
'text geo contact game'.split(), args) if x[1]) or 'none')
|
||||
)
|
||||
|
||||
markup = self._client.build_reply_markup(buttons, inline_only=True)
|
||||
if text is not None:
|
||||
text, msg_entities = await self._client._parse_message_text(
|
||||
text, parse_mode
|
||||
)
|
||||
if media:
|
||||
# "MediaAuto" means it will use whatever media the inline
|
||||
# result itself has (stickers, photos, or documents), while
|
||||
# respecting the user's text (caption) and formatting.
|
||||
return types.InputBotInlineMessageMediaAuto(
|
||||
message=text,
|
||||
entities=msg_entities,
|
||||
reply_markup=markup
|
||||
)
|
||||
else:
|
||||
return types.InputBotInlineMessageText(
|
||||
message=text,
|
||||
no_webpage=not link_preview,
|
||||
entities=msg_entities,
|
||||
reply_markup=markup
|
||||
)
|
||||
elif isinstance(geo, (types.InputGeoPoint, types.GeoPoint)):
|
||||
return types.InputBotInlineMessageMediaGeo(
|
||||
geo_point=utils.get_input_geo(geo),
|
||||
period=period,
|
||||
reply_markup=markup
|
||||
)
|
||||
elif isinstance(geo, (types.InputMediaVenue, types.MessageMediaVenue)):
|
||||
if isinstance(geo, types.InputMediaVenue):
|
||||
geo_point = geo.geo_point
|
||||
else:
|
||||
geo_point = geo.geo
|
||||
|
||||
return types.InputBotInlineMessageMediaVenue(
|
||||
geo_point=geo_point,
|
||||
title=geo.title,
|
||||
address=geo.address,
|
||||
provider=geo.provider,
|
||||
venue_id=geo.venue_id,
|
||||
venue_type=geo.venue_type,
|
||||
reply_markup=markup
|
||||
)
|
||||
elif isinstance(contact, (
|
||||
types.InputMediaContact, types.MessageMediaContact)):
|
||||
return types.InputBotInlineMessageMediaContact(
|
||||
phone_number=contact.phone_number,
|
||||
first_name=contact.first_name,
|
||||
last_name=contact.last_name,
|
||||
vcard=contact.vcard,
|
||||
reply_markup=markup
|
||||
)
|
||||
elif game:
|
||||
return types.InputBotInlineMessageGame(
|
||||
reply_markup=markup
|
||||
)
|
||||
else:
|
||||
raise ValueError('No text, game or valid geo or contact given')
|
176
.venv2/Lib/site-packages/telethon/tl/custom/inlineresult.py
Normal file
176
.venv2/Lib/site-packages/telethon/tl/custom/inlineresult.py
Normal file
@@ -0,0 +1,176 @@
|
||||
from .. import types, functions
|
||||
from ... import utils
|
||||
|
||||
|
||||
class InlineResult:
|
||||
"""
|
||||
Custom class that encapsulates a bot inline result providing
|
||||
an abstraction to easily access some commonly needed features
|
||||
(such as clicking a result to select it).
|
||||
|
||||
Attributes:
|
||||
|
||||
result (:tl:`BotInlineResult`):
|
||||
The original :tl:`BotInlineResult` object.
|
||||
"""
|
||||
# tdlib types are the following (InlineQueriesManager::answer_inline_query @ 1a4a834):
|
||||
# gif, article, audio, contact, file, geo, photo, sticker, venue, video, voice
|
||||
#
|
||||
# However, those documented in https://core.telegram.org/bots/api#inline-mode are different.
|
||||
ARTICLE = 'article'
|
||||
PHOTO = 'photo'
|
||||
GIF = 'gif'
|
||||
VIDEO = 'video'
|
||||
VIDEO_GIF = 'mpeg4_gif'
|
||||
AUDIO = 'audio'
|
||||
DOCUMENT = 'document'
|
||||
LOCATION = 'location'
|
||||
VENUE = 'venue'
|
||||
CONTACT = 'contact'
|
||||
GAME = 'game'
|
||||
|
||||
def __init__(self, client, original, query_id=None, *, entity=None):
|
||||
self._client = client
|
||||
self.result = original
|
||||
self._query_id = query_id
|
||||
self._entity = entity
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""
|
||||
The always-present type of this result. It will be one of:
|
||||
``'article'``, ``'photo'``, ``'gif'``, ``'mpeg4_gif'``, ``'video'``,
|
||||
``'audio'``, ``'voice'``, ``'document'``, ``'location'``, ``'venue'``,
|
||||
``'contact'``, ``'game'``.
|
||||
|
||||
You can access all of these constants through `InlineResult`,
|
||||
such as `InlineResult.ARTICLE`, `InlineResult.VIDEO_GIF`, etc.
|
||||
"""
|
||||
return self.result.type
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
"""
|
||||
The always-present :tl:`BotInlineMessage` that
|
||||
will be sent if `click` is called on this result.
|
||||
"""
|
||||
return self.result.send_message
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
"""
|
||||
The title for this inline result. It may be `None`.
|
||||
"""
|
||||
return self.result.title
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""
|
||||
The description for this inline result. It may be `None`.
|
||||
"""
|
||||
return self.result.description
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""
|
||||
The URL present in this inline results. If you want to "click"
|
||||
this URL to open it in your browser, you should use Python's
|
||||
`webbrowser.open(url)` for such task.
|
||||
"""
|
||||
if isinstance(self.result, types.BotInlineResult):
|
||||
return self.result.url
|
||||
|
||||
@property
|
||||
def photo(self):
|
||||
"""
|
||||
Returns either the :tl:`WebDocument` thumbnail for
|
||||
normal results or the :tl:`Photo` for media results.
|
||||
"""
|
||||
if isinstance(self.result, types.BotInlineResult):
|
||||
return self.result.thumb
|
||||
elif isinstance(self.result, types.BotInlineMediaResult):
|
||||
return self.result.photo
|
||||
|
||||
@property
|
||||
def document(self):
|
||||
"""
|
||||
Returns either the :tl:`WebDocument` content for
|
||||
normal results or the :tl:`Document` for media results.
|
||||
"""
|
||||
if isinstance(self.result, types.BotInlineResult):
|
||||
return self.result.content
|
||||
elif isinstance(self.result, types.BotInlineMediaResult):
|
||||
return self.result.document
|
||||
|
||||
async def click(self, entity=None, reply_to=None, comment_to=None,
|
||||
silent=False, clear_draft=False, hide_via=False,
|
||||
background=None):
|
||||
"""
|
||||
Clicks this result and sends the associated `message`.
|
||||
|
||||
Args:
|
||||
entity (`entity`):
|
||||
The entity to which the message of this result should be sent.
|
||||
|
||||
reply_to (`int` | `Message <telethon.tl.custom.message.Message>`, optional):
|
||||
If present, the sent message will reply to this ID or message.
|
||||
|
||||
comment_to (`int` | `Message <telethon.tl.custom.message.Message>`, optional):
|
||||
Similar to ``reply_to``, but replies in the linked group of a
|
||||
broadcast channel instead (effectively leaving a "comment to"
|
||||
the specified message).
|
||||
|
||||
silent (`bool`, optional):
|
||||
Whether the message should notify people with sound or not.
|
||||
Defaults to `False` (send with a notification sound unless
|
||||
the person has the chat muted). Set it to `True` to alter
|
||||
this behaviour.
|
||||
|
||||
clear_draft (`bool`, optional):
|
||||
Whether the draft should be removed after sending the
|
||||
message from this result or not. Defaults to `False`.
|
||||
|
||||
hide_via (`bool`, optional):
|
||||
Whether the "via @bot" should be hidden or not.
|
||||
Only works with certain bots (like @bing or @gif).
|
||||
|
||||
background (`bool`, optional):
|
||||
Whether the message should be send in background.
|
||||
|
||||
"""
|
||||
if entity:
|
||||
entity = await self._client.get_input_entity(entity)
|
||||
elif self._entity:
|
||||
entity = self._entity
|
||||
else:
|
||||
raise ValueError('You must provide the entity where the result should be sent to')
|
||||
|
||||
if comment_to:
|
||||
entity, reply_id = await self._client._get_comment_data(entity, comment_to)
|
||||
else:
|
||||
reply_id = None if reply_to is None else utils.get_message_id(reply_to)
|
||||
|
||||
req = functions.messages.SendInlineBotResultRequest(
|
||||
peer=entity,
|
||||
query_id=self._query_id,
|
||||
id=self.result.id,
|
||||
silent=silent,
|
||||
background=background,
|
||||
clear_draft=clear_draft,
|
||||
hide_via=hide_via,
|
||||
reply_to=None if reply_id is None else types.InputReplyToMessage(reply_id)
|
||||
)
|
||||
return self._client._get_response_message(
|
||||
req, await self._client(req), entity)
|
||||
|
||||
async def download_media(self, *args, **kwargs):
|
||||
"""
|
||||
Downloads the media in this result (if there is a document, the
|
||||
document will be downloaded; otherwise, the photo will if present).
|
||||
|
||||
This is a wrapper around `client.download_media
|
||||
<telethon.client.downloads.DownloadMethods.download_media>`.
|
||||
"""
|
||||
if self.document or self.photo:
|
||||
return await self._client.download_media(
|
||||
self.document or self.photo, *args, **kwargs)
|
83
.venv2/Lib/site-packages/telethon/tl/custom/inlineresults.py
Normal file
83
.venv2/Lib/site-packages/telethon/tl/custom/inlineresults.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import time
|
||||
|
||||
from .inlineresult import InlineResult
|
||||
|
||||
|
||||
class InlineResults(list):
|
||||
"""
|
||||
Custom class that encapsulates :tl:`BotResults` providing
|
||||
an abstraction to easily access some commonly needed features
|
||||
(such as clicking one of the results to select it)
|
||||
|
||||
Note that this is a list of `InlineResult
|
||||
<telethon.tl.custom.inlineresult.InlineResult>`
|
||||
so you can iterate over it or use indices to
|
||||
access its elements. In addition, it has some
|
||||
attributes.
|
||||
|
||||
Attributes:
|
||||
result (:tl:`BotResults`):
|
||||
The original :tl:`BotResults` object.
|
||||
|
||||
query_id (`int`):
|
||||
The random ID that identifies this query.
|
||||
|
||||
cache_time (`int`):
|
||||
For how long the results should be considered
|
||||
valid. You can call `results_valid` at any
|
||||
moment to determine if the results are still
|
||||
valid or not.
|
||||
|
||||
users (:tl:`User`):
|
||||
The users present in this inline query.
|
||||
|
||||
gallery (`bool`):
|
||||
Whether these results should be presented
|
||||
in a grid (as a gallery of images) or not.
|
||||
|
||||
next_offset (`str`, optional):
|
||||
The string to be used as an offset to get
|
||||
the next chunk of results, if any.
|
||||
|
||||
switch_pm (:tl:`InlineBotSwitchPM`, optional):
|
||||
If presents, the results should show a button to
|
||||
switch to a private conversation with the bot using
|
||||
the text in this object.
|
||||
"""
|
||||
def __init__(self, client, original, *, entity=None):
|
||||
super().__init__(InlineResult(client, x, original.query_id, entity=entity)
|
||||
for x in original.results)
|
||||
|
||||
self.result = original
|
||||
self.query_id = original.query_id
|
||||
self.cache_time = original.cache_time
|
||||
self._valid_until = time.time() + self.cache_time
|
||||
self.users = original.users
|
||||
self.gallery = bool(original.gallery)
|
||||
self.next_offset = original.next_offset
|
||||
self.switch_pm = original.switch_pm
|
||||
|
||||
def results_valid(self):
|
||||
"""
|
||||
Returns `True` if the cache time has not expired
|
||||
yet and the results can still be considered valid.
|
||||
"""
|
||||
return time.time() < self._valid_until
|
||||
|
||||
def _to_str(self, item_function):
|
||||
return ('[{}, query_id={}, cache_time={}, users={}, gallery={}, '
|
||||
'next_offset={}, switch_pm={}]'.format(
|
||||
', '.join(item_function(x) for x in self),
|
||||
self.query_id,
|
||||
self.cache_time,
|
||||
self.users,
|
||||
self.gallery,
|
||||
self.next_offset,
|
||||
self.switch_pm
|
||||
))
|
||||
|
||||
def __str__(self):
|
||||
return self._to_str(str)
|
||||
|
||||
def __repr__(self):
|
||||
return self._to_str(repr)
|
@@ -0,0 +1,9 @@
|
||||
from ..types import InputFile
|
||||
|
||||
|
||||
class InputSizedFile(InputFile):
|
||||
"""InputFile class with two extra parameters: md5 (digest) and size"""
|
||||
def __init__(self, id_, parts, name, md5, size):
|
||||
super().__init__(id_, parts, name, md5.hexdigest())
|
||||
self.md5 = md5.digest()
|
||||
self.size = size
|
1205
.venv2/Lib/site-packages/telethon/tl/custom/message.py
Normal file
1205
.venv2/Lib/site-packages/telethon/tl/custom/message.py
Normal file
File diff suppressed because it is too large
Load Diff
151
.venv2/Lib/site-packages/telethon/tl/custom/messagebutton.py
Normal file
151
.venv2/Lib/site-packages/telethon/tl/custom/messagebutton.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from .. import types, functions
|
||||
from ... import password as pwd_mod
|
||||
from ...errors import BotResponseTimeoutError
|
||||
try:
|
||||
import webbrowser
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
class MessageButton:
|
||||
"""
|
||||
.. note::
|
||||
|
||||
`Message.buttons <telethon.tl.custom.message.Message.buttons>`
|
||||
are instances of this type. If you want to **define** a reply
|
||||
markup for e.g. sending messages, refer to `Button
|
||||
<telethon.tl.custom.button.Button>` instead.
|
||||
|
||||
Custom class that encapsulates a message button providing
|
||||
an abstraction to easily access some commonly needed features
|
||||
(such as clicking the button itself).
|
||||
|
||||
Attributes:
|
||||
|
||||
button (:tl:`KeyboardButton`):
|
||||
The original :tl:`KeyboardButton` object.
|
||||
"""
|
||||
def __init__(self, client, original, chat, bot, msg_id):
|
||||
self.button = original
|
||||
self._bot = bot
|
||||
self._chat = chat
|
||||
self._msg_id = msg_id
|
||||
self._client = client
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
"""
|
||||
Returns the `telethon.client.telegramclient.TelegramClient`
|
||||
instance that created this instance.
|
||||
"""
|
||||
return self._client
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""The text string of the button."""
|
||||
return self.button.text
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
"""The `bytes` data for :tl:`KeyboardButtonCallback` objects."""
|
||||
if isinstance(self.button, types.KeyboardButtonCallback):
|
||||
return self.button.data
|
||||
|
||||
@property
|
||||
def inline_query(self):
|
||||
"""The query `str` for :tl:`KeyboardButtonSwitchInline` objects."""
|
||||
if isinstance(self.button, types.KeyboardButtonSwitchInline):
|
||||
return self.button.query
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""The url `str` for :tl:`KeyboardButtonUrl` objects."""
|
||||
if isinstance(self.button, types.KeyboardButtonUrl):
|
||||
return self.button.url
|
||||
|
||||
async def click(self, share_phone=None, share_geo=None, *, password=None):
|
||||
"""
|
||||
Emulates the behaviour of clicking this button.
|
||||
|
||||
If it's a normal :tl:`KeyboardButton` with text, a message will be
|
||||
sent, and the sent `Message <telethon.tl.custom.message.Message>` returned.
|
||||
|
||||
If it's an inline :tl:`KeyboardButtonCallback` with text and data,
|
||||
it will be "clicked" and the :tl:`BotCallbackAnswer` returned.
|
||||
|
||||
If it's an inline :tl:`KeyboardButtonSwitchInline` button, the
|
||||
:tl:`StartBotRequest` will be invoked and the resulting updates
|
||||
returned.
|
||||
|
||||
If it's a :tl:`KeyboardButtonUrl`, the URL of the button will
|
||||
be passed to ``webbrowser.open`` and return `True` on success.
|
||||
|
||||
If it's a :tl:`KeyboardButtonRequestPhone`, you must indicate that you
|
||||
want to ``share_phone=True`` in order to share it. Sharing it is not a
|
||||
default because it is a privacy concern and could happen accidentally.
|
||||
|
||||
You may also use ``share_phone=phone`` to share a specific number, in
|
||||
which case either `str` or :tl:`InputMediaContact` should be used.
|
||||
|
||||
If it's a :tl:`KeyboardButtonRequestGeoLocation`, you must pass a
|
||||
tuple in ``share_geo=(longitude, latitude)``. Note that Telegram seems
|
||||
to have some heuristics to determine impossible locations, so changing
|
||||
this value a lot quickly may not work as expected. You may also pass a
|
||||
:tl:`InputGeoPoint` if you find the order confusing.
|
||||
"""
|
||||
if isinstance(self.button, types.KeyboardButton):
|
||||
return await self._client.send_message(
|
||||
self._chat, self.button.text, parse_mode=None)
|
||||
elif isinstance(self.button, types.KeyboardButtonCallback):
|
||||
if password is not None:
|
||||
pwd = await self._client(functions.account.GetPasswordRequest())
|
||||
password = pwd_mod.compute_check(pwd, password)
|
||||
|
||||
req = functions.messages.GetBotCallbackAnswerRequest(
|
||||
peer=self._chat, msg_id=self._msg_id, data=self.button.data,
|
||||
password=password
|
||||
)
|
||||
try:
|
||||
return await self._client(req)
|
||||
except BotResponseTimeoutError:
|
||||
return None
|
||||
elif isinstance(self.button, types.KeyboardButtonSwitchInline):
|
||||
return await self._client(functions.messages.StartBotRequest(
|
||||
bot=self._bot, peer=self._chat, start_param=self.button.query
|
||||
))
|
||||
elif isinstance(self.button, types.KeyboardButtonUrl):
|
||||
if "webbrowser" in sys.modules:
|
||||
return webbrowser.open(self.button.url)
|
||||
elif isinstance(self.button, types.KeyboardButtonGame):
|
||||
req = functions.messages.GetBotCallbackAnswerRequest(
|
||||
peer=self._chat, msg_id=self._msg_id, game=True
|
||||
)
|
||||
try:
|
||||
return await self._client(req)
|
||||
except BotResponseTimeoutError:
|
||||
return None
|
||||
elif isinstance(self.button, types.KeyboardButtonRequestPhone):
|
||||
if not share_phone:
|
||||
raise ValueError('cannot click on phone buttons unless share_phone=True')
|
||||
|
||||
if share_phone == True or isinstance(share_phone, str):
|
||||
me = await self._client.get_me()
|
||||
share_phone = types.InputMediaContact(
|
||||
phone_number=me.phone if share_phone == True else share_phone,
|
||||
first_name=me.first_name or '',
|
||||
last_name=me.last_name or '',
|
||||
vcard=''
|
||||
)
|
||||
|
||||
return await self._client.send_file(self._chat, share_phone)
|
||||
elif isinstance(self.button, types.KeyboardButtonRequestGeoLocation):
|
||||
if not share_geo:
|
||||
raise ValueError('cannot click on geo buttons unless share_geo=(longitude, latitude)')
|
||||
|
||||
if isinstance(share_geo, (tuple, list)):
|
||||
long, lat = share_geo
|
||||
share_geo = types.InputMediaGeoPoint(types.InputGeoPoint(lat=lat, long=long))
|
||||
|
||||
return await self._client.send_file(self._chat, share_geo)
|
@@ -0,0 +1,138 @@
|
||||
from .. import types
|
||||
|
||||
|
||||
def _admin_prop(field_name, doc):
|
||||
"""
|
||||
Helper method to build properties that return `True` if the user is an
|
||||
administrator of a normal chat, or otherwise return `True` if the user
|
||||
has a specific permission being an admin of a channel.
|
||||
"""
|
||||
def fget(self):
|
||||
if not self.is_admin:
|
||||
return False
|
||||
if self.is_chat:
|
||||
return True
|
||||
|
||||
return getattr(self.participant.admin_rights, field_name)
|
||||
|
||||
return {'fget': fget, 'doc': doc}
|
||||
|
||||
|
||||
class ParticipantPermissions:
|
||||
"""
|
||||
Participant permissions information.
|
||||
|
||||
The properties in this objects are boolean values indicating whether the
|
||||
user has the permission or not.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
permissions = ...
|
||||
|
||||
if permissions.is_banned:
|
||||
"this user is banned"
|
||||
elif permissions.is_admin:
|
||||
"this user is an administrator"
|
||||
"""
|
||||
def __init__(self, participant, chat: bool):
|
||||
self.participant = participant
|
||||
self.is_chat = chat
|
||||
|
||||
@property
|
||||
def is_admin(self):
|
||||
"""
|
||||
Whether the user is an administrator of the chat or not. The creator
|
||||
also counts as begin an administrator, since they have all permissions.
|
||||
"""
|
||||
return self.is_creator or isinstance(self.participant, (
|
||||
types.ChannelParticipantAdmin,
|
||||
types.ChatParticipantAdmin
|
||||
))
|
||||
|
||||
@property
|
||||
def is_creator(self):
|
||||
"""
|
||||
Whether the user is the creator of the chat or not.
|
||||
"""
|
||||
return isinstance(self.participant, (
|
||||
types.ChannelParticipantCreator,
|
||||
types.ChatParticipantCreator
|
||||
))
|
||||
|
||||
@property
|
||||
def has_default_permissions(self):
|
||||
"""
|
||||
Whether the user is a normal user of the chat (not administrator, but
|
||||
not banned either, and has no restrictions applied).
|
||||
"""
|
||||
return isinstance(self.participant, (
|
||||
types.ChannelParticipant,
|
||||
types.ChatParticipant,
|
||||
types.ChannelParticipantSelf
|
||||
))
|
||||
|
||||
@property
|
||||
def is_banned(self):
|
||||
"""
|
||||
Whether the user is banned in the chat.
|
||||
"""
|
||||
return isinstance(self.participant, types.ChannelParticipantBanned)
|
||||
|
||||
@property
|
||||
def has_left(self):
|
||||
"""
|
||||
Whether the user left the chat.
|
||||
"""
|
||||
return isinstance(self.participant, types.ChannelParticipantLeft)
|
||||
|
||||
@property
|
||||
def add_admins(self):
|
||||
"""
|
||||
Whether the administrator can add new administrators with the same or
|
||||
less permissions than them.
|
||||
"""
|
||||
if not self.is_admin:
|
||||
return False
|
||||
|
||||
if self.is_chat:
|
||||
return self.is_creator
|
||||
|
||||
return self.participant.admin_rights.add_admins
|
||||
|
||||
ban_users = property(**_admin_prop('ban_users', """
|
||||
Whether the administrator can ban other users or not.
|
||||
"""))
|
||||
|
||||
pin_messages = property(**_admin_prop('pin_messages', """
|
||||
Whether the administrator can pin messages or not.
|
||||
"""))
|
||||
|
||||
invite_users = property(**_admin_prop('invite_users', """
|
||||
Whether the administrator can add new users to the chat.
|
||||
"""))
|
||||
|
||||
delete_messages = property(**_admin_prop('delete_messages', """
|
||||
Whether the administrator can delete messages from other participants.
|
||||
"""))
|
||||
|
||||
edit_messages = property(**_admin_prop('edit_messages', """
|
||||
Whether the administrator can edit messages.
|
||||
"""))
|
||||
|
||||
post_messages = property(**_admin_prop('post_messages', """
|
||||
Whether the administrator can post messages in the broadcast channel.
|
||||
"""))
|
||||
|
||||
change_info = property(**_admin_prop('change_info', """
|
||||
Whether the administrator can change the information about the chat,
|
||||
such as title or description.
|
||||
"""))
|
||||
|
||||
anonymous = property(**_admin_prop('anonymous', """
|
||||
Whether the administrator will remain anonymous when sending messages.
|
||||
"""))
|
||||
|
||||
manage_call = property(**_admin_prop('manage_call', """
|
||||
Whether the user will be able to manage group calls.
|
||||
"""))
|
119
.venv2/Lib/site-packages/telethon/tl/custom/qrlogin.py
Normal file
119
.venv2/Lib/site-packages/telethon/tl/custom/qrlogin.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import datetime
|
||||
|
||||
from .. import types, functions
|
||||
from ... import events
|
||||
|
||||
|
||||
class QRLogin:
|
||||
"""
|
||||
QR login information.
|
||||
|
||||
Most of the time, you will present the `url` as a QR code to the user,
|
||||
and while it's being shown, call `wait`.
|
||||
"""
|
||||
def __init__(self, client, ignored_ids):
|
||||
self._client = client
|
||||
self._request = functions.auth.ExportLoginTokenRequest(
|
||||
self._client.api_id, self._client.api_hash, ignored_ids)
|
||||
self._resp = None
|
||||
|
||||
async def recreate(self):
|
||||
"""
|
||||
Generates a new token and URL for a new QR code, useful if the code
|
||||
has expired before it was imported.
|
||||
"""
|
||||
self._resp = await self._client(self._request)
|
||||
|
||||
@property
|
||||
def token(self) -> bytes:
|
||||
"""
|
||||
The binary data representing the token.
|
||||
|
||||
It can be used by a previously-authorized client in a call to
|
||||
:tl:`auth.importLoginToken` to log the client that originally
|
||||
requested the QR login.
|
||||
"""
|
||||
return self._resp.token
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
"""
|
||||
The ``tg://login`` URI with the token. When opened by a Telegram
|
||||
application where the user is logged in, it will import the login
|
||||
token.
|
||||
|
||||
If you want to display a QR code to the user, this is the URL that
|
||||
should be launched when the QR code is scanned (the URL that should
|
||||
be contained in the QR code image you generate).
|
||||
|
||||
Whether you generate the QR code image or not is up to you, and the
|
||||
library can't do this for you due to the vast ways of generating and
|
||||
displaying the QR code that exist.
|
||||
|
||||
The URL simply consists of `token` base64-encoded.
|
||||
"""
|
||||
return 'tg://login?token={}'.format(base64.urlsafe_b64encode(self._resp.token).decode('utf-8').rstrip('='))
|
||||
|
||||
@property
|
||||
def expires(self) -> datetime.datetime:
|
||||
"""
|
||||
The `datetime` at which the QR code will expire.
|
||||
|
||||
If you want to try again, you will need to call `recreate`.
|
||||
"""
|
||||
return self._resp.expires
|
||||
|
||||
async def wait(self, timeout: float = None):
|
||||
"""
|
||||
Waits for the token to be imported by a previously-authorized client,
|
||||
either by scanning the QR, launching the URL directly, or calling the
|
||||
import method.
|
||||
|
||||
This method **must** be called before the QR code is scanned, and
|
||||
must be executing while the QR code is being scanned. Otherwise, the
|
||||
login will not complete.
|
||||
|
||||
Will raise `asyncio.TimeoutError` if the login doesn't complete on
|
||||
time.
|
||||
|
||||
Arguments
|
||||
timeout (float):
|
||||
The timeout, in seconds, to wait before giving up. By default
|
||||
the library will wait until the token expires, which is often
|
||||
what you want.
|
||||
|
||||
Returns
|
||||
On success, an instance of :tl:`User`. On failure it will raise.
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = (self._resp.expires - datetime.datetime.now(tz=datetime.timezone.utc)).total_seconds()
|
||||
|
||||
event = asyncio.Event()
|
||||
|
||||
async def handler(_update):
|
||||
event.set()
|
||||
|
||||
self._client.add_event_handler(handler, events.Raw(types.UpdateLoginToken))
|
||||
|
||||
try:
|
||||
# Will raise timeout error if it doesn't complete quick enough,
|
||||
# which we want to let propagate
|
||||
await asyncio.wait_for(event.wait(), timeout=timeout)
|
||||
finally:
|
||||
self._client.remove_event_handler(handler)
|
||||
|
||||
# We got here without it raising timeout error, so we can proceed
|
||||
resp = await self._client(self._request)
|
||||
if isinstance(resp, types.auth.LoginTokenMigrateTo):
|
||||
await self._client._switch_dc(resp.dc_id)
|
||||
resp = await self._client(functions.auth.ImportLoginTokenRequest(resp.token))
|
||||
# resp should now be auth.loginTokenSuccess
|
||||
|
||||
if isinstance(resp, types.auth.LoginTokenSuccess):
|
||||
user = resp.authorization.user
|
||||
await self._client._on_login(user)
|
||||
return user
|
||||
|
||||
raise TypeError('Login token response was unexpected: {}'.format(resp))
|
102
.venv2/Lib/site-packages/telethon/tl/custom/sendergetter.py
Normal file
102
.venv2/Lib/site-packages/telethon/tl/custom/sendergetter.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import abc
|
||||
|
||||
from ... import utils
|
||||
|
||||
|
||||
class SenderGetter(abc.ABC):
|
||||
"""
|
||||
Helper base class that introduces the `sender`, `input_sender`
|
||||
and `sender_id` properties and `get_sender` and `get_input_sender`
|
||||
methods.
|
||||
"""
|
||||
def __init__(self, sender_id=None, *, sender=None, input_sender=None):
|
||||
self._sender_id = sender_id
|
||||
self._sender = sender
|
||||
self._input_sender = input_sender
|
||||
self._client = None
|
||||
|
||||
@property
|
||||
def sender(self):
|
||||
"""
|
||||
Returns the :tl:`User` or :tl:`Channel` that sent this object.
|
||||
It may be `None` if Telegram didn't send the sender.
|
||||
|
||||
If you only need the ID, use `sender_id` instead.
|
||||
|
||||
If you need to call a method which needs
|
||||
this chat, use `input_sender` instead.
|
||||
|
||||
If you're using `telethon.events`, use `get_sender()` instead.
|
||||
"""
|
||||
return self._sender
|
||||
|
||||
async def get_sender(self):
|
||||
"""
|
||||
Returns `sender`, but will make an API call to find the
|
||||
sender unless it's already cached.
|
||||
|
||||
If you only need the ID, use `sender_id` instead.
|
||||
|
||||
If you need to call a method which needs
|
||||
this sender, use `get_input_sender()` instead.
|
||||
"""
|
||||
# ``sender.min`` is present both in :tl:`User` and :tl:`Channel`.
|
||||
# It's a flag that will be set if only minimal information is
|
||||
# available (such as display name, but username may be missing),
|
||||
# in which case we want to force fetch the entire thing because
|
||||
# the user explicitly called a method. If the user is okay with
|
||||
# cached information, they may use the property instead.
|
||||
if (self._sender is None or getattr(self._sender, 'min', None)) \
|
||||
and await self.get_input_sender():
|
||||
# self.get_input_sender may refresh in which case the sender may no longer be min
|
||||
# However it could still incur a cost so the cheap check is done twice instead.
|
||||
if self._sender is None or getattr(self._sender, 'min', None):
|
||||
try:
|
||||
self._sender =\
|
||||
await self._client.get_entity(self._input_sender)
|
||||
except ValueError:
|
||||
await self._refetch_sender()
|
||||
return self._sender
|
||||
|
||||
@property
|
||||
def input_sender(self):
|
||||
"""
|
||||
This :tl:`InputPeer` is the input version of the user/channel who
|
||||
sent the message. Similarly to `input_chat
|
||||
<telethon.tl.custom.chatgetter.ChatGetter.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 and self._sender_id and self._client:
|
||||
try:
|
||||
self._input_sender = self._client._mb_entity_cache.get(
|
||||
utils.resolve_id(self._sender_id)[0])._as_input_peer()
|
||||
except AttributeError:
|
||||
pass
|
||||
return self._input_sender
|
||||
|
||||
async def get_input_sender(self):
|
||||
"""
|
||||
Returns `input_sender`, but will make an API call to find the
|
||||
input sender unless it's already cached.
|
||||
"""
|
||||
if self.input_sender is None and self._sender_id and self._client:
|
||||
await self._refetch_sender()
|
||||
return self._input_sender
|
||||
|
||||
@property
|
||||
def sender_id(self):
|
||||
"""
|
||||
Returns the marked sender integer ID, if present.
|
||||
|
||||
If there is a sender in the object, `sender_id` will *always* be set,
|
||||
which is why you should use it instead of `sender.id <sender>`.
|
||||
"""
|
||||
return self._sender_id
|
||||
|
||||
async def _refetch_sender(self):
|
||||
"""
|
||||
Re-fetches sender information through other means.
|
||||
"""
|
Reference in New Issue
Block a user