mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-02-03 13:14:31 +03:00
Work towards message buttons
This commit is contained in:
parent
d15e84e595
commit
8fe89496d6
|
@ -43,7 +43,7 @@ The :class:`session.Storage` abstract base class defines the required methods to
|
|||
Telethon comes with two built-in storages:
|
||||
|
||||
* :class:`~session.SqliteSession`. This is used by default when a string or path is used.
|
||||
* :class:`~session.MemorySession`. This is used by default when the path is ``None``.
|
||||
* :class:`~session.MemorySession`. This is used by default when the path is :data:`None`.
|
||||
You can also use it directly when you have a :class:`~session.Session` instance.
|
||||
It's useful when you don't have file-system access.
|
||||
|
||||
|
|
|
@ -377,7 +377,7 @@ Now handlers are called in order until the filter for one returns :data:`True`.
|
|||
The default behaviour is that handlers after that one are not called.
|
||||
This behaviour can be changed with the ``check_all_handlers`` flag in :class:`Client` constructor.
|
||||
|
||||
:class:`events.CallbackQuery` no longer also handles "inline bot callback queries".
|
||||
``events.CallbackQuery`` has been renamed to :class:`events.ButtonCallback` and no longer also handles "inline bot callback queries".
|
||||
This was a hacky workaround.
|
||||
|
||||
:class:`events.MessageRead` no longer triggers when the *contents* of a message are read, such as voice notes being played.
|
||||
|
|
|
@ -1 +1,12 @@
|
|||
.. autoclass:: telethon.Client
|
||||
.. currentmodule:: telethon
|
||||
|
||||
Client class
|
||||
============
|
||||
|
||||
The :class:`Client` class is the "entry point" of the library.
|
||||
|
||||
Most client methods have an alias in the respective types.
|
||||
For example, :meth:`Client.forward_messages` can also be invoked from :meth:`types.Message.forward`.
|
||||
With a few exceptions, "client.verb_object" methods also exist as "object.verb".
|
||||
|
||||
.. autoclass:: Client
|
||||
|
|
|
@ -1,8 +1,25 @@
|
|||
Types
|
||||
=====
|
||||
|
||||
This section contains most custom types used by the library.
|
||||
:doc:`events` and the :doc:`client` get their own section to prevent the page from growing out of control.
|
||||
|
||||
Some of these are further divided into additional submodules.
|
||||
This keeps them neatly grouped and avoids polluting a single module too much.
|
||||
|
||||
|
||||
Core types
|
||||
----------
|
||||
|
||||
.. automodule:: telethon.types
|
||||
|
||||
|
||||
Keyboard buttons
|
||||
----------------
|
||||
|
||||
.. automodule:: telethon.types.buttons
|
||||
|
||||
|
||||
Errors
|
||||
------
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ from ..types import (
|
|||
PasswordToken,
|
||||
RecentAction,
|
||||
User,
|
||||
buttons,
|
||||
)
|
||||
from .auth import (
|
||||
bot_sign_in,
|
||||
|
@ -88,6 +89,7 @@ from .messages import (
|
|||
get_messages,
|
||||
get_messages_with_ids,
|
||||
pin_message,
|
||||
read_message,
|
||||
search_all_messages,
|
||||
search_messages,
|
||||
send_message,
|
||||
|
@ -126,8 +128,6 @@ class Client:
|
|||
"""
|
||||
A client capable of connecting to Telegram and sending requests.
|
||||
|
||||
This is the "entry point" of the library.
|
||||
|
||||
This class can be used as an asynchronous context manager to automatically :meth:`connect` and :meth:`disconnect`:
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -491,6 +491,58 @@ class Client:
|
|||
"""
|
||||
await download(self, media, file)
|
||||
|
||||
async def edit_draft(
|
||||
self,
|
||||
chat: ChatLike,
|
||||
text: Optional[str] = None,
|
||||
*,
|
||||
markdown: Optional[str] = None,
|
||||
html: Optional[str] = None,
|
||||
link_preview: bool = False,
|
||||
reply_to: Optional[int] = None,
|
||||
) -> Draft:
|
||||
"""
|
||||
Set a draft message in a chat.
|
||||
|
||||
This can also be used to clear the draft by setting the text to an empty string ``""``.
|
||||
|
||||
:param chat:
|
||||
The :term:`chat` where the draft will be saved to.
|
||||
|
||||
:param text: See :ref:`formatting`.
|
||||
:param markdown: See :ref:`formatting`.
|
||||
:param html: See :ref:`formatting`.
|
||||
:param link_preview: See :ref:`formatting`.
|
||||
|
||||
:param reply_to:
|
||||
The message identifier of the message to reply to.
|
||||
|
||||
:return: The created draft.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Edit message to have text without formatting
|
||||
await client.edit_message(chat, msg_id, text='New text')
|
||||
|
||||
# Remove the link preview without changing the text
|
||||
await client.edit_message(chat, msg_id, link_preview=False)
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`telethon.types.Message.edit`
|
||||
"""
|
||||
return await edit_draft(
|
||||
self,
|
||||
chat,
|
||||
text,
|
||||
markdown=markdown,
|
||||
html=html,
|
||||
link_preview=link_preview,
|
||||
reply_to=reply_to,
|
||||
)
|
||||
|
||||
async def edit_message(
|
||||
self,
|
||||
chat: ChatLike,
|
||||
|
@ -987,6 +1039,40 @@ class Client:
|
|||
"""
|
||||
return await pin_message(self, chat, message_id)
|
||||
|
||||
async def read_message(
|
||||
self, chat: ChatLike, message_id: Union[int, Literal["all"]]
|
||||
) -> None:
|
||||
"""
|
||||
Mark messages as read.
|
||||
|
||||
This will send a read acknowledgment to all messages with identifiers below and up-to the given message identifier.
|
||||
|
||||
This is often represented as a blue double-check (✓✓).
|
||||
|
||||
A single check (✓) in Telegram often indicates the message was sent and perhaps received, but not read.
|
||||
|
||||
A clock (🕒) in Telegram often indicates the message was not yet sent at all.
|
||||
This most commonly occurs when sending messages without a network connection.
|
||||
|
||||
:param chat:
|
||||
The chat where the messages to be marked as read are.
|
||||
|
||||
:param message_id:
|
||||
The identifier of the message to mark as read.
|
||||
All messages older (sent before) this one will also be marked as read.
|
||||
|
||||
The literal ``'all'`` may be used to mark all messages in a chat as read.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Mark all messages as read
|
||||
message = await client.read_message(chat, 'all')
|
||||
await message.delete()
|
||||
"""
|
||||
await read_message(self, chat, message_id)
|
||||
|
||||
def remove_event_handler(self, handler: Callable[[Event], Awaitable[Any]]) -> None:
|
||||
"""
|
||||
Remove the handler as a function to be called when events occur.
|
||||
|
@ -1417,6 +1503,9 @@ class Client:
|
|||
html: Optional[str] = None,
|
||||
link_preview: bool = False,
|
||||
reply_to: Optional[int] = None,
|
||||
buttons: Optional[
|
||||
Union[List[buttons.Button], List[List[buttons.Button]]]
|
||||
] = None,
|
||||
) -> Message:
|
||||
"""
|
||||
Send a message.
|
||||
|
@ -1432,6 +1521,11 @@ class Client:
|
|||
:param reply_to:
|
||||
The message identifier of the message to reply to.
|
||||
|
||||
:param buttons:
|
||||
The buttons to use for the message.
|
||||
|
||||
Only bot accounts can send buttons.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -1446,6 +1540,7 @@ class Client:
|
|||
html=html,
|
||||
link_preview=link_preview,
|
||||
reply_to=reply_to,
|
||||
buttons=buttons,
|
||||
)
|
||||
|
||||
async def send_photo(
|
||||
|
@ -1573,58 +1668,6 @@ class Client:
|
|||
def set_default_rights(self, chat: ChatLike, user: ChatLike) -> None:
|
||||
set_default_rights(self, chat, user)
|
||||
|
||||
async def edit_draft(
|
||||
self,
|
||||
chat: ChatLike,
|
||||
text: Optional[str] = None,
|
||||
*,
|
||||
markdown: Optional[str] = None,
|
||||
html: Optional[str] = None,
|
||||
link_preview: bool = False,
|
||||
reply_to: Optional[int] = None,
|
||||
) -> Draft:
|
||||
"""
|
||||
Set a draft message in a chat.
|
||||
|
||||
This can also be used to clear the draft by setting the text to an empty string ``""``.
|
||||
|
||||
:param chat:
|
||||
The :term:`chat` where the draft will be saved to.
|
||||
|
||||
:param text: See :ref:`formatting`.
|
||||
:param markdown: See :ref:`formatting`.
|
||||
:param html: See :ref:`formatting`.
|
||||
:param link_preview: See :ref:`formatting`.
|
||||
|
||||
:param reply_to:
|
||||
The message identifier of the message to reply to.
|
||||
|
||||
:return: The created draft.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Edit message to have text without formatting
|
||||
await client.edit_message(chat, msg_id, text='New text')
|
||||
|
||||
# Remove the link preview without changing the text
|
||||
await client.edit_message(chat, msg_id, link_preview=False)
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`telethon.types.Message.edit`
|
||||
"""
|
||||
return await edit_draft(
|
||||
self,
|
||||
chat,
|
||||
text,
|
||||
markdown=markdown,
|
||||
html=html,
|
||||
link_preview=link_preview,
|
||||
reply_to=reply_to,
|
||||
)
|
||||
|
||||
def set_handler_filter(
|
||||
self,
|
||||
handler: Callable[[Event], Awaitable[Any]],
|
||||
|
|
|
@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Tuple, Union
|
|||
from ...session import PackedChat
|
||||
from ...tl import abcs, functions, types
|
||||
from ..parsers import parse_html_message, parse_markdown_message
|
||||
from ..types import AsyncList, Chat, ChatLike, Message
|
||||
from ..types import AsyncList, Chat, ChatLike, Message, buttons
|
||||
from ..utils import build_chat_map, generate_random_id, peer_id
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -39,6 +39,40 @@ def parse_message(
|
|||
return parsed, entities or None
|
||||
|
||||
|
||||
def build_keyboard(
|
||||
btns: Optional[Union[List[buttons.Button], List[List[buttons.Button]]]]
|
||||
) -> Optional[abcs.ReplyMarkup]:
|
||||
# list[button] -> list[list[button]]
|
||||
# This does allow for "invalid" inputs (mixing lists and non-lists), but that's acceptable.
|
||||
buttons_lists_iter = (
|
||||
button if isinstance(button, list) else [button] for button in (btns or [])
|
||||
)
|
||||
# Remove empty rows (also making it easy to check if all-empty).
|
||||
buttons_lists = [bs for bs in buttons_lists_iter if bs]
|
||||
|
||||
if not buttons_lists:
|
||||
return None
|
||||
|
||||
rows: List[abcs.KeyboardButtonRow] = [
|
||||
types.KeyboardButtonRow(buttons=[btn._raw for btn in btns])
|
||||
for btns in buttons_lists
|
||||
]
|
||||
|
||||
# Guaranteed to have at least one, first one used to check if it's inline.
|
||||
# If the user mixed inline with non-inline, Telegram will complain.
|
||||
if isinstance(buttons_lists[0][0], buttons.InlineButton):
|
||||
return types.ReplyInlineMarkup(rows=rows)
|
||||
else:
|
||||
return types.ReplyKeyboardMarkup(
|
||||
resize=False,
|
||||
single_use=False,
|
||||
selective=False,
|
||||
persistent=False,
|
||||
rows=rows,
|
||||
placeholder=None,
|
||||
)
|
||||
|
||||
|
||||
async def send_message(
|
||||
self: Client,
|
||||
chat: ChatLike,
|
||||
|
@ -48,6 +82,7 @@ async def send_message(
|
|||
html: Optional[str] = None,
|
||||
link_preview: bool = False,
|
||||
reply_to: Optional[int] = None,
|
||||
buttons: Optional[Union[List[buttons.Button], List[List[buttons.Button]]]] = None,
|
||||
) -> Message:
|
||||
packed = await self._resolve_to_packed(chat)
|
||||
peer = packed._to_input_peer()
|
||||
|
@ -71,14 +106,14 @@ async def send_message(
|
|||
else None,
|
||||
message=message,
|
||||
random_id=random_id,
|
||||
reply_markup=None,
|
||||
reply_markup=build_keyboard(buttons),
|
||||
entities=entities,
|
||||
schedule_date=None,
|
||||
send_as=None,
|
||||
)
|
||||
if isinstance(message, str)
|
||||
else functions.messages.send_message(
|
||||
no_webpage=not message.web_preview,
|
||||
no_webpage=not message.link_preview,
|
||||
silent=message.silent,
|
||||
background=False,
|
||||
clear_draft=False,
|
||||
|
@ -531,6 +566,27 @@ async def unpin_message(
|
|||
)
|
||||
|
||||
|
||||
async def read_message(
|
||||
self: Client, chat: ChatLike, message_id: Union[int, Literal["all"]]
|
||||
) -> None:
|
||||
packed = await self._resolve_to_packed(chat)
|
||||
if message_id == "all":
|
||||
message_id = 0
|
||||
|
||||
if packed.is_channel():
|
||||
await self(
|
||||
functions.channels.read_history(
|
||||
channel=packed._to_input_channel(), max_id=message_id
|
||||
)
|
||||
)
|
||||
else:
|
||||
await self(
|
||||
functions.messages.read_history(
|
||||
peer=packed._to_input_peer(), max_id=message_id
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class MessageMap:
|
||||
__slots__ = ("_client", "_peer", "_random_id_to_id", "_id_to_message")
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from .event import Event
|
||||
from .messages import MessageDeleted, MessageEdited, MessageRead, NewMessage
|
||||
from .queries import CallbackQuery, InlineQuery
|
||||
from .queries import ButtonCallback, InlineQuery
|
||||
|
||||
__all__ = [
|
||||
"Event",
|
||||
|
@ -8,6 +8,6 @@ __all__ = [
|
|||
"MessageEdited",
|
||||
"MessageRead",
|
||||
"NewMessage",
|
||||
"CallbackQuery",
|
||||
"ButtonCallback",
|
||||
"InlineQuery",
|
||||
]
|
||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import TYPE_CHECKING, Dict, Optional, Self
|
||||
|
||||
from ...tl import abcs, types
|
||||
from ...tl import abcs, functions, types
|
||||
from ..types import Chat
|
||||
from .event import Event
|
||||
|
||||
|
@ -10,25 +10,61 @@ if TYPE_CHECKING:
|
|||
from ..client.client import Client
|
||||
|
||||
|
||||
class CallbackQuery(Event):
|
||||
class ButtonCallback(Event):
|
||||
"""
|
||||
Occurs when an inline button was pressed.
|
||||
Occurs when the user :meth:`~telethon.types.buttons.Callback.click`\ s a :class:`~telethon.types.buttons.Callback` button.
|
||||
|
||||
Only bot accounts can receive this event.
|
||||
Only bot accounts can receive this event, because only bots can send :class:`~telethon.types.buttons.Callback` buttons.
|
||||
"""
|
||||
|
||||
def __init__(self, update: types.UpdateBotCallbackQuery):
|
||||
self._update = update
|
||||
def __init__(
|
||||
self,
|
||||
client: Client,
|
||||
update: types.UpdateBotCallbackQuery,
|
||||
chat_map: Dict[int, Chat],
|
||||
):
|
||||
self._client = client
|
||||
self._raw = update
|
||||
self._chat_map = chat_map
|
||||
|
||||
@classmethod
|
||||
def _try_from_update(
|
||||
cls, client: Client, update: abcs.Update, chat_map: Dict[int, Chat]
|
||||
) -> Optional[Self]:
|
||||
if isinstance(update, types.UpdateBotCallbackQuery):
|
||||
return cls._create(update)
|
||||
if isinstance(update, types.UpdateBotCallbackQuery) and update.data is not None:
|
||||
return cls._create(client, update, chat_map)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def data(self) -> bytes:
|
||||
assert self._raw.data is not None
|
||||
return self._raw.data
|
||||
|
||||
async def answer(
|
||||
self,
|
||||
text: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Answer the callback query.
|
||||
|
||||
.. important::
|
||||
|
||||
You must call this function for the loading circle to stop on the user's side.
|
||||
|
||||
:param text:
|
||||
The text of the message to display to the user, usually as a toast.
|
||||
"""
|
||||
await self._client(
|
||||
functions.messages.set_bot_callback_answer(
|
||||
alert=False,
|
||||
query_id=self._raw.query_id,
|
||||
message=text,
|
||||
url=None,
|
||||
cache_time=0,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class InlineQuery(Event):
|
||||
"""
|
||||
|
@ -38,7 +74,7 @@ class InlineQuery(Event):
|
|||
"""
|
||||
|
||||
def __init__(self, update: types.UpdateBotInlineQuery):
|
||||
self._update = update
|
||||
self._raw = update
|
||||
|
||||
@classmethod
|
||||
def _try_from_update(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from .async_list import AsyncList
|
||||
from .callback_answer import CallbackAnswer
|
||||
from .chat import Channel, Chat, ChatLike, Group, User
|
||||
from .dialog import Dialog
|
||||
from .draft import Draft
|
||||
|
@ -13,6 +14,7 @@ from .recent_action import RecentAction
|
|||
|
||||
__all__ = [
|
||||
"AsyncList",
|
||||
"CallbackAnswer",
|
||||
"Channel",
|
||||
"Chat",
|
||||
"ChatLike",
|
||||
|
|
89
client/src/telethon/_impl/client/types/buttons/__init__.py
Normal file
89
client/src/telethon/_impl/client/types/buttons/__init__.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import weakref
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ....tl import abcs, types
|
||||
from .button import Button
|
||||
from .callback import Callback
|
||||
from .inline_button import InlineButton
|
||||
from .request_geo_location import RequestGeoLocation
|
||||
from .request_phone import RequestPhone
|
||||
from .request_poll import RequestPoll
|
||||
from .switch_inline import SwitchInline
|
||||
from .text import Text
|
||||
from .url import Url
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..message import Message
|
||||
|
||||
|
||||
def as_concrete_row(row: abcs.KeyboardButtonRow) -> types.KeyboardButtonRow:
|
||||
assert isinstance(row, types.KeyboardButtonRow)
|
||||
return row
|
||||
|
||||
|
||||
def create_button(message: Message, raw: abcs.KeyboardButton) -> Button:
|
||||
"""
|
||||
Create a custom button from a Telegram button.
|
||||
|
||||
Types with no friendly variant fallback to :class:`telethon.types.buttons.Button` or `telethon.types.buttons.Inline`.
|
||||
"""
|
||||
cls = Button
|
||||
|
||||
if isinstance(raw, types.KeyboardButtonCallback):
|
||||
cls = Callback
|
||||
elif isinstance(raw, types.KeyboardButtonRequestGeoLocation):
|
||||
cls = RequestGeoLocation
|
||||
elif isinstance(raw, types.KeyboardButtonRequestPhone):
|
||||
cls = RequestPhone
|
||||
elif isinstance(raw, types.KeyboardButtonRequestPoll):
|
||||
cls = RequestPoll
|
||||
elif isinstance(raw, types.KeyboardButtonSwitchInline):
|
||||
cls = SwitchInline
|
||||
elif isinstance(raw, types.KeyboardButton):
|
||||
cls = Text
|
||||
elif isinstance(raw, types.KeyboardButtonUrl):
|
||||
cls = Url
|
||||
elif isinstance(
|
||||
raw,
|
||||
(
|
||||
types.KeyboardButtonBuy,
|
||||
types.KeyboardButtonGame,
|
||||
types.KeyboardButtonUrlAuth,
|
||||
types.InputKeyboardButtonUrlAuth,
|
||||
types.KeyboardButtonWebView,
|
||||
),
|
||||
):
|
||||
cls = InlineButton
|
||||
elif isinstance(
|
||||
raw,
|
||||
(
|
||||
types.InputKeyboardButtonUserProfile,
|
||||
types.KeyboardButtonUserProfile,
|
||||
types.KeyboardButtonSimpleWebView,
|
||||
types.KeyboardButtonRequestPeer,
|
||||
),
|
||||
):
|
||||
cls = Button
|
||||
else:
|
||||
raise RuntimeError("unexpected case")
|
||||
|
||||
instance = cls.__new__(cls)
|
||||
instance._msg = weakref.ref(message)
|
||||
instance._raw = raw
|
||||
return instance
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Button",
|
||||
"Callback",
|
||||
"InlineButton",
|
||||
"RequestGeoLocation",
|
||||
"RequestPhone",
|
||||
"RequestPoll",
|
||||
"SwitchInline",
|
||||
"Text",
|
||||
"Url",
|
||||
"create",
|
||||
]
|
74
client/src/telethon/_impl/client/types/buttons/button.py
Normal file
74
client/src/telethon/_impl/client/types/buttons/button.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import weakref
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
from ....tl import types
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..message import Message
|
||||
|
||||
|
||||
ButtonTypes = Union[
|
||||
types.KeyboardButton,
|
||||
types.KeyboardButtonUrl,
|
||||
types.KeyboardButtonCallback,
|
||||
types.KeyboardButtonRequestPhone,
|
||||
types.KeyboardButtonRequestGeoLocation,
|
||||
types.KeyboardButtonSwitchInline,
|
||||
types.KeyboardButtonGame,
|
||||
types.KeyboardButtonBuy,
|
||||
types.KeyboardButtonUrlAuth,
|
||||
types.InputKeyboardButtonUrlAuth,
|
||||
types.KeyboardButtonRequestPoll,
|
||||
types.InputKeyboardButtonUserProfile,
|
||||
types.KeyboardButtonUserProfile,
|
||||
types.KeyboardButtonWebView,
|
||||
types.KeyboardButtonSimpleWebView,
|
||||
types.KeyboardButtonRequestPeer,
|
||||
]
|
||||
|
||||
|
||||
class Button:
|
||||
"""
|
||||
The button base type.
|
||||
|
||||
All other :mod:`~telethon.types.buttons` inherit this class.
|
||||
|
||||
You can only click buttons that have been received from Telegram.
|
||||
Attempting to click a button you created will fail with an error.
|
||||
|
||||
Not all buttons can be clicked, and each button will do something different when clicked.
|
||||
The reason for this is that Telethon cannot interact with any user to complete certain tasks.
|
||||
Only straightforward actions can be performed automatically, such as sending a text message.
|
||||
|
||||
To check if a button is clickable, use :func:`hasattr` on the ``'click'`` method.
|
||||
|
||||
:param text: See below.
|
||||
"""
|
||||
|
||||
def __init__(self, text: str) -> None:
|
||||
if self.__class__ == Button:
|
||||
raise TypeError(
|
||||
f"Can't instantiate abstract class {self.__class__.__name__}"
|
||||
)
|
||||
|
||||
self._raw: ButtonTypes = types.KeyboardButton(text=text)
|
||||
self._msg: Optional[weakref.ReferenceType[Message]] = None
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
"""
|
||||
The button's text that is displayed to the user.
|
||||
"""
|
||||
return self._raw.text
|
||||
|
||||
@text.setter
|
||||
def text(self, value: str) -> None:
|
||||
self._raw.text = value
|
||||
|
||||
def _message(self) -> Message:
|
||||
if self._msg and (message := self._msg()):
|
||||
return message
|
||||
else:
|
||||
raise ValueError("Buttons created by yourself cannot be clicked")
|
62
client/src/telethon/_impl/client/types/buttons/callback.py
Normal file
62
client/src/telethon/_impl/client/types/buttons/callback.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
from typing import Optional
|
||||
|
||||
from ....tl import functions, types
|
||||
from ..callback_answer import CallbackAnswer
|
||||
from .inline_button import InlineButton
|
||||
|
||||
|
||||
class Callback(InlineButton):
|
||||
"""
|
||||
Inline button that will trigger a :class:`telethon.events.ButtonCallback` with the button's data.
|
||||
|
||||
:param text: See below.
|
||||
:param data: See below.
|
||||
"""
|
||||
|
||||
def __init__(self, text: str, data: Optional[bytes] = None) -> None:
|
||||
super().__init__(text)
|
||||
self._raw = types.KeyboardButtonCallback(
|
||||
requires_password=False,
|
||||
text=text,
|
||||
data=data or text.encode("utf-8", errors="replace"),
|
||||
)
|
||||
|
||||
@property
|
||||
def data(self) -> bytes:
|
||||
"""
|
||||
The button's binary payload.
|
||||
|
||||
This data will be received by :class:`telethon.events.ButtonCallback` when the button is pressed.
|
||||
"""
|
||||
assert isinstance(self._raw, types.KeyboardButtonCallback)
|
||||
return self._raw.data
|
||||
|
||||
@data.setter
|
||||
def data(self, value: bytes) -> None:
|
||||
assert isinstance(self._raw, types.KeyboardButtonCallback)
|
||||
self._raw.data = value
|
||||
|
||||
async def click(self) -> Optional[CallbackAnswer]:
|
||||
"""
|
||||
Click the button, sending the button's :attr:`data` to the bot.
|
||||
|
||||
The bot will receive a :class:`~telethon.events.ButtonCallback` event
|
||||
which they must quickly :meth:`~telethon.events.ButtonCallback.answer`.
|
||||
|
||||
The bot's answer will be returned, or :data:`None` if they don't answer in time.
|
||||
"""
|
||||
message = self._message()
|
||||
packed = message.chat.pack()
|
||||
assert packed
|
||||
|
||||
return CallbackAnswer._create(
|
||||
await message._client(
|
||||
functions.messages.get_bot_callback_answer(
|
||||
game=False,
|
||||
peer=packed._to_input_peer(),
|
||||
msg_id=message.id,
|
||||
data=self.data,
|
||||
password=None,
|
||||
)
|
||||
)
|
||||
)
|
|
@ -0,0 +1,34 @@
|
|||
from .button import Button
|
||||
|
||||
|
||||
class InlineButton(Button):
|
||||
"""
|
||||
Inline button base type.
|
||||
|
||||
Inline buttons appear directly under a message (inline in the chat history).
|
||||
|
||||
You cannot create a naked :class:`InlineButton` directly.
|
||||
Instead, it can be used to check whether a button is inline or not.
|
||||
|
||||
Buttons that behave as a "custom key" and replace the user's virtual keyboard
|
||||
can be tested by checking that they are not inline.
|
||||
|
||||
.. rubric:: Example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.types import buttons
|
||||
|
||||
is_inline_button = isinstance(button, buttons.Inline)
|
||||
is_keyboard_button = not isinstance(button, buttons.Inline)
|
||||
|
||||
:param text: See below.
|
||||
"""
|
||||
|
||||
def __init__(self, text: str) -> None:
|
||||
if self.__class__ == InlineButton:
|
||||
raise TypeError(
|
||||
f"Can't instantiate abstract class {self.__class__.__name__}"
|
||||
)
|
||||
else:
|
||||
super().__init__(text)
|
|
@ -0,0 +1,14 @@
|
|||
from ....tl import types
|
||||
from .button import Button
|
||||
|
||||
|
||||
class RequestGeoLocation(Button):
|
||||
"""
|
||||
Keyboard button that will prompt the user to share the geo point with their current location.
|
||||
|
||||
:param text: See below.
|
||||
"""
|
||||
|
||||
def __init__(self, text: str) -> None:
|
||||
super().__init__(text)
|
||||
self._raw = types.KeyboardButtonRequestGeoLocation(text=text)
|
|
@ -0,0 +1,14 @@
|
|||
from ....tl import types
|
||||
from .button import Button
|
||||
|
||||
|
||||
class RequestPhone(Button):
|
||||
"""
|
||||
Keyboard button that will prompt the user to share the contact with their phone number.
|
||||
|
||||
:param text: See below.
|
||||
"""
|
||||
|
||||
def __init__(self, text: str) -> None:
|
||||
super().__init__(text)
|
||||
self._raw = types.KeyboardButtonRequestPhone(text=text)
|
|
@ -0,0 +1,14 @@
|
|||
from ....tl import types
|
||||
from .button import Button
|
||||
|
||||
|
||||
class RequestPoll(Button):
|
||||
"""
|
||||
Keyboard button that will prompt the user to create a poll.
|
||||
|
||||
:param text: See below.
|
||||
"""
|
||||
|
||||
def __init__(self, text: str, *, quiz: bool = False) -> None:
|
||||
super().__init__(text)
|
||||
self._raw = types.KeyboardButtonRequestPoll(text=text, quiz=quiz)
|
|
@ -0,0 +1,32 @@
|
|||
from typing import Optional
|
||||
|
||||
from ....tl import types
|
||||
from .inline_button import InlineButton
|
||||
|
||||
|
||||
class SwitchInline(InlineButton):
|
||||
"""
|
||||
Inline button that will switch the user to inline mode to trigger :class:`telethon.events.InlineQuery`.
|
||||
|
||||
:param text: See below.
|
||||
:param query: See below.
|
||||
"""
|
||||
|
||||
def __init__(self, text: str, query: Optional[str] = None) -> None:
|
||||
super().__init__(text)
|
||||
self._raw = types.KeyboardButtonSwitchInline(
|
||||
same_peer=False, text=text, query=query or "", peer_types=None
|
||||
)
|
||||
|
||||
@property
|
||||
def query(self) -> str:
|
||||
"""
|
||||
The query string to set by default on the user's message input.
|
||||
"""
|
||||
assert isinstance(self._raw, types.KeyboardButtonSwitchInline)
|
||||
return self._raw.query
|
||||
|
||||
@query.setter
|
||||
def query(self, value: str) -> None:
|
||||
assert isinstance(self._raw, types.KeyboardButtonSwitchInline)
|
||||
self._raw.query = value
|
38
client/src/telethon/_impl/client/types/buttons/text.py
Normal file
38
client/src/telethon/_impl/client/types/buttons/text.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .button import Button
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..message import Message
|
||||
|
||||
|
||||
class Text(Button):
|
||||
"""
|
||||
This is the most basic keyboard button and only has :attr:`text`.
|
||||
|
||||
Note that it is not possible to distinguish between a :meth:`click` to this button being and the user typing the text themselves.
|
||||
|
||||
:param text: See below.
|
||||
"""
|
||||
|
||||
def __init__(self, text: str) -> None:
|
||||
super().__init__(text)
|
||||
|
||||
@property
|
||||
def text(self) -> str:
|
||||
"""
|
||||
The button's text that is both displayed to the user and will be sent on :meth:`click`.
|
||||
"""
|
||||
return self._raw.text
|
||||
|
||||
@text.setter
|
||||
def text(self, value: str) -> None:
|
||||
self._raw.text = value
|
||||
|
||||
async def click(self) -> Message:
|
||||
"""
|
||||
Click the button, sending a message to the chat as-if the user typed and sent the text themselves.
|
||||
"""
|
||||
return await self._message().respond(self._raw.text)
|
30
client/src/telethon/_impl/client/types/buttons/url.py
Normal file
30
client/src/telethon/_impl/client/types/buttons/url.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from typing import Optional
|
||||
|
||||
from ....tl import types
|
||||
from .inline_button import InlineButton
|
||||
|
||||
|
||||
class Url(InlineButton):
|
||||
"""
|
||||
Inline button that will prompt the user to open the specified URL when clicked.
|
||||
|
||||
:param text: See below.
|
||||
:param url: See below.
|
||||
"""
|
||||
|
||||
def __init__(self, text: str, url: Optional[str] = None) -> None:
|
||||
super().__init__(text)
|
||||
self._raw = types.KeyboardButtonUrl(text=text, url=url or text)
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
"""
|
||||
The URL to open.
|
||||
"""
|
||||
assert isinstance(self._raw, types.KeyboardButtonUrl)
|
||||
return self._raw.url
|
||||
|
||||
@url.setter
|
||||
def url(self, value: str) -> None:
|
||||
assert isinstance(self._raw, types.KeyboardButtonUrl)
|
||||
self._raw.url = value
|
28
client/src/telethon/_impl/client/types/callback_answer.py
Normal file
28
client/src/telethon/_impl/client/types/callback_answer.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
from typing import Optional
|
||||
|
||||
from ...tl import abcs, types
|
||||
from .meta import NoPublicConstructor
|
||||
|
||||
|
||||
class CallbackAnswer(metaclass=NoPublicConstructor):
|
||||
"""
|
||||
A bot's :class:`~telethon.types.buttons.Callback` :meth:`~telethon.events.ButtonCallback.answer`.
|
||||
"""
|
||||
|
||||
def __init__(self, raw: abcs.messages.BotCallbackAnswer) -> None:
|
||||
assert isinstance(raw, types.messages.BotCallbackAnswer)
|
||||
self._raw = raw
|
||||
|
||||
@property
|
||||
def text(self) -> Optional[str]:
|
||||
"""
|
||||
The answer's text, usually displayed as a toast.
|
||||
"""
|
||||
return self._raw.message
|
||||
|
||||
@property
|
||||
def url(self) -> Optional[str]:
|
||||
"""
|
||||
The answer's URL.
|
||||
"""
|
||||
return self._raw.url
|
|
@ -56,7 +56,9 @@ class Draft(metaclass=NoPublicConstructor):
|
|||
@property
|
||||
def chat(self) -> Chat:
|
||||
"""
|
||||
The chat where the draft will be sent to.
|
||||
The chat where the draft is saved.
|
||||
|
||||
This is also the chat where the message will be sent to by :meth:`send`.
|
||||
"""
|
||||
from ..utils import expand_peer, peer_id
|
||||
|
||||
|
|
22
client/src/telethon/_impl/client/types/forward_info.py
Normal file
22
client/src/telethon/_impl/client/types/forward_info.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from typing import Self
|
||||
|
||||
from ...tl import types
|
||||
from .meta import NoPublicConstructor
|
||||
|
||||
|
||||
class ForwardInfo(metaclass=NoPublicConstructor):
|
||||
"""
|
||||
Information about where a message was forwarded from.
|
||||
|
||||
This is also known as the forward header, as it's often displayed at the top of messages.
|
||||
"""
|
||||
|
||||
__slots__ = ("_code", "_phone")
|
||||
|
||||
def __init__(self, code: types.auth.SentCode, phone: str) -> None:
|
||||
self._code = code
|
||||
self._phone = phone
|
||||
|
||||
@classmethod
|
||||
def _new(cls, code: types.auth.SentCode, phone: str) -> Self:
|
||||
return cls._create(code, phone)
|
|
@ -12,6 +12,12 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class InlineResult(metaclass=NoPublicConstructor):
|
||||
"""
|
||||
A single inline result from an inline query made to a bot.
|
||||
|
||||
This is returned when calling :meth:`telethon.Client.inline_query`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: Client,
|
||||
|
@ -30,10 +36,16 @@ class InlineResult(metaclass=NoPublicConstructor):
|
|||
|
||||
@property
|
||||
def title(self) -> str:
|
||||
"""
|
||||
The title of the result, or the empty string if there is none.
|
||||
"""
|
||||
return self._raw.title or ""
|
||||
|
||||
@property
|
||||
def description(self) -> Optional[str]:
|
||||
"""
|
||||
The description of the result, if available.
|
||||
"""
|
||||
return self._raw.description
|
||||
|
||||
async def send(
|
||||
|
@ -41,7 +53,7 @@ class InlineResult(metaclass=NoPublicConstructor):
|
|||
chat: Optional[ChatLike] = None,
|
||||
) -> Message:
|
||||
"""
|
||||
Send the inline result to the desired chat.
|
||||
Send the result to the desired chat.
|
||||
|
||||
:param chat:
|
||||
The chat where the inline result should be sent to.
|
||||
|
|
22
client/src/telethon/_impl/client/types/link_preview.py
Normal file
22
client/src/telethon/_impl/client/types/link_preview.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from typing import Self
|
||||
|
||||
from ...tl import types
|
||||
from .meta import NoPublicConstructor
|
||||
|
||||
|
||||
class LinkPreview(metaclass=NoPublicConstructor):
|
||||
"""
|
||||
Information about a link preview.
|
||||
|
||||
This comes from media attached to a message, and is automatically generated by Telegram.
|
||||
"""
|
||||
|
||||
__slots__ = ("_code", "_phone")
|
||||
|
||||
def __init__(self, code: types.auth.SentCode, phone: str) -> None:
|
||||
self._code = code
|
||||
self._phone = phone
|
||||
|
||||
@classmethod
|
||||
def _new(cls, code: types.auth.SentCode, phone: str) -> Self:
|
||||
return cls._create(code, phone)
|
|
@ -21,4 +21,10 @@ class LoginToken(metaclass=NoPublicConstructor):
|
|||
|
||||
@property
|
||||
def timeout(self) -> Optional[int]:
|
||||
"""
|
||||
Number of seconds before this token expires.
|
||||
|
||||
This property does not return different values as the current time advances.
|
||||
To determine when the token expires, add the timeout to the current time as soon as the token is obtained.
|
||||
"""
|
||||
return self._code.timeout
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional, Self, Union
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Self, Union
|
||||
|
||||
from ...tl import abcs, types
|
||||
from ..parsers import generate_html_message, generate_markdown_message
|
||||
from .buttons import Button, as_concrete_row, create_button
|
||||
from .chat import Chat, ChatLike
|
||||
from .file import File
|
||||
from .meta import NoPublicConstructor
|
||||
|
@ -109,10 +110,18 @@ class Message(metaclass=NoPublicConstructor):
|
|||
|
||||
@property
|
||||
def text(self) -> Optional[str]:
|
||||
"""
|
||||
The message text without any formatting.
|
||||
"""
|
||||
return getattr(self._raw, "message", None)
|
||||
|
||||
@property
|
||||
def text_html(self) -> Optional[str]:
|
||||
"""
|
||||
The message text formatted using standard `HTML elements <https://developer.mozilla.org/en-US/docs/Web/HTML/Element>`_.
|
||||
|
||||
See :ref:`formatting` to learn the HTML elements used.
|
||||
"""
|
||||
if text := getattr(self._raw, "message", None):
|
||||
return generate_html_message(
|
||||
text, getattr(self._raw, "entities", None) or []
|
||||
|
@ -122,6 +131,11 @@ class Message(metaclass=NoPublicConstructor):
|
|||
|
||||
@property
|
||||
def text_markdown(self) -> Optional[str]:
|
||||
"""
|
||||
The message text formatted as `CommonMark's markdown <https://commonmark.org/>`_.
|
||||
|
||||
See :ref:`formatting` to learn the formatting characters used.
|
||||
"""
|
||||
if text := getattr(self._raw, "message", None):
|
||||
return generate_markdown_message(
|
||||
text, getattr(self._raw, "entities", None) or []
|
||||
|
@ -131,22 +145,37 @@ class Message(metaclass=NoPublicConstructor):
|
|||
|
||||
@property
|
||||
def date(self) -> Optional[datetime.datetime]:
|
||||
"""
|
||||
The date when the message was sent.
|
||||
"""
|
||||
from ..utils import adapt_date
|
||||
|
||||
return adapt_date(getattr(self._raw, "date", None))
|
||||
|
||||
@property
|
||||
def chat(self) -> Chat:
|
||||
"""
|
||||
The :term:`chat` when the message was sent.
|
||||
"""
|
||||
from ..utils import expand_peer, peer_id
|
||||
|
||||
peer = self._raw.peer_id or types.PeerUser(user_id=0)
|
||||
broadcast = broadcast = getattr(self._raw, "post", None)
|
||||
return self._chat_map.get(peer_id(peer)) or expand_peer(
|
||||
peer, broadcast=broadcast
|
||||
)
|
||||
pid = peer_id(peer)
|
||||
if pid not in self._chat_map:
|
||||
self._chat_map[pid] = expand_peer(
|
||||
peer, broadcast=getattr(self._raw, "post", None)
|
||||
)
|
||||
return self._chat_map[pid]
|
||||
|
||||
@property
|
||||
def sender(self) -> Optional[Chat]:
|
||||
"""
|
||||
The :term:`chat` that sent the message.
|
||||
|
||||
This will usually be a :class:`User`, but can also be a :class:`Channel`.
|
||||
|
||||
If there is no sender, it means the message was sent by an anonymous user.
|
||||
"""
|
||||
from ..utils import expand_peer, peer_id
|
||||
|
||||
if (from_ := getattr(self._raw, "from_id", None)) is not None:
|
||||
|
@ -165,11 +194,21 @@ class Message(metaclass=NoPublicConstructor):
|
|||
|
||||
@property
|
||||
def photo(self) -> Optional[File]:
|
||||
"""
|
||||
The compressed photo media :attr:`file` in the message.
|
||||
|
||||
This can also be used as a way to check that the message media is a photo.
|
||||
"""
|
||||
photo = self._file()
|
||||
return photo if photo and photo._photo else None
|
||||
|
||||
@property
|
||||
def audio(self) -> Optional[File]:
|
||||
"""
|
||||
The audio media :attr:`file` in the message.
|
||||
|
||||
This can also be used as a way to check that the message media is an audio.
|
||||
"""
|
||||
audio = self._file()
|
||||
return (
|
||||
audio
|
||||
|
@ -182,6 +221,11 @@ class Message(metaclass=NoPublicConstructor):
|
|||
|
||||
@property
|
||||
def video(self) -> Optional[File]:
|
||||
"""
|
||||
The video media :attr:`file` in the message.
|
||||
|
||||
This can also be used as a way to check that the message media is a video.
|
||||
"""
|
||||
audio = self._file()
|
||||
return (
|
||||
audio
|
||||
|
@ -194,6 +238,16 @@ class Message(metaclass=NoPublicConstructor):
|
|||
|
||||
@property
|
||||
def file(self) -> Optional[File]:
|
||||
"""
|
||||
The downloadable file in the message.
|
||||
|
||||
This might also come from a link preview.
|
||||
|
||||
Unlike :attr:`photo`, :attr:`audio` and :attr:`video`,
|
||||
this property does not care about the media type, only whether it can be downloaded.
|
||||
|
||||
This means the file will be :data:`None` for other media types, such as polls, venues or contacts.
|
||||
"""
|
||||
return self._file()
|
||||
|
||||
@property
|
||||
|
@ -231,6 +285,7 @@ class Message(metaclass=NoPublicConstructor):
|
|||
markdown: Optional[str] = None,
|
||||
html: Optional[str] = None,
|
||||
link_preview: bool = False,
|
||||
buttons: Optional[Union[List[Button], List[List[Button]]]] = None,
|
||||
) -> Message:
|
||||
"""
|
||||
Alias for :meth:`telethon.Client.send_message`.
|
||||
|
@ -241,7 +296,12 @@ class Message(metaclass=NoPublicConstructor):
|
|||
:param link_preview: See :meth:`~telethon.Client.send_message`.
|
||||
"""
|
||||
return await self._client.send_message(
|
||||
self.chat, text, markdown=markdown, html=html, link_preview=link_preview
|
||||
self.chat,
|
||||
text,
|
||||
markdown=markdown,
|
||||
html=html,
|
||||
link_preview=link_preview,
|
||||
buttons=buttons,
|
||||
)
|
||||
|
||||
async def reply(
|
||||
|
@ -251,6 +311,7 @@ class Message(metaclass=NoPublicConstructor):
|
|||
markdown: Optional[str] = None,
|
||||
html: Optional[str] = None,
|
||||
link_preview: bool = False,
|
||||
buttons: Optional[Union[List[Button], List[List[Button]]]] = None,
|
||||
) -> Message:
|
||||
"""
|
||||
Alias for :meth:`telethon.Client.send_message` with the ``reply_to`` parameter set to this message.
|
||||
|
@ -267,6 +328,7 @@ class Message(metaclass=NoPublicConstructor):
|
|||
html=html,
|
||||
link_preview=link_preview,
|
||||
reply_to=self.id,
|
||||
buttons=buttons,
|
||||
)
|
||||
|
||||
async def delete(self, *, revoke: bool = True) -> None:
|
||||
|
@ -309,20 +371,23 @@ class Message(metaclass=NoPublicConstructor):
|
|||
"""
|
||||
return (await self._client.forward_messages(target, [self.id], self.chat))[0]
|
||||
|
||||
async def mark_read(self) -> None:
|
||||
pass
|
||||
async def read(self) -> None:
|
||||
"""
|
||||
Alias for :meth:`telethon.Client.read_message`.
|
||||
"""
|
||||
await self._client.read_message(self.chat, self.id)
|
||||
|
||||
async def pin(self) -> None:
|
||||
async def pin(self) -> Message:
|
||||
"""
|
||||
Alias for :meth:`telethon.Client.pin_message`.
|
||||
"""
|
||||
pass
|
||||
return await self._client.pin_message(self.chat, self.id)
|
||||
|
||||
async def unpin(self) -> None:
|
||||
"""
|
||||
Alias for :meth:`telethon.Client.unpin_message`.
|
||||
"""
|
||||
pass
|
||||
await self._client.unpin_message(self.chat, self.id)
|
||||
|
||||
# ---
|
||||
|
||||
|
@ -331,63 +396,46 @@ class Message(metaclass=NoPublicConstructor):
|
|||
pass
|
||||
|
||||
@property
|
||||
def buttons(self) -> None:
|
||||
pass
|
||||
def buttons(self) -> Optional[List[List[Button]]]:
|
||||
"""
|
||||
The buttons attached to the message.
|
||||
|
||||
These are displayed under the message if they are :class:`~telethon.types.InlineButton`,
|
||||
and replace the user's virtual keyboard otherwise.
|
||||
|
||||
The returned value is a list of rows, each row having a list of buttons, one per column.
|
||||
The amount of columns in each row can vary. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
buttons = [
|
||||
[col_0, col_1], # row 0
|
||||
[ col_0 ], # row 1
|
||||
[col_0, col_1, col_2], # row 2
|
||||
]
|
||||
|
||||
row = 2
|
||||
col = 1
|
||||
button = buttons[row][col] # the middle button on the bottom row
|
||||
"""
|
||||
markup = getattr(self._raw, "reply_markup", None)
|
||||
if not isinstance(markup, (types.ReplyKeyboardMarkup, types.ReplyInlineMarkup)):
|
||||
return None
|
||||
|
||||
return [
|
||||
[create_button(self, button) for button in row.buttons]
|
||||
for row in map(as_concrete_row, markup.rows)
|
||||
]
|
||||
|
||||
@property
|
||||
def web_preview(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def voice(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def video_note(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def gif(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def sticker(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def contact(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def game(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def geo(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def invoice(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def poll(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def venue(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def dice(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def via_bot(self) -> None:
|
||||
def link_preview(self) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def silent(self) -> bool:
|
||||
"""
|
||||
:data:`True` if the message is silent and should not cause a notification.
|
||||
"""
|
||||
return getattr(self._raw, "silent", None) or False
|
||||
|
||||
@property
|
||||
|
|
|
@ -20,4 +20,7 @@ class PasswordToken(metaclass=NoPublicConstructor):
|
|||
|
||||
@property
|
||||
def hint(self) -> str:
|
||||
"""
|
||||
The password hint, or the empty string if none is known.
|
||||
"""
|
||||
return self._password.hint or ""
|
||||
|
|
|
@ -27,4 +27,9 @@ class RecentAction(metaclass=NoPublicConstructor):
|
|||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
"""
|
||||
The identifier of this action.
|
||||
|
||||
This identifier is *not* the same as the one in the message that was edited or deleted.
|
||||
"""
|
||||
return self._raw.id
|
||||
|
|
|
@ -18,6 +18,11 @@ class RpcError(ValueError):
|
|||
|
||||
This is the parent class of all :data:`telethon.errors` subtypes.
|
||||
|
||||
:param code: See below.
|
||||
:param name: See below.
|
||||
:param value: See below.
|
||||
:param caused_by: Constructor identifier of the request that caused the error.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`/concepts/errors`
|
||||
|
@ -41,14 +46,27 @@ class RpcError(ValueError):
|
|||
|
||||
@property
|
||||
def code(self) -> int:
|
||||
"""
|
||||
Integer code of the error.
|
||||
|
||||
This usually reassembles an `HTTP status code <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status>`_.
|
||||
"""
|
||||
return self._code
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""
|
||||
Name of the error, usually in ``SCREAMING_CASE``.
|
||||
"""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def value(self) -> Optional[int]:
|
||||
"""
|
||||
Integer value contained within the error.
|
||||
|
||||
For example, if the :attr:`name` is ``'FLOOD_WAIT'``, this would be the number of seconds.
|
||||
"""
|
||||
return self._value
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -6,7 +6,7 @@ Classes related to the different event types that wrap incoming Telegram updates
|
|||
The :doc:`/concepts/updates` concept to learn how to listen to these events.
|
||||
"""
|
||||
from .._impl.client.events import (
|
||||
CallbackQuery,
|
||||
ButtonCallback,
|
||||
Event,
|
||||
InlineQuery,
|
||||
MessageDeleted,
|
||||
|
@ -16,7 +16,7 @@ from .._impl.client.events import (
|
|||
)
|
||||
|
||||
__all__ = [
|
||||
"CallbackQuery",
|
||||
"ButtonCallback",
|
||||
"Event",
|
||||
"InlineQuery",
|
||||
"MessageDeleted",
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
"""
|
||||
Classes for the various objects the library returns.
|
||||
"""
|
||||
from ._impl.client.types import (
|
||||
from .._impl.client.types import (
|
||||
AsyncList,
|
||||
CallbackAnswer,
|
||||
Channel,
|
||||
Chat,
|
||||
Dialog,
|
||||
|
@ -17,23 +18,27 @@ from ._impl.client.types import (
|
|||
RecentAction,
|
||||
User,
|
||||
)
|
||||
from ._impl.session import PackedChat, PackedType
|
||||
from .._impl.client.types.buttons import Button, InlineButton
|
||||
from .._impl.session import PackedChat, PackedType
|
||||
|
||||
__all__ = [
|
||||
"InlineResult",
|
||||
"AsyncList",
|
||||
"CallbackAnswer",
|
||||
"Channel",
|
||||
"Chat",
|
||||
"Dialog",
|
||||
"Draft",
|
||||
"File",
|
||||
"Group",
|
||||
"InlineResult",
|
||||
"LoginToken",
|
||||
"Message",
|
||||
"Participant",
|
||||
"PasswordToken",
|
||||
"RecentAction",
|
||||
"User",
|
||||
"Button",
|
||||
"InlineButton",
|
||||
"PackedChat",
|
||||
"PackedType",
|
||||
]
|
40
client/src/telethon/types/buttons.py
Normal file
40
client/src/telethon/types/buttons.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
"""
|
||||
Keyboard buttons.
|
||||
|
||||
This includes both the buttons returned by :attr:`telethon.types.Message.buttons`
|
||||
and those you can define when using :meth:`telethon.Client.send_message`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.types import buttons
|
||||
|
||||
# As a user account, you can search for and click on buttons:
|
||||
for row in message.buttons:
|
||||
for button in row:
|
||||
if isinstance(button, buttons.Callback) and button.data == b'data':
|
||||
await button.click()
|
||||
|
||||
# As a bot account, you can send them:
|
||||
await bot.send_message(chat, text, buttons=[
|
||||
buttons.Callback('Demo', b'data')
|
||||
])
|
||||
"""
|
||||
from .._impl.client.types.buttons import (
|
||||
Callback,
|
||||
RequestGeoLocation,
|
||||
RequestPhone,
|
||||
RequestPoll,
|
||||
SwitchInline,
|
||||
Text,
|
||||
Url,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Callback",
|
||||
"RequestGeoLocation",
|
||||
"RequestPhone",
|
||||
"RequestPoll",
|
||||
"SwitchInline",
|
||||
"Text",
|
||||
"Url",
|
||||
]
|
Loading…
Reference in New Issue
Block a user