diff --git a/telegram/_bot.py b/telegram/_bot.py index b79df08ff17..94c4b1f0365 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -9193,6 +9193,7 @@ async def send_paid_media( 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, @@ -9214,6 +9215,11 @@ async def send_paid_media( to the media. 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 +9258,7 @@ async def send_paid_media( "star_count": star_count, "media": media, "show_caption_above_media": show_caption_above_media, + "payload": payload, } return await self._send_message( diff --git a/telegram/_chat.py b/telegram/_chat.py index 6eb78978596..8c5f705248e 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -3350,6 +3350,7 @@ async def send_paid_media( 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 @@ async def send_paid_media( pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + payload=payload, ) diff --git a/telegram/_chatboost.py b/telegram/_chatboost.py index 7b972eec6d8..c39537442e5 100644 --- a/telegram/_chatboost.py +++ b/telegram/_chatboost.py @@ -187,15 +187,22 @@ def __init__(self, user: User, *, api_kwargs: Optional[JSONDict] = None): 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 @@ def __init__( 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 diff --git a/telegram/_giveaway.py b/telegram/_giveaway.py index b287433fe0b..7c08fdcb26e 100644 --- a/telegram/_giveaway.py +++ b/telegram/_giveaway.py @@ -56,6 +56,10 @@ 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. @@ -75,6 +79,10 @@ 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. """ @@ -86,6 +94,7 @@ class Giveaway(TelegramObject): "only_new_members", "premium_subscription_month_count", "prize_description", + "prize_star_count", "winner_count", "winners_selection_date", ) @@ -100,6 +109,7 @@ def __init__( 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 +123,7 @@ def __init__( 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 +156,28 @@ def de_json( 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 +199,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 +224,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 +245,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 +266,7 @@ def __init__( 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 +283,7 @@ def __init__( 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 +332,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 +363,7 @@ def __init__( 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, diff --git a/telegram/_payment/stars.py b/telegram/_payment/stars.py index 94f621d00e8..2a3f1dca280 100644 --- a/telegram/_payment/stars.py +++ b/telegram/_payment/stars.py @@ -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 @@ def __init__( 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, diff --git a/telegram/constants.py b/telegram/constants.py index 0f179b1a34d..58129b5ab68 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -2795,6 +2795,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: diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index 76b17bad02a..3d91acf0d4f 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -4235,6 +4235,7 @@ async def send_paid_media( 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 @@ async def send_paid_media( 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( diff --git a/tests/test_chat.py b/tests/test_chat.py index a3dcd6aa17f..72041c47b5c 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -1299,6 +1299,7 @@ async def make_assertion(*_, **kwargs): 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 @@ async def make_assertion(*_, **kwargs): 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") diff --git a/tests/test_chatboost.py b/tests/test_chatboost.py index f0ef143618a..dc393dc6043 100644 --- a/tests/test_chatboost.py +++ b/tests/test_chatboost.py @@ -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, ) diff --git a/tests/test_giveaway.py b/tests/test_giveaway.py index 35945118363..ce5f87a5053 100644 --- a/tests/test_giveaway.py +++ b/tests/test_giveaway.py @@ -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 @@ def test_de_json(self, bot): "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, bot) @@ -89,6 +92,7 @@ def test_de_json(self, bot): 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, bot) is None @@ -102,6 +106,7 @@ def test_de_json_localization(self, tz_bot, bot, raw_bot): "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 @@ def test_to_dict(self, giveaway): 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 @@ def test_equality(self, giveaway): 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 @@ def test_de_json(self, bot): "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, bot) @@ -245,6 +279,7 @@ def test_de_json(self, bot): 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, bot) is None @@ -291,6 +326,7 @@ def test_to_dict(self, giveaway_winners): 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 @@ def test_de_json(self, bot): "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, bot) @@ -370,6 +409,7 @@ def test_de_json(self, bot): 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, bot) is None @@ -380,6 +420,7 @@ def test_to_dict(self, giveaway_completed): 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 @@ def test_equality(self, giveaway_completed): 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, diff --git a/tests/test_message.py b/tests/test_message.py index 84b2b7c3929..9f085d3fca6 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -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), diff --git a/tests/test_stars.py b/tests/test_stars.py index ef700ae392a..1a91a73cfc2 100644 --- a/tests/test_stars.py +++ b/tests/test_stars.py @@ -74,6 +74,7 @@ def transaction_partner_user(): ] ) ], + paid_media_payload="payload", )
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: