diff --git a/changes/unreleased/4847.8ujbbBbaZ2VTEdRLeqirSZ.toml b/changes/unreleased/4847.8ujbbBbaZ2VTEdRLeqirSZ.toml index d79e6d4735d..85a19e02417 100644 --- a/changes/unreleased/4847.8ujbbBbaZ2VTEdRLeqirSZ.toml +++ b/changes/unreleased/4847.8ujbbBbaZ2VTEdRLeqirSZ.toml @@ -10,4 +10,5 @@ pull_requests = [ { uid = "4847", author_uid = "Bibo-Joshi", closes_threads = ["4845"] }, { uid = "4849", author_uid = "harshil21" }, { uid = "4851", author_uid = "harshil21" }, + { uid = "4857", author_uid = "aelkheir" }, ] diff --git a/docs/source/inclusions/bot_methods.rst b/docs/source/inclusions/bot_methods.rst index 1915ffca661..470fcb17ff6 100644 --- a/docs/source/inclusions/bot_methods.rst +++ b/docs/source/inclusions/bot_methods.rst @@ -449,6 +449,10 @@ - Used for transferring owned unique gifts to another user. * - :meth:`~telegram.Bot.transfer_business_account_stars` - Used for transfering Stars from the business account balance to the bot's balance. + * - :meth:`~telegram.Bot.send_checklist` + - Used for sending a checklist on behalf of the business account. + * - :meth:`~telegram.Bot.edit_message_checklist` + - Used for editing a checklist on behalf of the business account. .. raw:: html diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index e248edb31e8..870b31fb2da 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -87,6 +87,8 @@ Available Types telegram.inaccessiblemessage telegram.inlinekeyboardbutton telegram.inlinekeyboardmarkup + telegram.inputchecklist + telegram.inputchecklisttask telegram.inputfile telegram.inputmedia telegram.inputmediaanimation diff --git a/docs/source/telegram.inputchecklist.rst b/docs/source/telegram.inputchecklist.rst new file mode 100644 index 00000000000..f83345884a0 --- /dev/null +++ b/docs/source/telegram.inputchecklist.rst @@ -0,0 +1,6 @@ +InputChecklist +============== + +.. autoclass:: telegram.InputChecklist + :members: + :show-inheritance: diff --git a/docs/source/telegram.inputchecklisttask.rst b/docs/source/telegram.inputchecklisttask.rst new file mode 100644 index 00000000000..1cc14095b0c --- /dev/null +++ b/docs/source/telegram.inputchecklisttask.rst @@ -0,0 +1,6 @@ +InputChecklistTask +================== + +.. autoclass:: telegram.InputChecklistTask + :members: + :show-inheritance: diff --git a/src/telegram/__init__.py b/src/telegram/__init__.py index b8632ac53b9..98f3f4c18d1 100644 --- a/src/telegram/__init__.py +++ b/src/telegram/__init__.py @@ -140,6 +140,8 @@ "InlineQueryResultVideo", "InlineQueryResultVoice", "InlineQueryResultsButton", + "InputChecklist", + "InputChecklistTask", "InputContactMessageContent", "InputFile", "InputInvoiceMessageContent", @@ -304,6 +306,7 @@ "warnings", ) +from telegram._inputchecklist import InputChecklist, InputChecklistTask from telegram._payment.stars.staramount import StarAmount from telegram._payment.stars.startransactions import StarTransaction, StarTransactions from telegram._payment.stars.transactionpartner import ( diff --git a/src/telegram/_bot.py b/src/telegram/_bot.py index f79b34997e4..1577cc38d16 100644 --- a/src/telegram/_bot.py +++ b/src/telegram/_bot.py @@ -78,6 +78,7 @@ from telegram._gifts import AcceptedGiftTypes, Gift, Gifts from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton from telegram._inline.preparedinlinemessage import PreparedInlineMessage +from telegram._inputchecklist import InputChecklist from telegram._menubutton import MenuButton from telegram._message import Message from telegram._messageid import MessageId @@ -7555,6 +7556,142 @@ async def stop_poll( ) return Poll.de_json(result, self) + async def send_checklist( + self, + business_connection_id: str, + chat_id: int, + checklist: InputChecklist, + disable_notification: ODVInput[bool] = DEFAULT_NONE, + protect_content: ODVInput[bool] = DEFAULT_NONE, + message_effect_id: Optional[str] = None, + reply_parameters: Optional["ReplyParameters"] = None, + reply_markup: Optional["InlineKeyboardMarkup"] = None, + *, + allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + reply_to_message_id: Optional[int] = None, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: Optional[JSONDict] = None, + ) -> Message: + """ + Use this method to send a checklist on behalf of a connected business account. + + .. versionadded:: NEXT.VERSION + + Args: + business_connection_id (:obj:`str`): + |business_id_str| + chat_id (:obj:`int`): + Unique identifier for the target chat. + checklist (:class:`telegram.InputChecklist`): + The checklist to send. + disable_notification (:obj:`bool`, optional): + |disable_notification| + protect_content (:obj:`bool`, optional): + |protect_content| + message_effect_id (:obj:`str`, optional): + |message_effect_id| + reply_parameters (:class:`telegram.ReplyParameters`, optional): + |reply_parameters| + reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): + An object for an inline keyboard + + Keyword Args: + allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| + Mutually exclusive with :paramref:`reply_parameters`, which this is a convenience + parameter for + reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id| + Mutually exclusive with :paramref:`reply_parameters`, which this is a convenience + parameter for + + Returns: + :class:`telegram.Message`: On success, the sent Message is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data: JSONDict = { + "chat_id": chat_id, + "checklist": checklist, + } + + return await self._send_message( + "sendChecklist", + data, + disable_notification=disable_notification, + reply_markup=reply_markup, + protect_content=protect_content, + reply_parameters=reply_parameters, + message_effect_id=message_effect_id, + business_connection_id=business_connection_id, + allow_sending_without_reply=allow_sending_without_reply, + reply_to_message_id=reply_to_message_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + async def edit_message_checklist( + self, + business_connection_id: str, + chat_id: int, + message_id: int, + checklist: InputChecklist, + reply_markup: Optional["InlineKeyboardMarkup"] = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: Optional[JSONDict] = None, + ) -> Message: + """ + Use this method to edit a checklist on behalf of a connected business account. + + .. versionadded:: NEXT.VERSION + + Args: + business_connection_id (:obj:`str`): + |business_id_str| + chat_id (:obj:`int`): + Unique identifier for the target chat. + message_id (:obj:`int`): + Unique identifier for the target message. + checklist (:class:`telegram.InputChecklist`): + The new checklist. + reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): + An object for the new inline keyboard for the message. + + Returns: + :class:`telegram.Message`: On success, the sent Message is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data: JSONDict = { + "chat_id": chat_id, + "message_id": message_id, + "checklist": checklist, + } + + return await self._send_message( + "editMessageChecklist", + data, + reply_markup=reply_markup, + business_connection_id=business_connection_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + async def send_dice( self, chat_id: Union[int, str], @@ -11274,6 +11411,10 @@ def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002 """Alias for :meth:`send_poll`""" stopPoll = stop_poll """Alias for :meth:`stop_poll`""" + sendChecklist = send_checklist + """Alias for :meth:`send_checklist`""" + editMessageChecklist = edit_message_checklist + """Alias for :meth:`edit_message_checklist`""" sendDice = send_dice """Alias for :meth:`send_dice`""" getMyCommands = get_my_commands diff --git a/src/telegram/_callbackquery.py b/src/telegram/_callbackquery.py index 99b4ad115b5..49ae9159660 100644 --- a/src/telegram/_callbackquery.py +++ b/src/telegram/_callbackquery.py @@ -23,6 +23,7 @@ from telegram import constants from telegram._files.location import Location +from telegram._inputchecklist import InputChecklist from telegram._message import MaybeInaccessibleMessage, Message from telegram._telegramobject import TelegramObject from telegram._user import User @@ -345,6 +346,43 @@ async def edit_message_caption( show_caption_above_media=show_caption_above_media, ) + async def edit_message_checklist( + self, + checklist: InputChecklist, + reply_markup: Optional["InlineKeyboardMarkup"] = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: Optional[JSONDict] = None, + ) -> Union[Message, bool]: + """Shortcut for:: + + await update.callback_query.message.edit_checklist(*args, **kwargs) + + For the documentation of the arguments, please see + :meth:`telegram.Message.edit_checklist`. + + .. versionadded:: NEXT.VERSION + + Returns: + :class:`telegram.Message`: On success, the edited Message is returned. + + Raises: + :exc:`TypeError` if :attr:`message` is not accessible. + + """ + return await self._get_message().edit_checklist( + checklist=checklist, + reply_markup=reply_markup, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + async def edit_message_reply_markup( self, reply_markup: Optional["InlineKeyboardMarkup"] = None, diff --git a/src/telegram/_chat.py b/src/telegram/_chat.py index 02eb6629d6d..4efb4fd80bb 100644 --- a/src/telegram/_chat.py +++ b/src/telegram/_chat.py @@ -53,6 +53,7 @@ Document, Gift, InlineKeyboardMarkup, + InputChecklist, InputMediaAudio, InputMediaDocument, InputMediaPhoto, @@ -1471,6 +1472,54 @@ async def send_document( allow_paid_broadcast=allow_paid_broadcast, ) + async def send_checklist( + self, + business_connection_id: str, + checklist: "InputChecklist", + disable_notification: ODVInput[bool] = DEFAULT_NONE, + reply_markup: Optional["InlineKeyboardMarkup"] = None, + protect_content: ODVInput[bool] = DEFAULT_NONE, + reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, + *, + reply_to_message_id: Optional[int] = None, + allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: Optional[JSONDict] = None, + ) -> "Message": + """Shortcut for:: + + await bot.send_checklist(chat_id=update.effective_chat.id, *args, **kwargs) + + For the documentation of the arguments, please see :meth:`telegram.Bot.send_checklist`. + + .. versionadded:: NEXT.VERSION + + Returns: + :class:`telegram.Message`: On success, instance representing the message posted. + + """ + return await self.get_bot().send_checklist( + chat_id=self.id, + business_connection_id=business_connection_id, + checklist=checklist, + disable_notification=disable_notification, + protect_content=protect_content, + message_effect_id=message_effect_id, + reply_parameters=reply_parameters, + reply_markup=reply_markup, + reply_to_message_id=reply_to_message_id, + allow_sending_without_reply=allow_sending_without_reply, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + async def send_dice( self, disable_notification: ODVInput[bool] = DEFAULT_NONE, diff --git a/src/telegram/_inputchecklist.py b/src/telegram/_inputchecklist.py new file mode 100644 index 00000000000..2608c99ccf7 --- /dev/null +++ b/src/telegram/_inputchecklist.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2025 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains an objects that are related to Telegram input checklists.""" +from collections.abc import Sequence +from typing import Optional + +from telegram._messageentity import MessageEntity +from telegram._telegramobject import TelegramObject +from telegram._utils.argumentparsing import parse_sequence_arg +from telegram._utils.defaultvalue import DEFAULT_NONE +from telegram._utils.types import JSONDict, ODVInput + + +class InputChecklistTask(TelegramObject): + """ + Describes a task to add to a checklist. + + Objects of this class are comparable in terms of equality. + Two objects of this class are considered equal if their :attr:`id` is equal. + + .. versionadded:: NEXT.VERSION + + Args: + id (:obj:`int`): + Unique identifier of the task; must be positive and unique among all task identifiers + currently present in the checklist. + text (:obj:`str`): + Text of the task; + :tg-const:`telegram.constants.InputChecklistLimit.MIN_TEXT_LENGTH`\ +-:tg-const:`telegram.constants.InputChecklistLimit.MAX_TEXT_LENGTH` characters after + entities parsing. + parse_mode (:obj:`str`, optional): + |parse_mode| + text_entities (Sequence[:class:`telegram.MessageEntity`], optional): + List of special entities that appear in the text, which can be specified instead of + parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, and + custom_emoji entities are allowed. + + Attributes: + id (:obj:`int`): + Unique identifier of the task; must be positive and unique among all task identifiers + currently present in the checklist. + text (:obj:`str`): + Text of the task; + :tg-const:`telegram.constants.InputChecklistLimit.MIN_TEXT_LENGTH`\ +-:tg-const:`telegram.constants.InputChecklistLimit.MAX_TEXT_LENGTH` characters after + entities parsing. + parse_mode (:obj:`str`): + Optional. |parse_mode| + text_entities (Sequence[:class:`telegram.MessageEntity`]): + Optional. List of special entities that appear in the text, which can be specified + instead of parse_mode. Currently, only bold, italic, underline, strikethrough, spoiler, + and custom_emoji entities are allowed. + + """ + + __slots__ = ( + "id", + "parse_mode", + "text", + "text_entities", + ) + + def __init__( + self, + id: int, # pylint: disable=redefined-builtin + text: str, + parse_mode: ODVInput[str] = DEFAULT_NONE, + text_entities: Optional[Sequence[MessageEntity]] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.id: int = id + self.text: str = text + self.parse_mode: ODVInput[str] = parse_mode + self.text_entities: tuple[MessageEntity, ...] = parse_sequence_arg(text_entities) + + self._id_attrs = (self.id,) + + self._freeze() + + +class InputChecklist(TelegramObject): + """ + Describes a checklist to create. + + Objects of this class are comparable in terms of equality. + Two objects of this class are considered equal if their :attr:`tasks` is equal. + + .. versionadded:: NEXT.VERSION + + Args: + title (:obj:`str`): + Title of the checklist; + :tg-const:`telegram.constants.InputChecklistLimit.MIN_TITLE_LENGTH`\ +-:tg-const:`telegram.constants.InputChecklistLimit.MAX_TITLE_LENGTH` characters after + entities parsing. + parse_mode (:obj:`str`, optional): + |parse_mode| + title_entities (Sequence[:class:`telegram.MessageEntity`], optional): + List of special entities that appear in the title, which + can be specified instead of :paramref:`parse_mode`. Currently, only bold, italic, + underline, strikethrough, spoiler, and custom_emoji entities are allowed. + tasks (Sequence[:class:`telegram.InputChecklistTask`]): + List of + :tg-const:`telegram.constants.InputChecklistLimit.MIN_TASK_NUMBER`\ +-:tg-const:`telegram.constants.InputChecklistLimit.MAX_TASK_NUMBER` tasks in + the checklist. + others_can_add_tasks (:obj:`bool`, optional): + Pass :obj:`True` if other users can add tasks to the checklist. + others_can_mark_tasks_as_done (:obj:`bool`, optional): + Pass :obj:`True` if other users can mark tasks as done or not done in the checklist. + + Attributes: + title (:obj:`str`): + Title of the checklist; + :tg-const:`telegram.constants.InputChecklistLimit.MIN_TITLE_LENGTH`\ +-:tg-const:`telegram.constants.InputChecklistLimit.MAX_TITLE_LENGTH` characters after + entities parsing. + parse_mode (:obj:`str`): + Optional. |parse_mode| + title_entities (Sequence[:class:`telegram.MessageEntity`]): + Optional. List of special entities that appear in the title, which + can be specified instead of :paramref:`parse_mode`. Currently, only bold, italic, + underline, strikethrough, spoiler, and custom_emoji entities are allowed. + tasks (Sequence[:class:`telegram.InputChecklistTask`]): + List of + :tg-const:`telegram.constants.InputChecklistLimit.MIN_TASK_NUMBER`\ +-:tg-const:`telegram.constants.InputChecklistLimit.MAX_TASK_NUMBER` tasks in + the checklist. + others_can_add_tasks (:obj:`bool`): + Optional. Pass :obj:`True` if other users can add tasks to the checklist. + others_can_mark_tasks_as_done (:obj:`bool`): + Optional. Pass :obj:`True` if other users can mark tasks as done or not done in + the checklist. + + """ + + __slots__ = ( + "others_can_add_tasks", + "others_can_mark_tasks_as_done", + "parse_mode", + "tasks", + "title", + "title_entities", + ) + + def __init__( + self, + title: str, + tasks: Sequence[InputChecklistTask], + parse_mode: ODVInput[str] = DEFAULT_NONE, + title_entities: Optional[Sequence[MessageEntity]] = None, + others_can_add_tasks: Optional[bool] = None, + others_can_mark_tasks_as_done: Optional[bool] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.title: str = title + self.tasks: tuple[InputChecklistTask, ...] = parse_sequence_arg(tasks) + self.parse_mode: ODVInput[str] = parse_mode + self.title_entities: tuple[MessageEntity, ...] = parse_sequence_arg(title_entities) + self.others_can_add_tasks: Optional[bool] = others_can_add_tasks + self.others_can_mark_tasks_as_done: Optional[bool] = others_can_mark_tasks_as_done + + self._id_attrs = (self.tasks,) + + self._freeze() diff --git a/src/telegram/_message.py b/src/telegram/_message.py index 46d755bb71f..c8a27d37b25 100644 --- a/src/telegram/_message.py +++ b/src/telegram/_message.py @@ -52,6 +52,7 @@ from telegram._games.game import Game from telegram._gifts import GiftInfo from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup +from telegram._inputchecklist import InputChecklist from telegram._linkpreviewoptions import LinkPreviewOptions from telegram._messageautodeletetimerchanged import MessageAutoDeleteTimerChanged from telegram._messageentity import MessageEntity @@ -3269,6 +3270,63 @@ async def reply_dice( allow_paid_broadcast=allow_paid_broadcast, ) + async def reply_checklist( + self, + checklist: InputChecklist, + disable_notification: ODVInput[bool] = DEFAULT_NONE, + protect_content: ODVInput[bool] = DEFAULT_NONE, + message_effect_id: Optional[str] = None, + reply_parameters: Optional["ReplyParameters"] = None, + reply_markup: Optional["InlineKeyboardMarkup"] = None, + *, + reply_to_message_id: Optional[int] = None, + allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + do_quote: Optional[Union[bool, _ReplyKwargs]] = None, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: Optional[JSONDict] = None, + ) -> "Message": + """Shortcut for:: + + await bot.send_checklist( + business_connection_id=self.business_connection_id, + chat_id=update.effective_message.chat_id, + *args, + **kwargs, + ) + + For the documentation of the arguments, please see :meth:`telegram.Bot.send_checklist`. + + .. versionadded:: NEXT.VERSION + + Keyword Args: + do_quote (:obj:`bool` | :obj:`dict`, optional): |do_quote| + + Returns: + :class:`telegram.Message`: On success, instance representing the message posted. + + """ + chat_id, effective_reply_parameters = await self._parse_quote_arguments( + do_quote, reply_to_message_id, reply_parameters, allow_sending_without_reply + ) + return await self.get_bot().send_checklist( + business_connection_id=self.business_connection_id, + chat_id=chat_id, # type: ignore[arg-type] + checklist=checklist, + disable_notification=disable_notification, + reply_parameters=effective_reply_parameters, + reply_markup=reply_markup, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + protect_content=protect_content, + message_effect_id=message_effect_id, + ) + async def reply_chat_action( self, action: str, @@ -3878,6 +3936,53 @@ async def edit_caption( business_connection_id=self.business_connection_id, ) + async def edit_checklist( + self, + checklist: InputChecklist, + reply_markup: Optional["InlineKeyboardMarkup"] = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: Optional[JSONDict] = None, + ) -> "Message": + """Shortcut for:: + + await bot.edit_message_checklist( + business_connection_id=message.business_connection_id, + chat_id=message.chat_id, + message_id=message.message_id, + *args, **kwargs + ) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.edit_message_checklist`. + + .. versionadded:: NEXT.VERSION + + Note: + You can only edit messages that the bot sent itself (i.e. of the ``bot.send_*`` family + of methods) or channel posts, if the bot is an admin in that channel. However, this + behaviour is undocumented and might be changed by Telegram. + + Returns: + :class:`telegram.Message`: On success, the edited Message is returned. + + """ + return await self.get_bot().edit_message_checklist( + business_connection_id=self.business_connection_id, + chat_id=self.chat_id, + message_id=self.message_id, + checklist=checklist, + reply_markup=reply_markup, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + async def edit_media( self, media: "InputMedia", diff --git a/src/telegram/constants.py b/src/telegram/constants.py index 5ca7d4ebd5c..3512ff7648c 100644 --- a/src/telegram/constants.py +++ b/src/telegram/constants.py @@ -77,6 +77,7 @@ "InlineQueryResultLimit", "InlineQueryResultType", "InlineQueryResultsButtonLimit", + "InputChecklistLimit", "InputMediaType", "InputPaidMediaType", "InputProfilePhotoType", @@ -1412,6 +1413,47 @@ class InlineKeyboardMarkupLimit(IntEnum): """ +class InputChecklistLimit(IntEnum): + """This enum contains limitations for :class:`telegram.InputChecklist`/ + :class:`telegram.InputChecklistTask`. The enum + members of this enumeration are instances of :class:`int` and can be treated as such. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = () + + MIN_TITLE_LENGTH = 1 + """:obj:`int`: Minimum number of characters in a :obj:`str` passed as + :paramref:`~telegram.InputChecklist.title` parameter of :class:`telegram.InputChecklist` + """ + + MAX_TITLE_LENGTH = 255 + """:obj:`int`: Maximum number of characters in a :obj:`str` passed as + :paramref:`~telegram.InputChecklist.title` parameter of :class:`telegram.InputChecklist` + """ + + MIN_TEXT_LENGTH = 1 + """:obj:`int`: Minimum number of characters in a :obj:`str` passed as + :paramref:`~telegram.InputChecklistTask.text` parameter of :class:`telegram.InputChecklistTask` + """ + + MAX_TEXT_LENGTH = 100 + """:obj:`int`: Maximum number of characters in a :obj:`str` passed as + :paramref:`~telegram.InputChecklistTask.text` parameter of :class:`telegram.InputChecklistTask` + """ + + MIN_TASK_NUMBER = 1 + """:obj:`int`: Minimum number of tasks passed as :paramref:`~telegram.InputChecklist.tasks` + parameter of :class:`telegram.InputChecklist` + """ + + MAX_TASK_NUMBER = 30 + """:obj:`int`: Maximum number of tasks passed as :paramref:`~telegram.InputChecklistTask.tasks` + parameter of :class:`telegram.InputChecklistTask` + """ + + class InputMediaType(StringEnum): """This enum contains the available types of :class:`telegram.InputMedia`. The enum members of this enumeration are instances of :class:`str` and can be treated as such. diff --git a/src/telegram/ext/_extbot.py b/src/telegram/ext/_extbot.py index 7c6f5ef8cb8..3c956594632 100644 --- a/src/telegram/ext/_extbot.py +++ b/src/telegram/ext/_extbot.py @@ -62,6 +62,7 @@ Gifts, InlineKeyboardMarkup, InlineQueryResultsButton, + InputChecklist, InputMedia, InputPaidMedia, InputPollOption, @@ -2639,6 +2640,72 @@ async def send_contact( allow_paid_broadcast=allow_paid_broadcast, ) + async def send_checklist( + self, + business_connection_id: str, + chat_id: int, + checklist: InputChecklist, + disable_notification: ODVInput[bool] = DEFAULT_NONE, + protect_content: ODVInput[bool] = DEFAULT_NONE, + message_effect_id: Optional[str] = None, + reply_parameters: Optional["ReplyParameters"] = None, + reply_markup: Optional["InlineKeyboardMarkup"] = None, + *, + reply_to_message_id: Optional[int] = None, + allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: Optional[JSONDict] = None, + rate_limit_args: Optional[RLARGS] = None, + ) -> Message: + return await super().send_checklist( + business_connection_id=business_connection_id, + chat_id=chat_id, + checklist=checklist, + disable_notification=disable_notification, + protect_content=protect_content, + message_effect_id=message_effect_id, + reply_parameters=reply_parameters, + reply_markup=reply_markup, + reply_to_message_id=reply_to_message_id, + allow_sending_without_reply=allow_sending_without_reply, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + + async def edit_message_checklist( + self, + business_connection_id: str, + chat_id: int, + message_id: int, + checklist: InputChecklist, + reply_markup: Optional["InlineKeyboardMarkup"] = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: Optional[JSONDict] = None, + rate_limit_args: Optional[RLARGS] = None, + ) -> Message: + return await super().edit_message_checklist( + business_connection_id=business_connection_id, + chat_id=chat_id, + message_id=message_id, + checklist=checklist, + reply_markup=reply_markup, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + async def send_dice( self, chat_id: Union[int, str], @@ -5157,6 +5224,8 @@ async def get_my_star_balance( setPassportDataErrors = set_passport_data_errors sendPoll = send_poll stopPoll = stop_poll + sendChecklist = send_checklist + editMessageChecklist = edit_message_checklist sendDice = send_dice getMyCommands = get_my_commands setMyCommands = set_my_commands diff --git a/tests/test_business_methods.py b/tests/test_business_methods.py index bdbc2e2d9b0..4e44a84e37d 100644 --- a/tests/test_business_methods.py +++ b/tests/test_business_methods.py @@ -36,7 +36,12 @@ from telegram._files._inputstorycontent import InputStoryContentVideo from telegram._files.sticker import Sticker from telegram._gifts import AcceptedGiftTypes, Gift +from telegram._inline.inlinekeyboardbutton import InlineKeyboardButton +from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup +from telegram._inputchecklist import InputChecklist, InputChecklistTask +from telegram._message import Message from telegram._ownedgift import OwnedGiftRegular, OwnedGifts +from telegram._reply import ReplyParameters from telegram._utils.datetime import UTC from telegram._utils.defaultvalue import DEFAULT_NONE from telegram.constants import InputProfilePhotoType, InputStoryContentType @@ -638,3 +643,149 @@ async def make_assertion(*args, **kwargs): monkeypatch.setattr(offline_bot.request, "post", make_assertion) assert await offline_bot.delete_story(business_connection_id=self.bci, story_id=story_id) + + async def test_send_checklist_all_args(self, offline_bot, monkeypatch): + chat_id = 123 + checklist = InputChecklist( + title="My Checklist", + tasks=[InputChecklistTask(1, "Task 1"), InputChecklistTask(2, "Task 2")], + ) + disable_notification = True + protect_content = False + message_effect_id = 42 + reply_parameters = ReplyParameters(23, chat_id, allow_sending_without_reply=True) + reply_markup = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="test", callback_data="test2")]] + ) + json_message = Message(1, dtm.datetime.now(), Chat(1, ""), text="test").to_json() + + async def make_assertions(*args, **kwargs): + params = kwargs.get("request_data").parameters + assert params.get("business_connection_id") == self.bci + assert params.get("chat_id") == chat_id + assert params.get("checklist") == checklist.to_dict() + assert params.get("disable_notification") is disable_notification + assert params.get("protect_content") is protect_content + assert params.get("message_effect_id") == message_effect_id + assert params.get("reply_parameters") == reply_parameters.to_dict() + assert params.get("reply_markup") == reply_markup.to_dict() + + return 200, f'{{"ok": true, "result": {json_message}}}'.encode() + + monkeypatch.setattr(offline_bot.request, "do_request", make_assertions) + obj = await offline_bot.send_checklist( + business_connection_id=self.bci, + chat_id=chat_id, + checklist=checklist, + disable_notification=disable_notification, + protect_content=protect_content, + message_effect_id=message_effect_id, + reply_parameters=reply_parameters, + reply_markup=reply_markup, + ) + assert isinstance(obj, Message) + + @pytest.mark.parametrize("default_bot", [{"disable_notification": True}], indirect=True) + @pytest.mark.parametrize( + ("passed_value", "expected_value"), + [(DEFAULT_NONE, True), (False, False), (None, None)], + ) + async def test_send_checklist_default_disable_notification( + self, default_bot, monkeypatch, passed_value, expected_value + ): + async def make_assertion(url, request_data, *args, **kwargs): + assert request_data.parameters.get("disable_notification") is expected_value + return Message(1, dtm.datetime.now(), Chat(1, ""), text="test").to_dict() + + monkeypatch.setattr(default_bot.request, "post", make_assertion) + kwargs = { + "business_connection_id": self.bci, + "chat_id": 123, + "checklist": InputChecklist( + title="My Checklist", + tasks=[InputChecklistTask(1, "Task 1")], + ), + } + if passed_value is not DEFAULT_NONE: + kwargs["disable_notification"] = passed_value + + await default_bot.send_checklist(**kwargs) + + @pytest.mark.parametrize("default_bot", [{"protect_content": True}], indirect=True) + @pytest.mark.parametrize( + ("passed_value", "expected_value"), + [(DEFAULT_NONE, True), (False, False), (None, None)], + ) + async def test_send_checklist_default_protect_content( + self, default_bot, monkeypatch, passed_value, expected_value + ): + async def make_assertion(url, request_data, *args, **kwargs): + assert request_data.parameters.get("protect_content") is expected_value + return Message(1, dtm.datetime.now(), Chat(1, ""), text="test").to_dict() + + monkeypatch.setattr(default_bot.request, "post", make_assertion) + kwargs = { + "business_connection_id": self.bci, + "chat_id": 123, + "checklist": InputChecklist( + title="My Checklist", + tasks=[InputChecklistTask(1, "Task 1")], + ), + } + if passed_value is not DEFAULT_NONE: + kwargs["protect_content"] = passed_value + + await default_bot.send_checklist(**kwargs) + + async def test_send_checklist_mutually_exclusive_reply_parameters(self, offline_bot): + """Test that reply_to_message_id and allow_sending_without_reply are mutually exclusive + with reply_parameters.""" + with pytest.raises(ValueError, match="`reply_to_message_id` and"): + await offline_bot.send_checklist( + self.bci, + 123, + InputChecklist(title="My Checklist", tasks=[InputChecklistTask(1, "Task 1")]), + reply_to_message_id=1, + reply_parameters=True, + ) + + with pytest.raises(ValueError, match="`allow_sending_without_reply` and"): + await offline_bot.send_checklist( + self.bci, + 123, + InputChecklist(title="My Checklist", tasks=[InputChecklistTask(1, "Task 1")]), + allow_sending_without_reply=True, + reply_parameters=True, + ) + + async def test_edit_message_checklist_all_args(self, offline_bot, monkeypatch): + chat_id = 123 + message_id = 45 + checklist = InputChecklist( + title="My Checklist", + tasks=[InputChecklistTask(1, "Task 1"), InputChecklistTask(2, "Task 2")], + ) + reply_markup = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="test", callback_data="test2")]] + ) + json_message = Message(1, dtm.datetime.now(), Chat(1, ""), text="test").to_json() + + async def make_assertions(*args, **kwargs): + params = kwargs.get("request_data").parameters + assert params.get("business_connection_id") == self.bci + assert params.get("chat_id") == chat_id + assert params.get("message_id") == message_id + assert params.get("checklist") == checklist.to_dict() + assert params.get("reply_markup") == reply_markup.to_dict() + + return 200, f'{{"ok": true, "result": {json_message}}}'.encode() + + monkeypatch.setattr(offline_bot.request, "do_request", make_assertions) + obj = await offline_bot.edit_message_checklist( + business_connection_id=self.bci, + chat_id=chat_id, + message_id=message_id, + checklist=checklist, + reply_markup=reply_markup, + ) + assert isinstance(obj, Message) diff --git a/tests/test_callbackquery.py b/tests/test_callbackquery.py index 97ef9b2a627..6b759e885cb 100644 --- a/tests/test_callbackquery.py +++ b/tests/test_callbackquery.py @@ -21,7 +21,17 @@ import pytest -from telegram import Audio, Bot, CallbackQuery, Chat, InaccessibleMessage, Message, User +from telegram import ( + Audio, + Bot, + CallbackQuery, + Chat, + InaccessibleMessage, + InputChecklist, + InputChecklistTask, + Message, + User, +) from tests.auxil.bot_method_checks import ( check_defaults_handling, check_shortcut_call, @@ -230,6 +240,43 @@ async def make_assertion(*_, **kwargs): assert await callback_query.edit_message_caption(caption="new caption") assert await callback_query.edit_message_caption("new caption") + async def test_edit_message_checklist(self, monkeypatch, callback_query): + checklist = InputChecklist(title="My Checklist", tasks=[InputChecklistTask(1, "Task 1")]) + + if isinstance(callback_query.message, InaccessibleMessage): + with pytest.raises(TypeError, match="inaccessible message"): + await callback_query.edit_message_checklist(checklist) + return + + if callback_query.inline_message_id: + pytest.skip("Can't edit inline messages") + + async def make_assertion(*_, **kwargs): + chat_id = kwargs["chat_id"] == callback_query.message.chat_id + message_id = kwargs["message_id"] == callback_query.message.message_id + caption = kwargs["checklist"] == checklist + return chat_id and message_id and caption + + assert check_shortcut_signature( + CallbackQuery.edit_message_checklist, + Bot.edit_message_checklist, + ["chat_id", "message_id", "business_connection_id"], + [], + ) + assert await check_shortcut_call( + callback_query.edit_message_checklist, + callback_query.get_bot(), + "edit_message_checklist", + shortcut_kwargs=["chat_id", "message_id", "business_connection_id"], + ) + assert await check_defaults_handling( + callback_query.edit_message_checklist, callback_query.get_bot() + ) + + monkeypatch.setattr(callback_query.get_bot(), "edit_message_checklist", make_assertion) + assert await callback_query.edit_message_checklist(checklist=checklist) + assert await callback_query.edit_message_checklist(checklist) + async def test_edit_message_reply_markup(self, monkeypatch, callback_query): if isinstance(callback_query.message, InaccessibleMessage): with pytest.raises(TypeError, match="inaccessible message"): diff --git a/tests/test_chat.py b/tests/test_chat.py index 8e901fb91bf..4651393e473 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -20,7 +20,15 @@ import pytest -from telegram import Bot, Chat, ChatPermissions, ReactionTypeEmoji, User +from telegram import ( + Bot, + Chat, + ChatPermissions, + InputChecklist, + InputChecklistTask, + ReactionTypeEmoji, + User, +) from telegram.constants import ChatAction, ChatType, ReactionEmoji from telegram.helpers import escape_markdown from tests.auxil.bot_method_checks import ( @@ -579,6 +587,23 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(chat.get_bot(), "send_dice", make_assertion) assert await chat.send_dice(emoji="test_dice") + async def test_instance_method_send_checklist(self, monkeypatch, chat): + checklist = InputChecklist(title="My Checklist", tasks=[InputChecklistTask(1, "Task 1")]) + + async def make_assertion(*_, **kwargs): + return ( + kwargs["chat_id"] == chat.id + and kwargs["business_connection_id"] == "123" + and kwargs["checklist"] == checklist + ) + + assert check_shortcut_signature(Chat.send_checklist, Bot.send_checklist, ["chat_id"], []) + assert await check_shortcut_call(chat.send_checklist, chat.get_bot(), "send_checklist") + assert await check_defaults_handling(chat.send_checklist, chat.get_bot()) + + monkeypatch.setattr(chat.get_bot(), "send_checklist", make_assertion) + assert await chat.send_checklist("123", checklist) + async def test_instance_method_send_game(self, monkeypatch, chat): async def make_assertion(*_, **kwargs): return kwargs["chat_id"] == chat.id and kwargs["game_short_name"] == "test_game" diff --git a/tests/test_inputchecklist.py b/tests/test_inputchecklist.py new file mode 100644 index 00000000000..f21171eb5a3 --- /dev/null +++ b/tests/test_inputchecklist.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2025 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +import pytest + +from telegram import Dice, InputChecklist, InputChecklistTask, MessageEntity +from tests.auxil.slots import mro_slots + + +@pytest.fixture(scope="module") +def input_checklist_task(): + return InputChecklistTask( + id=InputChecklistTaskTestBase.id, + text=InputChecklistTaskTestBase.text, + parse_mode=InputChecklistTaskTestBase.parse_mode, + text_entities=InputChecklistTaskTestBase.text_entities, + ) + + +class InputChecklistTaskTestBase: + id = 1 + text = "buy food" + parse_mode = "MarkdownV2" + text_entities = [ + MessageEntity(type="bold", offset=0, length=3), + MessageEntity(type="italic", offset=4, length=4), + ] + + +class TestInputChecklistTaskWithoutRequest(InputChecklistTaskTestBase): + def test_slot_behaviour(self, input_checklist_task): + for attr in input_checklist_task.__slots__: + assert getattr(input_checklist_task, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(input_checklist_task)) == len( + set(mro_slots(input_checklist_task)) + ), "duplicate slot" + + def test_expected_values(self, input_checklist_task): + assert input_checklist_task.id == self.id + assert input_checklist_task.text == self.text + assert input_checklist_task.parse_mode == self.parse_mode + assert input_checklist_task.text_entities == tuple(self.text_entities) + + def test_to_dict(self, input_checklist_task): + iclt_dict = input_checklist_task.to_dict() + + assert isinstance(iclt_dict, dict) + assert iclt_dict["id"] == self.id + assert iclt_dict["text"] == self.text + assert iclt_dict["parse_mode"] == self.parse_mode + assert iclt_dict["text_entities"] == [entity.to_dict() for entity in self.text_entities] + + # Test that default-value parameter `parse_mode` is handled correctly + input_checklist_task = InputChecklistTask(id=1, text="text") + iclt_dict = input_checklist_task.to_dict() + assert "parse_mode" not in iclt_dict + + def test_equality(self, input_checklist_task): + a = input_checklist_task + b = InputChecklistTask(id=self.id, text=f"other {self.text}") + c = InputChecklistTask(id=self.id + 1, text=self.text) + d = Dice(value=1, emoji="🎲") + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) + + +@pytest.fixture(scope="module") +def input_checklist(): + return InputChecklist( + title=InputChecklistTestBase.title, + tasks=InputChecklistTestBase.tasks, + parse_mode=InputChecklistTestBase.parse_mode, + title_entities=InputChecklistTestBase.title_entities, + others_can_add_tasks=InputChecklistTestBase.others_can_add_tasks, + others_can_mark_tasks_as_done=InputChecklistTestBase.others_can_mark_tasks_as_done, + ) + + +class InputChecklistTestBase: + title = "test list" + tasks = [ + InputChecklistTask(id=1, text="eat"), + InputChecklistTask(id=2, text="sleep"), + ] + parse_mode = "MarkdownV2" + title_entities = [ + MessageEntity(type="bold", offset=0, length=4), + MessageEntity(type="italic", offset=5, length=4), + ] + others_can_add_tasks = True + others_can_mark_tasks_as_done = False + + +class TestInputChecklistWithoutRequest(InputChecklistTestBase): + def test_slot_behaviour(self, input_checklist): + for attr in input_checklist.__slots__: + assert getattr(input_checklist, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(input_checklist)) == len( + set(mro_slots(input_checklist)) + ), "duplicate slot" + + def test_expected_values(self, input_checklist): + assert input_checklist.title == self.title + assert input_checklist.tasks == tuple(self.tasks) + assert input_checklist.parse_mode == self.parse_mode + assert input_checklist.title_entities == tuple(self.title_entities) + assert input_checklist.others_can_add_tasks == self.others_can_add_tasks + assert input_checklist.others_can_mark_tasks_as_done == self.others_can_mark_tasks_as_done + + def test_to_dict(self, input_checklist): + icl_dict = input_checklist.to_dict() + + assert isinstance(icl_dict, dict) + assert icl_dict["title"] == self.title + assert icl_dict["tasks"] == [task.to_dict() for task in self.tasks] + assert icl_dict["parse_mode"] == self.parse_mode + assert icl_dict["title_entities"] == [entity.to_dict() for entity in self.title_entities] + assert icl_dict["others_can_add_tasks"] == self.others_can_add_tasks + assert icl_dict["others_can_mark_tasks_as_done"] == self.others_can_mark_tasks_as_done + + # Test that default-value parameter `parse_mode` is handled correctly + input_checklist = InputChecklist(title=self.title, tasks=self.tasks) + icl_dict = input_checklist.to_dict() + assert "parse_mode" not in icl_dict + + def test_equality(self, input_checklist): + a = input_checklist + b = InputChecklist( + title=f"other {self.title}", + tasks=[InputChecklistTask(id=1, text="eat"), InputChecklistTask(id=2, text="sleep")], + ) + c = InputChecklist( + title=self.title, + tasks=[InputChecklistTask(id=9, text="Other Task")], + ) + d = Dice(value=1, emoji="🎲") + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) diff --git a/tests/test_message.py b/tests/test_message.py index 805b877169a..bbdc8337688 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -37,10 +37,14 @@ Document, ExternalReplyInfo, Game, + Gift, + GiftInfo, Giveaway, GiveawayCompleted, GiveawayCreated, GiveawayWinners, + InputChecklist, + InputChecklistTask, InputPaidMediaPhoto, Invoice, LinkPreviewOptions, @@ -64,6 +68,12 @@ Story, SuccessfulPayment, TextQuote, + UniqueGift, + UniqueGiftBackdrop, + UniqueGiftBackdropColors, + UniqueGiftInfo, + UniqueGiftModel, + UniqueGiftSymbol, Update, User, UsersShared, @@ -77,15 +87,6 @@ Voice, WebAppData, ) -from telegram._gifts import Gift, GiftInfo -from telegram._uniquegift import ( - UniqueGift, - UniqueGiftBackdrop, - UniqueGiftBackdropColors, - UniqueGiftInfo, - UniqueGiftModel, - UniqueGiftSymbol, -) from telegram._utils.datetime import UTC from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import ODVInput @@ -2199,6 +2200,42 @@ async def make_assertion(*_, **kwargs): message, message.reply_dice, "send_dice", [], monkeypatch ) + async def test_reply_checklist(self, monkeypatch, message): + checklist = InputChecklist(title="My Checklist", tasks=[InputChecklistTask(1, "Task 1")]) + + async def make_assertion(*_, **kwargs): + return ( + kwargs["chat_id"] == message.chat_id + and kwargs["business_connection_id"] == message.business_connection_id + and kwargs["checklist"] == checklist + and kwargs["disable_notification"] is True + ) + + assert check_shortcut_signature( + Message.reply_checklist, + Bot.send_checklist, + ["chat_id", "business_connection_id", "reply_to_message_id"], + ["do_quote", "reply_to_message_id"], + ) + assert await check_shortcut_call( + message.reply_checklist, + message.get_bot(), + "send_checklist", + skip_params=["reply_to_message_id"], + shortcut_kwargs=["chat_id", "business_connection_id"], + ) + assert await check_defaults_handling(message.reply_checklist, message.get_bot()) + + monkeypatch.setattr(message.get_bot(), "send_checklist", make_assertion) + assert await message.reply_checklist(checklist, disable_notification=True) + await self.check_quote_parsing( + message, + message.reply_checklist, + "send_checklist", + [checklist, True], + monkeypatch, + ) + async def test_reply_action(self, monkeypatch, message: Message): async def make_assertion(*_, **kwargs): id_ = kwargs["chat_id"] == message.chat_id @@ -2534,6 +2571,34 @@ async def make_assertion(*_, **kwargs): monkeypatch.setattr(message.get_bot(), "edit_message_caption", make_assertion) assert await message.edit_caption(caption="new caption") + async def test_edit_checklist(self, monkeypatch, message): + checklist = InputChecklist(title="My Checklist", tasks=[InputChecklistTask(1, "Task 1")]) + + async def make_assertion(*_, **kwargs): + return ( + kwargs["business_connection_id"] == message.business_connection_id + and kwargs["chat_id"] == message.chat_id + and kwargs["message_id"] == message.message_id + and kwargs["checklist"] == checklist + ) + + assert check_shortcut_signature( + Message.edit_checklist, + Bot.edit_message_checklist, + ["chat_id", "message_id", "business_connection_id"], + [], + ) + assert await check_shortcut_call( + message.edit_checklist, + message.get_bot(), + "edit_message_checklist", + shortcut_kwargs=["chat_id", "message_id", "business_connection_id"], + ) + assert await check_defaults_handling(message.edit_checklist, message.get_bot()) + + monkeypatch.setattr(message.get_bot(), "edit_message_checklist", make_assertion) + assert await message.edit_checklist(checklist=checklist) + async def test_edit_media(self, monkeypatch, message): async def make_assertion(*_, **kwargs): chat_id = kwargs["chat_id"] == message.chat_id pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy