#!/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 objects that represent owned gifts.""" import datetime as dtm from collections.abc import Sequence from typing import TYPE_CHECKING, Final, Optional from telegram import constants from telegram._gifts import Gift from telegram._messageentity import MessageEntity from telegram._telegramobject import TelegramObject from telegram._uniquegift import UniqueGift from telegram._user import User from telegram._utils import enum 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 if TYPE_CHECKING: from telegram import Bot class OwnedGift(TelegramObject): """This object describes a gift received and owned by a user or a chat. Currently, it can be one of: * :class:`telegram.OwnedGiftRegular` * :class:`telegram.OwnedGiftUnique` Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`type` is equal. .. versionadded:: 22.1 Args: type (:obj:`str`): Type of the owned gift. Attributes: type (:obj:`str`): Type of the owned gift. """ __slots__ = ("type",) REGULAR: Final[str] = constants.OwnedGiftType.REGULAR """:const:`telegram.constants.OwnedGiftType.REGULAR`""" UNIQUE: Final[str] = constants.OwnedGiftType.UNIQUE """:const:`telegram.constants.OwnedGiftType.UNIQUE`""" def __init__( self, type: str, # pylint: disable=redefined-builtin *, api_kwargs: Optional[JSONDict] = None, ) -> None: super().__init__(api_kwargs=api_kwargs) self.type: str = enum.get_member(constants.OwnedGiftType, type, type) self._id_attrs = (self.type,) self._freeze() @classmethod def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "OwnedGift": """Converts JSON data to the appropriate :class:`OwnedGift` object, i.e. takes care of selecting the correct subclass. Args: data (dict[:obj:`str`, ...]): The JSON data. bot (:class:`telegram.Bot`, optional): The bot associated with this object. Returns: The Telegram object. """ data = cls._parse_data(data) _class_mapping: dict[str, type[OwnedGift]] = { cls.REGULAR: OwnedGiftRegular, cls.UNIQUE: OwnedGiftUnique, } if cls is OwnedGift and data.get("type") in _class_mapping: return _class_mapping[data.pop("type")].de_json(data=data, bot=bot) return super().de_json(data=data, bot=bot) class OwnedGifts(TelegramObject): """Contains the list of gifts received and owned by a user or a chat. Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`total_count` and :attr:`gifts` are equal. .. versionadded:: 22.1 Args: total_count (:obj:`int`): The total number of gifts owned by the user or the chat. gifts (Sequence[:class:`telegram.OwnedGift`]): The list of gifts. next_offset (:obj:`str`, optional): Offset for the next request. If empty, then there are no more results. Attributes: total_count (:obj:`int`): The total number of gifts owned by the user or the chat. gifts (Sequence[:class:`telegram.OwnedGift`]): The list of gifts. next_offset (:obj:`str`): Optional. Offset for the next request. If empty, then there are no more results. """ __slots__ = ( "gifts", "next_offset", "total_count", ) def __init__( self, total_count: int, gifts: Sequence[OwnedGift], next_offset: Optional[str] = None, *, api_kwargs: Optional[JSONDict] = None, ): super().__init__(api_kwargs=api_kwargs) self.total_count: int = total_count self.gifts: tuple[OwnedGift, ...] = parse_sequence_arg(gifts) self.next_offset: Optional[str] = next_offset self._id_attrs = (self.total_count, self.gifts) self._freeze() @classmethod def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "OwnedGifts": """See :meth:`telegram.TelegramObject.de_json`.""" data = cls._parse_data(data) data["gifts"] = de_list_optional(data.get("gifts"), OwnedGift, bot) return super().de_json(data=data, bot=bot) class OwnedGiftRegular(OwnedGift): """Describes a regular gift owned by a user or a chat. Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`gift` and :attr:`send_date` are equal. .. versionadded:: 22.1 Args: gift (:class:`telegram.Gift`): Information about the regular gift. owned_gift_id (:obj:`str`, optional): Unique identifier of the gift for the 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|. text (:obj:`str`, optional): Text of the message that was added to the gift. entities (Sequence[:class:`telegram.MessageEntity`], optional): Special entities that appear in the text. is_private (:obj:`bool`, optional): :obj:`True`, if the sender and gift text are shown only to the gift receiver; otherwise, everyone will be able to see them. 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_upgraded (:obj:`bool`, optional): :obj:`True`, if the gift can be upgraded to a unique gift; for gifts received on behalf of business accounts only. was_refunded (:obj:`bool`, optional): :obj:`True`, if the gift was refunded and isn't available anymore. convert_star_count (:obj:`int`, optional): Number of Telegram Stars that can be claimed by the receiver instead of the gift; omitted if the gift cannot be converted to Telegram Stars. prepaid_upgrade_star_count (:obj:`int`, optional): Number of Telegram Stars that were paid by the sender for the ability to upgrade the gift. Attributes: type (:obj:`str`): Type of the gift, always :attr:`~telegram.OwnedGift.REGULAR`. gift (:class:`telegram.Gift`): Information about the regular gift. owned_gift_id (:obj:`str`): Optional. Unique identifier of the gift for the 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|. text (:obj:`str`): Optional. Text of the message that was added to the gift. entities (Sequence[:class:`telegram.MessageEntity`]): Optional. Special entities that appear in the text. is_private (:obj:`bool`): Optional. :obj:`True`, if the sender and gift text are shown only to the gift receiver; otherwise, everyone will be able to see them. 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_upgraded (:obj:`bool`): Optional. :obj:`True`, if the gift can be upgraded to a unique gift; for gifts received on behalf of business accounts only. was_refunded (:obj:`bool`): Optional. :obj:`True`, if the gift was refunded and isn't available anymore. convert_star_count (:obj:`int`): Optional. Number of Telegram Stars that can be claimed by the receiver instead of the gift; omitted if the gift cannot be converted to Telegram Stars. prepaid_upgrade_star_count (:obj:`int`): Optional. Number of Telegram Stars that were paid by the sender for the ability to upgrade the gift. """ __slots__ = ( "can_be_upgraded", "convert_star_count", "entities", "gift", "is_private", "is_saved", "owned_gift_id", "prepaid_upgrade_star_count", "send_date", "sender_user", "text", "was_refunded", ) def __init__( self, gift: Gift, send_date: dtm.datetime, owned_gift_id: Optional[str] = None, sender_user: Optional[User] = None, text: Optional[str] = None, entities: Optional[Sequence[MessageEntity]] = None, is_private: Optional[bool] = None, is_saved: Optional[bool] = None, can_be_upgraded: Optional[bool] = None, was_refunded: Optional[bool] = None, convert_star_count: Optional[int] = None, prepaid_upgrade_star_count: Optional[int] = None, *, api_kwargs: Optional[JSONDict] = None, ) -> None: super().__init__(type=OwnedGift.REGULAR, api_kwargs=api_kwargs) with self._unfrozen(): self.gift: Gift = gift self.send_date: dtm.datetime = send_date self.owned_gift_id: Optional[str] = owned_gift_id self.sender_user: Optional[User] = sender_user self.text: Optional[str] = text self.entities: tuple[MessageEntity, ...] = parse_sequence_arg(entities) self.is_private: Optional[bool] = is_private self.is_saved: Optional[bool] = is_saved self.can_be_upgraded: Optional[bool] = can_be_upgraded self.was_refunded: Optional[bool] = was_refunded self.convert_star_count: Optional[int] = convert_star_count self.prepaid_upgrade_star_count: Optional[int] = prepaid_upgrade_star_count self._id_attrs = (self.type, self.gift, self.send_date) @classmethod def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "OwnedGiftRegular": """See :meth:`telegram.OwnedGift.de_json`.""" data = cls._parse_data(data) loc_tzinfo = extract_tzinfo_from_defaults(bot) 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"), Gift, bot) data["entities"] = de_list_optional(data.get("entities"), MessageEntity, bot) return super().de_json(data=data, bot=bot) # type: ignore[return-value] def parse_entity(self, entity: MessageEntity) -> str: """Returns the text in :attr:`text` from a given :class:`telegram.MessageEntity` of :attr:`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 ``OwnedGiftRegular.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:`entities`. Returns: :obj:`str`: The text of the given entity. Raises: RuntimeError: If the owned gift has no text. """ if not self.text: raise RuntimeError("This OwnedGiftRegular has no 'text'.") 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 owned gift's text 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:`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. Raises: RuntimeError: If the owned gift has no text. """ if not self.text: raise RuntimeError("This OwnedGiftRegular has no 'text'.") return parse_message_entities(self.text, self.entities, types) class OwnedGiftUnique(OwnedGift): """ Describes a unique gift received and owned by a user or a chat. Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`gift` and :attr:`send_date` are equal. .. versionadded:: 22.1 Args: gift (:class:`telegram.UniqueGift`): Information about the unique gift. owned_gift_id (:obj:`str`, optional): Unique identifier of the received gift for the 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| 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:: 22.3 Attributes: type (:obj:`str`): Type of the owned gift, always :tg-const:`~telegram.OwnedGift.UNIQUE`. gift (:class:`telegram.UniqueGift`): Information about the unique gift. owned_gift_id (:obj:`str`): Optional. Unique identifier of the received gift for the 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| 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:: 22.3 """ __slots__ = ( "can_be_transferred", "gift", "is_saved", "next_transfer_date", "owned_gift_id", "send_date", "sender_user", "transfer_star_count", ) def __init__( self, gift: UniqueGift, send_date: dtm.datetime, owned_gift_id: Optional[str] = None, sender_user: Optional[User] = None, 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: super().__init__(type=OwnedGift.UNIQUE, api_kwargs=api_kwargs) with self._unfrozen(): self.gift: UniqueGift = gift self.send_date: dtm.datetime = send_date self.owned_gift_id: Optional[str] = owned_gift_id self.sender_user: Optional[User] = sender_user 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) @classmethod def de_json(cls, data: JSONDict, bot: Optional["Bot"] = None) -> "OwnedGiftUnique": """See :meth:`telegram.OwnedGift.de_json`.""" data = cls._parse_data(data) loc_tzinfo = extract_tzinfo_from_defaults(bot) 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]