Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
This commit is contained in:
Harshil 2024-07-07 07:08:52 -04:00 committed by GitHub
parent 98bed6f01a
commit dba7866aab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 1470 additions and 120 deletions

View file

@ -11,7 +11,7 @@
:target: https://pypi.org/project/python-telegram-bot/ :target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions :alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-7.5-blue?logo=telegram .. image:: https://img.shields.io/badge/Bot%20API-7.6-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog :target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API version :alt: Supported Bot API version
@ -79,7 +79,7 @@ make the development of bots easy and straightforward. These classes are contain
Telegram API support Telegram API support
==================== ====================
All types and methods of the Telegram Bot API **7.5** are supported. All types and methods of the Telegram Bot API **7.6** are supported.
Installing Installing
========== ==========

View file

@ -33,6 +33,8 @@
- Used for sending media grouped together - Used for sending media grouped together
* - :meth:`~telegram.Bot.send_message` * - :meth:`~telegram.Bot.send_message`
- Used for sending text messages - Used for sending text messages
* - :meth:`~telegram.Bot.send_paid_media`
- Used for sending paid media to channels
* - :meth:`~telegram.Bot.send_photo` * - :meth:`~telegram.Bot.send_photo`
- Used for sending photos - Used for sending photos
* - :meth:`~telegram.Bot.send_poll` * - :meth:`~telegram.Bot.send_poll`

View file

@ -88,6 +88,9 @@ Available Types
telegram.inputmediadocument telegram.inputmediadocument
telegram.inputmediaphoto telegram.inputmediaphoto
telegram.inputmediavideo telegram.inputmediavideo
telegram.inputpaidmedia
telegram.inputpaidmediaphoto
telegram.inputpaidmediavideo
telegram.inputpolloption telegram.inputpolloption
telegram.inputsticker telegram.inputsticker
telegram.keyboardbutton telegram.keyboardbutton
@ -113,6 +116,11 @@ Available Types
telegram.messageoriginuser telegram.messageoriginuser
telegram.messagereactioncountupdated telegram.messagereactioncountupdated
telegram.messagereactionupdated telegram.messagereactionupdated
telegram.paidmedia
telegram.paidmediainfo
telegram.paidmediaphoto
telegram.paidmediapreview
telegram.paidmediavideo
telegram.photosize telegram.photosize
telegram.poll telegram.poll
telegram.pollanswer telegram.pollanswer
@ -125,22 +133,12 @@ Available Types
telegram.replykeyboardmarkup telegram.replykeyboardmarkup
telegram.replykeyboardremove telegram.replykeyboardremove
telegram.replyparameters telegram.replyparameters
telegram.revenuewithdrawalstate
telegram.revenuewithdrawalstatefailed
telegram.revenuewithdrawalstatepending
telegram.revenuewithdrawalstatesucceeded
telegram.sentwebappmessage telegram.sentwebappmessage
telegram.shareduser telegram.shareduser
telegram.startransaction
telegram.startransactions
telegram.story telegram.story
telegram.switchinlinequerychosenchat telegram.switchinlinequerychosenchat
telegram.telegramobject telegram.telegramobject
telegram.textquote telegram.textquote
telegram.transactionpartner
telegram.transactionpartnerfragment
telegram.transactionpartnerother
telegram.transactionpartneruser
telegram.update telegram.update
telegram.user telegram.user
telegram.userchatboosts telegram.userchatboosts

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,18 @@ Payments
telegram.labeledprice telegram.labeledprice
telegram.orderinfo telegram.orderinfo
telegram.precheckoutquery telegram.precheckoutquery
telegram.revenuewithdrawalstate
telegram.revenuewithdrawalstatefailed
telegram.revenuewithdrawalstatepending
telegram.revenuewithdrawalstatesucceeded
telegram.shippingaddress telegram.shippingaddress
telegram.shippingoption telegram.shippingoption
telegram.shippingquery telegram.shippingquery
telegram.startransaction
telegram.startransactions
telegram.successfulpayment telegram.successfulpayment
telegram.transactionpartner
telegram.transactionpartnerfragment
telegram.transactionpartnerother
telegram.transactionpartnertelegramads
telegram.transactionpartneruser

View file

@ -0,0 +1,7 @@
TransactionPartnerTelegramAds
=============================
.. autoclass:: telegram.TransactionPartnerTelegramAds
:members:
:show-inheritance:
:inherited-members: TelegramObject

View file

@ -142,6 +142,9 @@ __all__ = (
"InputMediaPhoto", "InputMediaPhoto",
"InputMediaVideo", "InputMediaVideo",
"InputMessageContent", "InputMessageContent",
"InputPaidMedia",
"InputPaidMediaPhoto",
"InputPaidMediaVideo",
"InputPollOption", "InputPollOption",
"InputSticker", "InputSticker",
"InputTextMessageContent", "InputTextMessageContent",
@ -173,6 +176,11 @@ __all__ = (
"MessageReactionCountUpdated", "MessageReactionCountUpdated",
"MessageReactionUpdated", "MessageReactionUpdated",
"OrderInfo", "OrderInfo",
"PaidMedia",
"PaidMediaInfo",
"PaidMediaPhoto",
"PaidMediaPreview",
"PaidMediaVideo",
"PassportData", "PassportData",
"PassportElementError", "PassportElementError",
"PassportElementErrorDataField", "PassportElementErrorDataField",
@ -223,6 +231,7 @@ __all__ = (
"TransactionPartner", "TransactionPartner",
"TransactionPartnerFragment", "TransactionPartnerFragment",
"TransactionPartnerOther", "TransactionPartnerOther",
"TransactionPartnerTelegramAds",
"TransactionPartnerUser", "TransactionPartnerUser",
"Update", "Update",
"User", "User",
@ -333,6 +342,9 @@ from ._files.inputmedia import (
InputMediaDocument, InputMediaDocument,
InputMediaPhoto, InputMediaPhoto,
InputMediaVideo, InputMediaVideo,
InputPaidMedia,
InputPaidMediaPhoto,
InputPaidMediaVideo,
) )
from ._files.inputsticker import InputSticker from ._files.inputsticker import InputSticker
from ._files.location import Location from ._files.location import Location
@ -405,6 +417,7 @@ from ._messageorigin import (
MessageOriginUser, MessageOriginUser,
) )
from ._messagereactionupdated import MessageReactionCountUpdated, MessageReactionUpdated from ._messagereactionupdated import MessageReactionCountUpdated, MessageReactionUpdated
from ._paidmedia import PaidMedia, PaidMediaInfo, PaidMediaPhoto, PaidMediaPreview, PaidMediaVideo
from ._passport.credentials import ( from ._passport.credentials import (
Credentials, Credentials,
DataCredentials, DataCredentials,
@ -436,16 +449,7 @@ from ._payment.precheckoutquery import PreCheckoutQuery
from ._payment.shippingaddress import ShippingAddress from ._payment.shippingaddress import ShippingAddress
from ._payment.shippingoption import ShippingOption from ._payment.shippingoption import ShippingOption
from ._payment.shippingquery import ShippingQuery from ._payment.shippingquery import ShippingQuery
from ._payment.successfulpayment import SuccessfulPayment from ._payment.stars import (
from ._poll import InputPollOption, Poll, PollAnswer, PollOption
from ._proximityalerttriggered import ProximityAlertTriggered
from ._reaction import ReactionCount, ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji
from ._reply import ExternalReplyInfo, ReplyParameters, TextQuote
from ._replykeyboardmarkup import ReplyKeyboardMarkup
from ._replykeyboardremove import ReplyKeyboardRemove
from ._sentwebappmessage import SentWebAppMessage
from ._shared import ChatShared, SharedUser, UsersShared
from ._stars import (
RevenueWithdrawalState, RevenueWithdrawalState,
RevenueWithdrawalStateFailed, RevenueWithdrawalStateFailed,
RevenueWithdrawalStatePending, RevenueWithdrawalStatePending,
@ -455,8 +459,18 @@ from ._stars import (
TransactionPartner, TransactionPartner,
TransactionPartnerFragment, TransactionPartnerFragment,
TransactionPartnerOther, TransactionPartnerOther,
TransactionPartnerTelegramAds,
TransactionPartnerUser, TransactionPartnerUser,
) )
from ._payment.successfulpayment import SuccessfulPayment
from ._poll import InputPollOption, Poll, PollAnswer, PollOption
from ._proximityalerttriggered import ProximityAlertTriggered
from ._reaction import ReactionCount, ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji
from ._reply import ExternalReplyInfo, ReplyParameters, TextQuote
from ._replykeyboardmarkup import ReplyKeyboardMarkup
from ._replykeyboardremove import ReplyKeyboardRemove
from ._sentwebappmessage import SentWebAppMessage
from ._shared import ChatShared, SharedUser, UsersShared
from ._story import Story from ._story import Story
from ._switchinlinequerychosenchat import SwitchInlineQueryChosenChat from ._switchinlinequerychosenchat import SwitchInlineQueryChosenChat
from ._telegramobject import TelegramObject from ._telegramobject import TelegramObject

View file

@ -70,7 +70,7 @@ from telegram._files.chatphoto import ChatPhoto
from telegram._files.contact import Contact from telegram._files.contact import Contact
from telegram._files.document import Document from telegram._files.document import Document
from telegram._files.file import File from telegram._files.file import File
from telegram._files.inputmedia import InputMedia from telegram._files.inputmedia import InputMedia, InputPaidMedia
from telegram._files.location import Location from telegram._files.location import Location
from telegram._files.photosize import PhotoSize from telegram._files.photosize import PhotoSize
from telegram._files.sticker import MaskPosition, Sticker, StickerSet from telegram._files.sticker import MaskPosition, Sticker, StickerSet
@ -84,11 +84,11 @@ from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton
from telegram._menubutton import MenuButton from telegram._menubutton import MenuButton
from telegram._message import Message from telegram._message import Message
from telegram._messageid import MessageId from telegram._messageid import MessageId
from telegram._payment.stars import StarTransactions
from telegram._poll import InputPollOption, Poll from telegram._poll import InputPollOption, Poll
from telegram._reaction import ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji from telegram._reaction import ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji
from telegram._reply import ReplyParameters from telegram._reply import ReplyParameters
from telegram._sentwebappmessage import SentWebAppMessage from telegram._sentwebappmessage import SentWebAppMessage
from telegram._stars import StarTransactions
from telegram._telegramobject import TelegramObject from telegram._telegramobject import TelegramObject
from telegram._update import Update from telegram._update import Update
from telegram._user import User from telegram._user import User
@ -578,13 +578,16 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
with new._unfrozen(): with new._unfrozen():
new.parse_mode = DefaultValue.get_value(new.parse_mode) new.parse_mode = DefaultValue.get_value(new.parse_mode)
data[key] = new data[key] = new
elif key == "media" and isinstance(val, Sequence): elif (
key == "media"
and isinstance(val, Sequence)
and not isinstance(val[0], InputPaidMedia)
):
# Copy objects as not to edit them in-place # Copy objects as not to edit them in-place
copy_list = [copy.copy(media) for media in val] copy_list = [copy.copy(media) for media in val]
for media in copy_list: for media in copy_list:
with media._unfrozen(): with media._unfrozen():
media.parse_mode = DefaultValue.get_value(media.parse_mode) media.parse_mode = DefaultValue.get_value(media.parse_mode)
data[key] = copy_list data[key] = copy_list
# 2) # 2)
else: else:
@ -7654,7 +7657,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout: ODVInput[float] = DEFAULT_NONE, pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None, api_kwargs: Optional[JSONDict] = None,
) -> MessageId: ) -> MessageId:
"""Use this method to copy messages of any kind. Service messages and invoice messages """Use this method to copy messages of any kind. Service messages, paid media messages,
giveaway messages, giveaway winners messages, and invoice messages
can't be copied. The method is analogous to the method :meth:`forward_message`, but the can't be copied. The method is analogous to the method :meth:`forward_message`, but the
copied message doesn't have a link to the original message. copied message doesn't have a link to the original message.
@ -7780,11 +7784,12 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
) -> Tuple["MessageId", ...]: ) -> Tuple["MessageId", ...]:
""" """
Use this method to copy messages of any kind. If some of the specified messages can't be Use this method to copy messages of any kind. If some of the specified messages can't be
found or copied, they are skipped. Service messages, giveaway messages, giveaway winners found or copied, they are skipped. Service messages, paid media messages, giveaway
messages, and invoice messages can't be copied. A quiz poll can be copied only if the value messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can
of the field correct_option_id is known to the bot. The method is analogous to the method be copied only if the value
:meth:`forward_messages`, but the copied messages don't have a link to the original of the field :attr:`telegram.Poll.correct_option_id` is known to the bot. The method is
message. Album grouping is kept for copied messages. analogous to the method :meth:`forward_messages`, but the copied messages don't have a
link to the original message. Album grouping is kept for copied messages.
.. versionadded:: 20.8 .. versionadded:: 20.8
@ -9163,6 +9168,94 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
bot=self, bot=self,
) )
async def send_paid_media(
self,
chat_id: Union[str, int],
star_count: int,
media: Sequence["InputPaidMedia"],
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
show_caption_above_media: Optional[bool] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
reply_markup: Optional[ReplyMarkup] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> Message:
"""Use this method to send paid media to channel chats.
.. versionadded:: NEXT.VERSION
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
star_count (:obj:`int`): The number of Telegram Stars that must be paid to buy access
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.
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|
caption_entities (Sequence[:class:`telegram.MessageEntity`], optional):
|caption_entities|
show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med|
disable_notification (:obj:`bool`, optional): |disable_notification|
protect_content (:obj:`bool`, optional): |protect_content|
reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters|
reply_markup (:class:`InlineKeyboardMarkup` | :class:`ReplyKeyboardMarkup` | \
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
Additional interface options. An object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user.
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
Mutually exclusive with :paramref:`reply_parameters`, which this is a convenience
parameter for
reply_to_message_id (:obj:`int`, optional): |reply_to_msg_id|
Mutually exclusive with :paramref:`reply_parameters`, which this is a convenience
parameter for
Returns:
:class:`telegram.Message`: On success, the sent message is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
"chat_id": chat_id,
"star_count": star_count,
"media": media,
"show_caption_above_media": show_caption_above_media,
}
return await self._send_message(
"sendPaidMedia",
data,
caption=caption,
parse_mode=parse_mode,
caption_entities=caption_entities,
disable_notification=disable_notification,
protect_content=protect_content,
reply_parameters=reply_parameters,
reply_markup=reply_markup,
allow_sending_without_reply=allow_sending_without_reply,
reply_to_message_id=reply_to_message_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002 def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002
"""See :meth:`telegram.TelegramObject.to_dict`.""" """See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name} data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
@ -9417,3 +9510,5 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""Alias for :meth:`refund_star_payment`""" """Alias for :meth:`refund_star_payment`"""
getStarTransactions = get_star_transactions getStarTransactions = get_star_transactions
"""Alias for :meth:`get_star_transactions`""" """Alias for :meth:`get_star_transactions`"""
sendPaidMedia = send_paid_media
"""Alias for :meth:`send_paid_media`"""

View file

@ -48,6 +48,7 @@ if TYPE_CHECKING:
InputMediaDocument, InputMediaDocument,
InputMediaPhoto, InputMediaPhoto,
InputMediaVideo, InputMediaVideo,
InputPaidMedia,
InputPollOption, InputPollOption,
LabeledPrice, LabeledPrice,
LinkPreviewOptions, LinkPreviewOptions,
@ -3257,6 +3258,60 @@ class _ChatBase(TelegramObject):
api_kwargs=api_kwargs, api_kwargs=api_kwargs,
) )
async def send_paid_media(
self,
star_count: int,
media: Sequence["InputPaidMedia"],
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
show_caption_above_media: Optional[bool] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
reply_markup: Optional[ReplyMarkup] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> "Message":
"""Shortcut for::
await bot.send_paid_media(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.send_paid_media`.
.. versionadded:: NEXT.VERSION
Returns:
:class:`telegram.Message`: On success, instance representing the message posted.
"""
return await self.get_bot().send_paid_media(
chat_id=self.id,
star_count=star_count,
media=media,
caption=caption,
parse_mode=parse_mode,
caption_entities=caption_entities,
show_caption_above_media=show_caption_above_media,
disable_notification=disable_notification,
protect_content=protect_content,
reply_parameters=reply_parameters,
reply_markup=reply_markup,
allow_sending_without_reply=allow_sending_without_reply,
reply_to_message_id=reply_to_message_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
class Chat(_ChatBase): class Chat(_ChatBase):
"""This object represents a chat. """This object represents a chat.

View file

@ -195,6 +195,10 @@ class ChatFullInfo(_ChatBase):
chats. chats.
location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which
the supergroup is connected. the supergroup is connected.
can_send_paid_media (:obj:`bool`, optional): :obj:`True`, if paid media messages can be
sent or forwarded to the channel chat. The field is available only for channel chats.
.. versionadded:: NEXT.VERSION
Attributes: Attributes:
id (:obj:`int`): Unique identifier for this chat. id (:obj:`int`): Unique identifier for this chat.
@ -345,6 +349,10 @@ class ChatFullInfo(_ChatBase):
chats. chats.
location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which
the supergroup is connected. the supergroup is connected.
can_send_paid_media (:obj:`bool`): Optional. :obj:`True`, if paid media messages can be
sent or forwarded to the channel chat. The field is available only for channel chats.
.. versionadded:: NEXT.VERSION
.. _accent colors: https://core.telegram.org/bots/api#accent-colors .. _accent colors: https://core.telegram.org/bots/api#accent-colors
.. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups .. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups
@ -360,6 +368,7 @@ class ChatFullInfo(_ChatBase):
"business_intro", "business_intro",
"business_location", "business_location",
"business_opening_hours", "business_opening_hours",
"can_send_paid_media",
"can_set_sticker_set", "can_set_sticker_set",
"custom_emoji_sticker_set_name", "custom_emoji_sticker_set_name",
"description", "description",
@ -434,6 +443,7 @@ class ChatFullInfo(_ChatBase):
custom_emoji_sticker_set_name: Optional[str] = None, custom_emoji_sticker_set_name: Optional[str] = None,
linked_chat_id: Optional[int] = None, linked_chat_id: Optional[int] = None,
location: Optional[ChatLocation] = None, location: Optional[ChatLocation] = None,
can_send_paid_media: Optional[bool] = None,
*, *,
api_kwargs: Optional[JSONDict] = None, api_kwargs: Optional[JSONDict] = None,
): ):
@ -496,6 +506,7 @@ class ChatFullInfo(_ChatBase):
self.business_intro: Optional[BusinessIntro] = business_intro self.business_intro: Optional[BusinessIntro] = business_intro
self.business_location: Optional[BusinessLocation] = business_location self.business_location: Optional[BusinessLocation] = business_location
self.business_opening_hours: Optional[BusinessOpeningHours] = business_opening_hours self.business_opening_hours: Optional[BusinessOpeningHours] = business_opening_hours
self.can_send_paid_media: Optional[bool] = can_send_paid_media
@classmethod @classmethod
def de_json( def de_json(

View file

@ -44,7 +44,7 @@ class _BaseThumbedMedium(_BaseMedium):
is supposed to be the same over time and for different bots. is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file. Can't be used to download or reuse the file.
file_size (:obj:`int`, optional): File size. file_size (:obj:`int`, optional): File size.
thumbnail (:class:`telegram.PhotoSize`, optional): Thumbnail as defined by sender. thumbnail (:class:`telegram.PhotoSize`, optional): Thumbnail as defined by the sender.
.. versionadded:: 20.2 .. versionadded:: 20.2
@ -54,7 +54,7 @@ class _BaseThumbedMedium(_BaseMedium):
is supposed to be the same over time and for different bots. is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file. Can't be used to download or reuse the file.
file_size (:obj:`int`): Optional. File size. file_size (:obj:`int`): Optional. File size.
thumbnail (:class:`telegram.PhotoSize`): Optional. Thumbnail as defined by sender. thumbnail (:class:`telegram.PhotoSize`): Optional. Thumbnail as defined by the sender.
.. versionadded:: 20.2 .. versionadded:: 20.2

View file

@ -39,11 +39,11 @@ class Animation(_BaseThumbedMedium):
file_unique_id (:obj:`str`): Unique identifier for this file, which file_unique_id (:obj:`str`): Unique identifier for this file, which
is supposed to be the same over time and for different bots. is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file. Can't be used to download or reuse the file.
width (:obj:`int`): Video width as defined by sender. width (:obj:`int`): Video width as defined by the sender.
height (:obj:`int`): Video height as defined by sender. height (:obj:`int`): Video height as defined by the sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender. duration (:obj:`int`): Duration of the video in seconds as defined by the sender.
file_name (:obj:`str`, optional): Original animation filename as defined by sender. file_name (:obj:`str`, optional): Original animation filename as defined by the sender.
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender. mime_type (:obj:`str`, optional): MIME type of the file as defined by the sender.
file_size (:obj:`int`, optional): File size in bytes. file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Animation thumbnail as defined by thumbnail (:class:`telegram.PhotoSize`, optional): Animation thumbnail as defined by
sender. sender.
@ -56,11 +56,11 @@ class Animation(_BaseThumbedMedium):
file_unique_id (:obj:`str`): Unique identifier for this file, which file_unique_id (:obj:`str`): Unique identifier for this file, which
is supposed to be the same over time and for different bots. is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file. Can't be used to download or reuse the file.
width (:obj:`int`): Video width as defined by sender. width (:obj:`int`): Video width as defined by the sender.
height (:obj:`int`): Video height as defined by sender. height (:obj:`int`): Video height as defined by the sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender. duration (:obj:`int`): Duration of the video in seconds as defined by the sender.
file_name (:obj:`str`): Optional. Original animation filename as defined by sender. file_name (:obj:`str`): Optional. Original animation filename as defined by the sender.
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender. mime_type (:obj:`str`): Optional. MIME type of the file as defined by the sender.
file_size (:obj:`int`): Optional. File size in bytes. file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Animation thumbnail as defined by thumbnail (:class:`telegram.PhotoSize`): Optional. Animation thumbnail as defined by
sender. sender.

View file

@ -39,12 +39,12 @@ class Audio(_BaseThumbedMedium):
or reuse the file. or reuse the file.
file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be
the same over time and for different bots. Can't be used to download or reuse the file. the same over time and for different bots. Can't be used to download or reuse the file.
duration (:obj:`int`): Duration of the audio in seconds as defined by sender. duration (:obj:`int`): Duration of the audio in seconds as defined by the sender.
performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio performer (:obj:`str`, optional): Performer of the audio as defined by the sender or by
tags. audio tags.
title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags. title (:obj:`str`, optional): Title of the audio as defined by the sender or by audio tags.
file_name (:obj:`str`, optional): Original filename as defined by sender. file_name (:obj:`str`, optional): Original filename as defined by the sender.
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender. mime_type (:obj:`str`, optional): MIME type of the file as defined by the sender.
file_size (:obj:`int`, optional): File size in bytes. file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Thumbnail of the album cover to thumbnail (:class:`telegram.PhotoSize`, optional): Thumbnail of the album cover to
which the music file belongs. which the music file belongs.
@ -56,12 +56,12 @@ class Audio(_BaseThumbedMedium):
or reuse the file. or reuse the file.
file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be
the same over time and for different bots. Can't be used to download or reuse the file. the same over time and for different bots. Can't be used to download or reuse the file.
duration (:obj:`int`): Duration of the audio in seconds as defined by sender. duration (:obj:`int`): Duration of the audio in seconds as defined by the sender.
performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio performer (:obj:`str`): Optional. Performer of the audio as defined by the sender or by
tags. audio tags.
title (:obj:`str`): Optional. Title of the audio as defined by sender or by audio tags. title (:obj:`str`): Optional. Title of the audio as defined by the sender or by audio tags.
file_name (:obj:`str`): Optional. Original filename as defined by sender. file_name (:obj:`str`): Optional. Original filename as defined by the sender.
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender. mime_type (:obj:`str`): Optional. MIME type of the file as defined by the sender.
file_size (:obj:`int`): Optional. File size in bytes. file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Thumbnail of the album cover to thumbnail (:class:`telegram.PhotoSize`): Optional. Thumbnail of the album cover to
which the music file belongs. which the music file belongs.

View file

@ -39,10 +39,11 @@ class Document(_BaseThumbedMedium):
or reuse the file. or reuse the file.
file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be
the same over time and for different bots. Can't be used to download or reuse the file. the same over time and for different bots. Can't be used to download or reuse the file.
file_name (:obj:`str`, optional): Original filename as defined by sender. file_name (:obj:`str`, optional): Original filename as defined by the sender.
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender. mime_type (:obj:`str`, optional): MIME type of the file as defined by the sender.
file_size (:obj:`int`, optional): File size in bytes. file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Document thumbnail as defined by sender. thumbnail (:class:`telegram.PhotoSize`, optional): Document thumbnail as defined by the
sender.
.. versionadded:: 20.2 .. versionadded:: 20.2
@ -51,10 +52,11 @@ class Document(_BaseThumbedMedium):
or reuse the file. or reuse the file.
file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be file_unique_id (:obj:`str`): Unique identifier for this file, which is supposed to be
the same over time and for different bots. Can't be used to download or reuse the file. the same over time and for different bots. Can't be used to download or reuse the file.
file_name (:obj:`str`): Optional. Original filename as defined by sender. file_name (:obj:`str`): Optional. Original filename as defined by the sender.
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender. mime_type (:obj:`str`): Optional. MIME type of the file as defined by the sender.
file_size (:obj:`int`): Optional. File size in bytes. file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Document thumbnail as defined by sender. thumbnail (:class:`telegram.PhotoSize`): Optional. Document thumbnail as defined by the
sender.
.. versionadded:: 20.2 .. versionadded:: 20.2

View file

@ -17,7 +17,7 @@
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""Base class for Telegram InputMedia Objects.""" """Base class for Telegram InputMedia Objects."""
from typing import Optional, Sequence, Tuple, Union from typing import Final, Optional, Sequence, Tuple, Union
from telegram import constants from telegram import constants
from telegram._files.animation import Animation from telegram._files.animation import Animation
@ -115,13 +115,162 @@ class InputMedia(TelegramObject):
) )
class InputPaidMedia(TelegramObject):
"""
Base class for Telegram InputPaidMedia Objects. Currently, it can be one of:
* :class:`telegram.InputMediaPhoto`
* :class:`telegram.InputMediaVideo`
.. seealso:: :wiki:`Working with Files and Media <Working-with-Files-and-Media>`
.. versionadded:: NEXT.VERSION
Args:
type (:obj:`str`): Type of media that the instance represents.
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.PhotoSize` | :class:`telegram.Video`): File to send. |fileinputnopath|
Lastly you can pass an existing telegram media object of the corresponding type
to send.
Attributes:
type (:obj:`str`): Type of the input media.
media (:obj:`str` | :class:`telegram.InputFile`): Media to send.
"""
PHOTO: Final[str] = constants.InputPaidMediaType.PHOTO
""":const:`telegram.constants.InputPaidMediaType.PHOTO`"""
VIDEO: Final[str] = constants.InputPaidMediaType.VIDEO
""":const:`telegram.constants.InputPaidMediaType.VIDEO`"""
__slots__ = ("media", "type")
def __init__(
self,
type: str, # pylint: disable=redefined-builtin
media: Union[str, InputFile, PhotoSize, Video],
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(api_kwargs=api_kwargs)
self.type: str = enum.get_member(constants.InputPaidMediaType, type, type)
self.media: Union[str, InputFile, PhotoSize, Video] = media
self._freeze()
class InputPaidMediaPhoto(InputPaidMedia):
"""The paid media to send is a photo.
.. seealso:: :wiki:`Working with Files and Media <Working-with-Files-and-Media>`
.. versionadded:: NEXT.VERSION
Args:
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.PhotoSize`): File to send. |fileinputnopath|
Lastly you can pass an existing :class:`telegram.PhotoSize` object to send.
Attributes:
type (:obj:`str`): Type of the media, always
:tg-const:`telegram.constants.InputPaidMediaType.PHOTO`.
media (:obj:`str` | :class:`telegram.InputFile`): Photo to send.
"""
__slots__ = ()
def __init__(
self,
media: Union[FileInput, PhotoSize],
*,
api_kwargs: Optional[JSONDict] = None,
):
media = parse_file_input(media, PhotoSize, attach=True, local_mode=True)
super().__init__(type=InputPaidMedia.PHOTO, media=media, api_kwargs=api_kwargs)
self._freeze()
class InputPaidMediaVideo(InputPaidMedia):
"""
The paid media to send is a video.
.. seealso:: :wiki:`Working with Files and Media <Working-with-Files-and-Media>`
.. versionadded:: NEXT.VERSION
Note:
* When using a :class:`telegram.Video` for the :attr:`media` attribute, it will take the
width, height and duration from that video, unless otherwise specified with the optional
arguments.
* :paramref:`thumbnail` will be ignored for small video files, for which Telegram can
easily generate thumbnails. However, this behaviour is undocumented and might be
changed by Telegram.
Args:
media (:obj:`str` | :term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | \
:class:`telegram.Video`): File to send. |fileinputnopath|
Lastly you can pass an existing :class:`telegram.Video` object to send.
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath|
width (:obj:`int`, optional): Video width.
height (:obj:`int`, optional): Video height.
duration (:obj:`int`, optional): Video duration in seconds.
supports_streaming (:obj:`bool`, optional): Pass :obj:`True`, if the uploaded video is
suitable for streaming.
Attributes:
type (:obj:`str`): Type of the media, always
:tg-const:`telegram.constants.InputPaidMediaType.VIDEO`.
media (:obj:`str` | :class:`telegram.InputFile`): Video to send.
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
width (:obj:`int`): Optional. Video width.
height (:obj:`int`): Optional. Video height.
duration (:obj:`int`): Optional. Video duration in seconds.
supports_streaming (:obj:`bool`): Optional. :obj:`True`, if the uploaded video is
suitable for streaming.
"""
__slots__ = ("duration", "height", "supports_streaming", "thumbnail", "width")
def __init__(
self,
media: Union[FileInput, Video],
thumbnail: Optional[FileInput] = None,
width: Optional[int] = None,
height: Optional[int] = None,
duration: Optional[int] = None,
supports_streaming: Optional[bool] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
if isinstance(media, Video):
width = width if width is not None else media.width
height = height if height is not None else media.height
duration = duration if duration is not None else media.duration
media = media.file_id
else:
# We use local_mode=True because we don't have access to the actual setting and want
# things to work in local mode.
media = parse_file_input(media, attach=True, local_mode=True)
super().__init__(type=InputPaidMedia.VIDEO, media=media, api_kwargs=api_kwargs)
with self._unfrozen():
self.thumbnail: Optional[Union[str, InputFile]] = InputMedia._parse_thumbnail_input(
thumbnail
)
self.width: Optional[int] = width
self.height: Optional[int] = height
self.duration: Optional[int] = duration
self.supports_streaming: Optional[bool] = supports_streaming
class InputMediaAnimation(InputMedia): class InputMediaAnimation(InputMedia):
"""Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. """Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent.
Note: Note:
When using a :class:`telegram.Animation` for the :attr:`media` attribute, it will take the When using a :class:`telegram.Animation` for the :attr:`media` attribute, it will take the
width, height and duration from that video, unless otherwise specified with the optional width, height and duration from that animation, unless otherwise specified with the
arguments. optional arguments.
.. seealso:: :wiki:`Working with Files and Media <Working-with-Files-and-Media>` .. seealso:: :wiki:`Working with Files and Media <Working-with-Files-and-Media>`
@ -510,10 +659,10 @@ class InputMediaAudio(InputMedia):
.. versionchanged:: 20.0 .. versionchanged:: 20.0
|sequenceclassargs| |sequenceclassargs|
duration (:obj:`int`, optional): Duration of the audio in seconds as defined by sender. duration (:obj:`int`, optional): Duration of the audio in seconds as defined by the sender.
performer (:obj:`str`, optional): Performer of the audio as defined by sender or by audio performer (:obj:`str`, optional): Performer of the audio as defined by the sender or by
tags. audio tags.
title (:obj:`str`, optional): Title of the audio as defined by sender or by audio tags. title (:obj:`str`, optional): Title of the audio as defined by the sender or by audio tags.
thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \ thumbnail (:term:`file object` | :obj:`bytes` | :class:`pathlib.Path` | :obj:`str`, \
optional): |thumbdocstringnopath| optional): |thumbdocstringnopath|
@ -533,9 +682,9 @@ class InputMediaAudio(InputMedia):
* |tupleclassattrs| * |tupleclassattrs|
* |alwaystuple| * |alwaystuple|
duration (:obj:`int`): Optional. Duration of the audio in seconds. duration (:obj:`int`): Optional. Duration of the audio in seconds.
performer (:obj:`str`): Optional. Performer of the audio as defined by sender or by audio performer (:obj:`str`): Optional. Performer of the audio as defined by the sender or by
tags. audio tags.
title (:obj:`str`): Optional. Title of the audio as defined by sender or by audio tags. title (:obj:`str`): Optional. Title of the audio as defined by the sender or by audio tags.
thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase| thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
.. versionadded:: 20.2 .. versionadded:: 20.2

View file

@ -32,8 +32,8 @@ class Location(TelegramObject):
considered equal, if their :attr:`longitude` and :attr:`latitude` are equal. considered equal, if their :attr:`longitude` and :attr:`latitude` are equal.
Args: Args:
longitude (:obj:`float`): Longitude as defined by sender. longitude (:obj:`float`): Longitude as defined by the sender.
latitude (:obj:`float`): Latitude as defined by sender. latitude (:obj:`float`): Latitude as defined by the sender.
horizontal_accuracy (:obj:`float`, optional): The radius of uncertainty for the location, horizontal_accuracy (:obj:`float`, optional): The radius of uncertainty for the location,
measured in meters; 0-:tg-const:`telegram.Location.HORIZONTAL_ACCURACY`. measured in meters; 0-:tg-const:`telegram.Location.HORIZONTAL_ACCURACY`.
live_period (:obj:`int`, optional): Time relative to the message sending date, during which live_period (:obj:`int`, optional): Time relative to the message sending date, during which
@ -45,8 +45,8 @@ class Location(TelegramObject):
approaching another chat member, in meters. For sent live locations only. approaching another chat member, in meters. For sent live locations only.
Attributes: Attributes:
longitude (:obj:`float`): Longitude as defined by sender. longitude (:obj:`float`): Longitude as defined by the sender.
latitude (:obj:`float`): Latitude as defined by sender. latitude (:obj:`float`): Latitude as defined by the sender.
horizontal_accuracy (:obj:`float`): Optional. The radius of uncertainty for the location, horizontal_accuracy (:obj:`float`): Optional. The radius of uncertainty for the location,
measured in meters; 0-:tg-const:`telegram.Location.HORIZONTAL_ACCURACY`. measured in meters; 0-:tg-const:`telegram.Location.HORIZONTAL_ACCURACY`.
live_period (:obj:`int`): Optional. Time relative to the message sending date, during which live_period (:obj:`int`): Optional. Time relative to the message sending date, during which

View file

@ -39,11 +39,11 @@ class Video(_BaseThumbedMedium):
file_unique_id (:obj:`str`): Unique identifier for this file, which file_unique_id (:obj:`str`): Unique identifier for this file, which
is supposed to be the same over time and for different bots. is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file. Can't be used to download or reuse the file.
width (:obj:`int`): Video width as defined by sender. width (:obj:`int`): Video width as defined by the sender.
height (:obj:`int`): Video height as defined by sender. height (:obj:`int`): Video height as defined by the sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender. duration (:obj:`int`): Duration of the video in seconds as defined by the sender.
file_name (:obj:`str`, optional): Original filename as defined by sender. file_name (:obj:`str`, optional): Original filename as defined by the sender.
mime_type (:obj:`str`, optional): MIME type of a file as defined by sender. mime_type (:obj:`str`, optional): MIME type of a file as defined by the sender.
file_size (:obj:`int`, optional): File size in bytes. file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Video thumbnail. thumbnail (:class:`telegram.PhotoSize`, optional): Video thumbnail.
@ -55,11 +55,11 @@ class Video(_BaseThumbedMedium):
file_unique_id (:obj:`str`): Unique identifier for this file, which file_unique_id (:obj:`str`): Unique identifier for this file, which
is supposed to be the same over time and for different bots. is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file. Can't be used to download or reuse the file.
width (:obj:`int`): Video width as defined by sender. width (:obj:`int`): Video width as defined by the sender.
height (:obj:`int`): Video height as defined by sender. height (:obj:`int`): Video height as defined by the sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender. duration (:obj:`int`): Duration of the video in seconds as defined by the sender.
file_name (:obj:`str`): Optional. Original filename as defined by sender. file_name (:obj:`str`): Optional. Original filename as defined by the sender.
mime_type (:obj:`str`): Optional. MIME type of a file as defined by sender. mime_type (:obj:`str`): Optional. MIME type of a file as defined by the sender.
file_size (:obj:`int`): Optional. File size in bytes. file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Video thumbnail. thumbnail (:class:`telegram.PhotoSize`): Optional. Video thumbnail.

View file

@ -42,7 +42,7 @@ class VideoNote(_BaseThumbedMedium):
Can't be used to download or reuse the file. Can't be used to download or reuse the file.
length (:obj:`int`): Video width and height (diameter of the video message) as defined length (:obj:`int`): Video width and height (diameter of the video message) as defined
by sender. by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender. duration (:obj:`int`): Duration of the video in seconds as defined by the sender.
file_size (:obj:`int`, optional): File size in bytes. file_size (:obj:`int`, optional): File size in bytes.
thumbnail (:class:`telegram.PhotoSize`, optional): Video thumbnail. thumbnail (:class:`telegram.PhotoSize`, optional): Video thumbnail.
@ -56,7 +56,7 @@ class VideoNote(_BaseThumbedMedium):
Can't be used to download or reuse the file. Can't be used to download or reuse the file.
length (:obj:`int`): Video width and height (diameter of the video message) as defined length (:obj:`int`): Video width and height (diameter of the video message) as defined
by sender. by sender.
duration (:obj:`int`): Duration of the video in seconds as defined by sender. duration (:obj:`int`): Duration of the video in seconds as defined by the sender.
file_size (:obj:`int`): Optional. File size in bytes. file_size (:obj:`int`): Optional. File size in bytes.
thumbnail (:class:`telegram.PhotoSize`): Optional. Video thumbnail. thumbnail (:class:`telegram.PhotoSize`): Optional. Video thumbnail.

View file

@ -35,8 +35,8 @@ class Voice(_BaseMedium):
file_unique_id (:obj:`str`): Unique identifier for this file, which file_unique_id (:obj:`str`): Unique identifier for this file, which
is supposed to be the same over time and for different bots. is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file. Can't be used to download or reuse the file.
duration (:obj:`int`): Duration of the audio in seconds as defined by sender. duration (:obj:`int`): Duration of the audio in seconds as defined by the sender.
mime_type (:obj:`str`, optional): MIME type of the file as defined by sender. mime_type (:obj:`str`, optional): MIME type of the file as defined by the sender.
file_size (:obj:`int`, optional): File size in bytes. file_size (:obj:`int`, optional): File size in bytes.
Attributes: Attributes:
@ -45,8 +45,8 @@ class Voice(_BaseMedium):
file_unique_id (:obj:`str`): Unique identifier for this file, which file_unique_id (:obj:`str`): Unique identifier for this file, which
is supposed to be the same over time and for different bots. is supposed to be the same over time and for different bots.
Can't be used to download or reuse the file. Can't be used to download or reuse the file.
duration (:obj:`int`): Duration of the audio in seconds as defined by sender. duration (:obj:`int`): Duration of the audio in seconds as defined by the sender.
mime_type (:obj:`str`): Optional. MIME type of the file as defined by sender. mime_type (:obj:`str`): Optional. MIME type of the file as defined by the sender.
file_size (:obj:`int`): Optional. File size in bytes. file_size (:obj:`int`): Optional. File size in bytes.
""" """

View file

@ -145,7 +145,10 @@ class MenuButtonWebApp(MenuButton):
web_app (:class:`telegram.WebAppInfo`): Description of the Web App that will be launched web_app (:class:`telegram.WebAppInfo`): Description of the Web App that will be launched
when the user presses the button. The Web App will be able to send an arbitrary when the user presses the button. The Web App will be able to send an arbitrary
message on behalf of the user using the method :meth:`~telegram.Bot.answerWebAppQuery` message on behalf of the user using the method :meth:`~telegram.Bot.answerWebAppQuery`
of :class:`~telegram.Bot`. of :class:`~telegram.Bot`. Alternatively, a ``t.me`` link to a Web App of the bot can
be specified in the object instead of the Web App's URL, in which case the Web App
will be opened as if the user pressed the link.
Attributes: Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.MenuButtonType.WEB_APP`. type (:obj:`str`): :tg-const:`telegram.constants.MenuButtonType.WEB_APP`.
@ -153,7 +156,9 @@ class MenuButtonWebApp(MenuButton):
web_app (:class:`telegram.WebAppInfo`): Description of the Web App that will be launched web_app (:class:`telegram.WebAppInfo`): Description of the Web App that will be launched
when the user presses the button. The Web App will be able to send an arbitrary when the user presses the button. The Web App will be able to send an arbitrary
message on behalf of the user using the method :meth:`~telegram.Bot.answerWebAppQuery` message on behalf of the user using the method :meth:`~telegram.Bot.answerWebAppQuery`
of :class:`~telegram.Bot`. of :class:`~telegram.Bot`. Alternatively, a ``t.me`` link to a Web App of the bot can
be specified in the object instead of the Web App's URL, in which case the Web App
will be opened as if the user pressed the link.
""" """
__slots__ = ("text", "web_app") __slots__ = ("text", "web_app")

View file

@ -52,6 +52,7 @@ from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._linkpreviewoptions import LinkPreviewOptions from telegram._linkpreviewoptions import LinkPreviewOptions
from telegram._messageautodeletetimerchanged import MessageAutoDeleteTimerChanged from telegram._messageautodeletetimerchanged import MessageAutoDeleteTimerChanged
from telegram._messageentity import MessageEntity from telegram._messageentity import MessageEntity
from telegram._paidmedia import PaidMediaInfo
from telegram._passport.passportdata import PassportData from telegram._passport.passportdata import PassportData
from telegram._payment.invoice import Invoice from telegram._payment.invoice import Invoice
from telegram._payment.successfulpayment import SuccessfulPayment from telegram._payment.successfulpayment import SuccessfulPayment
@ -380,7 +381,8 @@ class Message(MaybeInaccessibleMessage):
.. versionchanged:: 20.0 .. versionchanged:: 20.0
|sequenceclassargs| |sequenceclassargs|
caption (:obj:`str`, optional): Caption for the animation, audio, document, photo, video caption (:obj:`str`, optional): Caption for the animation, audio, document, paid media,
photo, video
or voice, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters. or voice, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters.
contact (:class:`telegram.Contact`, optional): Message is a shared contact, information contact (:class:`telegram.Contact`, optional): Message is a shared contact, information
about the contact. about the contact.
@ -571,6 +573,10 @@ class Message(MaybeInaccessibleMessage):
background set. background set.
.. versionadded:: 21.2 .. versionadded:: 21.2
paid_media (:obj:`telegram.PaidMediaInfo`, optional): Message contains paid media;
information about the paid media.
.. versionadded:: NEXT.VERSION
Attributes: Attributes:
message_id (:obj:`int`): Unique message identifier inside this chat. message_id (:obj:`int`): Unique message identifier inside this chat.
@ -692,7 +698,8 @@ class Message(MaybeInaccessibleMessage):
.. versionchanged:: 20.0 .. versionchanged:: 20.0
|tupleclassattrs| |tupleclassattrs|
caption (:obj:`str`): Optional. Caption for the animation, audio, document, photo, video caption (:obj:`str`): Optional. Caption for the animation, audio, document, paid media,
photo, video
or voice, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters. or voice, 0-:tg-const:`telegram.constants.MessageLimit.CAPTION_LENGTH` characters.
contact (:class:`telegram.Contact`): Optional. Message is a shared contact, information contact (:class:`telegram.Contact`): Optional. Message is a shared contact, information
about the contact. about the contact.
@ -884,6 +891,10 @@ class Message(MaybeInaccessibleMessage):
background set background set
.. versionadded:: 21.2 .. versionadded:: 21.2
paid_media (:obj:`telegram.PaidMediaInfo`): Optional. Message contains paid media;
information about the paid media.
.. versionadded:: NEXT.VERSION
.. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by .. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by
:attr:`~telegram.constants.ParseMode.MARKDOWN`, this method now raises a :attr:`~telegram.constants.ParseMode.MARKDOWN`, this method now raises a
@ -950,6 +961,7 @@ class Message(MaybeInaccessibleMessage):
"new_chat_members", "new_chat_members",
"new_chat_photo", "new_chat_photo",
"new_chat_title", "new_chat_title",
"paid_media",
"passport_data", "passport_data",
"photo", "photo",
"pinned_message", "pinned_message",
@ -1067,6 +1079,7 @@ class Message(MaybeInaccessibleMessage):
chat_background_set: Optional[ChatBackground] = None, chat_background_set: Optional[ChatBackground] = None,
effect_id: Optional[str] = None, effect_id: Optional[str] = None,
show_caption_above_media: Optional[bool] = None, show_caption_above_media: Optional[bool] = None,
paid_media: Optional[PaidMediaInfo] = None,
*, *,
api_kwargs: Optional[JSONDict] = None, api_kwargs: Optional[JSONDict] = None,
): ):
@ -1168,6 +1181,7 @@ class Message(MaybeInaccessibleMessage):
self.chat_background_set: Optional[ChatBackground] = chat_background_set self.chat_background_set: Optional[ChatBackground] = chat_background_set
self.effect_id: Optional[str] = effect_id self.effect_id: Optional[str] = effect_id
self.show_caption_above_media: Optional[bool] = show_caption_above_media self.show_caption_above_media: Optional[bool] = show_caption_above_media
self.paid_media: Optional[PaidMediaInfo] = paid_media
self._effective_attachment = DEFAULT_NONE self._effective_attachment = DEFAULT_NONE
@ -1283,6 +1297,7 @@ class Message(MaybeInaccessibleMessage):
data["users_shared"] = UsersShared.de_json(data.get("users_shared"), bot) data["users_shared"] = UsersShared.de_json(data.get("users_shared"), bot)
data["chat_shared"] = ChatShared.de_json(data.get("chat_shared"), bot) data["chat_shared"] = ChatShared.de_json(data.get("chat_shared"), bot)
data["chat_background_set"] = ChatBackground.de_json(data.get("chat_background_set"), bot) data["chat_background_set"] = ChatBackground.de_json(data.get("chat_background_set"), bot)
data["paid_media"] = PaidMediaInfo.de_json(data.get("paid_media"), bot)
# Unfortunately, this needs to be here due to cyclic imports # Unfortunately, this needs to be here due to cyclic imports
from telegram._giveaway import ( # pylint: disable=import-outside-toplevel from telegram._giveaway import ( # pylint: disable=import-outside-toplevel
@ -1346,6 +1361,7 @@ class Message(MaybeInaccessibleMessage):
Location, Location,
PassportData, PassportData,
Sequence[PhotoSize], Sequence[PhotoSize],
PaidMediaInfo,
Poll, Poll,
Sticker, Sticker,
Story, Story,
@ -1369,6 +1385,7 @@ class Message(MaybeInaccessibleMessage):
* :class:`telegram.Location` * :class:`telegram.Location`
* :class:`telegram.PassportData` * :class:`telegram.PassportData`
* List[:class:`telegram.PhotoSize`] * List[:class:`telegram.PhotoSize`]
* :class:`telegram.PaidMediaInfo`
* :class:`telegram.Poll` * :class:`telegram.Poll`
* :class:`telegram.Sticker` * :class:`telegram.Sticker`
* :class:`telegram.Story` * :class:`telegram.Story`
@ -1386,6 +1403,9 @@ class Message(MaybeInaccessibleMessage):
:attr:`dice`, :attr:`passport_data` and :attr:`poll` are now also considered to be an :attr:`dice`, :attr:`passport_data` and :attr:`poll` are now also considered to be an
attachment. attachment.
.. versionchanged:: NEXT.VERSION
:attr:`paid_media` is now also considered to be an attachment.
""" """
if not isinstance(self._effective_attachment, DefaultValue): if not isinstance(self._effective_attachment, DefaultValue):
return self._effective_attachment return self._effective_attachment

290
telegram/_paidmedia.py Normal file
View file

@ -0,0 +1,290 @@
#!/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 objects that represent paid media in Telegram."""
from typing import TYPE_CHECKING, Dict, Final, Optional, Sequence, Tuple, Type
from telegram import constants
from telegram._files.photosize import PhotoSize
from telegram._files.video import Video
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class PaidMedia(TelegramObject):
"""Describes the paid media added to a message. Currently, it can be one of:
* :class:`telegram.PaidMediaPreview`
* :class:`telegram.PaidMediaPhoto`
* :class:`telegram.PaidMediaVideo`
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:: NEXT.VERSION
Args:
type (:obj:`str`): Type of the paid media.
Attributes:
type (:obj:`str`): Type of the paid media.
"""
__slots__ = ("type",)
PREVIEW: Final[str] = constants.PaidMediaType.PREVIEW
""":const:`telegram.constants.PaidMediaType.PREVIEW`"""
PHOTO: Final[str] = constants.PaidMediaType.PHOTO
""":const:`telegram.constants.PaidMediaType.PHOTO`"""
VIDEO: Final[str] = constants.PaidMediaType.VIDEO
""":const:`telegram.constants.PaidMediaType.VIDEO`"""
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.PaidMediaType, type, type)
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PaidMedia"]:
"""Converts JSON data to the appropriate :class:`PaidMedia` 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)
if data is None:
return None
if not data and cls is PaidMedia:
return None
_class_mapping: Dict[str, Type[PaidMedia]] = {
cls.PREVIEW: PaidMediaPreview,
cls.PHOTO: PaidMediaPhoto,
cls.VIDEO: PaidMediaVideo,
}
if cls is PaidMedia 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 PaidMediaPreview(PaidMedia):
"""The paid media isn't available before the payment.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`width`, :attr:`height`, and :attr:`duration`
are equal.
.. versionadded:: NEXT.VERSION
Args:
type (:obj:`str`): Type of the paid media, always :tg-const:`telegram.PaidMedia.PREVIEW`.
width (:obj:`int`, optional): Media width as defined by the sender.
height (:obj:`int`, optional): Media height as defined by the sender.
duration (:obj:`int`, optional): Duration of the media in seconds as defined by the sender.
Attributes:
type (:obj:`str`): Type of the paid media, always :tg-const:`telegram.PaidMedia.PREVIEW`.
width (:obj:`int`): Optional. Media width as defined by the sender.
height (:obj:`int`): Optional. Media height as defined by the sender.
duration (:obj:`int`): Optional. Duration of the media in seconds as defined by the sender.
"""
__slots__ = ("duration", "height", "width")
def __init__(
self,
width: Optional[int] = None,
height: Optional[int] = None,
duration: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=PaidMedia.PREVIEW, api_kwargs=api_kwargs)
with self._unfrozen():
self.width: Optional[int] = width
self.height: Optional[int] = height
self.duration: Optional[int] = duration
self._id_attrs = (self.type, self.width, self.height, self.duration)
class PaidMediaPhoto(PaidMedia):
"""
The paid media is a photo.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`photo` are equal.
.. versionadded:: NEXT.VERSION
Args:
type (:obj:`str`): Type of the paid media, always :tg-const:`telegram.PaidMedia.PHOTO`.
photo (Sequence[:class:`telegram.PhotoSize`]): The photo.
Attributes:
type (:obj:`str`): Type of the paid media, always :tg-const:`telegram.PaidMedia.PHOTO`.
photo (Tuple[:class:`telegram.PhotoSize`]): The photo.
"""
__slots__ = ("photo",)
def __init__(
self,
photo: Sequence["PhotoSize"],
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=PaidMedia.PHOTO, api_kwargs=api_kwargs)
with self._unfrozen():
self.photo: Tuple[PhotoSize, ...] = parse_sequence_arg(photo)
self._id_attrs = (self.type, self.photo)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PaidMediaPhoto"]:
data = cls._parse_data(data)
if not data:
return None
data["photo"] = PhotoSize.de_list(data.get("photo"), bot=bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class PaidMediaVideo(PaidMedia):
"""
The paid media is a video.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`video` are equal.
.. versionadded:: NEXT.VERSION
Args:
type (:obj:`str`): Type of the paid media, always :tg-const:`telegram.PaidMedia.VIDEO`.
video (:class:`telegram.Video`): The video.
Attributes:
type (:obj:`str`): Type of the paid media, always :tg-const:`telegram.PaidMedia.VIDEO`.
video (:class:`telegram.Video`): The video.
"""
__slots__ = ("video",)
def __init__(
self,
video: Video,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=PaidMedia.VIDEO, api_kwargs=api_kwargs)
with self._unfrozen():
self.video: Video = video
self._id_attrs = (self.type, self.video)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PaidMediaVideo"]:
data = cls._parse_data(data)
if not data:
return None
data["video"] = Video.de_json(data.get("video"), bot=bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class PaidMediaInfo(TelegramObject):
"""
Describes the paid media added to a message.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`star_count` and :attr:`paid_media` are equal.
.. versionadded:: NEXT.VERSION
Args:
star_count (:obj:`int`): The number of Telegram Stars that must be paid to buy access to
the media.
paid_media (Sequence[:class:`telegram.PaidMedia`]): Information about the paid media.
Attributes:
star_count (:obj:`int`): The number of Telegram Stars that must be paid to buy access to
the media.
paid_media (Tuple[:class:`telegram.PaidMedia`]): Information about the paid media.
"""
__slots__ = ("paid_media", "star_count")
def __init__(
self,
star_count: int,
paid_media: Sequence[PaidMedia],
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.star_count: int = star_count
self.paid_media: Tuple[PaidMedia, ...] = parse_sequence_arg(paid_media)
self._id_attrs = (self.star_count, self.paid_media)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["PaidMediaInfo"]:
data = cls._parse_data(data)
if not data:
return None
data["paid_media"] = PaidMedia.de_list(data.get("paid_media"), bot=bot)
return super().de_json(data=data, bot=bot)

View file

@ -50,7 +50,7 @@ class PreCheckoutQuery(TelegramObject):
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, `currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies). (2 for the majority of currencies).
invoice_payload (:obj:`str`): Bot specified invoice payload. invoice_payload (:obj:`str`): Bot-specified invoice payload.
shipping_option_id (:obj:`str`, optional): Identifier of the shipping option chosen by the shipping_option_id (:obj:`str`, optional): Identifier of the shipping option chosen by the
user. user.
order_info (:class:`telegram.OrderInfo`, optional): Order info provided by the user. order_info (:class:`telegram.OrderInfo`, optional): Order info provided by the user.
@ -66,7 +66,7 @@ class PreCheckoutQuery(TelegramObject):
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, `currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies). (2 for the majority of currencies).
invoice_payload (:obj:`str`): Bot specified invoice payload. invoice_payload (:obj:`str`): Bot-specified invoice payload.
shipping_option_id (:obj:`str`): Optional. Identifier of the shipping option chosen by the shipping_option_id (:obj:`str`): Optional. Identifier of the shipping option chosen by the
user. user.
order_info (:class:`telegram.OrderInfo`): Optional. Order info provided by the user. order_info (:class:`telegram.OrderInfo`): Optional. Order info provided by the user.

View file

@ -43,13 +43,13 @@ class ShippingQuery(TelegramObject):
Args: Args:
id (:obj:`str`): Unique query identifier. id (:obj:`str`): Unique query identifier.
from_user (:class:`telegram.User`): User who sent the query. from_user (:class:`telegram.User`): User who sent the query.
invoice_payload (:obj:`str`): Bot specified invoice payload. invoice_payload (:obj:`str`): Bot-specified invoice payload.
shipping_address (:class:`telegram.ShippingAddress`): User specified shipping address. shipping_address (:class:`telegram.ShippingAddress`): User specified shipping address.
Attributes: Attributes:
id (:obj:`str`): Unique query identifier. id (:obj:`str`): Unique query identifier.
from_user (:class:`telegram.User`): User who sent the query. from_user (:class:`telegram.User`): User who sent the query.
invoice_payload (:obj:`str`): Bot specified invoice payload. invoice_payload (:obj:`str`): Bot-specified invoice payload.
shipping_address (:class:`telegram.ShippingAddress`): User specified shipping address. shipping_address (:class:`telegram.ShippingAddress`): User specified shipping address.

View file

@ -183,8 +183,9 @@ class TransactionPartner(TelegramObject):
"""This object describes the source of a transaction, or its recipient for outgoing """This object describes the source of a transaction, or its recipient for outgoing
transactions. Currently, it can be one of: transactions. Currently, it can be one of:
* :class:`TransactionPartnerFragment`
* :class:`TransactionPartnerUser` * :class:`TransactionPartnerUser`
* :class:`TransactionPartnerFragment`
* :class:`TransactionPartnerTelegramAds`
* :class:`TransactionPartnerOther` * :class:`TransactionPartnerOther`
Objects of this class are comparable in terms of equality. Two objects of this class are Objects of this class are comparable in terms of equality. Two objects of this class are
@ -207,6 +208,8 @@ class TransactionPartner(TelegramObject):
""":const:`telegram.constants.TransactionPartnerType.USER`""" """:const:`telegram.constants.TransactionPartnerType.USER`"""
OTHER: Final[str] = constants.TransactionPartnerType.OTHER OTHER: Final[str] = constants.TransactionPartnerType.OTHER
""":const:`telegram.constants.TransactionPartnerType.OTHER`""" """:const:`telegram.constants.TransactionPartnerType.OTHER`"""
TELEGRAM_ADS: Final[str] = constants.TransactionPartnerType.TELEGRAM_ADS
""":const:`telegram.constants.TransactionPartnerType.TELEGRAM_ADS`"""
def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None) -> None: def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(api_kwargs=api_kwargs) super().__init__(api_kwargs=api_kwargs)
@ -242,6 +245,7 @@ class TransactionPartner(TelegramObject):
cls.FRAGMENT: TransactionPartnerFragment, cls.FRAGMENT: TransactionPartnerFragment,
cls.USER: TransactionPartnerUser, cls.USER: TransactionPartnerUser,
cls.OTHER: TransactionPartnerOther, cls.OTHER: TransactionPartnerOther,
cls.TELEGRAM_ADS: TransactionPartnerTelegramAds,
} }
if cls is TransactionPartner and data.get("type") in _class_mapping: if cls is TransactionPartner and data.get("type") in _class_mapping:
@ -305,20 +309,29 @@ class TransactionPartnerUser(TransactionPartner):
Args: Args:
user (:class:`telegram.User`): Information about the user. user (:class:`telegram.User`): Information about the user.
invoice_payload (:obj:`str`, optional): Bot-specified invoice payload.
Attributes: Attributes:
type (:obj:`str`): The type of the transaction partner, type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.USER`. always :tg-const:`telegram.TransactionPartner.USER`.
user (:class:`telegram.User`): Information about the user. user (:class:`telegram.User`): Information about the user.
invoice_payload (:obj:`str`): Optional. Bot-specified invoice payload.
""" """
__slots__ = ("user",) __slots__ = ("invoice_payload", "user")
def __init__(self, user: "User", *, api_kwargs: Optional[JSONDict] = None) -> None: def __init__(
self,
user: "User",
invoice_payload: Optional[str] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=TransactionPartner.USER, api_kwargs=api_kwargs) super().__init__(type=TransactionPartner.USER, api_kwargs=api_kwargs)
with self._unfrozen(): with self._unfrozen():
self.user: User = user self.user: User = user
self.invoice_payload: Optional[str] = invoice_payload
self._id_attrs = ( self._id_attrs = (
self.type, self.type,
self.user, self.user,
@ -355,6 +368,23 @@ class TransactionPartnerOther(TransactionPartner):
self._freeze() self._freeze()
class TransactionPartnerTelegramAds(TransactionPartner):
"""Describes a withdrawal transaction to the Telegram Ads platform.
.. versionadded:: NEXT.VERSION
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.TELEGRAM_ADS`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=TransactionPartner.TELEGRAM_ADS, api_kwargs=api_kwargs)
self._freeze()
class StarTransaction(TelegramObject): class StarTransaction(TelegramObject):
"""Describes a Telegram Star transaction. """Describes a Telegram Star transaction.

View file

@ -44,7 +44,7 @@ class SuccessfulPayment(TelegramObject):
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, `currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies). (2 for the majority of currencies).
invoice_payload (:obj:`str`): Bot specified invoice payload. invoice_payload (:obj:`str`): Bot-specified invoice payload.
shipping_option_id (:obj:`str`, optional): Identifier of the shipping option chosen by the shipping_option_id (:obj:`str`, optional): Identifier of the shipping option chosen by the
user. user.
order_info (:class:`telegram.OrderInfo`, optional): Order info provided by the user. order_info (:class:`telegram.OrderInfo`, optional): Order info provided by the user.
@ -60,7 +60,7 @@ class SuccessfulPayment(TelegramObject):
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, `currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_,
it shows the number of digits past the decimal point for each currency it shows the number of digits past the decimal point for each currency
(2 for the majority of currencies). (2 for the majority of currencies).
invoice_payload (:obj:`str`): Bot specified invoice payload. invoice_payload (:obj:`str`): Bot-specified invoice payload.
shipping_option_id (:obj:`str`): Optional. Identifier of the shipping option chosen by the shipping_option_id (:obj:`str`): Optional. Identifier of the shipping option chosen by the
user. user.
order_info (:class:`telegram.OrderInfo`): Optional. Order info provided by the user. order_info (:class:`telegram.OrderInfo`): Optional. Order info provided by the user.

View file

@ -38,7 +38,7 @@ if TYPE_CHECKING:
class InputPollOption(TelegramObject): class InputPollOption(TelegramObject):
""" """
This object contains information about one answer option in a poll to send. This object contains information about one answer option in a poll to be sent.
Objects of this class are comparable in terms of equality. Two objects of this class are Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text` is equal. considered equal, if their :attr:`text` is equal.

View file

@ -37,6 +37,7 @@ from telegram._giveaway import Giveaway, GiveawayWinners
from telegram._linkpreviewoptions import LinkPreviewOptions from telegram._linkpreviewoptions import LinkPreviewOptions
from telegram._messageentity import MessageEntity from telegram._messageentity import MessageEntity
from telegram._messageorigin import MessageOrigin from telegram._messageorigin import MessageOrigin
from telegram._paidmedia import PaidMediaInfo
from telegram._payment.invoice import Invoice from telegram._payment.invoice import Invoice
from telegram._poll import Poll from telegram._poll import Poll
from telegram._story import Story from telegram._story import Story
@ -101,6 +102,10 @@ class ExternalReplyInfo(TelegramObject):
poll (:class:`telegram.Poll`, optional): Message is a native poll, information about the poll (:class:`telegram.Poll`, optional): Message is a native poll, information about the
poll. poll.
venue (:class:`telegram.Venue`, optional): Message is a venue, information about the venue. venue (:class:`telegram.Venue`, optional): Message is a venue, information about the venue.
paid_media (:class:`telegram.PaidMedia`, optional): Message contains paid media;
information about the paid media.
.. versionadded:: NEXT.VERSION
Attributes: Attributes:
origin (:class:`telegram.MessageOrigin`): Origin of the message replied to by the given origin (:class:`telegram.MessageOrigin`): Origin of the message replied to by the given
@ -144,6 +149,10 @@ class ExternalReplyInfo(TelegramObject):
poll (:class:`telegram.Poll`): Optional. Message is a native poll, information about the poll (:class:`telegram.Poll`): Optional. Message is a native poll, information about the
poll. poll.
venue (:class:`telegram.Venue`): Optional. Message is a venue, information about the venue. venue (:class:`telegram.Venue`): Optional. Message is a venue, information about the venue.
paid_media (:class:`telegram.PaidMedia`): Optional. Message contains paid media;
information about the paid media.
.. versionadded:: NEXT.VERSION
""" """
__slots__ = ( __slots__ = (
@ -162,6 +171,7 @@ class ExternalReplyInfo(TelegramObject):
"location", "location",
"message_id", "message_id",
"origin", "origin",
"paid_media",
"photo", "photo",
"poll", "poll",
"sticker", "sticker",
@ -197,6 +207,7 @@ class ExternalReplyInfo(TelegramObject):
location: Optional[Location] = None, location: Optional[Location] = None,
poll: Optional[Poll] = None, poll: Optional[Poll] = None,
venue: Optional[Venue] = None, venue: Optional[Venue] = None,
paid_media: Optional[PaidMediaInfo] = None,
*, *,
api_kwargs: Optional[JSONDict] = None, api_kwargs: Optional[JSONDict] = None,
): ):
@ -225,6 +236,7 @@ class ExternalReplyInfo(TelegramObject):
self.location: Optional[Location] = location self.location: Optional[Location] = location
self.poll: Optional[Poll] = poll self.poll: Optional[Poll] = poll
self.venue: Optional[Venue] = venue self.venue: Optional[Venue] = venue
self.paid_media: Optional[PaidMediaInfo] = paid_media
self._id_attrs = (self.origin,) self._id_attrs = (self.origin,)
@ -263,6 +275,7 @@ class ExternalReplyInfo(TelegramObject):
data["location"] = Location.de_json(data.get("location"), bot) data["location"] = Location.de_json(data.get("location"), bot)
data["poll"] = Poll.de_json(data.get("poll"), bot) data["poll"] = Poll.de_json(data.get("poll"), bot)
data["venue"] = Venue.de_json(data.get("venue"), bot) data["venue"] = Venue.de_json(data.get("venue"), bot)
data["paid_media"] = PaidMediaInfo.de_json(data.get("paid_media"), bot)
return super().de_json(data=data, bot=bot) return super().de_json(data=data, bot=bot)

View file

@ -71,6 +71,7 @@ __all__ = [
"InlineQueryResultType", "InlineQueryResultType",
"InlineQueryResultsButtonLimit", "InlineQueryResultsButtonLimit",
"InputMediaType", "InputMediaType",
"InputPaidMediaType",
"InvoiceLimit", "InvoiceLimit",
"KeyboardButtonRequestUsersLimit", "KeyboardButtonRequestUsersLimit",
"LocationLimit", "LocationLimit",
@ -82,6 +83,7 @@ __all__ = [
"MessageLimit", "MessageLimit",
"MessageOriginType", "MessageOriginType",
"MessageType", "MessageType",
"PaidMediaType",
"ParseMode", "ParseMode",
"PollLimit", "PollLimit",
"PollType", "PollType",
@ -149,7 +151,7 @@ class _AccentColor(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`. #: :data:`telegram.__bot_api_version_info__`.
#: #:
#: .. versionadded:: 20.0 #: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=5) BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=6)
#: :obj:`str`: Telegram Bot API #: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as #: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`. #: :data:`telegram.__bot_api_version__`.
@ -1259,6 +1261,21 @@ class InputMediaType(StringEnum):
""":obj:`str`: Type of :class:`telegram.InputMediaVideo`.""" """:obj:`str`: Type of :class:`telegram.InputMediaVideo`."""
class InputPaidMediaType(StringEnum):
"""This enum contains the available types of :class:`telegram.InputPaidMedia`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
PHOTO = "photo"
""":obj:`str`: Type of :class:`telegram.InputMediaPhoto`."""
VIDEO = "video"
""":obj:`str`: Type of :class:`telegram.InputMediaVideo`."""
class InlineQueryLimit(IntEnum): class InlineQueryLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.InlineQuery`/ """This enum contains limitations for :class:`telegram.InlineQuery`/
:meth:`telegram.Bot.answer_inline_query`. The enum members of this enumeration are instances :meth:`telegram.Bot.answer_inline_query`. The enum members of this enumeration are instances
@ -1602,6 +1619,11 @@ class MessageAttachmentType(StringEnum):
""":obj:`str`: Messages with :attr:`telegram.Message.invoice`.""" """:obj:`str`: Messages with :attr:`telegram.Message.invoice`."""
LOCATION = "location" LOCATION = "location"
""":obj:`str`: Messages with :attr:`telegram.Message.location`.""" """:obj:`str`: Messages with :attr:`telegram.Message.location`."""
PAID_MEDIA = "paid_media"
""":obj:`str`: Messages with :attr:`telegram.Message.paid_media`.
.. versionadded:: NEXT.VERSION
"""
PASSPORT_DATA = "passport_data" PASSPORT_DATA = "passport_data"
""":obj:`str`: Messages with :attr:`telegram.Message.passport_data`.""" """:obj:`str`: Messages with :attr:`telegram.Message.passport_data`."""
PHOTO = "photo" PHOTO = "photo"
@ -1883,6 +1905,11 @@ class MessageType(StringEnum):
""":obj:`str`: Messages with :attr:`telegram.Message.new_chat_title`.""" """:obj:`str`: Messages with :attr:`telegram.Message.new_chat_title`."""
NEW_CHAT_PHOTO = "new_chat_photo" NEW_CHAT_PHOTO = "new_chat_photo"
""":obj:`str`: Messages with :attr:`telegram.Message.new_chat_photo`.""" """:obj:`str`: Messages with :attr:`telegram.Message.new_chat_photo`."""
PAID_MEDIA = "paid_media"
""":obj:`str`: Messages with :attr:`telegram.Message.paid_media`.
.. versionadded:: NEXT.VERSION
"""
PASSPORT_DATA = "passport_data" PASSPORT_DATA = "passport_data"
""":obj:`str`: Messages with :attr:`telegram.Message.passport_data`.""" """:obj:`str`: Messages with :attr:`telegram.Message.passport_data`."""
PHOTO = "photo" PHOTO = "photo"
@ -1951,6 +1978,24 @@ class MessageType(StringEnum):
""" """
class PaidMediaType(StringEnum):
"""
This enum contains the available types of :class:`telegram.PaidMedia`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
PREVIEW = "preview"
""":obj:`str`: The type of :class:`telegram.PaidMediaPreview`."""
VIDEO = "video"
""":obj:`str`: The type of :class:`telegram.PaidMediaVideo`."""
PHOTO = "photo"
""":obj:`str`: The type of :class:`telegram.PaidMediaPhoto`."""
class PollingLimit(IntEnum): class PollingLimit(IntEnum):
"""This enum contains limitations for :paramref:`telegram.Bot.get_updates.limit`. """This enum contains limitations for :paramref:`telegram.Bot.get_updates.limit`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such. The enum members of this enumeration are instances of :class:`int` and can be treated as such.
@ -2490,6 +2535,8 @@ class TransactionPartnerType(StringEnum):
""":obj:`str`: Transaction with a user.""" """:obj:`str`: Transaction with a user."""
OTHER = "other" OTHER = "other"
""":obj:`str`: Transaction with unknown source or recipient.""" """:obj:`str`: Transaction with unknown source or recipient."""
TELEGRAM_ADS = "telegram_ads"
""":obj:`str`: Transaction with Telegram Ads."""
class ParseMode(StringEnum): class ParseMode(StringEnum):

View file

@ -107,6 +107,7 @@ if TYPE_CHECKING:
InputMediaDocument, InputMediaDocument,
InputMediaPhoto, InputMediaPhoto,
InputMediaVideo, InputMediaVideo,
InputPaidMedia,
InputSticker, InputSticker,
LabeledPrice, LabeledPrice,
MessageEntity, MessageEntity,
@ -4216,6 +4217,50 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
) )
async def send_paid_media(
self,
chat_id: Union[str, int],
star_count: int,
media: Sequence["InputPaidMedia"],
caption: Optional[str] = None,
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Optional[Sequence["MessageEntity"]] = None,
show_caption_above_media: Optional[bool] = None,
disable_notification: ODVInput[bool] = DEFAULT_NONE,
protect_content: ODVInput[bool] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
reply_markup: Optional[ReplyMarkup] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
rate_limit_args: Optional[RLARGS] = None,
) -> Message:
return await super().send_paid_media(
chat_id=chat_id,
star_count=star_count,
media=media,
caption=caption,
parse_mode=parse_mode,
caption_entities=caption_entities,
show_caption_above_media=show_caption_above_media,
disable_notification=disable_notification,
protect_content=protect_content,
reply_parameters=reply_parameters,
reply_markup=reply_markup,
allow_sending_without_reply=allow_sending_without_reply,
reply_to_message_id=reply_to_message_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
# updated camelCase aliases # updated camelCase aliases
getMe = get_me getMe = get_me
sendMessage = send_message sendMessage = send_message
@ -4339,3 +4384,4 @@ class ExtBot(Bot, Generic[RLARGS]):
replaceStickerInSet = replace_sticker_in_set replaceStickerInSet = replace_sticker_in_set
refundStarPayment = refund_star_payment refundStarPayment = refund_star_payment
getStarTransactions = get_star_transactions getStarTransactions = get_star_transactions
sendPaidMedia = send_paid_media

View file

@ -23,7 +23,7 @@ from datetime import datetime
from typing import List, Optional, Sequence, Tuple, final from typing import List, Optional, Sequence, Tuple, final
from telegram._files.inputfile import InputFile from telegram._files.inputfile import InputFile
from telegram._files.inputmedia import InputMedia from telegram._files.inputmedia import InputMedia, InputPaidMedia
from telegram._files.inputsticker import InputSticker from telegram._files.inputsticker import InputSticker
from telegram._telegramobject import TelegramObject from telegram._telegramobject import TelegramObject
from telegram._utils.datetime import to_timestamp from telegram._utils.datetime import to_timestamp
@ -117,7 +117,7 @@ class RequestParameter:
return value.attach_uri, [value] return value.attach_uri, [value]
return None, [value] return None, [value]
if isinstance(value, InputMedia) and isinstance(value.media, InputFile): if isinstance(value, (InputMedia, InputPaidMedia)) and isinstance(value.media, InputFile):
# We call to_dict and change the returned dict instead of overriding # We call to_dict and change the returned dict instead of overriding
# value.media in case the same value is reused for another request # value.media in case the same value is reused for another request
data = value.to_dict() data = value.to_dict()

View file

@ -31,6 +31,8 @@ from telegram import (
InputMediaDocument, InputMediaDocument,
InputMediaPhoto, InputMediaPhoto,
InputMediaVideo, InputMediaVideo,
InputPaidMediaPhoto,
InputPaidMediaVideo,
Message, Message,
MessageEntity, MessageEntity,
ReplyParameters, ReplyParameters,
@ -134,6 +136,25 @@ def input_media_document(class_thumb_file):
) )
@pytest.fixture(scope="module")
def input_paid_media_photo():
return InputPaidMediaPhoto(
media=TestInputMediaPhotoBase.media,
)
@pytest.fixture(scope="module")
def input_paid_media_video(class_thumb_file):
return InputPaidMediaVideo(
media=TestInputMediaVideoBase.media,
thumbnail=class_thumb_file,
width=TestInputMediaVideoBase.width,
height=TestInputMediaVideoBase.height,
duration=TestInputMediaVideoBase.duration,
supports_streaming=TestInputMediaVideoBase.supports_streaming,
)
class TestInputMediaVideoBase: class TestInputMediaVideoBase:
type_ = "video" type_ = "video"
media = "NOTAREALFILEID" media = "NOTAREALFILEID"
@ -514,6 +535,91 @@ class TestInputMediaDocumentWithoutRequest(TestInputMediaDocumentBase):
assert input_media_document.thumbnail == data_file("telegram.jpg").as_uri() assert input_media_document.thumbnail == data_file("telegram.jpg").as_uri()
class TestInputPaidMediaPhotoWithoutRequest(TestInputMediaPhotoBase):
def test_slot_behaviour(self, input_paid_media_photo):
inst = input_paid_media_photo
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_expected_values(self, input_paid_media_photo):
assert input_paid_media_photo.type == self.type_
assert input_paid_media_photo.media == self.media
def test_to_dict(self, input_paid_media_photo):
input_paid_media_photo_dict = input_paid_media_photo.to_dict()
assert input_paid_media_photo_dict["type"] == input_paid_media_photo.type
assert input_paid_media_photo_dict["media"] == input_paid_media_photo.media
def test_with_photo(self, photo): # noqa: F811
# fixture found in test_photo
input_paid_media_photo = InputPaidMediaPhoto(photo)
assert input_paid_media_photo.type == self.type_
assert input_paid_media_photo.media == photo.file_id
def test_with_photo_file(self, photo_file): # noqa: F811
# fixture found in test_photo
input_paid_media_photo = InputPaidMediaPhoto(photo_file)
assert input_paid_media_photo.type == self.type_
assert isinstance(input_paid_media_photo.media, InputFile)
def test_with_local_files(self):
input_paid_media_photo = InputPaidMediaPhoto(data_file("telegram.jpg"))
assert input_paid_media_photo.media == data_file("telegram.jpg").as_uri()
class TestInputPaidMediaVideoWithoutRequest(TestInputMediaVideoBase):
def test_slot_behaviour(self, input_paid_media_video):
inst = input_paid_media_video
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_expected_values(self, input_paid_media_video):
assert input_paid_media_video.type == self.type_
assert input_paid_media_video.media == self.media
assert input_paid_media_video.width == self.width
assert input_paid_media_video.height == self.height
assert input_paid_media_video.duration == self.duration
assert input_paid_media_video.supports_streaming == self.supports_streaming
assert isinstance(input_paid_media_video.thumbnail, InputFile)
def test_to_dict(self, input_paid_media_video):
input_paid_media_video_dict = input_paid_media_video.to_dict()
assert input_paid_media_video_dict["type"] == input_paid_media_video.type
assert input_paid_media_video_dict["media"] == input_paid_media_video.media
assert input_paid_media_video_dict["width"] == input_paid_media_video.width
assert input_paid_media_video_dict["height"] == input_paid_media_video.height
assert input_paid_media_video_dict["duration"] == input_paid_media_video.duration
assert (
input_paid_media_video_dict["supports_streaming"]
== input_paid_media_video.supports_streaming
)
assert input_paid_media_video_dict["thumbnail"] == input_paid_media_video.thumbnail
def test_with_video(self, video): # noqa: F811
# fixture found in test_video
input_paid_media_video = InputPaidMediaVideo(video)
assert input_paid_media_video.type == self.type_
assert input_paid_media_video.media == video.file_id
assert input_paid_media_video.width == video.width
assert input_paid_media_video.height == video.height
assert input_paid_media_video.duration == video.duration
def test_with_video_file(self, video_file): # noqa: F811
# fixture found in test_video
input_paid_media_video = InputPaidMediaVideo(video_file)
assert input_paid_media_video.type == self.type_
assert isinstance(input_paid_media_video.media, InputFile)
def test_with_local_files(self):
input_paid_media_video = InputPaidMediaVideo(
data_file("telegram.mp4"), thumbnail=data_file("telegram.jpg")
)
assert input_paid_media_video.media == data_file("telegram.mp4").as_uri()
assert input_paid_media_video.thumbnail == data_file("telegram.jpg").as_uri()
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def media_group(photo, thumb): # noqa: F811 def media_group(photo, thumb): # noqa: F811
return [ return [
@ -1044,3 +1150,20 @@ class TestSendMediaGroupWithRequest:
assert message.caption_entities == () assert message.caption_entities == ()
# make sure that the media was not modified # make sure that the media was not modified
assert media.parse_mode == copied_media.parse_mode assert media.parse_mode == copied_media.parse_mode
async def test_send_paid_media(self, bot, channel_id, photo_file, video_file): # noqa: F811
msg = await bot.send_paid_media(
chat_id=channel_id,
star_count=20,
media=[
InputPaidMediaPhoto(media=photo_file),
InputPaidMediaVideo(media=video_file),
],
caption="bye onlyfans",
show_caption_above_media=True,
)
assert isinstance(msg, Message)
assert msg.caption == "bye onlyfans"
assert msg.show_caption_above_media
assert msg.paid_media.star_count == 20

View file

@ -1244,6 +1244,22 @@ class TestChatWithoutRequest(TestChatBase):
123, [ReactionTypeEmoji(ReactionEmoji.THUMBS_DOWN)], True 123, [ReactionTypeEmoji(ReactionEmoji.THUMBS_DOWN)], True
) )
async def test_instance_method_send_paid_media(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return (
kwargs["chat_id"] == chat.id
and kwargs["media"] == "media"
and kwargs["star_count"] == 42
and kwargs["caption"] == "stars"
)
assert check_shortcut_signature(Chat.send_paid_media, Bot.send_paid_media, ["chat_id"], [])
assert await check_shortcut_call(chat.send_paid_media, chat.get_bot(), "send_paid_media")
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")
def test_mention_html(self): def test_mention_html(self):
chat = Chat(id=1, type="foo") chat = Chat(id=1, type="foo")
with pytest.raises(TypeError, match="Can not create a mention to a private group chat"): with pytest.raises(TypeError, match="Can not create a mention to a private group chat"):

View file

@ -55,13 +55,15 @@ def chat_full_info(bot):
bio=TestChatFullInfoBase.bio, bio=TestChatFullInfoBase.bio,
linked_chat_id=TestChatFullInfoBase.linked_chat_id, linked_chat_id=TestChatFullInfoBase.linked_chat_id,
location=TestChatFullInfoBase.location, location=TestChatFullInfoBase.location,
has_private_forwards=True, has_private_forwards=TestChatFullInfoBase.has_private_forwards,
has_protected_content=True, has_protected_content=TestChatFullInfoBase.has_protected_content,
has_visible_history=True, has_visible_history=TestChatFullInfoBase.has_visible_history,
join_to_send_messages=True, join_to_send_messages=TestChatFullInfoBase.join_to_send_messages,
join_by_request=True, join_by_request=TestChatFullInfoBase.join_by_request,
has_restricted_voice_and_video_messages=True, has_restricted_voice_and_video_messages=(
is_forum=True, TestChatFullInfoBase.has_restricted_voice_and_video_messages
),
is_forum=TestChatFullInfoBase.is_forum,
active_usernames=TestChatFullInfoBase.active_usernames, active_usernames=TestChatFullInfoBase.active_usernames,
emoji_status_custom_emoji_id=TestChatFullInfoBase.emoji_status_custom_emoji_id, emoji_status_custom_emoji_id=TestChatFullInfoBase.emoji_status_custom_emoji_id,
emoji_status_expiration_date=TestChatFullInfoBase.emoji_status_expiration_date, emoji_status_expiration_date=TestChatFullInfoBase.emoji_status_expiration_date,
@ -76,10 +78,11 @@ def chat_full_info(bot):
business_intro=TestChatFullInfoBase.business_intro, business_intro=TestChatFullInfoBase.business_intro,
business_location=TestChatFullInfoBase.business_location, business_location=TestChatFullInfoBase.business_location,
business_opening_hours=TestChatFullInfoBase.business_opening_hours, business_opening_hours=TestChatFullInfoBase.business_opening_hours,
birthdate=Birthdate(1, 1), birthdate=TestChatFullInfoBase.birthdate,
personal_chat=TestChatFullInfoBase.personal_chat, personal_chat=TestChatFullInfoBase.personal_chat,
first_name="first_name", first_name=TestChatFullInfoBase.first_name,
last_name="last_name", last_name=TestChatFullInfoBase.last_name,
can_send_paid_media=TestChatFullInfoBase.can_send_paid_media,
) )
chat.set_bot(bot) chat.set_bot(bot)
chat._unfreeze() chat._unfreeze()
@ -136,6 +139,7 @@ class TestChatFullInfoBase:
personal_chat = Chat(3, "private", "private") personal_chat = Chat(3, "private", "private")
first_name = "first_name" first_name = "first_name"
last_name = "last_name" last_name = "last_name"
can_send_paid_media = True
class TestChatFullInfoWithoutRequest(TestChatFullInfoBase): class TestChatFullInfoWithoutRequest(TestChatFullInfoBase):
@ -188,6 +192,7 @@ class TestChatFullInfoWithoutRequest(TestChatFullInfoBase):
"personal_chat": self.personal_chat.to_dict(), "personal_chat": self.personal_chat.to_dict(),
"first_name": self.first_name, "first_name": self.first_name,
"last_name": self.last_name, "last_name": self.last_name,
"can_send_paid_media": self.can_send_paid_media,
} }
cfi = ChatFullInfo.de_json(json_dict, bot) cfi = ChatFullInfo.de_json(json_dict, bot)
assert cfi.id == self.id_ assert cfi.id == self.id_
@ -232,6 +237,7 @@ class TestChatFullInfoWithoutRequest(TestChatFullInfoBase):
assert cfi.first_name == self.first_name assert cfi.first_name == self.first_name
assert cfi.last_name == self.last_name assert cfi.last_name == self.last_name
assert cfi.max_reaction_count == self.max_reaction_count assert cfi.max_reaction_count == self.max_reaction_count
assert cfi.can_send_paid_media == self.can_send_paid_media
def test_de_json_localization(self, bot, raw_bot, tz_bot): def test_de_json_localization(self, bot, raw_bot, tz_bot):
json_dict = { json_dict = {
@ -305,6 +311,7 @@ class TestChatFullInfoWithoutRequest(TestChatFullInfoBase):
assert cfi_dict["personal_chat"] == cfi.personal_chat.to_dict() assert cfi_dict["personal_chat"] == cfi.personal_chat.to_dict()
assert cfi_dict["first_name"] == cfi.first_name assert cfi_dict["first_name"] == cfi.first_name
assert cfi_dict["last_name"] == cfi.last_name assert cfi_dict["last_name"] == cfi.last_name
assert cfi_dict["can_send_paid_media"] == cfi.can_send_paid_media
assert cfi_dict["max_reaction_count"] == cfi.max_reaction_count assert cfi_dict["max_reaction_count"] == cfi.max_reaction_count

View file

@ -216,6 +216,8 @@ class TestConstantsWithoutRequest:
name = to_snake_case(match.group(1)) name = to_snake_case(match.group(1))
if name == "photo_size": if name == "photo_size":
name = "photo" name = "photo"
if name == "paid_media_info":
name = "paid_media"
try: try:
constants.MessageAttachmentType(name) constants.MessageAttachmentType(name)
except ValueError: except ValueError:

View file

@ -46,6 +46,8 @@ from telegram import (
MessageAutoDeleteTimerChanged, MessageAutoDeleteTimerChanged,
MessageEntity, MessageEntity,
MessageOriginChat, MessageOriginChat,
PaidMediaInfo,
PaidMediaPreview,
PassportData, PassportData,
PhotoSize, PhotoSize,
Poll, Poll,
@ -275,6 +277,7 @@ def message(bot):
{"chat_background_set": ChatBackground(type=BackgroundTypeChatTheme("ice"))}, {"chat_background_set": ChatBackground(type=BackgroundTypeChatTheme("ice"))},
{"effect_id": "123456789"}, {"effect_id": "123456789"},
{"show_caption_above_media": True}, {"show_caption_above_media": True},
{"paid_media": PaidMediaInfo(5, [PaidMediaPreview(10, 10, 10)])},
], ],
ids=[ ids=[
"reply", "reply",
@ -346,6 +349,7 @@ def message(bot):
"chat_background_set", "chat_background_set",
"effect_id", "effect_id",
"show_caption_above_media", "show_caption_above_media",
"paid_media",
], ],
) )
def message_params(bot, request): def message_params(bot, request):
@ -1221,6 +1225,7 @@ class TestMessageWithoutRequest(TestMessageBase):
"game", "game",
"invoice", "invoice",
"location", "location",
"paid_media",
"passport_data", "passport_data",
"photo", "photo",
"poll", "poll",

View file

@ -127,6 +127,8 @@ PTB_EXTRA_PARAMS = {
"InputTextMessageContent": {"disable_web_page_preview"}, # convenience arg, here for bw compat "InputTextMessageContent": {"disable_web_page_preview"}, # convenience arg, here for bw compat
"RevenueWithdrawalState": {"type"}, # attributes common to all subclasses "RevenueWithdrawalState": {"type"}, # attributes common to all subclasses
"TransactionPartner": {"type"}, # attributes common to all subclasses "TransactionPartner": {"type"}, # attributes common to all subclasses
"PaidMedia": {"type"}, # attributes common to all subclasses
"InputPaidMedia": {"type", "media"}, # attributes common to all subclasses
} }
@ -153,6 +155,8 @@ PTB_IGNORED_PARAMS = {
r"BackgroundFill\w+": {"type"}, r"BackgroundFill\w+": {"type"},
r"RevenueWithdrawalState\w+": {"type"}, r"RevenueWithdrawalState\w+": {"type"},
r"TransactionPartner\w+": {"type"}, r"TransactionPartner\w+": {"type"},
r"PaidMedia\w+": {"type"},
r"InputPaidMedia\w+": {"type"},
} }

325
tests/test_paidmedia.py Normal file
View file

@ -0,0 +1,325 @@
#!/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/].
from copy import deepcopy
import pytest
from telegram import (
Dice,
PaidMedia,
PaidMediaInfo,
PaidMediaPhoto,
PaidMediaPreview,
PaidMediaVideo,
PhotoSize,
Video,
)
from telegram.constants import PaidMediaType
from tests.auxil.slots import mro_slots
@pytest.fixture(
scope="module",
params=[
PaidMedia.PREVIEW,
PaidMedia.PHOTO,
PaidMedia.VIDEO,
],
)
def pm_scope_type(request):
return request.param
@pytest.fixture(
scope="module",
params=[
PaidMediaPreview,
PaidMediaPhoto,
PaidMediaVideo,
],
ids=[
PaidMedia.PREVIEW,
PaidMedia.PHOTO,
PaidMedia.VIDEO,
],
)
def pm_scope_class(request):
return request.param
@pytest.fixture(
scope="module",
params=[
(
PaidMediaPreview,
PaidMedia.PREVIEW,
),
(
PaidMediaPhoto,
PaidMedia.PHOTO,
),
(
PaidMediaVideo,
PaidMedia.VIDEO,
),
],
ids=[
PaidMedia.PREVIEW,
PaidMedia.PHOTO,
PaidMedia.VIDEO,
],
)
def pm_scope_class_and_type(request):
return request.param
@pytest.fixture(scope="module")
def paid_media(pm_scope_class_and_type):
# We use de_json here so that we don't have to worry about which class gets which arguments
return pm_scope_class_and_type[0].de_json(
{
"type": pm_scope_class_and_type[1],
"width": TestPaidMediaBase.width,
"height": TestPaidMediaBase.height,
"duration": TestPaidMediaBase.duration,
"video": TestPaidMediaBase.video.to_dict(),
"photo": [p.to_dict() for p in TestPaidMediaBase.photo],
},
bot=None,
)
def paid_media_video():
return PaidMediaVideo(video=TestPaidMediaBase.video)
def paid_media_photo():
return PaidMediaPhoto(photo=TestPaidMediaBase.photo)
@pytest.fixture(scope="module")
def paid_media_info():
return PaidMediaInfo(
star_count=TestPaidMediaInfoBase.star_count,
paid_media=[paid_media_video(), paid_media_photo()],
)
class TestPaidMediaBase:
width = 640
height = 480
duration = 60
video = Video(
file_id="video_file_id",
width=640,
height=480,
file_unique_id="file_unique_id",
duration=60,
)
photo = (
PhotoSize(
file_id="photo_file_id",
width=640,
height=480,
file_unique_id="file_unique_id",
),
)
class TestPaidMediaWithoutRequest(TestPaidMediaBase):
def test_slot_behaviour(self, paid_media):
inst = paid_media
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, pm_scope_class_and_type):
cls = pm_scope_class_and_type[0]
type_ = pm_scope_class_and_type[1]
json_dict = {
"type": type_,
"width": self.width,
"height": self.height,
"duration": self.duration,
"video": self.video.to_dict(),
"photo": [p.to_dict() for p in self.photo],
}
pm = PaidMedia.de_json(json_dict, bot)
assert set(pm.api_kwargs.keys()) == {
"width",
"height",
"duration",
"video",
"photo",
} - set(cls.__slots__)
assert isinstance(pm, PaidMedia)
assert type(pm) is cls
assert pm.type == type_
if "width" in cls.__slots__:
assert pm.width == self.width
assert pm.height == self.height
assert pm.duration == self.duration
if "video" in cls.__slots__:
assert pm.video == self.video
if "photo" in cls.__slots__:
assert pm.photo == self.photo
assert cls.de_json(None, bot) is None
assert PaidMedia.de_json({}, bot) is None
def test_de_json_invalid_type(self, bot):
json_dict = {
"type": "invalid",
"width": self.width,
"height": self.height,
"duration": self.duration,
"video": self.video.to_dict(),
"photo": [p.to_dict() for p in self.photo],
}
pm = PaidMedia.de_json(json_dict, bot)
assert pm.api_kwargs == {
"width": self.width,
"height": self.height,
"duration": self.duration,
"video": self.video.to_dict(),
"photo": [p.to_dict() for p in self.photo],
}
assert type(pm) is PaidMedia
assert pm.type == "invalid"
def test_de_json_subclass(self, pm_scope_class, bot):
"""This makes sure that e.g. PaidMediaPreivew(data) never returns a
TransactionPartnerPhoto instance."""
json_dict = {
"type": "invalid",
"width": self.width,
"height": self.height,
"duration": self.duration,
"video": self.video.to_dict(),
"photo": [p.to_dict() for p in self.photo],
}
assert type(pm_scope_class.de_json(json_dict, bot)) is pm_scope_class
def test_to_dict(self, paid_media):
pm_dict = paid_media.to_dict()
assert isinstance(pm_dict, dict)
assert pm_dict["type"] == paid_media.type
if hasattr(paid_media_info, "width"):
assert pm_dict["width"] == paid_media.width
assert pm_dict["height"] == paid_media.height
assert pm_dict["duration"] == paid_media.duration
if hasattr(paid_media_info, "video"):
assert pm_dict["video"] == paid_media.video.to_dict()
if hasattr(paid_media_info, "photo"):
assert pm_dict["photo"] == [p.to_dict() for p in paid_media.photo]
def test_type_enum_conversion(self):
assert type(PaidMedia("video").type) is PaidMediaType
assert PaidMedia("unknown").type == "unknown"
def test_equality(self, paid_media, bot):
a = PaidMedia("base_type")
b = PaidMedia("base_type")
c = paid_media
d = deepcopy(paid_media)
e = Dice(4, "emoji")
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)
assert c == d
assert hash(c) == hash(d)
assert c != e
assert hash(c) != hash(e)
if hasattr(c, "video"):
json_dict = c.to_dict()
json_dict["video"] = Video("different", "d2", 1, 1, 1).to_dict()
f = c.__class__.de_json(json_dict, bot)
assert c != f
assert hash(c) != hash(f)
if hasattr(c, "photo"):
json_dict = c.to_dict()
json_dict["photo"] = [PhotoSize("different", "d2", 1, 1, 1).to_dict()]
f = c.__class__.de_json(json_dict, bot)
assert c != f
assert hash(c) != hash(f)
class TestPaidMediaInfoBase:
star_count = 200
paid_media = [paid_media_video(), paid_media_photo()]
class TestPaidMediaInfoWithoutRequest(TestPaidMediaInfoBase):
def test_slot_behaviour(self, paid_media_info):
inst = paid_media_info
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 = {
"star_count": self.star_count,
"paid_media": [t.to_dict() for t in self.paid_media],
}
pmi = PaidMediaInfo.de_json(json_dict, bot)
pmi_none = PaidMediaInfo.de_json(None, bot)
assert pmi.paid_media == tuple(self.paid_media)
assert pmi.star_count == self.star_count
assert pmi_none is None
def test_to_dict(self, paid_media_info):
assert paid_media_info.to_dict() == {
"star_count": self.star_count,
"paid_media": [t.to_dict() for t in self.paid_media],
}
def test_equality(self):
pmi1 = PaidMediaInfo(
star_count=self.star_count, paid_media=[paid_media_video(), paid_media_photo()]
)
pmi2 = PaidMediaInfo(
star_count=self.star_count, paid_media=[paid_media_video(), paid_media_photo()]
)
pmi3 = PaidMediaInfo(star_count=100, paid_media=[paid_media_photo()])
assert pmi1 == pmi2
assert hash(pmi1) == hash(pmi2)
assert pmi1 != pmi3
assert hash(pmi1) != hash(pmi3)

View file

@ -29,6 +29,8 @@ from telegram import (
LinkPreviewOptions, LinkPreviewOptions,
MessageEntity, MessageEntity,
MessageOriginUser, MessageOriginUser,
PaidMediaInfo,
PaidMediaPreview,
ReplyParameters, ReplyParameters,
TextQuote, TextQuote,
User, User,
@ -44,6 +46,7 @@ def external_reply_info():
message_id=TestExternalReplyInfoBase.message_id, message_id=TestExternalReplyInfoBase.message_id,
link_preview_options=TestExternalReplyInfoBase.link_preview_options, link_preview_options=TestExternalReplyInfoBase.link_preview_options,
giveaway=TestExternalReplyInfoBase.giveaway, giveaway=TestExternalReplyInfoBase.giveaway,
paid_media=TestExternalReplyInfoBase.paid_media,
) )
@ -59,6 +62,7 @@ class TestExternalReplyInfoBase:
dtm.datetime.now(dtm.timezone.utc).replace(microsecond=0), dtm.datetime.now(dtm.timezone.utc).replace(microsecond=0),
1, 1,
) )
paid_media = PaidMediaInfo(5, [PaidMediaPreview(10, 10, 10)])
class TestExternalReplyInfoWithoutRequest(TestExternalReplyInfoBase): class TestExternalReplyInfoWithoutRequest(TestExternalReplyInfoBase):
@ -76,6 +80,7 @@ class TestExternalReplyInfoWithoutRequest(TestExternalReplyInfoBase):
"message_id": self.message_id, "message_id": self.message_id,
"link_preview_options": self.link_preview_options.to_dict(), "link_preview_options": self.link_preview_options.to_dict(),
"giveaway": self.giveaway.to_dict(), "giveaway": self.giveaway.to_dict(),
"paid_media": self.paid_media.to_dict(),
} }
external_reply_info = ExternalReplyInfo.de_json(json_dict, bot) external_reply_info = ExternalReplyInfo.de_json(json_dict, bot)
@ -86,6 +91,7 @@ class TestExternalReplyInfoWithoutRequest(TestExternalReplyInfoBase):
assert external_reply_info.message_id == self.message_id assert external_reply_info.message_id == self.message_id
assert external_reply_info.link_preview_options == self.link_preview_options assert external_reply_info.link_preview_options == self.link_preview_options
assert external_reply_info.giveaway == self.giveaway assert external_reply_info.giveaway == self.giveaway
assert external_reply_info.paid_media == self.paid_media
assert ExternalReplyInfo.de_json(None, bot) is None assert ExternalReplyInfo.de_json(None, bot) is None
@ -98,6 +104,7 @@ class TestExternalReplyInfoWithoutRequest(TestExternalReplyInfoBase):
assert ext_reply_info_dict["message_id"] == self.message_id assert ext_reply_info_dict["message_id"] == self.message_id
assert ext_reply_info_dict["link_preview_options"] == self.link_preview_options.to_dict() assert ext_reply_info_dict["link_preview_options"] == self.link_preview_options.to_dict()
assert ext_reply_info_dict["giveaway"] == self.giveaway.to_dict() assert ext_reply_info_dict["giveaway"] == self.giveaway.to_dict()
assert ext_reply_info_dict["paid_media"] == self.paid_media.to_dict()
def test_equality(self, external_reply_info): def test_equality(self, external_reply_info):
a = external_reply_info a = external_reply_info

View file

@ -33,6 +33,7 @@ from telegram import (
TransactionPartner, TransactionPartner,
TransactionPartnerFragment, TransactionPartnerFragment,
TransactionPartnerOther, TransactionPartnerOther,
TransactionPartnerTelegramAds,
TransactionPartnerUser, TransactionPartnerUser,
User, User,
) )
@ -101,6 +102,7 @@ def star_transactions():
TransactionPartner.FRAGMENT, TransactionPartner.FRAGMENT,
TransactionPartner.OTHER, TransactionPartner.OTHER,
TransactionPartner.USER, TransactionPartner.USER,
TransactionPartner.TELEGRAM_ADS,
], ],
) )
def tp_scope_type(request): def tp_scope_type(request):
@ -113,11 +115,13 @@ def tp_scope_type(request):
TransactionPartnerFragment, TransactionPartnerFragment,
TransactionPartnerOther, TransactionPartnerOther,
TransactionPartnerUser, TransactionPartnerUser,
TransactionPartnerTelegramAds,
], ],
ids=[ ids=[
TransactionPartner.FRAGMENT, TransactionPartner.FRAGMENT,
TransactionPartner.OTHER, TransactionPartner.OTHER,
TransactionPartner.USER, TransactionPartner.USER,
TransactionPartner.TELEGRAM_ADS,
], ],
) )
def tp_scope_class(request): def tp_scope_class(request):
@ -130,11 +134,13 @@ def tp_scope_class(request):
(TransactionPartnerFragment, TransactionPartner.FRAGMENT), (TransactionPartnerFragment, TransactionPartner.FRAGMENT),
(TransactionPartnerOther, TransactionPartner.OTHER), (TransactionPartnerOther, TransactionPartner.OTHER),
(TransactionPartnerUser, TransactionPartner.USER), (TransactionPartnerUser, TransactionPartner.USER),
(TransactionPartnerTelegramAds, TransactionPartner.TELEGRAM_ADS),
], ],
ids=[ ids=[
TransactionPartner.FRAGMENT, TransactionPartner.FRAGMENT,
TransactionPartner.OTHER, TransactionPartner.OTHER,
TransactionPartner.USER, TransactionPartner.USER,
TransactionPartner.TELEGRAM_ADS,
], ],
) )
def tp_scope_class_and_type(request): def tp_scope_class_and_type(request):
@ -147,6 +153,7 @@ def transaction_partner(tp_scope_class_and_type):
return tp_scope_class_and_type[0].de_json( return tp_scope_class_and_type[0].de_json(
{ {
"type": tp_scope_class_and_type[1], "type": tp_scope_class_and_type[1],
"invoice_payload": TestTransactionPartnerBase.invoice_payload,
"withdrawal_state": TestTransactionPartnerBase.withdrawal_state.to_dict(), "withdrawal_state": TestTransactionPartnerBase.withdrawal_state.to_dict(),
"user": TestTransactionPartnerBase.user.to_dict(), "user": TestTransactionPartnerBase.user.to_dict(),
}, },
@ -244,6 +251,7 @@ class TestStarTransactionWithoutRequest(TestStarTransactionBase):
} }
st = StarTransaction.de_json(json_dict, bot) st = StarTransaction.de_json(json_dict, bot)
st_none = StarTransaction.de_json(None, bot) st_none = StarTransaction.de_json(None, bot)
assert st.api_kwargs == {}
assert st.id == self.id assert st.id == self.id
assert st.amount == self.amount assert st.amount == self.amount
assert st.date == from_timestamp(self.date) assert st.date == from_timestamp(self.date)
@ -329,6 +337,7 @@ class TestStarTransactionsWithoutRequest(TestStarTransactionsBase):
} }
st = StarTransactions.de_json(json_dict, bot) st = StarTransactions.de_json(json_dict, bot)
st_none = StarTransactions.de_json(None, bot) st_none = StarTransactions.de_json(None, bot)
assert st.api_kwargs == {}
assert st.transactions == tuple(self.transactions) assert st.transactions == tuple(self.transactions)
assert st_none is None assert st_none is None
@ -359,6 +368,7 @@ class TestStarTransactionsWithoutRequest(TestStarTransactionsBase):
class TestTransactionPartnerBase: class TestTransactionPartnerBase:
withdrawal_state = withdrawal_state_succeeded() withdrawal_state = withdrawal_state_succeeded()
user = transaction_partner_user().user user = transaction_partner_user().user
invoice_payload = "payload"
class TestTransactionPartnerWithoutRequest(TestTransactionPartnerBase): class TestTransactionPartnerWithoutRequest(TestTransactionPartnerBase):
@ -374,11 +384,14 @@ class TestTransactionPartnerWithoutRequest(TestTransactionPartnerBase):
json_dict = { json_dict = {
"type": type_, "type": type_,
"invoice_payload": self.invoice_payload,
"withdrawal_state": self.withdrawal_state.to_dict(), "withdrawal_state": self.withdrawal_state.to_dict(),
"user": self.user.to_dict(), "user": self.user.to_dict(),
} }
tp = TransactionPartner.de_json(json_dict, bot) tp = TransactionPartner.de_json(json_dict, bot)
assert set(tp.api_kwargs.keys()) == {"user", "withdrawal_state"} - set(cls.__slots__) assert set(tp.api_kwargs.keys()) == {"user", "withdrawal_state", "invoice_payload"} - set(
cls.__slots__
)
assert isinstance(tp, TransactionPartner) assert isinstance(tp, TransactionPartner)
assert type(tp) is cls assert type(tp) is cls
@ -387,6 +400,7 @@ class TestTransactionPartnerWithoutRequest(TestTransactionPartnerBase):
assert tp.withdrawal_state == self.withdrawal_state assert tp.withdrawal_state == self.withdrawal_state
if "user" in cls.__slots__: if "user" in cls.__slots__:
assert tp.user == self.user assert tp.user == self.user
assert tp.invoice_payload == self.invoice_payload
assert cls.de_json(None, bot) is None assert cls.de_json(None, bot) is None
assert TransactionPartner.de_json({}, bot) is None assert TransactionPartner.de_json({}, bot) is None
@ -394,6 +408,7 @@ class TestTransactionPartnerWithoutRequest(TestTransactionPartnerBase):
def test_de_json_invalid_type(self, bot): def test_de_json_invalid_type(self, bot):
json_dict = { json_dict = {
"type": "invalid", "type": "invalid",
"invoice_payload": self.invoice_payload,
"withdrawal_state": self.withdrawal_state.to_dict(), "withdrawal_state": self.withdrawal_state.to_dict(),
"user": self.user.to_dict(), "user": self.user.to_dict(),
} }
@ -401,6 +416,7 @@ class TestTransactionPartnerWithoutRequest(TestTransactionPartnerBase):
assert tp.api_kwargs == { assert tp.api_kwargs == {
"withdrawal_state": self.withdrawal_state.to_dict(), "withdrawal_state": self.withdrawal_state.to_dict(),
"user": self.user.to_dict(), "user": self.user.to_dict(),
"invoice_payload": self.invoice_payload,
} }
assert type(tp) is TransactionPartner assert type(tp) is TransactionPartner
@ -411,6 +427,7 @@ class TestTransactionPartnerWithoutRequest(TestTransactionPartnerBase):
TransactionPartnerFragment instance.""" TransactionPartnerFragment instance."""
json_dict = { json_dict = {
"type": "invalid", "type": "invalid",
"invoice_payload": self.invoice_payload,
"withdrawal_state": self.withdrawal_state.to_dict(), "withdrawal_state": self.withdrawal_state.to_dict(),
"user": self.user.to_dict(), "user": self.user.to_dict(),
} }
@ -423,6 +440,7 @@ class TestTransactionPartnerWithoutRequest(TestTransactionPartnerBase):
assert tp_dict["type"] == transaction_partner.type assert tp_dict["type"] == transaction_partner.type
if hasattr(transaction_partner, "user"): if hasattr(transaction_partner, "user"):
assert tp_dict["user"] == transaction_partner.user.to_dict() assert tp_dict["user"] == transaction_partner.user.to_dict()
assert tp_dict["invoice_payload"] == transaction_partner.invoice_payload
if hasattr(transaction_partner, "withdrawal_state"): if hasattr(transaction_partner, "withdrawal_state"):
assert tp_dict["withdrawal_state"] == transaction_partner.withdrawal_state.to_dict() assert tp_dict["withdrawal_state"] == transaction_partner.withdrawal_state.to_dict()