Full Support for Bot API 9.1 (#4847)

Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
Co-authored-by: Abdelrahman Elkheir <90580077+aelkheir@users.noreply.github.com>
This commit is contained in:
Bibo-Joshi
2025-07-20 21:39:21 +02:00
committed by GitHub
parent e9dd490b2c
commit 1111d342d6
37 changed files with 2569 additions and 24 deletions
+2 -2
View File
@@ -11,7 +11,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-9.0-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-9.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API version
@@ -81,7 +81,7 @@ After installing_ the library, be sure to check out the section on `working with
Telegram API support
~~~~~~~~~~~~~~~~~~~~
All types and methods of the Telegram Bot API **9.0** are natively supported by this library.
All types and methods of the Telegram Bot API **9.1** are natively supported by this library.
In addition, Bot API functionality not yet natively included can still be used as described `in our wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Bot-API-Forward-Compatibility>`_.
Notable Features
@@ -0,0 +1,18 @@
highlights = "Full Support for Bot API 9.1"
features = """
New filters based on Bot API 9.1:
* ``filters.StatusUpdate.DIRECT_MESSAGE_PRICE_CHANGED`` for ``Message.direct_message_price_changed``
* ``filters.StatusUpdate.CHECKLIST_TASKS_ADDED`` for ``Message.checklist_tasks_added``
* ``filters.StatusUpdate.CHECKLIST_TASKS_DONE`` for ``Message.checklist_tasks_done``
* ``filters.CHECKLIST`` for ``Message.checklist``
"""
pull_requests = [
{ uid = "4847", author_uid = "Bibo-Joshi", closes_threads = ["4845"] },
{ uid = "4848", author_uid = "Bibo-Joshi" },
{ uid = "4849", author_uid = "harshil21" },
{ uid = "4851", author_uid = "harshil21" },
{ uid = "4857", author_uid = "aelkheir" },
]
+6
View File
@@ -390,6 +390,8 @@
- Used to generate an HTTP link for an invoice
* - :meth:`~telegram.Bot.edit_user_star_subscription`
- Used for editing a user's star subscription
* - :meth:`~telegram.Bot.get_my_star_balance`
- Used for obtaining the bot's Telegram Stars balance
* - :meth:`~telegram.Bot.get_star_transactions`
- Used for obtaining the bot's Telegram Stars transactions
* - :meth:`~telegram.Bot.refund_star_payment`
@@ -447,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
+7
View File
@@ -31,6 +31,10 @@ Available Types
telegram.chat
telegram.chatadministratorrights
telegram.chatbackground
telegram.checklist
telegram.checklisttask
telegram.checklisttasksadded
telegram.checklisttasksdone
telegram.copytextbutton
telegram.backgroundtype
telegram.backgroundtypefill
@@ -66,6 +70,7 @@ Available Types
telegram.chatshared
telegram.contact
telegram.dice
telegram.directmessagepricechanged
telegram.document
telegram.externalreplyinfo
telegram.file
@@ -85,6 +90,8 @@ Available Types
telegram.inaccessiblemessage
telegram.inlinekeyboardbutton
telegram.inlinekeyboardmarkup
telegram.inputchecklist
telegram.inputchecklisttask
telegram.inputfile
telegram.inputmedia
telegram.inputmediaanimation
+6
View File
@@ -0,0 +1,6 @@
Checklist
=========
.. autoclass:: telegram.Checklist
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
ChecklistTask
=============
.. autoclass:: telegram.ChecklistTask
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
ChecklistTasksAdded
===================
.. autoclass:: telegram.ChecklistTasksAdded
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
ChecklistTasksDone
==================
.. autoclass:: telegram.ChecklistTasksDone
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
DirectMessagePriceChanged
=========================
.. autoclass:: telegram.DirectMessagePriceChanged
:members:
:show-inheritance:
+6
View File
@@ -0,0 +1,6 @@
InputChecklist
==============
.. autoclass:: telegram.InputChecklist
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
InputChecklistTask
==================
.. autoclass:: telegram.InputChecklistTask
:members:
:show-inheritance:
+10
View File
@@ -82,12 +82,17 @@ __all__ = (
"ChatPermissions",
"ChatPhoto",
"ChatShared",
"Checklist",
"ChecklistTask",
"ChecklistTasksAdded",
"ChecklistTasksDone",
"ChosenInlineResult",
"Contact",
"CopyTextButton",
"Credentials",
"DataCredentials",
"Dice",
"DirectMessagePriceChanged",
"Document",
"EncryptedCredentials",
"EncryptedPassportElement",
@@ -138,6 +143,8 @@ __all__ = (
"InlineQueryResultVideo",
"InlineQueryResultVoice",
"InlineQueryResultsButton",
"InputChecklist",
"InputChecklistTask",
"InputContactMessageContent",
"InputFile",
"InputInvoiceMessageContent",
@@ -302,6 +309,7 @@ __all__ = (
"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 (
@@ -381,9 +389,11 @@ from ._chatmember import (
)
from ._chatmemberupdated import ChatMemberUpdated
from ._chatpermissions import ChatPermissions
from ._checklists import Checklist, ChecklistTask, ChecklistTasksAdded, ChecklistTasksDone
from ._choseninlineresult import ChosenInlineResult
from ._copytextbutton import CopyTextButton
from ._dice import Dice
from ._directmessagepricechanged import DirectMessagePriceChanged
from ._files._inputstorycontent import (
InputStoryContent,
InputStoryContentPhoto,
+173
View File
@@ -78,6 +78,7 @@ from telegram._games.gamehighscore import GameHighScore
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 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
)
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],
@@ -11072,6 +11209,36 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
api_kwargs=api_kwargs,
)
async def get_my_star_balance(
self,
*,
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,
) -> StarAmount:
"""A method to get the current Telegram Stars balance of the bot. Requires no parameters.
.. versionadded:: NEXT.VERSION
Returns:
:class:`telegram.StarAmount`
Raises:
:class:`telegram.error.TelegramError`
"""
return StarAmount.de_json(
await self._post(
"getMyStarBalance",
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
)
def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002
"""See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
@@ -11244,6 +11411,10 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
"""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
@@ -11386,3 +11557,5 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
"""Alias for :meth:`remove_chat_verification`"""
removeUserVerification = remove_user_verification
"""Alias for :meth:`remove_user_verification`"""
getMyStarBalance = get_my_star_balance
"""Alias for :meth:`get_my_star_balance`"""
+38
View File
@@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Final, Optional, Union
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 @@ class CallbackQuery(TelegramObject):
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,
+49
View File
@@ -53,6 +53,7 @@ if TYPE_CHECKING:
Document,
Gift,
InlineKeyboardMarkup,
InputChecklist,
InputMediaAudio,
InputMediaDocument,
InputMediaPhoto,
@@ -1471,6 +1472,54 @@ class _ChatBase(TelegramObject):
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,
+392
View File
@@ -0,0 +1,392 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# 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 related to Telegram checklists."""
import datetime as dtm
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._messageentity import MessageEntity
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.entities import parse_message_entities, parse_message_entity
from telegram._utils.types import JSONDict
from telegram.constants import ZERO_DATE
if TYPE_CHECKING:
from telegram import Bot, Message
class ChecklistTask(TelegramObject):
"""
Describes a task in a checklist.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if all their :attr:`id` is equal.
.. versionadded:: NEXT.VERSION
Args:
id (:obj:`int`): Unique identifier of the task.
text (:obj:`str`): Text of the task.
text_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special
entities that appear in the task text.
completed_by_user (:class:`telegram.User`, optional): User that completed the task; omitted
if the task wasn't completed
completion_date (:class:`datetime.datetime`, optional): Point in time when
the task was completed; :attr:`~telegram.constants.ZERO_DATE` if the task wasn't
completed
|datetime_localization|
Attributes:
id (:obj:`int`): Unique identifier of the task.
text (:obj:`str`): Text of the task.
text_entities (Tuple[:class:`telegram.MessageEntity`]): Optional. Special
entities that appear in the task text.
completed_by_user (:class:`telegram.User`): Optional. User that completed the task; omitted
if the task wasn't completed
completion_date (:class:`datetime.datetime`): Optional. Point in time when
the task was completed; :attr:`~telegram.constants.ZERO_DATE` if the task wasn't
completed
|datetime_localization|
"""
__slots__ = (
"completed_by_user",
"completion_date",
"id",
"text",
"text_entities",
)
def __init__(
self,
id: int, # pylint: disable=redefined-builtin
text: str,
text_entities: Optional[Sequence[MessageEntity]] = None,
completed_by_user: Optional[User] = None,
completion_date: Optional[dtm.datetime] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.id: int = id
self.text: str = text
self.text_entities: tuple[MessageEntity, ...] = parse_sequence_arg(text_entities)
self.completed_by_user: Optional[User] = completed_by_user
self.completion_date: Optional[dtm.datetime] = completion_date
self._id_attrs = (self.id,)
self._freeze()
@classmethod
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChecklistTask":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
if (date := data.get("completion_date")) == 0:
data["completion_date"] = ZERO_DATE
else:
data["completion_date"] = from_timestamp(date, tzinfo=loc_tzinfo)
data["completed_by_user"] = de_json_optional(data.get("completed_by_user"), User, bot)
data["text_entities"] = de_list_optional(data.get("text_entities"), MessageEntity, bot)
return super().de_json(data=data, bot=bot)
def parse_entity(self, entity: MessageEntity) -> str:
"""Returns the text in :attr:`text`
from a given :class:`telegram.MessageEntity` of :attr:`text_entities`.
Note:
This method is present because Telegram calculates the offset and length in
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
(That is, you can't just slice ``ChecklistTask.text`` with the offset and length.)
Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to :attr:`text_entities`.
Returns:
:obj:`str`: The text of the given entity.
"""
return parse_message_entity(self.text, entity)
def parse_entities(self, types: Optional[list[str]] = None) -> dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this checklist task filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
Note:
This method should always be used instead of the :attr:`text_entities`
attribute, since it calculates the correct substring from the message text based on
UTF-16 codepoints. See :attr:`parse_entity` for more info.
Args:
types (list[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
return parse_message_entities(self.text, self.text_entities, types)
class Checklist(TelegramObject):
"""
Describes a checklist.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if all their :attr:`tasks` are equal.
.. versionadded:: NEXT.VERSION
Args:
title (:obj:`str`): Title of the checklist.
title_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special
entities that appear in the checklist title.
tasks (Sequence[:class:`telegram.ChecklistTask`]): List of tasks in the checklist.
others_can_add_tasks (:obj:`bool`, optional): :obj:`True` if users other than the creator
of the list can add tasks to the list
others_can_mark_tasks_as_done (:obj:`bool`, optional): :obj:`True` if users other than the
creator of the list can mark tasks as done or not done
Attributes:
title (:obj:`str`): Title of the checklist.
title_entities (Tuple[:class:`telegram.MessageEntity`]): Optional. Special
entities that appear in the checklist title.
tasks (Tuple[:class:`telegram.ChecklistTask`]): List of tasks in the checklist.
others_can_add_tasks (:obj:`bool`): Optional. :obj:`True` if users other than the creator
of the list can add tasks to the list
others_can_mark_tasks_as_done (:obj:`bool`): Optional. :obj:`True` if users other than the
creator of the list can mark tasks as done or not done
"""
__slots__ = (
"others_can_add_tasks",
"others_can_mark_tasks_as_done",
"tasks",
"title",
"title_entities",
)
def __init__(
self,
title: str,
tasks: Sequence[ChecklistTask],
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.title_entities: tuple[MessageEntity, ...] = parse_sequence_arg(title_entities)
self.tasks: tuple[ChecklistTask, ...] = parse_sequence_arg(tasks)
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()
@classmethod
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "Checklist":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
data["title_entities"] = de_list_optional(data.get("title_entities"), MessageEntity, bot)
data["tasks"] = de_list_optional(data.get("tasks"), ChecklistTask, bot)
return super().de_json(data=data, bot=bot)
def parse_entity(self, entity: MessageEntity) -> str:
"""Returns the text in :attr:`title`
from a given :class:`telegram.MessageEntity` of :attr:`title_entities`.
Note:
This method is present because Telegram calculates the offset and length in
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
(That is, you can't just slice :attr:`title` with the offset and length.)
Args:
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
be an entity that belongs to :attr:`title_entities`.
Returns:
:obj:`str`: The text of the given entity.
"""
return parse_message_entity(self.title, entity)
def parse_entities(self, types: Optional[list[str]] = None) -> dict[MessageEntity, str]:
"""
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
It contains entities from this checklist's title filtered by their ``type`` attribute as
the key, and the text that each entity belongs to as the value of the :obj:`dict`.
Note:
This method should always be used instead of the :attr:`title_entities`
attribute, since it calculates the correct substring from the message text based on
UTF-16 codepoints. See :attr:`parse_entity` for more info.
Args:
types (list[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
``type`` attribute of an entity is contained in this list, it will be returned.
Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.
Returns:
dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
the text that belongs to them, calculated based on UTF-16 codepoints.
"""
return parse_message_entities(self.title, self.title_entities, types)
class ChecklistTasksDone(TelegramObject):
"""
Describes a service message about checklist tasks marked as done or not done.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their :attr:`marked_as_done_task_ids` and
:attr:`marked_as_not_done_task_ids` are equal.
.. versionadded:: NEXT.VERSION
Args:
checklist_message (:class:`telegram.Message`, optional): Message containing the checklist
whose tasks were marked as done or not done. Note that the ~:class:`telegram.Message`
object in this field will not contain the :attr:`~telegram.Message.reply_to_message`
field even if it itself is a reply.
marked_as_done_task_ids (Sequence[:obj:`int`], optional): Identifiers of the tasks that
were marked as done
marked_as_not_done_task_ids (Sequence[:obj:`int`], optional): Identifiers of the tasks that
were marked as not done
Attributes:
checklist_message (:class:`telegram.Message`): Optional. Message containing the checklist
whose tasks were marked as done or not done. Note that the ~:class:`telegram.Message`
object in this field will not contain the :attr:`~telegram.Message.reply_to_message`
field even if it itself is a reply.
marked_as_done_task_ids (Tuple[:obj:`int`]): Optional. Identifiers of the tasks that were
marked as done
marked_as_not_done_task_ids (Tuple[:obj:`int`]): Optional. Identifiers of the tasks that
were marked as not done
"""
__slots__ = (
"checklist_message",
"marked_as_done_task_ids",
"marked_as_not_done_task_ids",
)
def __init__(
self,
checklist_message: Optional["Message"] = None,
marked_as_done_task_ids: Optional[Sequence[int]] = None,
marked_as_not_done_task_ids: Optional[Sequence[int]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.checklist_message: Optional[Message] = checklist_message
self.marked_as_done_task_ids: tuple[int, ...] = parse_sequence_arg(marked_as_done_task_ids)
self.marked_as_not_done_task_ids: tuple[int, ...] = parse_sequence_arg(
marked_as_not_done_task_ids
)
self._id_attrs = (self.marked_as_done_task_ids, self.marked_as_not_done_task_ids)
self._freeze()
@classmethod
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChecklistTasksDone":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
# needs to be imported here to avoid circular import issues
from telegram import Message # pylint: disable=import-outside-toplevel # noqa: PLC0415
data["checklist_message"] = de_json_optional(data.get("checklist_message"), Message, bot)
return super().de_json(data=data, bot=bot)
class ChecklistTasksAdded(TelegramObject):
"""
Describes a service message about tasks added to a checklist.
Objects of this class are comparable in terms of equality.
Two objects of this class are considered equal, if their :attr:`tasks` are equal.
.. versionadded:: NEXT.VERSION
Args:
checklist_message (:class:`telegram.Message`, optional): Message containing the checklist
to which tasks were added. Note that the ~:class:`telegram.Message`
object in this field will not contain the :attr:`~telegram.Message.reply_to_message`
field even if it itself is a reply.
tasks (Sequence[:class:`telegram.ChecklistTask`]): List of tasks added to the checklist
Attributes:
checklist_message (:class:`telegram.Message`): Optional. Message containing the checklist
to which tasks were added. Note that the ~:class:`telegram.Message`
object in this field will not contain the :attr:`~telegram.Message.reply_to_message`
field even if it itself is a reply.
tasks (Tuple[:class:`telegram.ChecklistTask`]): List of tasks added to the checklist
"""
__slots__ = ("checklist_message", "tasks")
def __init__(
self,
tasks: Sequence[ChecklistTask],
checklist_message: Optional["Message"] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.checklist_message: Optional[Message] = checklist_message
self.tasks: tuple[ChecklistTask, ...] = parse_sequence_arg(tasks)
self._id_attrs = (self.tasks,)
self._freeze()
@classmethod
def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "ChecklistTasksAdded":
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
# needs to be imported here to avoid circular import issues
from telegram import Message # pylint: disable=import-outside-toplevel # noqa: PLC0415
data["checklist_message"] = de_json_optional(data.get("checklist_message"), Message, bot)
data["tasks"] = ChecklistTask.de_list(data.get("tasks", []), bot)
return super().de_json(data=data, bot=bot)
@@ -0,0 +1,73 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# 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 object that represents a Direct Message Price."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class DirectMessagePriceChanged(TelegramObject):
"""
Describes a service message about a change in the price of direct messages sent to a channel
chat.
.. versionadded:: NEXT.VERSION
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`are_direct_messages_enabled`, and
:attr:`direct_message_star_count` are equal.
Args:
are_direct_messages_enabled (:obj:`bool`):
:obj:`True`, if direct messages are enabled for the channel chat; :obj:`False`
otherwise.
direct_message_star_count (:obj:`int`, optional):
The new number of Telegram Stars that must be paid by users for each direct message
sent to the channel. Does not apply to users who have been exempted by administrators.
Defaults to ``0``.
Attributes:
are_direct_messages_enabled (:obj:`bool`):
:obj:`True`, if direct messages are enabled for the channel chat; :obj:`False`
otherwise.
direct_message_star_count (:obj:`int`):
Optional. The new number of Telegram Stars that must be paid by users for each direct
message sent to the channel. Does not apply to users who have been exempted by
administrators. Defaults to ``0``.
"""
__slots__ = ("are_direct_messages_enabled", "direct_message_star_count")
def __init__(
self,
are_direct_messages_enabled: bool,
direct_message_star_count: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.are_direct_messages_enabled: bool = are_direct_messages_enabled
self.direct_message_star_count: Optional[int] = direct_message_star_count
self._id_attrs = (self.are_direct_messages_enabled, self.direct_message_star_count)
self._freeze()
+186
View File
@@ -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 <devs@python-telegram-bot.org>
#
# 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()
+163
View File
@@ -28,7 +28,9 @@ from typing import TYPE_CHECKING, Optional, TypedDict, Union
from telegram._chat import Chat
from telegram._chatbackground import ChatBackground
from telegram._chatboost import ChatBoostAdded
from telegram._checklists import Checklist, ChecklistTasksAdded, ChecklistTasksDone
from telegram._dice import Dice
from telegram._directmessagepricechanged import DirectMessagePriceChanged
from telegram._files.animation import Animation
from telegram._files.audio import Audio
from telegram._files.contact import Contact
@@ -51,6 +53,7 @@ from telegram._forumtopic import (
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
@@ -524,6 +527,9 @@ class Message(MaybeInaccessibleMessage):
by a spoiler animation.
.. versionadded:: 20.0
checklist (:class:`telegram.Checklist`, optional): Message is a checklist
.. versionadded:: NEXT.VERSION
users_shared (:class:`telegram.UsersShared`, optional): Service message: users were shared
with the bot
@@ -601,6 +607,14 @@ class Message(MaybeInaccessibleMessage):
background set.
.. versionadded:: 21.2
checklist_tasks_done (:class:`telegram.ChecklistTasksDone`, optional): Service message:
some tasks in a checklist were marked as done or not done
.. versionadded:: NEXT.VERSION
checklist_tasks_added (:class:`telegram.ChecklistTasksAdded`, optional): Service message:
tasks were added to a checklist
.. versionadded:: NEXT.VERSION
paid_media (:class:`telegram.PaidMediaInfo`, optional): Message contains paid media;
information about the paid media.
@@ -609,6 +623,11 @@ class Message(MaybeInaccessibleMessage):
message about a refunded payment, information about the payment.
.. versionadded:: 21.4
direct_message_price_changed (:class:`telegram.DirectMessagePriceChanged`, optional):
Service message: the price for paid messages in the corresponding direct messages chat
of a channel has changed.
.. versionadded:: NEXT.VERSION
Attributes:
message_id (:obj:`int`): Unique message identifier inside this chat. In specific instances
@@ -868,6 +887,9 @@ class Message(MaybeInaccessibleMessage):
by a spoiler animation.
.. versionadded:: 20.0
checklist (:class:`telegram.Checklist`): Optional. Message is a checklist
.. versionadded:: NEXT.VERSION
users_shared (:class:`telegram.UsersShared`): Optional. Service message: users were shared
with the bot
@@ -946,6 +968,14 @@ class Message(MaybeInaccessibleMessage):
background set
.. versionadded:: 21.2
checklist_tasks_done (:class:`telegram.ChecklistTasksDone`): Optional. Service message:
some tasks in a checklist were marked as done or not done
.. versionadded:: NEXT.VERSION
checklist_tasks_added (:class:`telegram.ChecklistTasksAdded`): Optional. Service message:
tasks were added to a checklist
.. versionadded:: NEXT.VERSION
paid_media (:class:`telegram.PaidMediaInfo`): Optional. Message contains paid media;
information about the paid media.
@@ -954,6 +984,11 @@ class Message(MaybeInaccessibleMessage):
message about a refunded payment, information about the payment.
.. versionadded:: 21.4
direct_message_price_changed (:class:`telegram.DirectMessagePriceChanged`):
Optional. Service message: the price for paid messages in the corresponding direct
messages chat of a channel has changed.
.. versionadded:: NEXT.VERSION
.. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by
:attr:`~telegram.constants.ParseMode.MARKDOWN`, this method now raises a
@@ -983,10 +1018,14 @@ class Message(MaybeInaccessibleMessage):
"channel_chat_created",
"chat_background_set",
"chat_shared",
"checklist",
"checklist_tasks_added",
"checklist_tasks_done",
"connected_website",
"contact",
"delete_chat_photo",
"dice",
"direct_message_price_changed",
"document",
"edit_date",
"effect_id",
@@ -1152,6 +1191,10 @@ class Message(MaybeInaccessibleMessage):
unique_gift: Optional[UniqueGiftInfo] = None,
paid_message_price_changed: Optional[PaidMessagePriceChanged] = None,
paid_star_count: Optional[int] = None,
direct_message_price_changed: Optional[DirectMessagePriceChanged] = None,
checklist: Optional[Checklist] = None,
checklist_tasks_done: Optional[ChecklistTasksDone] = None,
checklist_tasks_added: Optional[ChecklistTasksAdded] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -1233,6 +1276,7 @@ class Message(MaybeInaccessibleMessage):
)
self.write_access_allowed: Optional[WriteAccessAllowed] = write_access_allowed
self.has_media_spoiler: Optional[bool] = has_media_spoiler
self.checklist: Optional[Checklist] = checklist
self.users_shared: Optional[UsersShared] = users_shared
self.chat_shared: Optional[ChatShared] = chat_shared
self.story: Optional[Story] = story
@@ -1251,6 +1295,8 @@ class Message(MaybeInaccessibleMessage):
self.sender_business_bot: Optional[User] = sender_business_bot
self.is_from_offline: Optional[bool] = is_from_offline
self.chat_background_set: Optional[ChatBackground] = chat_background_set
self.checklist_tasks_done: Optional[ChecklistTasksDone] = checklist_tasks_done
self.checklist_tasks_added: Optional[ChecklistTasksAdded] = checklist_tasks_added
self.effect_id: Optional[str] = effect_id
self.show_caption_above_media: Optional[bool] = show_caption_above_media
self.paid_media: Optional[PaidMediaInfo] = paid_media
@@ -1261,6 +1307,9 @@ class Message(MaybeInaccessibleMessage):
paid_message_price_changed
)
self.paid_star_count: Optional[int] = paid_star_count
self.direct_message_price_changed: Optional[DirectMessagePriceChanged] = (
direct_message_price_changed
)
self._effective_attachment = DEFAULT_NONE
@@ -1437,6 +1486,16 @@ class Message(MaybeInaccessibleMessage):
data["reply_to_story"] = de_json_optional(data.get("reply_to_story"), Story, bot)
data["boost_added"] = de_json_optional(data.get("boost_added"), ChatBoostAdded, bot)
data["sender_business_bot"] = de_json_optional(data.get("sender_business_bot"), User, bot)
data["direct_message_price_changed"] = de_json_optional(
data.get("direct_message_price_changed"), DirectMessagePriceChanged, bot
)
data["checklist"] = de_json_optional(data.get("checklist"), Checklist, bot)
data["checklist_tasks_done"] = de_json_optional(
data.get("checklist_tasks_done"), ChecklistTasksDone, bot
)
data["checklist_tasks_added"] = de_json_optional(
data.get("checklist_tasks_added"), ChecklistTasksAdded, bot
)
api_kwargs = {}
# This is a deprecated field that TG still returns for backwards compatibility
@@ -3250,6 +3309,63 @@ class Message(MaybeInaccessibleMessage):
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,
@@ -3859,6 +3975,53 @@ class Message(MaybeInaccessibleMessage):
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",
+16 -2
View File
@@ -347,13 +347,17 @@ class OwnedGiftUnique(OwnedGift):
bot; for gifts received on behalf of business accounts only.
sender_user (:class:`telegram.User`, optional): Sender of the gift if it is a known user.
send_date (:obj:`datetime.datetime`): Date the gift was sent as :class:`datetime.datetime`.
|datetime_localization|.
|datetime_localization|
is_saved (:obj:`bool`, optional): :obj:`True`, if the gift is displayed on the account's
profile page; for gifts received on behalf of business accounts only.
can_be_transferred (:obj:`bool`, optional): :obj:`True`, if the gift can be transferred to
another owner; for gifts received on behalf of business accounts only.
transfer_star_count (:obj:`int`, optional): Number of Telegram Stars that must be paid
to transfer the gift; omitted if the bot cannot transfer the gift.
next_transfer_date (:obj:`datetime.datetime`, optional): Date when the gift can be
transferred. If it's in the past, then the gift can be transferred now.
|datetime_localization|
.. versionadded:: NEXT.VERSION
Attributes:
type (:obj:`str`): Type of the owned gift, always :tg-const:`~telegram.OwnedGift.UNIQUE`.
@@ -362,19 +366,24 @@ class OwnedGiftUnique(OwnedGift):
bot; for gifts received on behalf of business accounts only.
sender_user (:class:`telegram.User`): Optional. Sender of the gift if it is a known user.
send_date (:obj:`datetime.datetime`): Date the gift was sent as :class:`datetime.datetime`.
|datetime_localization|.
|datetime_localization|
is_saved (:obj:`bool`): Optional. :obj:`True`, if the gift is displayed on the account's
profile page; for gifts received on behalf of business accounts only.
can_be_transferred (:obj:`bool`): Optional. :obj:`True`, if the gift can be transferred to
another owner; for gifts received on behalf of business accounts only.
transfer_star_count (:obj:`int`): Optional. Number of Telegram Stars that must be paid
to transfer the gift; omitted if the bot cannot transfer the gift.
next_transfer_date (:obj:`datetime.datetime`): Optional. Date when the gift can be
transferred. If it's in the past, then the gift can be transferred now.
|datetime_localization|
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
"can_be_transferred",
"gift",
"is_saved",
"next_transfer_date",
"owned_gift_id",
"send_date",
"sender_user",
@@ -390,6 +399,7 @@ class OwnedGiftUnique(OwnedGift):
is_saved: Optional[bool] = None,
can_be_transferred: Optional[bool] = None,
transfer_star_count: Optional[int] = None,
next_transfer_date: Optional[dtm.datetime] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
@@ -403,6 +413,7 @@ class OwnedGiftUnique(OwnedGift):
self.is_saved: Optional[bool] = is_saved
self.can_be_transferred: Optional[bool] = can_be_transferred
self.transfer_star_count: Optional[int] = transfer_star_count
self.next_transfer_date: Optional[dtm.datetime] = next_transfer_date
self._id_attrs = (self.type, self.gift, self.send_date)
@@ -415,5 +426,8 @@ class OwnedGiftUnique(OwnedGift):
data["send_date"] = from_timestamp(data.get("send_date"), tzinfo=loc_tzinfo)
data["sender_user"] = de_json_optional(data.get("sender_user"), User, bot)
data["gift"] = de_json_optional(data.get("gift"), UniqueGift, bot)
data["next_transfer_date"] = from_timestamp(
data.get("next_transfer_date"), tzinfo=loc_tzinfo
)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
+11
View File
@@ -21,6 +21,7 @@ from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional, Union
from telegram._chat import Chat
from telegram._checklists import Checklist
from telegram._dice import Dice
from telegram._files.animation import Animation
from telegram._files.audio import Audio
@@ -89,6 +90,9 @@ class ExternalReplyInfo(TelegramObject):
the file.
has_media_spoiler (:obj:`bool`, optional): :obj:`True`, if the message media is covered by
a spoiler animation.
checklist (:class:`telegram.Checklist`, optional): Message is a checklist
.. versionadded:: NEXT.VERSION
contact (:class:`telegram.Contact`, optional): Message is a shared contact, information
about the contact.
dice (:class:`telegram.Dice`, optional): Message is a dice with random value.
@@ -138,6 +142,9 @@ class ExternalReplyInfo(TelegramObject):
the file.
has_media_spoiler (:obj:`bool`): Optional. :obj:`True`, if the message media is covered by
a spoiler animation.
checklist (:class:`telegram.Checklist`): Optional. Message is a checklist
.. versionadded:: NEXT.VERSION
contact (:class:`telegram.Contact`): Optional. Message is a shared contact, information
about the contact.
dice (:class:`telegram.Dice`): Optional. Message is a dice with random value.
@@ -164,6 +171,7 @@ class ExternalReplyInfo(TelegramObject):
"animation",
"audio",
"chat",
"checklist",
"contact",
"dice",
"document",
@@ -213,6 +221,7 @@ class ExternalReplyInfo(TelegramObject):
poll: Optional[Poll] = None,
venue: Optional[Venue] = None,
paid_media: Optional[PaidMediaInfo] = None,
checklist: Optional[Checklist] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -232,6 +241,7 @@ class ExternalReplyInfo(TelegramObject):
self.video_note: Optional[VideoNote] = video_note
self.voice: Optional[Voice] = voice
self.has_media_spoiler: Optional[bool] = has_media_spoiler
self.checklist: Optional[Checklist] = checklist
self.contact: Optional[Contact] = contact
self.dice: Optional[Dice] = dice
self.game: Optional[Game] = game
@@ -278,6 +288,7 @@ class ExternalReplyInfo(TelegramObject):
data["poll"] = de_json_optional(data.get("poll"), Poll, bot)
data["venue"] = de_json_optional(data.get("venue"), Venue, bot)
data["paid_media"] = de_json_optional(data.get("paid_media"), PaidMediaInfo, bot)
data["checklist"] = de_json_optional(data.get("checklist"), Checklist, bot)
return super().de_json(data=data, bot=bot)
+46 -4
View File
@@ -18,6 +18,7 @@
# 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 classes related to unique gifs."""
import datetime as dtm
from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
@@ -25,6 +26,7 @@ from telegram._files.sticker import Sticker
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
@@ -340,31 +342,63 @@ class UniqueGiftInfo(TelegramObject):
Args:
gift (:class:`UniqueGift`): Information about the gift.
origin (:obj:`str`): Origin of the gift. Currently, either :attr:`UPGRADE`
or :attr:`TRANSFER`.
origin (:obj:`str`): Origin of the gift. Currently, either :attr:`UPGRADE` for gifts
upgraded from regular gifts, :attr:`TRANSFER` for gifts transferred from other users
or channels, or :attr:`RESALE` for gifts bought from other users.
.. versionchanged:: NEXT.VERSION
The :attr:`RESALE` origin was added.
owned_gift_id (:obj:`str`, optional) Unique identifier of the received gift for the
bot; only present for gifts received on behalf of business accounts.
transfer_star_count (:obj:`int`, optional): Number of Telegram Stars that must be paid
to transfer the gift; omitted if the bot cannot transfer the gift.
last_resale_star_count (:obj:`int`, optional): For gifts bought from other users, the price
paid for the gift.
.. versionadded:: NEXT.VERSION
next_transfer_date (:obj:`datetime.datetime`, optional): Date when the gift can be
transferred. If it's in the past, then the gift can be transferred now.
|datetime_localization|
.. versionadded:: NEXT.VERSION
Attributes:
gift (:class:`UniqueGift`): Information about the gift.
origin (:obj:`str`): Origin of the gift. Currently, either :attr:`UPGRADE`
or :attr:`TRANSFER`.
origin (:obj:`str`): Origin of the gift. Currently, either :attr:`UPGRADE` for gifts
upgraded from regular gifts, :attr:`TRANSFER` for gifts transferred from other users
or channels, or :attr:`RESALE` for gifts bought from other users.
.. versionchanged:: NEXT.VERSION
The :attr:`RESALE` origin was added.
owned_gift_id (:obj:`str`) Optional. Unique identifier of the received gift for the
bot; only present for gifts received on behalf of business accounts.
transfer_star_count (:obj:`int`): Optional. Number of Telegram Stars that must be paid
to transfer the gift; omitted if the bot cannot transfer the gift.
last_resale_star_count (:obj:`int`): Optional. For gifts bought from other users, the price
paid for the gift.
.. versionadded:: NEXT.VERSION
next_transfer_date (:obj:`datetime.datetime`): Optional. Date when the gift can be
transferred. If it's in the past, then the gift can be transferred now.
|datetime_localization|
.. versionadded:: NEXT.VERSION
"""
UPGRADE: Final[str] = constants.UniqueGiftInfoOrigin.UPGRADE
""":const:`telegram.constants.UniqueGiftInfoOrigin.UPGRADE`"""
TRANSFER: Final[str] = constants.UniqueGiftInfoOrigin.TRANSFER
""":const:`telegram.constants.UniqueGiftInfoOrigin.TRANSFER`"""
RESALE: Final[str] = constants.UniqueGiftInfoOrigin.RESALE
""":const:`telegram.constants.UniqueGiftInfoOrigin.RESALE`
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
"gift",
"last_resale_star_count",
"next_transfer_date",
"origin",
"owned_gift_id",
"transfer_star_count",
@@ -376,6 +410,8 @@ class UniqueGiftInfo(TelegramObject):
origin: str,
owned_gift_id: Optional[str] = None,
transfer_star_count: Optional[int] = None,
last_resale_star_count: Optional[int] = None,
next_transfer_date: Optional[dtm.datetime] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@@ -386,6 +422,8 @@ class UniqueGiftInfo(TelegramObject):
# Optional
self.owned_gift_id: Optional[str] = owned_gift_id
self.transfer_star_count: Optional[int] = transfer_star_count
self.last_resale_star_count: Optional[int] = last_resale_star_count
self.next_transfer_date: Optional[dtm.datetime] = next_transfer_date
self._id_attrs = (self.gift, self.origin)
@@ -396,6 +434,10 @@ class UniqueGiftInfo(TelegramObject):
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["gift"] = de_json_optional(data.get("gift"), UniqueGift, bot)
data["next_transfer_date"] = from_timestamp(
data.get("next_transfer_date"), tzinfo=loc_tzinfo
)
return super().de_json(data=data, bot=bot)
+73 -2
View File
@@ -77,6 +77,7 @@ __all__ = [
"InlineQueryResultLimit",
"InlineQueryResultType",
"InlineQueryResultsButtonLimit",
"InputChecklistLimit",
"InputMediaType",
"InputPaidMediaType",
"InputProfilePhotoType",
@@ -172,7 +173,7 @@ class _AccentColor(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=9, minor=0)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=9, minor=1)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@@ -188,6 +189,7 @@ SUPPORTED_WEBHOOK_PORTS: Final[list[int]] = [443, 80, 88, 8443]
#: :obj:`datetime.datetime`, value of unix 0.
#: This date literal is used in :class:`telegram.InaccessibleMessage`
# and :class:`telegram.ChecklistTask`.
#:
#: .. versionadded:: 20.8
ZERO_DATE: Final[dtm.datetime] = dtm.datetime(1970, 1, 1, tzinfo=UTC)
@@ -1411,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.
@@ -2058,6 +2101,21 @@ class MessageType(StringEnum):
.. versionadded:: 21.2
"""
CHECKLIST = "checklist"
""":obj:`str`: Messages with :attr:`telegram.Message.checklist`.
.. versionadded:: NEXT.VERSION
"""
CHECKLIST_TASKS_ADDED = "checklist_tasks_added"
""":obj:`str`: Messages with :attr:`telegram.Message.checklist_tasks_added`.
.. versionadded:: NEXT.VERSION
"""
CHECKLIST_TASKS_DONE = "checklist_tasks_done"
""":obj:`str`: Messages with :attr:`telegram.Message.checklist_tasks_done`.
.. versionadded:: NEXT.VERSION
"""
CONNECTED_WEBSITE = "connected_website"
""":obj:`str`: Messages with :attr:`telegram.Message.connected_website`."""
CONTACT = "contact"
@@ -2066,6 +2124,11 @@ class MessageType(StringEnum):
""":obj:`str`: Messages with :attr:`telegram.Message.delete_chat_photo`."""
DICE = "dice"
""":obj:`str`: Messages with :attr:`telegram.Message.dice`."""
DIRECT_MESSAGE_PRICE_CHANGED = "direct_message_price_changed"
""":obj:`str`: Messages with :attr:`telegram.Message.direct_message_price_changed`.
.. versionadded:: NEXT.VERSION
"""
DOCUMENT = "document"
""":obj:`str`: Messages with :attr:`telegram.Message.document`."""
EFFECT_ID = "effect_id"
@@ -3115,10 +3178,13 @@ class PollLimit(IntEnum):
to the :paramref:`~telegram.Bot.send_poll.options` parameter of
:meth:`telegram.Bot.send_poll`.
"""
MAX_OPTION_NUMBER = 10
MAX_OPTION_NUMBER = 12
""":obj:`int`: Maximum number of strings passed in a :obj:`list`
to the :paramref:`~telegram.Bot.send_poll.options` parameter of
:meth:`telegram.Bot.send_poll`.
.. versionchanged:: NEXT.VERSION
This value was changed from ``10`` to ``12`` in accordance to Bot API 9.1.
"""
MAX_EXPLANATION_LENGTH = 200
""":obj:`int`: Maximum number of characters in a :obj:`str` passed as the
@@ -3173,6 +3239,11 @@ class UniqueGiftInfoOrigin(StringEnum):
""":obj:`str` gift upgraded"""
TRANSFER = "transfer"
""":obj:`str` gift transfered"""
RESALE = "resale"
""":obj:`str` gift bought from other users
.. versionadded:: NEXT.VERSION
"""
class UpdateType(StringEnum):
+88
View File
@@ -62,6 +62,7 @@ from telegram import (
Gifts,
InlineKeyboardMarkup,
InlineQueryResultsButton,
InputChecklist,
InputMedia,
InputPaidMedia,
InputPollOption,
@@ -2639,6 +2640,72 @@ class ExtBot(Bot, Generic[RLARGS]):
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],
@@ -5057,6 +5124,24 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def get_my_star_balance(
self,
*,
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,
) -> StarAmount:
return await super().get_my_star_balance(
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),
)
# updated camelCase aliases
getMe = get_me
sendMessage = send_message
@@ -5139,6 +5224,8 @@ class ExtBot(Bot, Generic[RLARGS]):
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
@@ -5210,3 +5297,4 @@ class ExtBot(Bot, Generic[RLARGS]):
verifyUser = verify_user
removeChatVerification = remove_chat_verification
removeUserVerification = remove_user_verification
getMyStarBalance = get_my_star_balance
+56
View File
@@ -46,6 +46,7 @@ __all__ = (
"AUDIO",
"BOOST_ADDED",
"CAPTION",
"CHECKLIST",
"COMMAND",
"CONTACT",
"EFFECT_ID",
@@ -920,6 +921,20 @@ class ChatType: # A convenience namespace for Chat types.
"""Updates from supergroup."""
class _Checklist(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.checklist)
CHECKLIST = _Checklist(name="filters.CHECKLIST")
"""Messages that contain :attr:`telegram.Message.checklist`.
.. versionadded:: NEXT.VERSION
"""
class Command(MessageFilter):
"""
Messages with a :attr:`telegram.MessageEntity.BOT_COMMAND`. By default, only allows
@@ -1918,7 +1933,10 @@ class StatusUpdate:
StatusUpdate.CHAT_BACKGROUND_SET.check_update(update)
or StatusUpdate.CHAT_CREATED.check_update(update)
or StatusUpdate.CHAT_SHARED.check_update(update)
or StatusUpdate.CHECKLIST_TASKS_ADDED.check_update(update)
or StatusUpdate.CHECKLIST_TASKS_DONE.check_update(update)
or StatusUpdate.CONNECTED_WEBSITE.check_update(update)
or StatusUpdate.DIRECT_MESSAGE_PRICE_CHANGED.check_update(update)
or StatusUpdate.DELETE_CHAT_PHOTO.check_update(update)
or StatusUpdate.FORUM_TOPIC_CLOSED.check_update(update)
or StatusUpdate.FORUM_TOPIC_CREATED.check_update(update)
@@ -1988,6 +2006,30 @@ class StatusUpdate:
.. versionadded:: 20.1
"""
class _ChecklistTasksAdded(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.checklist_tasks_added)
CHECKLIST_TASKS_ADDED = _ChecklistTasksAdded(name="filters.StatusUpdate.CHECKLIST_TASKS_ADDED")
"""Messages that contain :attr:`telegram.Message.checklist_tasks_added`.
.. versionadded:: NEXT.VERSION
"""
class _ChecklistTasksDone(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.checklist_tasks_done)
CHECKLIST_TASKS_DONE = _ChecklistTasksDone(name="filters.StatusUpdate.CHECKLIST_TASKS_DONE")
"""Messages that contain :attr:`telegram.Message.checklist_tasks_done`.
.. versionadded:: NEXT.VERSION
"""
class _ConnectedWebsite(MessageFilter):
__slots__ = ()
@@ -1997,6 +2039,20 @@ class StatusUpdate:
CONNECTED_WEBSITE = _ConnectedWebsite(name="filters.StatusUpdate.CONNECTED_WEBSITE")
"""Messages that contain :attr:`telegram.Message.connected_website`."""
class _DirectMessagePriceChanged(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.direct_message_price_changed)
DIRECT_MESSAGE_PRICE_CHANGED = _DirectMessagePriceChanged(
name="filters.StatusUpdate.DIRECT_MESSAGE_PRICE_CHANGED"
)
"""Messages that contain :attr:`telegram.Message.direct_message_price_changed`.
.. versionadded:: NEXT.VERSION
"""
class _DeleteChatPhoto(MessageFilter):
__slots__ = ()
+23 -1
View File
@@ -1116,7 +1116,22 @@ class TestFilters:
assert filters.StatusUpdate.PAID_MESSAGE_PRICE_CHANGED.check_update(update)
update.message.paid_message_price_changed = None
def test_filters_forwarded(self, update, message_origin_user):
update.message.direct_message_price_changed = "direct_message_price_changed"
assert filters.StatusUpdate.ALL.check_update(update)
assert filters.StatusUpdate.DIRECT_MESSAGE_PRICE_CHANGED.check_update(update)
update.message.direct_message_price_changed = None
update.message.checklist_tasks_added = "checklist_tasks_added"
assert filters.StatusUpdate.ALL.check_update(update)
assert filters.StatusUpdate.CHECKLIST_TASKS_ADDED.check_update(update)
update.message.checklist_tasks_added = None
update.message.checklist_tasks_done = "checklist_tasks_done"
assert filters.StatusUpdate.ALL.check_update(update)
assert filters.StatusUpdate.CHECKLIST_TASKS_DONE.check_update(update)
update.message.checklist_tasks_done = None
def test_filters_forwarded(self, update):
assert filters.FORWARDED.check_update(update)
update.message.forward_origin = MessageOriginHiddenUser(dtm.datetime.utcnow(), 1)
assert filters.FORWARDED.check_update(update)
@@ -2792,3 +2807,10 @@ class TestFilters:
update.message.sender_boost_count = "test"
assert filters.SENDER_BOOST_COUNT.check_update(update)
assert str(filters.SENDER_BOOST_COUNT) == "filters.SENDER_BOOST_COUNT"
def test_filters_checklist(self, update):
assert not filters.CHECKLIST.check_update(update)
update.message.checklist = "test"
assert filters.CHECKLIST.check_update(update)
assert str(filters.CHECKLIST) == "filters.CHECKLIST"
+17
View File
@@ -79,6 +79,7 @@ from telegram import (
User,
WebAppInfo,
)
from telegram._payment.stars.staramount import StarAmount
from telegram._utils.datetime import UTC, from_timestamp, localize, to_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE
from telegram._utils.strings import to_camel_case
@@ -2574,6 +2575,17 @@ class TestBotWithoutRequest:
await offline_bot.remove_chat_verification(1234)
async def test_get_my_star_balance(self, offline_bot, monkeypatch):
sa = StarAmount(1000).to_json()
async def do_request(url, request_data: RequestData, *args, **kwargs):
assert not request_data.parameters
return 200, f'{{"ok": true, "result": {sa}}}'.encode()
monkeypatch.setattr(offline_bot.request, "do_request", do_request)
obj = await offline_bot.get_my_star_balance()
assert isinstance(obj, StarAmount)
class TestBotWithRequest:
"""
@@ -4540,3 +4552,8 @@ class TestBotWithRequest:
assert edited_link.name == "sub_name_2"
assert sub_link.subscription_period == 2592000
assert sub_link.subscription_price == 13
async def test_get_my_star_balance(self, bot):
balance = await bot.get_my_star_balance()
assert isinstance(balance, StarAmount)
assert balance.amount == 0
+151
View File
@@ -36,7 +36,12 @@ from telegram import (
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 @@ class TestBusinessMethodsWithoutRequest(BusinessMethodsTestBase):
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)
+48 -1
View File
@@ -21,7 +21,17 @@ import datetime as dtm
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 @@ class TestCallbackQueryWithoutRequest(CallbackQueryTestBase):
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"):
+26 -1
View File
@@ -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 @@ class TestChatWithoutRequest(ChatTestBase):
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"
+439
View File
@@ -0,0 +1,439 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# 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 datetime as dtm
import pytest
from telegram import (
Checklist,
ChecklistTask,
ChecklistTasksAdded,
ChecklistTasksDone,
Dice,
MessageEntity,
User,
)
from telegram._utils.datetime import UTC, to_timestamp
from telegram.constants import ZERO_DATE
from tests.auxil.build_messages import make_message
from tests.auxil.slots import mro_slots
class ChecklistTaskTestBase:
id = 42
text = "here is a text"
text_entities = [
MessageEntity(type="bold", offset=0, length=4),
MessageEntity(type="italic", offset=5, length=2),
]
completed_by_user = User(id=1, first_name="Test", last_name="User", is_bot=False)
completion_date = dtm.datetime.now(tz=UTC).replace(microsecond=0)
@pytest.fixture(scope="module")
def checklist_task():
return ChecklistTask(
id=ChecklistTaskTestBase.id,
text=ChecklistTaskTestBase.text,
text_entities=ChecklistTaskTestBase.text_entities,
completed_by_user=ChecklistTaskTestBase.completed_by_user,
completion_date=ChecklistTaskTestBase.completion_date,
)
class TestChecklistTaskWithoutRequest(ChecklistTaskTestBase):
def test_slot_behaviour(self, checklist_task):
for attr in checklist_task.__slots__:
assert getattr(checklist_task, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(checklist_task)) == len(
set(mro_slots(checklist_task))
), "duplicate slot"
def test_to_dict(self, checklist_task):
clt_dict = checklist_task.to_dict()
assert isinstance(clt_dict, dict)
assert clt_dict["id"] == self.id
assert clt_dict["text"] == self.text
assert clt_dict["text_entities"] == [entity.to_dict() for entity in self.text_entities]
assert clt_dict["completed_by_user"] == self.completed_by_user.to_dict()
assert clt_dict["completion_date"] == to_timestamp(self.completion_date)
def test_de_json(self, offline_bot):
json_dict = {
"id": self.id,
"text": self.text,
"text_entities": [entity.to_dict() for entity in self.text_entities],
"completed_by_user": self.completed_by_user.to_dict(),
"completion_date": to_timestamp(self.completion_date),
}
clt = ChecklistTask.de_json(json_dict, offline_bot)
assert isinstance(clt, ChecklistTask)
assert clt.id == self.id
assert clt.text == self.text
assert clt.text_entities == tuple(self.text_entities)
assert clt.completed_by_user == self.completed_by_user
assert clt.completion_date == self.completion_date
assert clt.api_kwargs == {}
def test_de_json_required_fields(self, offline_bot):
json_dict = {
"id": self.id,
"text": self.text,
}
clt = ChecklistTask.de_json(json_dict, offline_bot)
assert isinstance(clt, ChecklistTask)
assert clt.id == self.id
assert clt.text == self.text
assert clt.text_entities == ()
assert clt.completed_by_user is None
assert clt.completion_date is None
assert clt.api_kwargs == {}
def test_de_json_localization(self, offline_bot, raw_bot, tz_bot):
json_dict = {
"id": self.id,
"text": self.text,
"completion_date": to_timestamp(self.completion_date),
}
clt_bot = ChecklistTask.de_json(json_dict, offline_bot)
clt_bot_raw = ChecklistTask.de_json(json_dict, raw_bot)
clt_bot_tz = ChecklistTask.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing tzinfo objects is not reliable
completion_date_offset = clt_bot_tz.completion_date.utcoffset()
completion_date_offset_tz = tz_bot.defaults.tzinfo.utcoffset(
clt_bot_tz.completion_date.replace(tzinfo=None)
)
assert clt_bot.completion_date.tzinfo == UTC
assert clt_bot_raw.completion_date.tzinfo == UTC
assert completion_date_offset_tz == completion_date_offset
@pytest.mark.parametrize(
("completion_date", "expected"),
[
(None, None),
(0, ZERO_DATE),
(1735689600, dtm.datetime(2025, 1, 1, tzinfo=UTC)),
],
)
def test_de_json_completion_date(self, offline_bot, completion_date, expected):
json_dict = {
"id": self.id,
"text": self.text,
"completion_date": completion_date,
}
clt = ChecklistTask.de_json(json_dict, offline_bot)
assert isinstance(clt, ChecklistTask)
assert clt.completion_date == expected
def test_parse_entity(self, checklist_task):
assert checklist_task.parse_entity(checklist_task.text_entities[0]) == "here"
def test_parse_entities(self, checklist_task):
assert checklist_task.parse_entities(MessageEntity.BOLD) == {
checklist_task.text_entities[0]: "here"
}
assert checklist_task.parse_entities() == {
checklist_task.text_entities[0]: "here",
checklist_task.text_entities[1]: "is",
}
def test_equality(self, checklist_task):
clt1 = checklist_task
clt2 = ChecklistTask(
id=self.id,
text="other text",
)
clt3 = ChecklistTask(
id=self.id + 1,
text=self.text,
)
clt4 = Dice(value=1, emoji="🎲")
assert clt1 == clt2
assert hash(clt1) == hash(clt2)
assert clt1 != clt3
assert hash(clt1) != hash(clt3)
assert clt1 != clt4
assert hash(clt1) != hash(clt4)
class ChecklistTestBase:
title = "Checklist Title"
title_entities = [
MessageEntity(type="bold", offset=0, length=9),
MessageEntity(type="italic", offset=10, length=5),
]
tasks = [
ChecklistTask(
id=1,
text="Task 1",
),
ChecklistTask(
id=2,
text="Task 2",
),
]
others_can_add_tasks = True
others_can_mark_tasks_as_done = False
@pytest.fixture(scope="module")
def checklist():
return Checklist(
title=ChecklistTestBase.title,
title_entities=ChecklistTestBase.title_entities,
tasks=ChecklistTestBase.tasks,
others_can_add_tasks=ChecklistTestBase.others_can_add_tasks,
others_can_mark_tasks_as_done=ChecklistTestBase.others_can_mark_tasks_as_done,
)
class TestChecklistWithoutRequest(ChecklistTestBase):
def test_slot_behaviour(self, checklist):
for attr in checklist.__slots__:
assert getattr(checklist, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(checklist)) == len(set(mro_slots(checklist))), "duplicate slot"
def test_to_dict(self, checklist):
cl_dict = checklist.to_dict()
assert isinstance(cl_dict, dict)
assert cl_dict["title"] == self.title
assert cl_dict["title_entities"] == [entity.to_dict() for entity in self.title_entities]
assert cl_dict["tasks"] == [task.to_dict() for task in self.tasks]
assert cl_dict["others_can_add_tasks"] is self.others_can_add_tasks
assert cl_dict["others_can_mark_tasks_as_done"] is self.others_can_mark_tasks_as_done
def test_de_json(self, offline_bot):
json_dict = {
"title": self.title,
"title_entities": [entity.to_dict() for entity in self.title_entities],
"tasks": [task.to_dict() for task in self.tasks],
"others_can_add_tasks": self.others_can_add_tasks,
"others_can_mark_tasks_as_done": self.others_can_mark_tasks_as_done,
}
cl = Checklist.de_json(json_dict, offline_bot)
assert isinstance(cl, Checklist)
assert cl.title == self.title
assert cl.title_entities == tuple(self.title_entities)
assert cl.tasks == tuple(self.tasks)
assert cl.others_can_add_tasks is self.others_can_add_tasks
assert cl.others_can_mark_tasks_as_done is self.others_can_mark_tasks_as_done
assert cl.api_kwargs == {}
def test_de_json_required_fields(self, offline_bot):
json_dict = {
"title": self.title,
"tasks": [task.to_dict() for task in self.tasks],
}
cl = Checklist.de_json(json_dict, offline_bot)
assert isinstance(cl, Checklist)
assert cl.title == self.title
assert cl.title_entities == ()
assert cl.tasks == tuple(self.tasks)
assert not cl.others_can_add_tasks
assert not cl.others_can_mark_tasks_as_done
def test_parse_entity(self, checklist):
assert checklist.parse_entity(checklist.title_entities[0]) == "Checklist"
assert checklist.parse_entity(checklist.title_entities[1]) == "Title"
def test_parse_entities(self, checklist):
assert checklist.parse_entities(MessageEntity.BOLD) == {
checklist.title_entities[0]: "Checklist"
}
assert checklist.parse_entities() == {
checklist.title_entities[0]: "Checklist",
checklist.title_entities[1]: "Title",
}
def test_equality(self, checklist, checklist_task):
cl1 = checklist
cl2 = Checklist(
title=self.title + " other",
tasks=[ChecklistTask(id=1, text="something"), ChecklistTask(id=2, text="something")],
)
cl3 = Checklist(
title=self.title + " other",
tasks=[ChecklistTask(id=42, text="Task 2")],
)
cl4 = checklist_task
assert cl1 == cl2
assert hash(cl1) == hash(cl2)
assert cl1 != cl3
assert hash(cl1) != hash(cl3)
assert cl1 != cl4
assert hash(cl1) != hash(cl4)
class ChecklistTasksDoneTestBase:
checklist_message = make_message("Checklist message")
marked_as_done_task_ids = [1, 2, 3]
marked_as_not_done_task_ids = [4, 5]
@pytest.fixture(scope="module")
def checklist_tasks_done():
return ChecklistTasksDone(
checklist_message=ChecklistTasksDoneTestBase.checklist_message,
marked_as_done_task_ids=ChecklistTasksDoneTestBase.marked_as_done_task_ids,
marked_as_not_done_task_ids=ChecklistTasksDoneTestBase.marked_as_not_done_task_ids,
)
class TestChecklistTasksDoneWithoutRequest(ChecklistTasksDoneTestBase):
def test_slot_behaviour(self, checklist_tasks_done):
for attr in checklist_tasks_done.__slots__:
assert getattr(checklist_tasks_done, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(checklist_tasks_done)) == len(
set(mro_slots(checklist_tasks_done))
), "duplicate slot"
def test_to_dict(self, checklist_tasks_done):
cltd_dict = checklist_tasks_done.to_dict()
assert isinstance(cltd_dict, dict)
assert cltd_dict["checklist_message"] == self.checklist_message.to_dict()
assert cltd_dict["marked_as_done_task_ids"] == self.marked_as_done_task_ids
assert cltd_dict["marked_as_not_done_task_ids"] == self.marked_as_not_done_task_ids
def test_de_json(self, offline_bot):
json_dict = {
"checklist_message": self.checklist_message.to_dict(),
"marked_as_done_task_ids": self.marked_as_done_task_ids,
"marked_as_not_done_task_ids": self.marked_as_not_done_task_ids,
}
cltd = ChecklistTasksDone.de_json(json_dict, offline_bot)
assert isinstance(cltd, ChecklistTasksDone)
assert cltd.checklist_message == self.checklist_message
assert cltd.marked_as_done_task_ids == tuple(self.marked_as_done_task_ids)
assert cltd.marked_as_not_done_task_ids == tuple(self.marked_as_not_done_task_ids)
assert cltd.api_kwargs == {}
def test_de_json_required_fields(self, offline_bot):
cltd = ChecklistTasksDone.de_json({}, offline_bot)
assert isinstance(cltd, ChecklistTasksDone)
assert cltd.checklist_message is None
assert cltd.marked_as_done_task_ids == ()
assert cltd.marked_as_not_done_task_ids == ()
assert cltd.api_kwargs == {}
def test_equality(self, checklist_tasks_done):
cltd1 = checklist_tasks_done
cltd2 = ChecklistTasksDone(
checklist_message=None,
marked_as_done_task_ids=[1, 2, 3],
marked_as_not_done_task_ids=[4, 5],
)
cltd3 = ChecklistTasksDone(
checklist_message=make_message("Checklist message"),
marked_as_done_task_ids=[1, 2, 3],
)
cltd4 = make_message("Not a checklist tasks done")
assert cltd1 == cltd2
assert hash(cltd1) == hash(cltd2)
assert cltd1 != cltd3
assert hash(cltd1) != hash(cltd3)
assert cltd1 != cltd4
assert hash(cltd1) != hash(cltd4)
class ChecklistTasksAddedTestBase:
checklist_message = make_message("Checklist message")
tasks = [
ChecklistTask(id=1, text="Task 1"),
ChecklistTask(id=2, text="Task 2"),
ChecklistTask(id=3, text="Task 3"),
]
@pytest.fixture(scope="module")
def checklist_tasks_added():
return ChecklistTasksAdded(
checklist_message=ChecklistTasksAddedTestBase.checklist_message,
tasks=ChecklistTasksAddedTestBase.tasks,
)
class TestChecklistTasksAddedWithoutRequest(ChecklistTasksAddedTestBase):
def test_slot_behaviour(self, checklist_tasks_added):
for attr in checklist_tasks_added.__slots__:
assert getattr(checklist_tasks_added, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(checklist_tasks_added)) == len(
set(mro_slots(checklist_tasks_added))
), "duplicate slot"
def test_to_dict(self, checklist_tasks_added):
clta_dict = checklist_tasks_added.to_dict()
assert isinstance(clta_dict, dict)
assert clta_dict["checklist_message"] == self.checklist_message.to_dict()
assert clta_dict["tasks"] == [task.to_dict() for task in self.tasks]
def test_de_json(self, offline_bot):
json_dict = {
"checklist_message": self.checklist_message.to_dict(),
"tasks": [task.to_dict() for task in self.tasks],
}
clta = ChecklistTasksAdded.de_json(json_dict, offline_bot)
assert isinstance(clta, ChecklistTasksAdded)
assert clta.checklist_message == self.checklist_message
assert clta.tasks == tuple(self.tasks)
assert clta.api_kwargs == {}
def test_de_json_required_fields(self, offline_bot):
clta = ChecklistTasksAdded.de_json(
{"tasks": [task.to_dict() for task in self.tasks]}, offline_bot
)
assert isinstance(clta, ChecklistTasksAdded)
assert clta.checklist_message is None
assert clta.tasks == tuple(self.tasks)
assert clta.api_kwargs == {}
def test_equality(self, checklist_tasks_added):
clta1 = checklist_tasks_added
clta2 = ChecklistTasksAdded(
checklist_message=None,
tasks=[
ChecklistTask(id=1, text="Other Task 1"),
ChecklistTask(id=2, text="Other Task 2"),
ChecklistTask(id=3, text="Other Task 3"),
],
)
clta3 = ChecklistTasksAdded(
checklist_message=make_message("Checklist message"),
tasks=[ChecklistTask(id=1, text="Task 1")],
)
clta4 = make_message("Not a checklist tasks added")
assert clta1 == clta2
assert hash(clta1) == hash(clta2)
assert clta1 != clta3
assert hash(clta1) != hash(clta3)
assert clta1 != clta4
assert hash(clta1) != hash(clta4)
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2025
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# 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 object for testing a Direct Message Price."""
from typing import TYPE_CHECKING
import pytest
from telegram import DirectMessagePriceChanged, User
from tests.auxil.slots import mro_slots
if TYPE_CHECKING:
from telegram._utils.types import JSONDict
@pytest.fixture
def direct_message_price_changed():
return DirectMessagePriceChanged(
are_direct_messages_enabled=DirectMessagePriceChangedTestBase.are_direct_messages_enabled,
direct_message_star_count=DirectMessagePriceChangedTestBase.direct_message_star_count,
)
class DirectMessagePriceChangedTestBase:
are_direct_messages_enabled: bool = True
direct_message_star_count: int = 100
class TestDirectMessagePriceChangedWithoutRequest(DirectMessagePriceChangedTestBase):
def test_slot_behaviour(self, direct_message_price_changed):
action = direct_message_price_changed
for attr in action.__slots__:
assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"
def test_de_json(self, offline_bot):
json_dict: JSONDict = {
"are_direct_messages_enabled": self.are_direct_messages_enabled,
"direct_message_star_count": self.direct_message_star_count,
}
dmpc = DirectMessagePriceChanged.de_json(json_dict, offline_bot)
assert dmpc.api_kwargs == {}
assert dmpc.are_direct_messages_enabled == self.are_direct_messages_enabled
assert dmpc.direct_message_star_count == self.direct_message_star_count
def test_to_dict(self, direct_message_price_changed):
dmpc_dict = direct_message_price_changed.to_dict()
assert dmpc_dict["are_direct_messages_enabled"] == self.are_direct_messages_enabled
assert dmpc_dict["direct_message_star_count"] == self.direct_message_star_count
def test_equality(self, direct_message_price_changed):
dmpc1 = direct_message_price_changed
dmpc2 = DirectMessagePriceChanged(
are_direct_messages_enabled=self.are_direct_messages_enabled,
direct_message_star_count=self.direct_message_star_count,
)
assert dmpc1 == dmpc2
assert hash(dmpc1) == hash(dmpc2)
dmpc3 = DirectMessagePriceChanged(
are_direct_messages_enabled=False,
direct_message_star_count=self.direct_message_star_count,
)
assert dmpc1 != dmpc3
assert hash(dmpc1) != hash(dmpc3)
not_a_dmpc = User(id=1, first_name="wrong", is_bot=False)
assert dmpc1 != not_a_dmpc
assert hash(dmpc1) != hash(not_a_dmpc)
+167
View File
@@ -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 <devs@python-telegram-bot.org>
#
# 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)
+101 -9
View File
@@ -31,15 +31,24 @@ from telegram import (
ChatBackground,
ChatBoostAdded,
ChatShared,
Checklist,
ChecklistTask,
ChecklistTasksAdded,
ChecklistTasksDone,
Contact,
Dice,
DirectMessagePriceChanged,
Document,
ExternalReplyInfo,
Game,
Gift,
GiftInfo,
Giveaway,
GiveawayCompleted,
GiveawayCreated,
GiveawayWinners,
InputChecklist,
InputChecklistTask,
InputPaidMediaPhoto,
Invoice,
LinkPreviewOptions,
@@ -63,6 +72,12 @@ from telegram import (
Story,
SuccessfulPayment,
TextQuote,
UniqueGift,
UniqueGiftBackdrop,
UniqueGiftBackdropColors,
UniqueGiftInfo,
UniqueGiftModel,
UniqueGiftSymbol,
Update,
User,
UsersShared,
@@ -76,15 +91,6 @@ from telegram import (
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
@@ -331,6 +337,24 @@ def message(bot):
{"refunded_payment": RefundedPayment("EUR", 243, "payload", "charge_id", "provider_id")},
{"paid_star_count": 291},
{"paid_message_price_changed": PaidMessagePriceChanged(291)},
{"direct_message_price_changed": DirectMessagePriceChanged(True, 100)},
{
"checklist": Checklist(
"checklist_id",
tasks=[ChecklistTask(id=42, text="task 1"), ChecklistTask(id=43, text="task 2")],
)
},
{
"checklist_tasks_done": ChecklistTasksDone(
marked_as_done_task_ids=[1, 2, 3],
marked_as_not_done_task_ids=[4, 5],
)
},
{
"checklist_tasks_added": ChecklistTasksAdded(
tasks=[ChecklistTask(id=42, text="task 1"), ChecklistTask(id=43, text="task 2")],
)
},
],
ids=[
"reply",
@@ -408,6 +432,10 @@ def message(bot):
"refunded_payment",
"paid_star_count",
"paid_message_price_changed",
"direct_message_price_changed",
"checklist",
"checklist_tasks_done",
"checklist_tasks_added",
],
)
def message_params(bot, request):
@@ -2196,6 +2224,42 @@ class TestMessageWithoutRequest(MessageTestBase):
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
@@ -2531,6 +2595,34 @@ class TestMessageWithoutRequest(MessageTestBase):
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
+7 -2
View File
@@ -18,7 +18,6 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime as dtm
from collections.abc import Sequence
from copy import deepcopy
import pytest
@@ -96,6 +95,7 @@ class OwnedGiftTestBase:
prepaid_upgrade_star_count = 200
can_be_transferred = True
transfer_star_count = 300
next_transfer_date = dtm.datetime.now(tz=UTC).replace(microsecond=0)
class TestOwnedGiftWithoutRequest(OwnedGiftTestBase):
@@ -139,6 +139,7 @@ class TestOwnedGiftWithoutRequest(OwnedGiftTestBase):
"prepaid_upgrade_star_count": self.prepaid_upgrade_star_count,
"can_be_transferred": self.can_be_transferred,
"transfer_star_count": self.transfer_star_count,
"next_transfer_date": to_timestamp(self.next_transfer_date),
}
og = OwnedGift.de_json(json_dict, offline_bot)
@@ -292,6 +293,7 @@ def owned_gift_unique():
is_saved=TestOwnedGiftUniqueWithoutRequest.is_saved,
can_be_transferred=TestOwnedGiftUniqueWithoutRequest.can_be_transferred,
transfer_star_count=TestOwnedGiftUniqueWithoutRequest.transfer_star_count,
next_transfer_date=TestOwnedGiftUniqueWithoutRequest.next_transfer_date,
)
@@ -313,6 +315,7 @@ class TestOwnedGiftUniqueWithoutRequest(OwnedGiftTestBase):
"is_saved": self.is_saved,
"can_be_transferred": self.can_be_transferred,
"transfer_star_count": self.transfer_star_count,
"next_transfer_date": to_timestamp(self.next_transfer_date),
}
ogu = OwnedGiftUnique.de_json(json_dict, offline_bot)
assert ogu.gift == self.unique_gift
@@ -322,6 +325,7 @@ class TestOwnedGiftUniqueWithoutRequest(OwnedGiftTestBase):
assert ogu.is_saved == self.is_saved
assert ogu.can_be_transferred == self.can_be_transferred
assert ogu.transfer_star_count == self.transfer_star_count
assert ogu.next_transfer_date == self.next_transfer_date
assert ogu.api_kwargs == {}
def test_to_dict(self, owned_gift_unique):
@@ -335,6 +339,7 @@ class TestOwnedGiftUniqueWithoutRequest(OwnedGiftTestBase):
assert json_dict["is_saved"] == self.is_saved
assert json_dict["can_be_transferred"] == self.can_be_transferred
assert json_dict["transfer_star_count"] == self.transfer_star_count
assert json_dict["next_transfer_date"] == to_timestamp(self.next_transfer_date)
def test_equality(self, owned_gift_unique):
a = owned_gift_unique
@@ -365,7 +370,7 @@ def owned_gifts(request):
class OwnedGiftsTestBase:
total_count = 2
next_offset = "next_offset_str"
gifts: Sequence[OwnedGifts] = [
gifts: list[OwnedGift] = [
OwnedGiftRegular(
gift=Gift(
id="id1",
+13
View File
@@ -24,6 +24,8 @@ import pytest
from telegram import (
BotCommand,
Chat,
Checklist,
ChecklistTask,
ExternalReplyInfo,
Giveaway,
LinkPreviewOptions,
@@ -47,6 +49,7 @@ def external_reply_info():
link_preview_options=ExternalReplyInfoTestBase.link_preview_options,
giveaway=ExternalReplyInfoTestBase.giveaway,
paid_media=ExternalReplyInfoTestBase.paid_media,
checklist=ExternalReplyInfoTestBase.checklist,
)
@@ -63,6 +66,13 @@ class ExternalReplyInfoTestBase:
1,
)
paid_media = PaidMediaInfo(5, [PaidMediaPreview(10, 10, 10)])
checklist = Checklist(
title="Checklist Title",
tasks=[
ChecklistTask(text="Item 1", id=1),
ChecklistTask(text="Item 2", id=2),
],
)
class TestExternalReplyInfoWithoutRequest(ExternalReplyInfoTestBase):
@@ -81,6 +91,7 @@ class TestExternalReplyInfoWithoutRequest(ExternalReplyInfoTestBase):
"link_preview_options": self.link_preview_options.to_dict(),
"giveaway": self.giveaway.to_dict(),
"paid_media": self.paid_media.to_dict(),
"checklist": self.checklist.to_dict(),
}
external_reply_info = ExternalReplyInfo.de_json(json_dict, offline_bot)
@@ -92,6 +103,7 @@ class TestExternalReplyInfoWithoutRequest(ExternalReplyInfoTestBase):
assert external_reply_info.link_preview_options == self.link_preview_options
assert external_reply_info.giveaway == self.giveaway
assert external_reply_info.paid_media == self.paid_media
assert external_reply_info.checklist == self.checklist
def test_to_dict(self, external_reply_info):
ext_reply_info_dict = external_reply_info.to_dict()
@@ -103,6 +115,7 @@ class TestExternalReplyInfoWithoutRequest(ExternalReplyInfoTestBase):
assert ext_reply_info_dict["link_preview_options"] == self.link_preview_options.to_dict()
assert ext_reply_info_dict["giveaway"] == self.giveaway.to_dict()
assert ext_reply_info_dict["paid_media"] == self.paid_media.to_dict()
assert ext_reply_info_dict["checklist"] == self.checklist.to_dict()
def test_equality(self, external_reply_info):
a = external_reply_info
+42
View File
@@ -17,6 +17,8 @@
# 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 datetime as dtm
import pytest
from telegram import (
@@ -29,6 +31,8 @@ from telegram import (
UniqueGiftModel,
UniqueGiftSymbol,
)
from telegram._utils.datetime import UTC, to_timestamp
from telegram.constants import UniqueGiftInfoOrigin
from tests.auxil.slots import mro_slots
@@ -383,6 +387,8 @@ def unique_gift_info():
origin=UniqueGiftInfoTestBase.origin,
owned_gift_id=UniqueGiftInfoTestBase.owned_gift_id,
transfer_star_count=UniqueGiftInfoTestBase.transfer_star_count,
last_resale_star_count=UniqueGiftInfoTestBase.last_resale_star_count,
next_transfer_date=UniqueGiftInfoTestBase.next_transfer_date,
)
@@ -410,6 +416,8 @@ class UniqueGiftInfoTestBase:
origin = UniqueGiftInfo.UPGRADE
owned_gift_id = "some_id"
transfer_star_count = 10
last_resale_star_count = 5
next_transfer_date = dtm.datetime.now(tz=UTC).replace(microsecond=0)
class TestUniqueGiftInfoWithoutRequest(UniqueGiftInfoTestBase):
@@ -426,6 +434,8 @@ class TestUniqueGiftInfoWithoutRequest(UniqueGiftInfoTestBase):
"origin": self.origin,
"owned_gift_id": self.owned_gift_id,
"transfer_star_count": self.transfer_star_count,
"last_resale_star_count": self.last_resale_star_count,
"next_transfer_date": to_timestamp(self.next_transfer_date),
}
unique_gift_info = UniqueGiftInfo.de_json(json_dict, offline_bot)
assert unique_gift_info.api_kwargs == {}
@@ -433,6 +443,32 @@ class TestUniqueGiftInfoWithoutRequest(UniqueGiftInfoTestBase):
assert unique_gift_info.origin == self.origin
assert unique_gift_info.owned_gift_id == self.owned_gift_id
assert unique_gift_info.transfer_star_count == self.transfer_star_count
assert unique_gift_info.last_resale_star_count == self.last_resale_star_count
assert unique_gift_info.next_transfer_date == self.next_transfer_date
def test_de_json_localization(self, tz_bot, offline_bot, raw_bot):
json_dict = {
"gift": self.gift.to_dict(),
"origin": self.origin,
"owned_gift_id": self.owned_gift_id,
"transfer_star_count": self.transfer_star_count,
"last_resale_star_count": self.last_resale_star_count,
"next_transfer_date": to_timestamp(self.next_transfer_date),
}
unique_gift_info_raw = UniqueGiftInfo.de_json(json_dict, raw_bot)
unique_gift_info_offline = UniqueGiftInfo.de_json(json_dict, offline_bot)
unique_gift_info_tz = UniqueGiftInfo.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing timezones is unpredicatable
unique_gift_info_tz_offset = unique_gift_info_tz.next_transfer_date.utcoffset()
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(
unique_gift_info_tz.next_transfer_date.replace(tzinfo=None)
)
assert unique_gift_info_raw.next_transfer_date.tzinfo == UTC
assert unique_gift_info_offline.next_transfer_date.tzinfo == UTC
assert unique_gift_info_tz_offset == tz_bot_offset
def test_to_dict(self, unique_gift_info):
json_dict = unique_gift_info.to_dict()
@@ -440,6 +476,12 @@ class TestUniqueGiftInfoWithoutRequest(UniqueGiftInfoTestBase):
assert json_dict["origin"] == self.origin
assert json_dict["owned_gift_id"] == self.owned_gift_id
assert json_dict["transfer_star_count"] == self.transfer_star_count
assert json_dict["last_resale_star_count"] == self.last_resale_star_count
assert json_dict["next_transfer_date"] == to_timestamp(self.next_transfer_date)
def test_enum_type_conversion(self, unique_gift_info):
assert type(unique_gift_info.origin) is UniqueGiftInfoOrigin
assert unique_gift_info.origin == UniqueGiftInfoOrigin.UPGRADE
def test_equality(self, unique_gift_info):
a = unique_gift_info