#!/usr/bin/env python # # A library that provides a Python interface to the Telegram Bot API # Copyright (C) 2015-2025 # Leandro Toledo de Souza # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser Public License for more details. # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an objects 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 their :attr:`id` is equal. .. versionadded:: 22.3 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:: 22.3 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:: 22.3 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:: 22.3 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)