Bot API 7.9 (#4429)

Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
This commit is contained in:
Poolitzer 2024-09-01 09:32:42 +02:00 committed by GitHub
parent 1e05381133
commit 01f689373c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 557 additions and 54 deletions

View file

@ -11,7 +11,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-7.8-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-7.9-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API version
@ -81,7 +81,7 @@ After installing_ the library, be sure to check out the section on `working with
Telegram API support
~~~~~~~~~~~~~~~~~~~~
All types and methods of the Telegram Bot API **7.8** are natively supported by this library.
All types and methods of the Telegram Bot API **7.9** are natively supported by this library.
In addition, Bot API functionality not yet natively included can still be used as described `in our wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Bot-API-Forward-Compatibility>`_.
Notable Features

View file

@ -130,6 +130,7 @@ Available Types
telegram.reactiontype
telegram.reactiontypecustomemoji
telegram.reactiontypeemoji
telegram.reactiontypepaid
telegram.replykeyboardmarkup
telegram.replykeyboardremove
telegram.replyparameters

View file

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

View file

@ -204,6 +204,7 @@ __all__ = (
"ReactionType",
"ReactionTypeCustomEmoji",
"ReactionTypeEmoji",
"ReactionTypePaid",
"RefundedPayment",
"ReplyKeyboardMarkup",
"ReplyKeyboardRemove",
@ -467,7 +468,13 @@ from ._payment.stars import (
from ._payment.successfulpayment import SuccessfulPayment
from ._poll import InputPollOption, Poll, PollAnswer, PollOption
from ._proximityalerttriggered import ProximityAlertTriggered
from ._reaction import ReactionCount, ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji
from ._reaction import (
ReactionCount,
ReactionType,
ReactionTypeCustomEmoji,
ReactionTypeEmoji,
ReactionTypePaid,
)
from ._reply import ExternalReplyInfo, ReplyParameters, TextQuote
from ._replykeyboardmarkup import ReplyKeyboardMarkup
from ._replykeyboardremove import ReplyKeyboardRemove

View file

@ -18,6 +18,7 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Bot."""
import asyncio
import contextlib
import copy
@ -8179,7 +8180,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
) -> bool:
"""
Use this method to edit name and icon of a topic in a forum supergroup chat. The bot must
be an administrator in the chat for this to work and must have
be an administrator in the chat for this to work and must have the
:paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights,
unless it is the creator of the topic.
@ -8447,7 +8448,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
) -> bool:
"""
Use this method to edit the name of the 'General' topic in a forum supergroup chat. The bot
must be an administrator in the chat for this to work and must have
must be an administrator in the chat for this to work and must have the
:attr:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights.
.. versionadded:: 20.0
@ -8946,7 +8947,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""
Use this method to change the chosen reactions on a message. Service messages can't be
reacted to. Automatically forwarded messages from a channel to its discussion group have
the same available reactions as messages in the channel.
the same available reactions as messages in the channel. Bots can't use paid reactions.
.. versionadded:: 20.8
@ -8959,7 +8960,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
:class:`telegram.ReactionType` | :obj:`str`, optional): A list of reaction
types to set on the message. Currently, as non-premium users, bots can set up to
one reaction per message. A custom emoji reaction can be used if it is either
already present on the message or explicitly allowed by chat administrators.
already present on the message or explicitly allowed by chat administrators. Paid
reactions can't be used by bots.
Tip:
Passed :obj:`str` values will be converted to either
@ -9201,6 +9203,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
protect_content: ODVInput[bool] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
reply_markup: Optional[ReplyMarkup] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -9210,12 +9213,14 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: Optional[JSONDict] = None,
) -> Message:
"""Use this method to send paid media to channel chats.
"""Use this method to send paid media.
.. versionadded:: 21.4
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel| If the chat is a channel, all
Telegram Star proceeds from this media will be credited to the chat's balance.
Otherwise, they will be credited to the bot's balance.
star_count (:obj:`int`): The number of Telegram Stars that must be paid to buy access
to the media.
media (Sequence[:class:`telegram.InputPaidMedia`]): A list describing the media to be
@ -9233,6 +9238,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
: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.
business_connection_id (:obj:`str`, optional): |business_id_str|
.. versionadded:: NEXT.VERSION
Keyword Args:
allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply|
@ -9274,8 +9282,122 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)
async def create_chat_subscription_invite_link(
self,
chat_id: Union[str, int],
subscription_period: int,
subscription_price: int,
name: Optional[str] = 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,
) -> ChatInviteLink:
"""
Use this method to create a `subscription invite link <https://telegram.org/blog/\
superchannels-star-reactions-subscriptions#star-subscriptions>`_ for a channel chat.
The bot must have the :attr:`~telegram.ChatPermissions.can_invite_users` administrator
right. The link can be edited using the :meth:`edit_chat_subscription_invite_link` or
revoked using the :meth:`revoke_chat_invite_link`.
.. versionadded:: NEXT.VERSION
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
subscription_period (:obj:`int`): The number of seconds the subscription will be
active for before the next payment. Currently, it must always be
:tg-const:`telegram.constants.ChatSubscriptionLimit.SUBSCRIPTION_PERIOD` (30 days).
subscription_price (:obj:`int`): The number of Telegram Stars a user must pay initially
and after each subsequent subscription period to be a member of the chat;
:tg-const:`telegram.constants.ChatSubscriptionLimit.MIN_PRICE`-
:tg-const:`telegram.constants.ChatSubscriptionLimit.MAX_PRICE`.
name (:obj:`str`, optional): Invite link name;
0-:tg-const:`telegram.constants.ChatInviteLinkLimit.NAME_LENGTH` characters.
Returns:
:class:`telegram.ChatInviteLink`
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
"chat_id": chat_id,
"subscription_period": subscription_period,
"subscription_price": subscription_price,
"name": name,
}
result = await self._post(
"createChatSubscriptionInviteLink",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return ChatInviteLink.de_json(result, self) # type: ignore[return-value]
async def edit_chat_subscription_invite_link(
self,
chat_id: Union[str, int],
invite_link: Union[str, "ChatInviteLink"],
name: Optional[str] = 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,
) -> ChatInviteLink:
"""
Use this method to edit a subscription invite link created by the bot. The bot must have
:attr:`telegram.ChatPermissions.can_invite_users` administrator right.
.. versionadded:: NEXT.VERSION
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
invite_link (:obj:`str` | :obj:`telegram.ChatInviteLink`): The invite link to edit.
name (:obj:`str`, optional): Invite link name;
0-:tg-const:`telegram.constants.ChatInviteLinkLimit.NAME_LENGTH` characters.
Tip:
Omitting this argument removes the name of the invite link.
Returns:
:class:`telegram.ChatInviteLink`
Raises:
:class:`telegram.error.TelegramError`
"""
link = invite_link.invite_link if isinstance(invite_link, ChatInviteLink) else invite_link
data: JSONDict = {
"chat_id": chat_id,
"invite_link": link,
"name": name,
}
result = await self._post(
"editChatSubscriptionInviteLink",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
return ChatInviteLink.de_json(result, self) # type: ignore[return-value]
def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002
"""See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
@ -9532,3 +9654,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""Alias for :meth:`get_star_transactions`"""
sendPaidMedia = send_paid_media
"""Alias for :meth:`send_paid_media`"""
createChatSubscriptionInviteLink = create_chat_subscription_invite_link
"""Alias for :meth:`create_chat_subscription_invite_link`"""
editChatSubscriptionInviteLink = edit_chat_subscription_invite_link
"""Alias for :meth:`edit_chat_subscription_invite_link`"""

View file

@ -2666,6 +2666,81 @@ class _ChatBase(TelegramObject):
api_kwargs=api_kwargs,
)
async def create_subscription_invite_link(
self,
subscription_period: int,
subscription_price: int,
name: Optional[str] = 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,
) -> "ChatInviteLink":
"""Shortcut for::
await bot.create_chat_subscription_invite_link(
chat_id=update.effective_chat.id, *args, **kwargs
)
For the documentation of the arguments, please see
:meth:`telegram.Bot.create_chat_subscription_invite_link`.
.. versionadded:: NEXT.VERSION
Returns:
:class:`telegram.ChatInviteLink`
"""
return await self.get_bot().create_chat_subscription_invite_link(
chat_id=self.id,
subscription_period=subscription_period,
subscription_price=subscription_price,
name=name,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def edit_subscription_invite_link(
self,
invite_link: Union[str, "ChatInviteLink"],
name: Optional[str] = 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,
) -> "ChatInviteLink":
"""Shortcut for::
await bot.edit_chat_subscription_invite_link(
chat_id=update.effective_chat.id, *args, **kwargs
)
For the documentation of the arguments, please see
:meth:`telegram.Bot.edit_chat_subscription_invite_link`.
.. versionadded:: NEXT.VERSION
Returns:
:class:`telegram.ChatInviteLink`
"""
return await self.get_bot().edit_chat_subscription_invite_link(
chat_id=self.id,
invite_link=invite_link,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
name=name,
)
async def approve_join_request(
self,
user_id: int,
@ -3274,6 +3349,7 @@ class _ChatBase(TelegramObject):
protect_content: ODVInput[bool] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
reply_markup: Optional[ReplyMarkup] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -3314,6 +3390,7 @@ class _ChatBase(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
business_connection_id=business_connection_id,
)

View file

@ -69,6 +69,16 @@ class ChatInviteLink(TelegramObject):
created using this link.
.. versionadded:: 13.8
subscription_period (:obj:`int`, optional): The number of seconds the subscription will be
active for before the next payment.
.. versionadded:: NEXT.VERSION
subscription_price (:obj:`int`, optional): The amount of Telegram Stars a user must pay
initially and after each subsequent subscription period to be a member of the chat
using the link.
.. versionadded:: NEXT.VERSION
Attributes:
invite_link (:obj:`str`): The invite link. If the link was created by another chat
administrator, then the second part of the link will be replaced with ``''``.
@ -96,6 +106,15 @@ class ChatInviteLink(TelegramObject):
created using this link.
.. versionadded:: 13.8
subscription_period (:obj:`int`): Optional. The number of seconds the subscription will be
active for before the next payment.
.. versionadded:: NEXT.VERSION
subscription_price (:obj:`int`): Optional. The amount of Telegram Stars a user must pay
initially and after each subsequent subscription period to be a member of the chat
using the link.
.. versionadded:: NEXT.VERSION
"""
@ -109,6 +128,8 @@ class ChatInviteLink(TelegramObject):
"member_limit",
"name",
"pending_join_request_count",
"subscription_period",
"subscription_price",
)
def __init__(
@ -122,6 +143,8 @@ class ChatInviteLink(TelegramObject):
member_limit: Optional[int] = None,
name: Optional[str] = None,
pending_join_request_count: Optional[int] = None,
subscription_period: Optional[int] = None,
subscription_price: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
@ -140,6 +163,9 @@ class ChatInviteLink(TelegramObject):
self.pending_join_request_count: Optional[int] = (
int(pending_join_request_count) if pending_join_request_count is not None else None
)
self.subscription_period: Optional[int] = subscription_period
self.subscription_price: Optional[int] = subscription_price
self._id_attrs = (
self.invite_link,
self.creates_join_request,

View file

@ -17,6 +17,7 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram ChatMember."""
import datetime
from typing import TYPE_CHECKING, Dict, Final, Optional, Type
@ -391,24 +392,34 @@ class ChatMemberMember(ChatMember):
Args:
user (:class:`telegram.User`): Information about the user.
until_date (:class:`datetime.datetime`, optional): Date when the user's subscription will
expire.
.. versionadded:: NEXT.VERSION
Attributes:
status (:obj:`str`): The member's status in the chat,
always :tg-const:`telegram.ChatMember.MEMBER`.
user (:class:`telegram.User`): Information about the user.
until_date (:class:`datetime.datetime`): Optional. Date when the user's subscription will
expire.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
__slots__ = ("until_date",)
def __init__(
self,
user: User,
until_date: Optional[datetime.datetime] = None,
*,
api_kwargs: Optional[JSONDict] = None,
):
super().__init__(status=ChatMember.MEMBER, user=user, api_kwargs=api_kwargs)
self._freeze()
with self._unfrozen():
self.until_date: Optional[datetime.datetime] = until_date
class ChatMemberRestricted(ChatMember):

View file

@ -280,15 +280,14 @@ class Message(MaybeInaccessibleMessage):
Args:
message_id (:obj:`int`): Unique message identifier inside this chat.
from_user (:class:`telegram.User`, optional): Sender of the message; empty for messages
sent to channels. For backward compatibility, this will contain a fake sender user in
non-channel chats, if the message was sent on behalf of a chat.
sender_chat (:class:`telegram.Chat`, optional): Sender of the message, sent on behalf of a
chat. For example, the channel itself for channel posts, the supergroup itself for
messages from anonymous group administrators, the linked channel for messages
automatically forwarded to the discussion group. For backward compatibility,
:attr:`from_user` contains a fake sender user in non-channel chats, if the message was
sent on behalf of a chat.
from_user (:class:`telegram.User`, optional): Sender of the message; may be empty for
messages sent to channels. For backward compatibility, if the message was sent on
behalf of a chat, the field contains a fake sender user in non-channel chats.
sender_chat (:class:`telegram.Chat`, optional): Sender of the message when sent on behalf
of a chat. For example, the supergroup itself for messages sent by its anonymous
administrators or a linked channel for messages automatically forwarded to the
channel's discussion group. For backward compatibility, if the message was sent on
behalf of a chat, the field from contains a fake sender user in non-channel chats.
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
:class:`datetime.datetime`.
@ -591,15 +590,14 @@ class Message(MaybeInaccessibleMessage):
Attributes:
message_id (:obj:`int`): Unique message identifier inside this chat.
from_user (:class:`telegram.User`): Optional. Sender of the message; empty for messages
sent to channels. For backward compatibility, this will contain a fake sender user in
non-channel chats, if the message was sent on behalf of a chat.
sender_chat (:class:`telegram.Chat`): Optional. Sender of the message, sent on behalf of a
chat. For example, the channel itself for channel posts, the supergroup itself for
messages from anonymous group administrators, the linked channel for messages
automatically forwarded to the discussion group. For backward compatibility,
:attr:`from_user` contains a fake sender user in non-channel chats, if the message was
sent on behalf of a chat.
from_user (:class:`telegram.User`): Optional. Sender of the message; may be empty for
messages sent to channels. For backward compatibility, if the message was sent on
behalf of a chat, the field contains a fake sender user in non-channel chats.
sender_chat (:class:`telegram.Chat`): Optional. Sender of the message when sent on behalf
of a chat. For example, the supergroup itself for messages sent by its anonymous
administrators or a linked channel for messages automatically forwarded to the
channel's discussion group. For backward compatibility, if the message was sent on
behalf of a chat, the field from contains a fake sender user in non-channel chats.
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
:class:`datetime.datetime`.

View file

@ -23,6 +23,7 @@ from datetime import datetime
from typing import TYPE_CHECKING, Dict, Final, Optional, Sequence, Tuple, Type
from telegram import constants
from telegram._paidmedia import PaidMedia
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
@ -310,20 +311,33 @@ class TransactionPartnerUser(TransactionPartner):
Args:
user (:class:`telegram.User`): Information about the user.
invoice_payload (:obj:`str`, optional): Bot-specified invoice payload.
paid_media (Sequence[:class:`telegram.PaidMedia`], optional): Information about the paid
media bought by the user.
.. versionadded:: NEXT.VERSION
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.USER`.
user (:class:`telegram.User`): Information about the user.
invoice_payload (:obj:`str`): Optional. Bot-specified invoice payload.
paid_media (Tuple[:class:`telegram.PaidMedia`]): Optional. Information about the paid
media bought by the user.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("invoice_payload", "user")
__slots__ = (
"invoice_payload",
"paid_media",
"user",
)
def __init__(
self,
user: "User",
invoice_payload: Optional[str] = None,
paid_media: Optional[Sequence[PaidMedia]] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
@ -332,6 +346,7 @@ class TransactionPartnerUser(TransactionPartner):
with self._unfrozen():
self.user: User = user
self.invoice_payload: Optional[str] = invoice_payload
self.paid_media: Optional[Tuple[PaidMedia, ...]] = parse_sequence_arg(paid_media)
self._id_attrs = (
self.type,
self.user,
@ -347,6 +362,7 @@ class TransactionPartnerUser(TransactionPartner):
return None
data["user"] = User.de_json(data.get("user"), bot)
data["paid_media"] = PaidMedia.de_list(data.get("paid_media"), bot=bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]

View file

@ -17,7 +17,8 @@
# 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 represents a Telegram ReactionType."""
from typing import TYPE_CHECKING, Final, Literal, Optional, Union
from typing import TYPE_CHECKING, Dict, Final, Literal, Optional, Type, Union
from telegram import constants
from telegram._telegramobject import TelegramObject
@ -30,16 +31,22 @@ if TYPE_CHECKING:
class ReactionType(TelegramObject):
"""Base class for Telegram ReactionType Objects.
There exist :class:`telegram.ReactionTypeEmoji` and :class:`telegram.ReactionTypeCustomEmoji`.
There exist :class:`telegram.ReactionTypeEmoji`, :class:`telegram.ReactionTypeCustomEmoji`
and :class:`telegram.ReactionTypePaid`.
.. versionadded:: 20.8
.. versionchanged:: NEXT.VERSION
Added paid reaction.
Args:
type (:obj:`str`): Type of the reaction. Can be
:attr:`~telegram.ReactionType.EMOJI` or :attr:`~telegram.ReactionType.CUSTOM_EMOJI`.
:attr:`~telegram.ReactionType.EMOJI`, :attr:`~telegram.ReactionType.CUSTOM_EMOJI` or
:attr:`~telegram.ReactionType.PAID`.
Attributes:
type (:obj:`str`): Type of the reaction. Can be
:attr:`~telegram.ReactionType.EMOJI` or :attr:`~telegram.ReactionType.CUSTOM_EMOJI`.
:attr:`~telegram.ReactionType.EMOJI`, :attr:`~telegram.ReactionType.CUSTOM_EMOJI` or
:attr:`~telegram.ReactionType.PAID`.
"""
@ -49,11 +56,16 @@ class ReactionType(TelegramObject):
""":const:`telegram.constants.ReactionType.EMOJI`"""
CUSTOM_EMOJI: Final[constants.ReactionType] = constants.ReactionType.CUSTOM_EMOJI
""":const:`telegram.constants.ReactionType.CUSTOM_EMOJI`"""
PAID: Final[constants.ReactionType] = constants.ReactionType.PAID
""":const:`telegram.constants.ReactionType.PAID`
.. versionadded:: NEXT.VERSION
"""
def __init__(
self,
type: Union[ # pylint: disable=redefined-builtin
Literal["emoji", "custom_emoji"], constants.ReactionType
Literal["emoji", "custom_emoji", "paid"], constants.ReactionType
],
*,
api_kwargs: Optional[JSONDict] = None,
@ -71,14 +83,20 @@ class ReactionType(TelegramObject):
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
if data is None:
return None
if cls is ReactionType and data.get("type") in [cls.EMOJI, cls.CUSTOM_EMOJI]:
reaction_type = data.pop("type")
if reaction_type == cls.EMOJI:
return ReactionTypeEmoji.de_json(data=data, bot=bot)
return ReactionTypeCustomEmoji.de_json(data=data, bot=bot)
if not data and cls is ReactionType:
return None
_class_mapping: Dict[str, Type[ReactionType]] = {
cls.EMOJI: ReactionTypeEmoji,
cls.CUSTOM_EMOJI: ReactionTypeCustomEmoji,
cls.PAID: ReactionTypePaid,
}
if cls is ReactionType and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data, bot)
return super().de_json(data=data, bot=bot)
@ -152,6 +170,24 @@ class ReactionTypeCustomEmoji(ReactionType):
self._id_attrs = (self.custom_emoji_id,)
class ReactionTypePaid(ReactionType):
"""
The reaction is paid.
.. versionadded:: NEXT.VERSION
Attributes:
type (:obj:`str`): Type of the reaction,
always :tg-const:`telegram.ReactionType.PAID`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None):
super().__init__(type=ReactionType.PAID, api_kwargs=api_kwargs)
self._freeze()
class ReactionCount(TelegramObject):
"""This class represents a reaction added to a message along with the number of times it was
added.

View file

@ -54,6 +54,7 @@ __all__ = [
"ChatLimit",
"ChatMemberStatus",
"ChatPhotoSize",
"ChatSubscriptionLimit",
"ChatType",
"ContactLimit",
"CustomEmojiStickerLimit",
@ -151,7 +152,7 @@ class _AccentColor(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=8)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=9)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@ -2903,6 +2904,11 @@ class ReactionType(StringEnum):
""":obj:`str`: A :class:`telegram.ReactionType` with a normal emoji."""
CUSTOM_EMOJI = "custom_emoji"
""":obj:`str`: A :class:`telegram.ReactionType` with a custom emoji."""
PAID = "paid"
""":obj:`str`: A :class:`telegram.ReactionType` with a paid reaction.
.. versionadded:: NEXT.VERSION
"""
class ReactionEmoji(StringEnum):
@ -3096,3 +3102,22 @@ class BackgroundFillType(StringEnum):
""":obj:`str`: A :class:`telegram.BackgroundFill` with gradient fill."""
FREEFORM_GRADIENT = "freeform_gradient"
""":obj:`str`: A :class:`telegram.BackgroundFill` with freeform_gradient fill."""
class ChatSubscriptionLimit(IntEnum):
"""This enum contains limitations for
:paramref:`telegram.Bot.create_chat_subscription_invite_link.subscription_period` and
:paramref:`telegram.Bot.create_chat_subscription_invite_link.subscription_price`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
SUBSCRIPTION_PERIOD = 2592000
""":obj:`int`: The number of seconds the subscription will be active."""
MIN_PRICE = 1
""":obj:`int`: Amount of stars a user pays, minimum amount the subscription can be set to."""
MAX_PRICE = 2500
""":obj:`int`: Amount of stars a user pays, maximum amount the subscription can be set to."""

View file

@ -4234,6 +4234,7 @@ class ExtBot(Bot, Generic[RLARGS]):
protect_content: ODVInput[bool] = DEFAULT_NONE,
reply_parameters: Optional["ReplyParameters"] = None,
reply_markup: Optional[ReplyMarkup] = None,
business_connection_id: Optional[str] = None,
*,
allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE,
reply_to_message_id: Optional[int] = None,
@ -4263,6 +4264,57 @@ class ExtBot(Bot, Generic[RLARGS]):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
business_connection_id=business_connection_id,
)
async def create_chat_subscription_invite_link(
self,
chat_id: Union[str, int],
subscription_period: int,
subscription_price: int,
name: Optional[str] = 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,
) -> ChatInviteLink:
return await super().create_chat_subscription_invite_link(
chat_id=chat_id,
subscription_period=subscription_period,
subscription_price=subscription_price,
name=name,
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),
)
async def edit_chat_subscription_invite_link(
self,
chat_id: Union[str, int],
invite_link: Union[str, "ChatInviteLink"],
name: Optional[str] = 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,
) -> ChatInviteLink:
return await super().edit_chat_subscription_invite_link(
chat_id=chat_id,
invite_link=invite_link,
name=name,
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
@ -4388,4 +4440,6 @@ class ExtBot(Bot, Generic[RLARGS]):
replaceStickerInSet = replace_sticker_in_set
refundStarPayment = refund_star_payment
getStarTransactions = get_star_transactions
createChatSubscriptionInviteLink = create_chat_subscription_invite_link
editChatSubscriptionInviteLink = edit_chat_subscription_invite_link
sendPaidMedia = send_paid_media

View file

@ -29,15 +29,16 @@ from telegram._utils.strings import TextEncoding
# purposes than testing.
FALLBACKS = (
"W3sidG9rZW4iOiAiNTc5Njk0NzE0OkFBRnBLOHc2emtrVXJENHhTZVl3RjNNTzhlLTRHcm1jeTdjIiwgInBheW1lbnRfc"
"HJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpRME5qWmxOekk1WWpKaSIsICJjaGF0X2 lkIjogIjY3NTY2N"
"jIyNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTMxMDkxMTEzNSIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTgzOD"
"AwNDU3NyIsICJjaGFubmVsX2lkIjogIkBweXRob250ZWxlZ3JhbWJvdHRlc3RzIi wgIm5hbWUiOiAiUFRCIHRlc3RzIG"
"ZhbGxiYWNrIDEiLCAidXNlcm5hbWUiOiAiQHB0Yl9mYWxsYmFja18xX2JvdCJ9LCB7InRva2VuIjogIjU1ODE5NDA2Njp"
"BQUZ3RFBJRmx6R1VsQ2FXSHRUT0VYNFJGclg4dTlETXFmbyIsIC JwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY"
"4NTA2MzpURVNUOllqRXdPRFF3TVRGbU5EY3kiLCAiY2hhdF9pZCI6ICI2NzU2NjYyMjQiLCAic3VwZXJfZ3JvdXBfaWQi"
"OiAiLTEwMDEyMjEyMTY4MzAiLCAiZm9ydW1fZ3 JvdXBfaWQiOiAiLTEwMDE4NTc4NDgzMTQiLCAiY2hhbm5lbF9pZCI6"
"ICJAcHl0aG9udGVsZWdyYW1ib3R0ZXN0cyIsICJuYW1lIjogIlBUQiB0ZXN0cyBmYWxsYmFjayAyIiwgInVzZXJuYW1lI"
"jogIkBwdGJfZmFsbGJhY2tfMl9ib3QifV0="
"HJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpRME5qWmxOekk1WWpKaSIsICJjaGF0X2lkIjogIjY3NTY2Nj"
"IyNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTMxMDkxMTEzNSIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTgzODA"
"wNDU3NyIsICJjaGFubmVsX2lkIjogIkBweXRob250ZWxlZ3JhbWJvdHRlc3RzIiwgIm5hbWUiOiAiUFRCIHRlc3RzIGZh"
"bGxiYWNrIDEiLCAidXNlcm5hbWUiOiAiQHB0Yl9mYWxsYmFja18xX2JvdCIsICJzdWJzY3JpcHRpb25fY2hhbm5lbF9pZ"
"CI6IC0xMDAyMjI5NjQ5MzAzfSwgeyJ0b2tlbiI6ICI1NTgxOTQwNjY6QUFGd0RQSUZsekdVbENhV0h0VE9FWDRSRnJYOH"
"U5RE1xZm8iLCAicGF5bWVudF9wcm92aWRlcl90b2tlbiI6ICIyODQ2ODUwNjM6VEVTVDpZakV3T0RRd01URm1ORGN5Iiw"
"gImNoYXRfaWQiOiAiNjc1NjY2MjI0IiwgInN1cGVyX2dyb3VwX2lkIjogIi0xMDAxMjIxMjE2ODMwIiwgImZvcnVtX2dy"
"b3VwX2lkIjogIi0xMDAxODU3ODQ4MzE0IiwgImNoYW5uZWxfaWQiOiAiQHB5dGhvbnRlbGVncmFtYm90dGVzdHMiLCAib"
"mFtZSI6ICJQVEIgdGVzdHMgZmFsbGJhY2sgMiIsICJ1c2VybmFtZSI6ICJAcHRiX2ZhbGxiYWNrXzJfYm90IiwgInN1Yn"
"NjcmlwdGlvbl9jaGFubmVsX2lkIjogLTEwMDIyMjk2NDkzMDN9XQ=="
)
GITHUB_ACTION = os.getenv("GITHUB_ACTION", None)

View file

@ -206,6 +206,11 @@ def provider_token(bot_info):
return bot_info["payment_provider_token"]
@pytest.fixture(scope="session")
def subscription_channel_id(bot_info):
return bot_info["subscription_channel_id"]
@pytest.fixture
async def app(bot_info):
# We build a new bot each time so that we use `app` in a context manager without problems

View file

@ -2265,6 +2265,21 @@ class TestBotWithoutRequest:
obj = await bot.get_star_transactions(offset=3)
assert isinstance(obj, StarTransactions)
async def test_create_chat_subscription_invite_link(
self,
monkeypatch,
bot,
):
# Since the chat invite link object does not say if the sub args are passed we can
# only check here
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
assert request_data.parameters.get("subscription_period") == 2592000
assert request_data.parameters.get("subscription_price") == 6
monkeypatch.setattr(bot.request, "post", make_assertion)
await bot.create_chat_subscription_invite_link(1234, 2592000, 6)
class TestBotWithRequest:
"""
@ -4261,3 +4276,23 @@ class TestBotWithRequest:
transactions = await bot.get_star_transactions(limit=1)
assert isinstance(transactions, StarTransactions)
assert len(transactions.transactions) == 0
async def test_create_edit_chat_subscription_link(
self, bot, subscription_channel_id, channel_id
):
sub_link = await bot.create_chat_subscription_invite_link(
subscription_channel_id,
name="sub_name",
subscription_period=2592000,
subscription_price=13,
)
assert sub_link.name == "sub_name"
assert sub_link.subscription_period == 2592000
assert sub_link.subscription_price == 13
edited_link = await bot.edit_chat_subscription_invite_link(
chat_id=subscription_channel_id, invite_link=sub_link, name="sub_name_2"
)
assert edited_link.name == "sub_name_2"
assert sub_link.subscription_period == 2592000
assert sub_link.subscription_price == 13

View file

@ -889,6 +889,54 @@ class TestChatWithoutRequest(TestChatBase):
monkeypatch.setattr(chat.get_bot(), "revoke_chat_invite_link", make_assertion)
assert await chat.revoke_invite_link(invite_link=link)
async def test_create_subscription_invite_link(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return (
kwargs["chat_id"] == chat.id
and kwargs["subscription_price"] == 42
and kwargs["subscription_period"] == 42
)
assert check_shortcut_signature(
Chat.create_subscription_invite_link,
Bot.create_chat_subscription_invite_link,
["chat_id"],
[],
)
assert await check_shortcut_call(
chat.create_subscription_invite_link,
chat.get_bot(),
"create_chat_subscription_invite_link",
)
assert await check_defaults_handling(chat.create_subscription_invite_link, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "create_chat_subscription_invite_link", make_assertion)
assert await chat.create_subscription_invite_link(
subscription_price=42, subscription_period=42
)
async def test_edit_subscription_invite_link(self, monkeypatch, chat):
link = "ThisIsALink"
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id and kwargs["invite_link"] == link
assert check_shortcut_signature(
Chat.edit_subscription_invite_link,
Bot.edit_chat_subscription_invite_link,
["chat_id"],
[],
)
assert await check_shortcut_call(
chat.edit_subscription_invite_link,
chat.get_bot(),
"edit_chat_subscription_invite_link",
)
assert await check_defaults_handling(chat.edit_subscription_invite_link, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "edit_chat_subscription_invite_link", make_assertion)
assert await chat.edit_subscription_invite_link(invite_link=link)
async def test_instance_method_get_menu_button(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id

View file

@ -42,6 +42,8 @@ def invite_link(creator):
member_limit=TestChatInviteLinkBase.member_limit,
name=TestChatInviteLinkBase.name,
pending_join_request_count=TestChatInviteLinkBase.pending_join_request_count,
subscription_period=TestChatInviteLinkBase.subscription_period,
subscription_price=TestChatInviteLinkBase.subscription_price,
)
@ -54,6 +56,8 @@ class TestChatInviteLinkBase:
member_limit = 42
name = "LinkName"
pending_join_request_count = 42
subscription_period = 43
subscription_price = 44
class TestChatInviteLinkWithoutRequest(TestChatInviteLinkBase):
@ -91,6 +95,8 @@ class TestChatInviteLinkWithoutRequest(TestChatInviteLinkBase):
"member_limit": self.member_limit,
"name": self.name,
"pending_join_request_count": str(self.pending_join_request_count),
"subscription_period": self.subscription_period,
"subscription_price": self.subscription_price,
}
invite_link = ChatInviteLink.de_json(json_dict, bot)
@ -106,6 +112,8 @@ class TestChatInviteLinkWithoutRequest(TestChatInviteLinkBase):
assert invite_link.member_limit == self.member_limit
assert invite_link.name == self.name
assert invite_link.pending_join_request_count == self.pending_join_request_count
assert invite_link.subscription_period == self.subscription_period
assert invite_link.subscription_price == self.subscription_price
def test_de_json_localization(self, tz_bot, bot, raw_bot, creator):
json_dict = {
@ -146,6 +154,8 @@ class TestChatInviteLinkWithoutRequest(TestChatInviteLinkBase):
assert invite_link_dict["member_limit"] == self.member_limit
assert invite_link_dict["name"] == self.name
assert invite_link_dict["pending_join_request_count"] == self.pending_join_request_count
assert invite_link_dict["subscription_period"] == self.subscription_period
assert invite_link_dict["subscription_price"] == self.subscription_price
def test_equality(self):
a = ChatInviteLink("link", User(1, "", False), True, True, True)

View file

@ -101,7 +101,7 @@ def chat_member_administrator():
def chat_member_member():
return ChatMemberMember(CMDefaults.user)
return ChatMemberMember(CMDefaults.user, until_date=CMDefaults.until_date)
def chat_member_restricted():
@ -230,7 +230,9 @@ class TestChatMemberTypesWithoutRequest:
def test_de_json_chatmemberbanned_localization(self, chat_member_type, tz_bot, bot, raw_bot):
# We only test two classes because the other three don't have datetimes in them.
if isinstance(chat_member_type, (ChatMemberBanned, ChatMemberRestricted)):
if isinstance(
chat_member_type, (ChatMemberBanned, ChatMemberRestricted, ChatMemberMember)
):
json_dict = make_json_dict(chat_member_type, include_optional_args=True)
chatmember_raw = ChatMember.de_json(json_dict, raw_bot)
chatmember_bot = ChatMember.de_json(json_dict, bot)

View file

@ -28,6 +28,7 @@ from telegram import (
ReactionType,
ReactionTypeCustomEmoji,
ReactionTypeEmoji,
ReactionTypePaid,
)
from telegram.constants import ReactionEmoji
from tests.auxil.slots import mro_slots
@ -48,6 +49,10 @@ def reaction_type_emoji():
return ReactionTypeEmoji(RTDefaults.normal_emoji)
def reaction_type_paid():
return ReactionTypePaid()
def make_json_dict(instance: ReactionType, include_optional_args: bool = False) -> dict:
"""Used to make the json dict which we use for testing de_json. Similar to iter_args()"""
json_dict = {"type": instance.type}
@ -99,6 +104,7 @@ def reaction_type(request):
[
reaction_type_custom_emoji,
reaction_type_emoji,
reaction_type_paid,
],
indirect=True,
)
@ -112,6 +118,7 @@ class TestReactionTypesWithoutRequest:
def test_de_json_required_args(self, bot, reaction_type):
cls = reaction_type.__class__
assert cls.de_json(None, bot) is None
assert ReactionType.de_json({}, bot) is None
json_dict = make_json_dict(reaction_type)
const_reaction_type = ReactionType.de_json(json_dict, bot)
@ -155,7 +162,7 @@ class TestReactionTypesWithoutRequest:
assert reaction_type_dict["type"] == reaction_type.type
if reaction_type.type == ReactionType.EMOJI:
assert reaction_type_dict["emoji"] == reaction_type.emoji
else:
elif reaction_type.type == ReactionType.CUSTOM_EMOJI:
assert reaction_type_dict["custom_emoji_id"] == reaction_type.custom_emoji_id
for slot in reaction_type.__slots__: # additional verification for the optional args

View file

@ -24,6 +24,8 @@ import pytest
from telegram import (
Dice,
PaidMediaPhoto,
PhotoSize,
RevenueWithdrawalState,
RevenueWithdrawalStateFailed,
RevenueWithdrawalStatePending,
@ -62,6 +64,16 @@ def withdrawal_state_pending():
def transaction_partner_user():
return TransactionPartnerUser(
user=User(id=1, is_bot=False, first_name="first_name", username="username"),
invoice_payload="payload",
paid_media=[
PaidMediaPhoto(
photo=[
PhotoSize(
file_id="file_id", width=1, height=1, file_unique_id="file_unique_id"
)
]
)
],
)