Co-authored-by: aelkheir <90580077+aelkheir@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
This commit is contained in:
Harshil 2024-09-17 12:09:19 -04:00 committed by GitHub
parent 9248c539d0
commit 67a97ae5a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 668 additions and 81 deletions

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-7.9-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-7.10-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 **7.9** are natively supported by this library.
All types and methods of the Telegram Bot API **7.10** 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

View file

@ -120,6 +120,7 @@ Available Types
telegram.paidmediainfo
telegram.paidmediaphoto
telegram.paidmediapreview
telegram.paidmediapurchased
telegram.paidmediavideo
telegram.photosize
telegram.poll

View file

@ -18,6 +18,7 @@ Handlers
telegram.ext.inlinequeryhandler
telegram.ext.messagehandler
telegram.ext.messagereactionhandler
telegram.ext.paidmediapurchasedhandler
telegram.ext.pollanswerhandler
telegram.ext.pollhandler
telegram.ext.precheckoutqueryhandler

View file

@ -0,0 +1,6 @@
PaidMediaPurchasedHandler
=========================
.. autoclass:: telegram.ext.PaidMediaPurchasedHandler
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
PaidMediaPurchased
==================
.. autoclass:: telegram.PaidMediaPurchased
:members:
:show-inheritance:

View file

@ -180,6 +180,7 @@ __all__ = (
"PaidMediaInfo",
"PaidMediaPhoto",
"PaidMediaPreview",
"PaidMediaPurchased",
"PaidMediaVideo",
"PassportData",
"PassportElementError",
@ -419,7 +420,14 @@ from ._messageorigin import (
MessageOriginUser,
)
from ._messagereactionupdated import MessageReactionCountUpdated, MessageReactionUpdated
from ._paidmedia import PaidMedia, PaidMediaInfo, PaidMediaPhoto, PaidMediaPreview, PaidMediaVideo
from ._paidmedia import (
PaidMedia,
PaidMediaInfo,
PaidMediaPhoto,
PaidMediaPreview,
PaidMediaPurchased,
PaidMediaVideo,
)
from ._passport.credentials import (
Credentials,
DataCredentials,

View file

@ -9193,6 +9193,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
reply_parameters: Optional["ReplyParameters"] = None,
reply_markup: Optional[ReplyMarkup] = None,
business_connection_id: Optional[str] = None,
payload: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -9211,9 +9212,15 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
Telegram Star proceeds from this media will be credited to the chat's balance.
Otherwise, they will be credited to the bot's balance.
star_count (:obj:`int`): The number of Telegram Stars that must be paid to buy access
to the media.
to the media; :tg-const:`telegram.constants.InvoiceLimit.MIN_STAR_COUNT` -
:tg-const:`telegram.constants.InvoiceLimit.MAX_STAR_COUNT`.
media (Sequence[:class:`telegram.InputPaidMedia`]): A list describing the media to be
sent; up to :tg-const:`telegram.constants.MediaGroupLimit.MAX_MEDIA_LENGTH` items.
payload (:obj:`str`, optional): Bot-defined paid media payload,
0-:tg-const:`telegram.constants.InvoiceLimit.MAX_PAYLOAD_LENGTH` bytes. This will
not be displayed to the user, use it for your internal processes.
.. versionadded:: NEXT.VERSION
caption (:obj:`str`, optional): Caption of the media to be sent,
0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters.
parse_mode (:obj:`str`, optional): |parse_mode|
@ -9252,6 +9259,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"star_count": star_count,
"media": media,
"show_caption_above_media": show_caption_above_media,
"payload": payload,
}
return await self._send_message(

View file

@ -3350,6 +3350,7 @@ class _ChatBase(TelegramObject):
reply_parameters: Optional["ReplyParameters"] = None,
reply_markup: Optional[ReplyMarkup] = None,
business_connection_id: Optional[str] = None,
payload: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -3391,6 +3392,7 @@ class _ChatBase(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
payload=payload,
)

View file

@ -187,15 +187,22 @@ class ChatBoostSourceGiftCode(ChatBoostSource):
class ChatBoostSourceGiveaway(ChatBoostSource):
"""
The boost was obtained by the creation of a Telegram Premium giveaway. This boosts the chat 4
times for the duration of the corresponding Telegram Premium subscription.
The boost was obtained by the creation of a Telegram Premium giveaway or a Telegram Star.
This boosts the chat 4 times for the duration of the corresponding Telegram Premium
subscription for Telegram Premium giveaways and :attr:`prize_star_count` / 500 times for
one year for Telegram Star giveaways.
.. versionadded:: 20.8
Args:
giveaway_message_id (:obj:`int`): Identifier of a message in the chat with the giveaway;
the message could have been deleted already. May be 0 if the message isn't sent yet.
user (:class:`telegram.User`, optional): User that won the prize in the giveaway if any.
user (:class:`telegram.User`, optional): User that won the prize in the giveaway if any;
for Telegram Premium giveaways only.
prize_star_count (:obj:`int`, optional): The number of Telegram Stars to be split between
giveaway winners; for Telegram Star giveaways only.
.. versionadded:: NEXT.VERSION
is_unclaimed (:obj:`bool`, optional): :obj:`True`, if the giveaway was completed, but
there was no user to win the prize.
@ -205,17 +212,22 @@ class ChatBoostSourceGiveaway(ChatBoostSource):
giveaway_message_id (:obj:`int`): Identifier of a message in the chat with the giveaway;
the message could have been deleted already. May be 0 if the message isn't sent yet.
user (:class:`telegram.User`): Optional. User that won the prize in the giveaway if any.
prize_star_count (:obj:`int`): Optional. The number of Telegram Stars to be split between
giveaway winners; for Telegram Star giveaways only.
.. versionadded:: NEXT.VERSION
is_unclaimed (:obj:`bool`): Optional. :obj:`True`, if the giveaway was completed, but
there was no user to win the prize.
"""
__slots__ = ("giveaway_message_id", "is_unclaimed", "user")
__slots__ = ("giveaway_message_id", "is_unclaimed", "prize_star_count", "user")
def __init__(
self,
giveaway_message_id: int,
user: Optional[User] = None,
is_unclaimed: Optional[bool] = None,
prize_star_count: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -224,6 +236,7 @@ class ChatBoostSourceGiveaway(ChatBoostSource):
with self._unfrozen():
self.giveaway_message_id: int = giveaway_message_id
self.user: Optional[User] = user
self.prize_star_count: Optional[int] = prize_star_count
self.is_unclaimed: Optional[bool] = is_unclaimed

View file

@ -56,8 +56,13 @@ class Giveaway(TelegramObject):
country codes indicating the countries from which eligible users for the giveaway must
come. If empty, then all users can participate in the giveaway. Users with a phone
number that was bought on Fragment can always participate in giveaways.
prize_star_count (:obj:`int`, optional): The number of Telegram Stars to be split between
giveaway winners; for Telegram Star giveaways only.
.. versionadded:: NEXT.VERSION
premium_subscription_month_count (:obj:`int`, optional): The number of months the Telegram
Premium subscription won from the giveaway will be active for.
Premium subscription won from the giveaway will be active for; for Telegram Premium
giveaways only.
Attributes:
chats (Sequence[:class:`telegram.Chat`]): The list of chats which the user must join to
@ -75,8 +80,13 @@ class Giveaway(TelegramObject):
country codes indicating the countries from which eligible users for the giveaway must
come. If empty, then all users can participate in the giveaway. Users with a phone
number that was bought on Fragment can always participate in giveaways.
prize_star_count (:obj:`int`): Optional. The number of Telegram Stars to be split between
giveaway winners; for Telegram Star giveaways only.
.. versionadded:: NEXT.VERSION
premium_subscription_month_count (:obj:`int`): Optional. The number of months the Telegram
Premium subscription won from the giveaway will be active for.
Premium subscription won from the giveaway will be active for; for Telegram Premium
giveaways only.
"""
__slots__ = (
@ -86,6 +96,7 @@ class Giveaway(TelegramObject):
"only_new_members",
"premium_subscription_month_count",
"prize_description",
"prize_star_count",
"winner_count",
"winners_selection_date",
)
@ -100,6 +111,7 @@ class Giveaway(TelegramObject):
prize_description: Optional[str] = None,
country_codes: Optional[Sequence[str]] = None,
premium_subscription_month_count: Optional[int] = None,
prize_star_count: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -113,6 +125,7 @@ class Giveaway(TelegramObject):
self.prize_description: Optional[str] = prize_description
self.country_codes: Tuple[str, ...] = parse_sequence_arg(country_codes)
self.premium_subscription_month_count: Optional[int] = premium_subscription_month_count
self.prize_star_count: Optional[int] = prize_star_count
self._id_attrs = (
self.chats,
@ -145,13 +158,28 @@ class Giveaway(TelegramObject):
class GiveawayCreated(TelegramObject):
"""This object represents a service message about the creation of a scheduled giveaway.
Currently holds no information.
Args:
prize_star_count (:obj:`int`, optional): The number of Telegram Stars to be
split between giveaway winners; for Telegram Star giveaways only.
.. versionadded:: NEXT.VERSION
Attributes:
prize_star_count (:obj:`int`): Optional. The number of Telegram Stars to be
split between giveaway winners; for Telegram Star giveaways only.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
__slots__ = ("prize_star_count",)
def __init__(self, *, api_kwargs: Optional[JSONDict] = None):
def __init__(
self, prize_star_count: Optional[int] = None, *, api_kwargs: Optional[JSONDict] = None
):
super().__init__(api_kwargs=api_kwargs)
self.prize_star_count: Optional[int] = prize_star_count
self._freeze()
@ -173,6 +201,10 @@ class GiveawayWinners(TelegramObject):
winner_count (:obj:`int`): Total number of winners in the giveaway
winners (Sequence[:class:`telegram.User`]): List of up to
:tg-const:`telegram.constants.GiveawayLimit.MAX_WINNERS` winners of the giveaway
prize_star_count (:obj:`int`, optional): The number of Telegram Stars to be split between
giveaway winners; for Telegram Star giveaways only.
.. versionadded:: NEXT.VERSION
additional_chat_count (:obj:`int`, optional): The number of other chats the user had to
join in order to be eligible for the giveaway
premium_subscription_month_count (:obj:`int`, optional): The number of months the Telegram
@ -194,6 +226,10 @@ class GiveawayWinners(TelegramObject):
:tg-const:`telegram.constants.GiveawayLimit.MAX_WINNERS` winners of the giveaway
additional_chat_count (:obj:`int`): Optional. The number of other chats the user had to
join in order to be eligible for the giveaway
prize_star_count (:obj:`int`): Optional. The number of Telegram Stars to be split between
giveaway winners; for Telegram Star giveaways only.
.. versionadded:: NEXT.VERSION
premium_subscription_month_count (:obj:`int`): Optional. The number of months the Telegram
Premium subscription won from the giveaway will be active for
unclaimed_prize_count (:obj:`int`): Optional. Number of undistributed prizes
@ -211,6 +247,7 @@ class GiveawayWinners(TelegramObject):
"only_new_members",
"premium_subscription_month_count",
"prize_description",
"prize_star_count",
"unclaimed_prize_count",
"was_refunded",
"winner_count",
@ -231,6 +268,7 @@ class GiveawayWinners(TelegramObject):
only_new_members: Optional[bool] = None,
was_refunded: Optional[bool] = None,
prize_description: Optional[str] = None,
prize_star_count: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -247,6 +285,7 @@ class GiveawayWinners(TelegramObject):
self.only_new_members: Optional[bool] = only_new_members
self.was_refunded: Optional[bool] = was_refunded
self.prize_description: Optional[str] = prize_description
self.prize_star_count: Optional[int] = prize_star_count
self._id_attrs = (
self.chat,
@ -295,21 +334,29 @@ class GiveawayCompleted(TelegramObject):
unclaimed_prize_count (:obj:`int`, optional): Number of undistributed prizes
giveaway_message (:class:`telegram.Message`, optional): Message with the giveaway that was
completed, if it wasn't deleted
is_star_giveaway (:obj:`bool`, optional): :obj:`True`, if the giveaway is a Telegram Star
giveaway. Otherwise, currently, the giveaway is a Telegram Premium giveaway.
.. versionadded:: NEXT.VERSION
Attributes:
winner_count (:obj:`int`): Number of winners in the giveaway
unclaimed_prize_count (:obj:`int`): Optional. Number of undistributed prizes
giveaway_message (:class:`telegram.Message`): Optional. Message with the giveaway that was
completed, if it wasn't deleted
is_star_giveaway (:obj:`bool`): Optional. :obj:`True`, if the giveaway is a Telegram Star
giveaway. Otherwise, currently, the giveaway is a Telegram Premium giveaway.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("giveaway_message", "unclaimed_prize_count", "winner_count")
__slots__ = ("giveaway_message", "is_star_giveaway", "unclaimed_prize_count", "winner_count")
def __init__(
self,
winner_count: int,
unclaimed_prize_count: Optional[int] = None,
giveaway_message: Optional["Message"] = None,
is_star_giveaway: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -318,6 +365,7 @@ class GiveawayCompleted(TelegramObject):
self.winner_count: int = winner_count
self.unclaimed_prize_count: Optional[int] = unclaimed_prize_count
self.giveaway_message: Optional[Message] = giveaway_message
self.is_star_giveaway: Optional[bool] = is_star_giveaway
self._id_attrs = (
self.winner_count,

View file

@ -24,6 +24,7 @@ from telegram import constants
from telegram._files.photosize import PhotoSize
from telegram._files.video import Video
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
@ -288,3 +289,52 @@ class PaidMediaInfo(TelegramObject):
data["paid_media"] = PaidMedia.de_list(data.get("paid_media"), bot=bot)
return super().de_json(data=data, bot=bot)
class PaidMediaPurchased(TelegramObject):
"""This object contains information about a paid media purchase.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`from_user` and :attr:`paid_media_payload` are equal.
Note:
In Python :keyword:`from` is a reserved word. Use :paramref:`from_user` instead.
.. versionadded:: NEXT.VERSION
Args:
from_user (:class:`telegram.User`): User who purchased the media.
paid_media_payload (:obj:`str`): Bot-specified paid media payload.
Attributes:
from_user (:class:`telegram.User`): User who purchased the media.
paid_media_payload (:obj:`str`): Bot-specified paid media payload.
"""
__slots__ = ("from_user", "paid_media_payload")
def __init__(
self,
from_user: "User",
paid_media_payload: str,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.from_user: User = from_user
self.paid_media_payload: str = paid_media_payload
self._id_attrs = (self.from_user, self.paid_media_payload)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PaidMediaPurchased"]:
data = cls._parse_data(data)
if not data:
return None
data["from_user"] = User.de_json(data=data.pop("from"), bot=bot)
return super().de_json(data=data, bot=bot)

View file

@ -328,6 +328,9 @@ class TransactionPartnerUser(TransactionPartner):
media bought by the user.
.. versionadded:: 21.5
paid_media_payload (:obj:`str`, optional): Optional. Bot-specified paid media payload.
.. versionadded:: NEXT.VERSION
Attributes:
type (:obj:`str`): The type of the transaction partner,
@ -338,19 +341,20 @@ class TransactionPartnerUser(TransactionPartner):
media bought by the user.
.. versionadded:: 21.5
paid_media_payload (:obj:`str`): Optional. Optional. Bot-specified paid media payload.
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
"invoice_payload",
"paid_media",
"user",
)
__slots__ = ("invoice_payload", "paid_media", "paid_media_payload", "user")
def __init__(
self,
user: "User",
invoice_payload: Optional[str] = None,
paid_media: Optional[Sequence[PaidMedia]] = None,
paid_media_payload: Optional[str] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
@ -360,6 +364,7 @@ class TransactionPartnerUser(TransactionPartner):
self.user: User = user
self.invoice_payload: Optional[str] = invoice_payload
self.paid_media: Optional[Tuple[PaidMedia, ...]] = parse_sequence_arg(paid_media)
self.paid_media_payload: Optional[str] = paid_media_payload
self._id_attrs = (
self.type,
self.user,

View file

@ -30,6 +30,7 @@ from telegram._choseninlineresult import ChosenInlineResult
from telegram._inline.inlinequery import InlineQuery
from telegram._message import Message
from telegram._messagereactionupdated import MessageReactionCountUpdated, MessageReactionUpdated
from telegram._paidmedia import PaidMediaPurchased
from telegram._payment.precheckoutquery import PreCheckoutQuery
from telegram._payment.shippingquery import ShippingQuery
from telegram._poll import Poll, PollAnswer
@ -156,6 +157,11 @@ class Update(TelegramObject):
.. versionadded:: 21.1
purchased_paid_media (:class:`telegram.PaidMediaPurchased`, optional): A user purchased
paid media with a non-empty payload sent by the bot in a non-channel chat.
.. versionadded:: NEXT.VERSION
Attributes:
update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a
@ -263,6 +269,11 @@ class Update(TelegramObject):
were deleted from a connected business account.
.. versionadded:: 21.1
purchased_paid_media (:class:`telegram.PaidMediaPurchased`): Optional. A user purchased
paid media with a non-empty payload sent by the bot in a non-channel chat.
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
@ -290,6 +301,7 @@ class Update(TelegramObject):
"poll",
"poll_answer",
"pre_checkout_query",
"purchased_paid_media",
"removed_chat_boost",
"shipping_query",
"update_id",
@ -383,6 +395,13 @@ class Update(TelegramObject):
""":const:`telegram.constants.UpdateType.DELETED_BUSINESS_MESSAGES`
.. versionadded:: 21.1"""
PURCHASED_PAID_MEDIA: Final[str] = constants.UpdateType.PURCHASED_PAID_MEDIA
""":const:`telegram.constants.UpdateType.PURCHASED_PAID_MEDIA`
.. versionadded:: NEXT.VERSION
"""
ALL_TYPES: Final[List[str]] = list(constants.UpdateType)
"""List[:obj:`str`]: A list of all available update types.
@ -413,6 +432,7 @@ class Update(TelegramObject):
business_message: Optional[Message] = None,
edited_business_message: Optional[Message] = None,
deleted_business_messages: Optional[BusinessMessagesDeleted] = None,
purchased_paid_media: Optional[PaidMediaPurchased] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -444,6 +464,7 @@ class Update(TelegramObject):
self.deleted_business_messages: Optional[BusinessMessagesDeleted] = (
deleted_business_messages
)
self.purchased_paid_media: Optional[PaidMediaPurchased] = purchased_paid_media
self._effective_user: Optional[User] = None
self._effective_sender: Optional[Union[User, Chat]] = None
@ -475,6 +496,9 @@ class Update(TelegramObject):
This property now also considers :attr:`business_connection`, :attr:`business_message`
and :attr:`edited_business_message`.
.. versionchanged:: NEXT.VERSION
This property now also considers :attr:`purchased_paid_media`.
Example:
* If :attr:`message` is present, this will give
:attr:`telegram.Message.from_user`.
@ -531,6 +555,9 @@ class Update(TelegramObject):
elif self.business_connection:
user = self.business_connection.user
elif self.purchased_paid_media:
user = self.purchased_paid_media.from_user
self._effective_user = user
return user
@ -601,7 +628,8 @@ class Update(TelegramObject):
This is the case, if :attr:`inline_query`,
:attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
:attr:`shipping_query`, :attr:`pre_checkout_query`, :attr:`poll`,
:attr:`poll_answer`, or :attr:`business_connection` is present.
:attr:`poll_answer`, :attr:`business_connection`, or :attr:`purchased_paid_media`
is present.
.. versionchanged:: 21.1
This property now also considers :attr:`business_message`,
@ -768,5 +796,8 @@ class Update(TelegramObject):
data["deleted_business_messages"] = BusinessMessagesDeleted.de_json(
data.get("deleted_business_messages"), bot
)
data["purchased_paid_media"] = PaidMediaPurchased.de_json(
data.get("purchased_paid_media"), bot
)
return super().de_json(data=data, bot=bot)

View file

@ -152,7 +152,7 @@ class _AccentColor(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=9)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=10)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@ -552,6 +552,42 @@ class AccentColor(Enum):
"""
class BackgroundTypeType(StringEnum):
"""This enum contains the available types of :class:`telegram.BackgroundType`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: 21.2
"""
__slots__ = ()
FILL = "fill"
""":obj:`str`: A :class:`telegram.BackgroundType` with fill background."""
WALLPAPER = "wallpaper"
""":obj:`str`: A :class:`telegram.BackgroundType` with wallpaper background."""
PATTERN = "pattern"
""":obj:`str`: A :class:`telegram.BackgroundType` with pattern background."""
CHAT_THEME = "chat_theme"
""":obj:`str`: A :class:`telegram.BackgroundType` with chat_theme background."""
class BackgroundFillType(StringEnum):
"""This enum contains the available types of :class:`telegram.BackgroundFill`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: 21.2
"""
__slots__ = ()
SOLID = "solid"
""":obj:`str`: A :class:`telegram.BackgroundFill` with solid fill."""
GRADIENT = "gradient"
""":obj:`str`: A :class:`telegram.BackgroundFill` with gradient fill."""
FREEFORM_GRADIENT = "freeform_gradient"
""":obj:`str`: A :class:`telegram.BackgroundFill` with freeform_gradient fill."""
class BotCommandLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.BotCommand` and
:meth:`telegram.Bot.set_my_commands`.
@ -833,6 +869,25 @@ class ChatLimit(IntEnum):
"""
class ChatSubscriptionLimit(IntEnum):
"""This enum contains limitations for
:paramref:`telegram.Bot.create_chat_subscription_invite_link.subscription_period` and
:paramref:`telegram.Bot.create_chat_subscription_invite_link.subscription_price`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 21.5
"""
__slots__ = ()
SUBSCRIPTION_PERIOD = 2592000
""":obj:`int`: The number of seconds the subscription will be active."""
MIN_PRICE = 1
""":obj:`int`: Amount of stars a user pays, minimum amount the subscription can be set to."""
MAX_PRICE = 2500
""":obj:`int`: Amount of stars a user pays, maximum amount the subscription can be set to."""
class BackgroundTypeLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.BackgroundTypeFill`,
:class:`telegram.BackgroundTypeWallpaper` and :class:`telegram.BackgroundTypePattern`.
@ -2724,6 +2779,11 @@ class UpdateType(StringEnum):
.. versionadded:: 21.1
"""
PURCHASED_PAID_MEDIA = "purchased_paid_media"
""":obj:`str`: Updates with :attr:`telegram.Update.purchased_paid_media`.
.. versionadded:: NEXT.VERSION
"""
class InvoiceLimit(IntEnum):
@ -2795,6 +2855,8 @@ class InvoiceLimit(IntEnum):
:meth:`telegram.Bot.send_invoice`.
* :paramref:`~telegram.Bot.create_invoice_link.payload` parameter of
:meth:`telegram.Bot.create_invoice_link`.
* :paramref:`~telegram.Bot.send_paid_media.payload` parameter of
:meth:`telegram.Bot.send_paid_media`.
"""
MAX_TIP_AMOUNTS = 4
""":obj:`int`: Maximum length of a :obj:`Sequence` passed as:
@ -2804,6 +2866,20 @@ class InvoiceLimit(IntEnum):
* :paramref:`~telegram.Bot.create_invoice_link.suggested_tip_amounts` parameter of
:meth:`telegram.Bot.create_invoice_link`.
"""
MIN_STAR_COUNT = 1
""":obj:`int`: Minimum amount of starts that must be paid to buy access to a paid media
passed as :paramref:`~telegram.Bot.send_paid_media.star_count` parameter of
:meth:`telegram.Bot.send_paid_media`.
.. versionadded:: NEXT.VERSION
"""
MAX_STAR_COUNT = 2500
""":obj:`int`: Maximum amount of starts that must be paid to buy access to a paid media
passed as :paramref:`~telegram.Bot.send_paid_media.star_count` parameter of
:meth:`telegram.Bot.send_paid_media`.
.. versionadded:: NEXT.VERSION
"""
class UserProfilePhotosLimit(IntEnum):
@ -3066,58 +3142,3 @@ class ReactionEmoji(StringEnum):
""":obj:`str`: Woman Shrugging"""
POUTING_FACE = "😡"
""":obj:`str`: Pouting face"""
class BackgroundTypeType(StringEnum):
"""This enum contains the available types of :class:`telegram.BackgroundType`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: 21.2
"""
__slots__ = ()
FILL = "fill"
""":obj:`str`: A :class:`telegram.BackgroundType` with fill background."""
WALLPAPER = "wallpaper"
""":obj:`str`: A :class:`telegram.BackgroundType` with wallpaper background."""
PATTERN = "pattern"
""":obj:`str`: A :class:`telegram.BackgroundType` with pattern background."""
CHAT_THEME = "chat_theme"
""":obj:`str`: A :class:`telegram.BackgroundType` with chat_theme background."""
class BackgroundFillType(StringEnum):
"""This enum contains the available types of :class:`telegram.BackgroundFill`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: 21.2
"""
__slots__ = ()
SOLID = "solid"
""":obj:`str`: A :class:`telegram.BackgroundFill` with solid fill."""
GRADIENT = "gradient"
""":obj:`str`: A :class:`telegram.BackgroundFill` with gradient fill."""
FREEFORM_GRADIENT = "freeform_gradient"
""":obj:`str`: A :class:`telegram.BackgroundFill` with freeform_gradient fill."""
class ChatSubscriptionLimit(IntEnum):
"""This enum contains limitations for
:paramref:`telegram.Bot.create_chat_subscription_invite_link.subscription_period` and
:paramref:`telegram.Bot.create_chat_subscription_invite_link.subscription_price`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 21.5
"""
__slots__ = ()
SUBSCRIPTION_PERIOD = 2592000
""":obj:`int`: The number of seconds the subscription will be active."""
MIN_PRICE = 1
""":obj:`int`: Amount of stars a user pays, minimum amount the subscription can be set to."""
MAX_PRICE = 2500
""":obj:`int`: Amount of stars a user pays, maximum amount the subscription can be set to."""

View file

@ -48,6 +48,7 @@ __all__ = (
"JobQueue",
"MessageHandler",
"MessageReactionHandler",
"PaidMediaPurchasedHandler",
"PersistenceInput",
"PicklePersistence",
"PollAnswerHandler",
@ -89,6 +90,7 @@ from ._handlers.conversationhandler import ConversationHandler
from ._handlers.inlinequeryhandler import InlineQueryHandler
from ._handlers.messagehandler import MessageHandler
from ._handlers.messagereactionhandler import MessageReactionHandler
from ._handlers.paidmediapurchasedhandler import PaidMediaPurchasedHandler
from ._handlers.pollanswerhandler import PollAnswerHandler
from ._handlers.pollhandler import PollHandler
from ._handlers.precheckoutqueryhandler import PreCheckoutQueryHandler

View file

@ -4235,6 +4235,7 @@ class ExtBot(Bot, Generic[RLARGS]):
reply_parameters: Optional["ReplyParameters"] = None,
reply_markup: Optional[ReplyMarkup] = None,
business_connection_id: Optional[str] = None,
payload: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -4265,6 +4266,7 @@ class ExtBot(Bot, Generic[RLARGS]):
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
business_connection_id=business_connection_id,
payload=payload,
)
async def create_chat_subscription_invite_link(

View file

@ -0,0 +1,95 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 the PaidMediaPurchased class."""
from typing import Optional
from telegram import Update
from telegram._utils.defaultvalue import DEFAULT_TRUE
from telegram._utils.types import SCT, DVType
from telegram.ext._handlers.basehandler import BaseHandler
from telegram.ext._utils._update_parsing import parse_chat_id, parse_username
from telegram.ext._utils.types import CCT, RT, HandlerCallback
class PaidMediaPurchasedHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram
:attr:`purchased paid media <telegram.Update.purchased_paid_media>`.
.. versionadded:: NEXT.VERSION
Args:
callback (:term:`coroutine function`): The callback function for this handler. Will be
called when :meth:`check_update` has determined that an update should be processed by
this handler. Callback signature::
async def callback(update: Update, context: CallbackContext)
user_id (:obj:`int` | Collection[:obj:`int`], optional): Filters requests to allow only
those which are from the specified user ID(s).
username (:obj:`str` | Collection[:obj:`str`], optional): Filters requests to allow only
those which are from the specified username(s).
block (:obj:`bool`, optional): Determines whether the return value of the callback should
be awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`. Defaults to :obj:`True`.
.. seealso:: :wiki:`Concurrency`
Attributes:
callback (:term:`coroutine function`): The callback function for this handler.
block (:obj:`bool`): Determines whether the return value of the callback should be
awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`.
"""
__slots__ = (
"_user_ids",
"_usernames",
)
def __init__(
self: "PaidMediaPurchasedHandler[CCT, RT]",
callback: HandlerCallback[Update, CCT, RT],
user_id: Optional[SCT[int]] = None,
username: Optional[SCT[str]] = None,
block: DVType[bool] = DEFAULT_TRUE,
):
super().__init__(callback, block=block)
self._user_ids = parse_chat_id(user_id)
self._usernames = parse_username(username)
def check_update(self, update: object) -> bool:
"""Determines whether an update should be passed to this handler's :attr:`callback`.
Args:
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
Returns:
:obj:`bool`
"""
if not isinstance(update, Update) or not update.purchased_paid_media:
return False
if not self._user_ids and not self._usernames:
return True
if update.purchased_paid_media.from_user.id in self._user_ids:
return True
return update.purchased_paid_media.from_user.username in self._usernames

View file

@ -72,7 +72,7 @@ complete and correct. To run it, export an environment variable first:
$ export TEST_OFFICIAL=true
and then run ``pytest tests/test_official.py``. Note: You need py 3.10+ to run this test.
and then run ``pytest tests/test_official/test_official.py``. Note: You need py 3.10+ to run this test.
We also have another marker, ``@pytest.mark.dev``, which you can add to tests that you want to run selectively.
Use as follows:

View file

@ -0,0 +1,169 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# 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 asyncio
import datetime
import pytest
from telegram import (
Bot,
CallbackQuery,
Chat,
ChosenInlineResult,
Message,
PaidMediaPurchased,
PreCheckoutQuery,
ShippingQuery,
Update,
User,
)
from telegram._utils.datetime import UTC
from telegram.ext import CallbackContext, JobQueue, PaidMediaPurchasedHandler
from tests.auxil.slots import mro_slots
message = Message(1, None, Chat(1, ""), from_user=User(1, "", False), text="Text")
params = [
{"message": message},
{"edited_message": message},
{"callback_query": CallbackQuery(1, User(1, "", False), "chat", message=message)},
{"channel_post": message},
{"edited_channel_post": message},
{"chosen_inline_result": ChosenInlineResult("id", User(1, "", False), "")},
{"shipping_query": ShippingQuery("id", User(1, "", False), "", None)},
{"pre_checkout_query": PreCheckoutQuery("id", User(1, "", False), "", 0, "")},
{"callback_query": CallbackQuery(1, User(1, "", False), "chat")},
]
ids = (
"message",
"edited_message",
"callback_query",
"channel_post",
"edited_channel_post",
"chosen_inline_result",
"shipping_query",
"pre_checkout_query",
"callback_query_without_message",
)
@pytest.fixture(scope="class", params=params, ids=ids)
def false_update(request):
return Update(update_id=2, **request.param)
@pytest.fixture(scope="class")
def time():
return datetime.datetime.now(tz=UTC)
@pytest.fixture(scope="class")
def purchased_paid_media(bot):
bc = PaidMediaPurchased(
from_user=User(1, "name", username="user_a", is_bot=False),
paid_media_payload="payload",
)
bc.set_bot(bot)
return bc
@pytest.fixture
def purchased_paid_media_update(bot, purchased_paid_media):
return Update(0, purchased_paid_media=purchased_paid_media)
class TestPaidMediaPurchasedHandler:
test_flag = False
def test_slot_behaviour(self):
action = PaidMediaPurchasedHandler(self.callback)
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"
@pytest.fixture(autouse=True)
def _reset(self):
self.test_flag = False
async def callback(self, update, context):
self.test_flag = (
isinstance(context, CallbackContext)
and isinstance(context.bot, Bot)
and isinstance(update, Update)
and isinstance(context.update_queue, asyncio.Queue)
and isinstance(context.job_queue, JobQueue)
and isinstance(context.user_data, dict)
and isinstance(context.bot_data, dict)
and isinstance(
update.purchased_paid_media,
PaidMediaPurchased,
)
)
def test_with_user_id(self, purchased_paid_media_update):
handler = PaidMediaPurchasedHandler(self.callback, user_id=1)
assert handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, user_id=[1])
assert handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, user_id=2, username="@user_a")
assert handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, user_id=2)
assert not handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, user_id=[2])
assert not handler.check_update(purchased_paid_media_update)
def test_with_username(self, purchased_paid_media_update):
handler = PaidMediaPurchasedHandler(self.callback, username="user_a")
assert handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, username="@user_a")
assert handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, username=["user_a"])
assert handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, username=["@user_a"])
assert handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, user_id=1, username="@user_b")
assert handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, username="user_b")
assert not handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, username="@user_b")
assert not handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, username=["user_b"])
assert not handler.check_update(purchased_paid_media_update)
handler = PaidMediaPurchasedHandler(self.callback, username=["@user_b"])
assert not handler.check_update(purchased_paid_media_update)
purchased_paid_media_update.purchased_paid_media.from_user._unfreeze()
purchased_paid_media_update.purchased_paid_media.from_user.username = None
assert not handler.check_update(purchased_paid_media_update)
def test_other_update_types(self, false_update):
handler = PaidMediaPurchasedHandler(self.callback)
assert not handler.check_update(false_update)
assert not handler.check_update(True)
async def test_context(self, app, purchased_paid_media_update):
handler = PaidMediaPurchasedHandler(callback=self.callback)
app.add_handler(handler)
async with app:
await app.process_update(purchased_paid_media_update)
assert self.test_flag

View file

@ -1299,6 +1299,7 @@ class TestChatWithoutRequest(ChatTestBase):
and kwargs["media"] == "media"
and kwargs["star_count"] == 42
and kwargs["caption"] == "stars"
and kwargs["payload"] == "payload"
)
assert check_shortcut_signature(Chat.send_paid_media, Bot.send_paid_media, ["chat_id"], [])
@ -1306,7 +1307,9 @@ class TestChatWithoutRequest(ChatTestBase):
assert await check_defaults_handling(chat.send_paid_media, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "send_paid_media", make_assertion)
assert await chat.send_paid_media(media="media", star_count=42, caption="stars")
assert await chat.send_paid_media(
media="media", star_count=42, caption="stars", payload="payload"
)
def test_mention_html(self):
chat = Chat(id=1, type="foo")

View file

@ -50,6 +50,7 @@ class ChatBoostDefaults:
user = User(1, "user", False)
date = to_timestamp(datetime.datetime.utcnow())
default_source = ChatBoostSourcePremium(user)
prize_star_count = 99
@pytest.fixture(scope="module")
@ -91,6 +92,7 @@ def chat_boost_source_giveaway():
user=ChatBoostDefaults.user,
giveaway_message_id=ChatBoostDefaults.giveaway_message_id,
is_unclaimed=ChatBoostDefaults.is_unclaimed,
prize_star_count=ChatBoostDefaults.prize_star_count,
)

View file

@ -48,6 +48,7 @@ def giveaway():
premium_subscription_month_count=(
TestGiveawayWithoutRequest.premium_subscription_month_count
),
prize_star_count=TestGiveawayWithoutRequest.prize_star_count,
)
@ -60,6 +61,7 @@ class TestGiveawayWithoutRequest:
prize_description = "prize_description"
country_codes = ["DE", "US"]
premium_subscription_month_count = 3
prize_star_count = 99
def test_slot_behaviour(self, giveaway):
for attr in giveaway.__slots__:
@ -76,6 +78,7 @@ class TestGiveawayWithoutRequest:
"prize_description": self.prize_description,
"country_codes": self.country_codes,
"premium_subscription_month_count": self.premium_subscription_month_count,
"prize_star_count": self.prize_star_count,
}
giveaway = Giveaway.de_json(json_dict, offline_bot)
@ -89,6 +92,7 @@ class TestGiveawayWithoutRequest:
assert giveaway.prize_description == self.prize_description
assert giveaway.country_codes == tuple(self.country_codes)
assert giveaway.premium_subscription_month_count == self.premium_subscription_month_count
assert giveaway.prize_star_count == self.prize_star_count
assert Giveaway.de_json(None, offline_bot) is None
@ -102,6 +106,7 @@ class TestGiveawayWithoutRequest:
"prize_description": self.prize_description,
"country_codes": self.country_codes,
"premium_subscription_month_count": self.premium_subscription_month_count,
"prize_star_count": self.prize_star_count,
}
giveaway_raw = Giveaway.de_json(json_dict, raw_bot)
@ -133,6 +138,7 @@ class TestGiveawayWithoutRequest:
giveaway_dict["premium_subscription_month_count"]
== self.premium_subscription_month_count
)
assert giveaway_dict["prize_star_count"] == self.prize_star_count
def test_equality(self, giveaway):
a = giveaway
@ -164,15 +170,40 @@ class TestGiveawayWithoutRequest:
assert hash(a) != hash(e)
@pytest.fixture(scope="module")
def giveaway_created():
return GiveawayCreated(
prize_star_count=TestGiveawayCreatedWithoutRequest.prize_star_count,
)
class TestGiveawayCreatedWithoutRequest:
def test_slot_behaviour(self):
giveaway_created = GiveawayCreated()
prize_star_count = 99
def test_slot_behaviour(self, giveaway_created):
for attr in giveaway_created.__slots__:
assert getattr(giveaway_created, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(giveaway_created)) == len(
set(mro_slots(giveaway_created))
), "duplicate slot"
def test_de_json(self, bot):
json_dict = {
"prize_star_count": self.prize_star_count,
}
gac = GiveawayCreated.de_json(json_dict, bot)
assert gac.api_kwargs == {}
assert gac.prize_star_count == self.prize_star_count
assert Giveaway.de_json(None, bot) is None
def test_to_dict(self, giveaway_created):
gac_dict = giveaway_created.to_dict()
assert isinstance(gac_dict, dict)
assert gac_dict["prize_star_count"] == self.prize_star_count
@pytest.fixture(scope="module")
def giveaway_winners():
@ -190,6 +221,7 @@ def giveaway_winners():
additional_chat_count=TestGiveawayWinnersWithoutRequest.additional_chat_count,
unclaimed_prize_count=TestGiveawayWinnersWithoutRequest.unclaimed_prize_count,
was_refunded=TestGiveawayWinnersWithoutRequest.was_refunded,
prize_star_count=TestGiveawayWinnersWithoutRequest.prize_star_count,
)
@ -205,6 +237,7 @@ class TestGiveawayWinnersWithoutRequest:
only_new_members = True
was_refunded = True
prize_description = "prize_description"
prize_star_count = 99
def test_slot_behaviour(self, giveaway_winners):
for attr in giveaway_winners.__slots__:
@ -226,6 +259,7 @@ class TestGiveawayWinnersWithoutRequest:
"only_new_members": self.only_new_members,
"was_refunded": self.was_refunded,
"prize_description": self.prize_description,
"prize_star_count": self.prize_star_count,
}
giveaway_winners = GiveawayWinners.de_json(json_dict, offline_bot)
@ -245,6 +279,7 @@ class TestGiveawayWinnersWithoutRequest:
assert giveaway_winners.only_new_members == self.only_new_members
assert giveaway_winners.was_refunded == self.was_refunded
assert giveaway_winners.prize_description == self.prize_description
assert giveaway_winners.prize_star_count == self.prize_star_count
assert GiveawayWinners.de_json(None, offline_bot) is None
@ -291,6 +326,7 @@ class TestGiveawayWinnersWithoutRequest:
assert giveaway_winners_dict["only_new_members"] == self.only_new_members
assert giveaway_winners_dict["was_refunded"] == self.was_refunded
assert giveaway_winners_dict["prize_description"] == self.prize_description
assert giveaway_winners_dict["prize_star_count"] == self.prize_star_count
def test_equality(self, giveaway_winners):
a = giveaway_winners
@ -336,12 +372,14 @@ def giveaway_completed():
winner_count=TestGiveawayCompletedWithoutRequest.winner_count,
unclaimed_prize_count=TestGiveawayCompletedWithoutRequest.unclaimed_prize_count,
giveaway_message=TestGiveawayCompletedWithoutRequest.giveaway_message,
is_star_giveaway=TestGiveawayCompletedWithoutRequest.is_star_giveaway,
)
class TestGiveawayCompletedWithoutRequest:
winner_count = 42
unclaimed_prize_count = 4
is_star_giveaway = True
giveaway_message = Message(
message_id=1,
date=dtm.datetime.now(dtm.timezone.utc),
@ -362,6 +400,7 @@ class TestGiveawayCompletedWithoutRequest:
"winner_count": self.winner_count,
"unclaimed_prize_count": self.unclaimed_prize_count,
"giveaway_message": self.giveaway_message.to_dict(),
"is_star_giveaway": self.is_star_giveaway,
}
giveaway_completed = GiveawayCompleted.de_json(json_dict, offline_bot)
@ -370,6 +409,7 @@ class TestGiveawayCompletedWithoutRequest:
assert giveaway_completed.winner_count == self.winner_count
assert giveaway_completed.unclaimed_prize_count == self.unclaimed_prize_count
assert giveaway_completed.giveaway_message == self.giveaway_message
assert giveaway_completed.is_star_giveaway == self.is_star_giveaway
assert GiveawayCompleted.de_json(None, offline_bot) is None
@ -380,6 +420,7 @@ class TestGiveawayCompletedWithoutRequest:
assert giveaway_completed_dict["winner_count"] == self.winner_count
assert giveaway_completed_dict["unclaimed_prize_count"] == self.unclaimed_prize_count
assert giveaway_completed_dict["giveaway_message"] == self.giveaway_message.to_dict()
assert giveaway_completed_dict["is_star_giveaway"] == self.is_star_giveaway
def test_equality(self, giveaway_completed):
a = giveaway_completed
@ -387,6 +428,7 @@ class TestGiveawayCompletedWithoutRequest:
winner_count=self.winner_count,
unclaimed_prize_count=self.unclaimed_prize_count,
giveaway_message=self.giveaway_message,
is_star_giveaway=self.is_star_giveaway,
)
c = GiveawayCompleted(
winner_count=self.winner_count + 30,

View file

@ -236,7 +236,7 @@ def message(bot):
winner_count=5,
)
},
{"giveaway_created": GiveawayCreated()},
{"giveaway_created": GiveawayCreated(prize_star_count=99)},
{
"giveaway_winners": GiveawayWinners(
chat=Chat(1, Chat.CHANNEL),

View file

@ -27,8 +27,10 @@ from telegram import (
PaidMediaInfo,
PaidMediaPhoto,
PaidMediaPreview,
PaidMediaPurchased,
PaidMediaVideo,
PhotoSize,
User,
Video,
)
from telegram.constants import PaidMediaType
@ -122,6 +124,14 @@ def paid_media_info():
)
@pytest.fixture(scope="module")
def paid_media_purchased():
return PaidMediaPurchased(
from_user=PaidMediaPurchasedTestBase.from_user,
paid_media_payload=PaidMediaPurchasedTestBase.paid_media_payload,
)
class PaidMediaTestBase:
width = 640
height = 480
@ -323,3 +333,54 @@ class TestPaidMediaInfoWithoutRequest(PaidMediaInfoTestBase):
assert pmi1 != pmi3
assert hash(pmi1) != hash(pmi3)
class PaidMediaPurchasedTestBase:
from_user = User(1, "user", False)
paid_media_payload = "payload"
class TestPaidMediaPurchasedWithoutRequest(PaidMediaPurchasedTestBase):
def test_slot_behaviour(self, paid_media_purchased):
inst = paid_media_purchased
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
def test_de_json(self, bot):
json_dict = {
"from": self.from_user.to_dict(),
"paid_media_payload": self.paid_media_payload,
}
pmp = PaidMediaPurchased.de_json(json_dict, bot)
pmp_none = PaidMediaPurchased.de_json(None, bot)
assert pmp.from_user == self.from_user
assert pmp.paid_media_payload == self.paid_media_payload
assert pmp.api_kwargs == {}
assert pmp_none is None
def test_to_dict(self, paid_media_purchased):
assert paid_media_purchased.to_dict() == {
"from": self.from_user.to_dict(),
"paid_media_payload": self.paid_media_payload,
}
def test_equality(self):
pmp1 = PaidMediaPurchased(
from_user=self.from_user,
paid_media_payload=self.paid_media_payload,
)
pmp2 = PaidMediaPurchased(
from_user=self.from_user,
paid_media_payload=self.paid_media_payload,
)
pmp3 = PaidMediaPurchased(
from_user=User(2, "user", False),
paid_media_payload="other",
)
assert pmp1 == pmp2
assert hash(pmp1) == hash(pmp2)
assert pmp1 != pmp3
assert hash(pmp1) != hash(pmp3)

View file

@ -74,6 +74,7 @@ def transaction_partner_user():
]
)
],
paid_media_payload="payload",
)

View file

@ -40,6 +40,7 @@ from telegram import (
Message,
MessageReactionCountUpdated,
MessageReactionUpdated,
PaidMediaPurchased,
Poll,
PollAnswer,
PollOption,
@ -143,6 +144,11 @@ business_message = Message(
User(1, "", False),
)
purchased_paid_media = PaidMediaPurchased(
from_user=User(1, "", False),
paid_media_payload="payload",
)
params = [
{"message": message},
@ -178,6 +184,7 @@ params = [
{"deleted_business_messages": deleted_business_messages},
{"business_message": business_message},
{"edited_business_message": business_message},
{"purchased_paid_media": purchased_paid_media},
# Must be last to conform with `ids` below!
{"callback_query": CallbackQuery(1, User(1, "", False), "chat")},
]
@ -205,6 +212,7 @@ all_types = (
"deleted_business_messages",
"business_message",
"edited_business_message",
"purchased_paid_media",
)
ids = (*all_types, "callback_query_without_message")
@ -290,6 +298,7 @@ class TestUpdateWithoutRequest(UpdateTestBase):
or update.poll is not None
or update.poll_answer is not None
or update.business_connection is not None
or update.purchased_paid_media is not None
):
assert chat.id == 1
else:
@ -403,6 +412,7 @@ class TestUpdateWithoutRequest(UpdateTestBase):
or update.message_reaction_count is not None
or update.deleted_business_messages is not None
or update.business_connection is not None
or update.purchased_paid_media is not None
):
assert eff_message.message_id == message.message_id
else: