From c1c5438f37f582567f629cc1b4aa625ed17e6e82 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Wed, 29 May 2024 13:59:23 -0400 Subject: [PATCH 01/12] Remove Functionality Deprecated in Bot API 7.3 (#4266) --- docs/auxil/sphinx_hooks.py | 1 + docs/source/telegram.chat.rst | 2 + docs/source/telegram.chatfullinfo.rst | 4 +- docs/source/telegram.photosize.rst | 2 +- telegram/_bot.py | 8 +- telegram/_chat.py | 1029 +++---------------------- telegram/_chatfullinfo.py | 470 +++++++++-- telegram/_message.py | 2 +- telegram/constants.py | 5 +- tests/test_chat.py | 248 +----- tests/test_chatfullinfo.py | 202 +++-- tests/test_official/exceptions.py | 5 +- tests/test_telegramobject.py | 6 +- 13 files changed, 709 insertions(+), 1275 deletions(-) diff --git a/docs/auxil/sphinx_hooks.py b/docs/auxil/sphinx_hooks.py index 3074ac7af..2cfbfe140 100644 --- a/docs/auxil/sphinx_hooks.py +++ b/docs/auxil/sphinx_hooks.py @@ -46,6 +46,7 @@ PRIVATE_BASE_CLASSES = { "_BaseThumbedMedium": "TelegramObject", "_BaseMedium": "TelegramObject", "_CredentialsBase": "TelegramObject", + "_ChatBase": "TelegramObject", } diff --git a/docs/source/telegram.chat.rst b/docs/source/telegram.chat.rst index 3ef967247..d69b08b60 100644 --- a/docs/source/telegram.chat.rst +++ b/docs/source/telegram.chat.rst @@ -1,6 +1,8 @@ Chat ==== +.. Also lists methods of _ChatBase, but not the ones of TelegramObject .. autoclass:: telegram.Chat :members: :show-inheritance: + :inherited-members: TelegramObject diff --git a/docs/source/telegram.chatfullinfo.rst b/docs/source/telegram.chatfullinfo.rst index f15dbeeda..3bbc9fa9e 100644 --- a/docs/source/telegram.chatfullinfo.rst +++ b/docs/source/telegram.chatfullinfo.rst @@ -1,6 +1,8 @@ ChatFullInfo ============ +.. Also lists methods of _ChatBase, but not the ones of TelegramObject .. autoclass:: telegram.ChatFullInfo :members: - :show-inheritance: \ No newline at end of file + :show-inheritance: + :inherited-members: TelegramObject \ No newline at end of file diff --git a/docs/source/telegram.photosize.rst b/docs/source/telegram.photosize.rst index d36e6e27f..be044f116 100644 --- a/docs/source/telegram.photosize.rst +++ b/docs/source/telegram.photosize.rst @@ -1,6 +1,6 @@ PhotoSize ========= -.. Also lists methods of _BaseThumbedMedium, but not the ones of TelegramObject +.. Also lists methods of _BaseMedium, but not the ones of TelegramObject .. autoclass:: telegram.PhotoSize :members: diff --git a/telegram/_bot.py b/telegram/_bot.py index cf08284c7..3ae0ce5f1 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -1152,7 +1152,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): Note: Since the release of Bot API 5.5 it can be impossible to forward messages from some chats. Use the attributes :attr:`telegram.Message.has_protected_content` and - :attr:`telegram.Chat.has_protected_content` to check this. + :attr:`telegram.ChatFullInfo.has_protected_content` to check this. As a workaround, it is still possible to use :meth:`copy_message`. However, this behaviour is undocumented and might be changed by Telegram. @@ -4610,8 +4610,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): ) -> bool: """Use this method to set a new group sticker set for a supergroup. The bot must be an administrator in the chat for this to work and must have the appropriate - admin rights. Use the field :attr:`telegram.Chat.can_set_sticker_set` optionally returned - in :meth:`get_chat` requests to check if the bot can use this method. + admin rights. Use the field :attr:`telegram.ChatFullInfo.can_set_sticker_set` optionally + returned in :meth:`get_chat` requests to check if the bot can use this method. Args: chat_id (:obj:`int` | :obj:`str`): |chat_id_group| @@ -4644,7 +4644,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): ) -> bool: """Use this method to delete a group sticker set from a supergroup. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. - Use the field :attr:`telegram.Chat.can_set_sticker_set` optionally returned in + Use the field :attr:`telegram.ChatFullInfo.can_set_sticker_set` optionally returned in :meth:`get_chat` requests to check if the bot can use this method. Args: diff --git a/telegram/_chat.py b/telegram/_chat.py index b94d006e1..8250a8f17 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -20,36 +20,25 @@ """This module contains an object that represents a Telegram Chat.""" from datetime import datetime from html import escape -from typing import TYPE_CHECKING, Any, Final, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Final, Optional, Sequence, Tuple, Union from telegram import constants -from telegram._birthdate import Birthdate -from telegram._chatlocation import ChatLocation from telegram._chatpermissions import ChatPermissions -from telegram._files.chatphoto import ChatPhoto from telegram._forumtopic import ForumTopic from telegram._menubutton import MenuButton from telegram._reaction import ReactionType from telegram._telegramobject import TelegramObject from telegram._utils import enum -from telegram._utils.argumentparsing import parse_sequence_arg -from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.defaultvalue import DEFAULT_NONE from telegram._utils.types import CorrectOptionID, FileInput, JSONDict, ODVInput, ReplyMarkup -from telegram._utils.warnings import warn from telegram.helpers import escape_markdown from telegram.helpers import mention_html as helpers_mention_html from telegram.helpers import mention_markdown as helpers_mention_markdown -from telegram.warnings import PTBDeprecationWarning if TYPE_CHECKING: from telegram import ( Animation, Audio, - Bot, - BusinessIntro, - BusinessLocation, - BusinessOpeningHours, ChatInviteLink, ChatMember, Contact, @@ -77,722 +66,41 @@ if TYPE_CHECKING: ) -_deprecated_attrs = ( - "accent_color_id", - "active_usernames", - "available_reactions", - "background_custom_emoji_id", - "bio", - "birthdate", - "business_intro", - "business_location", - "business_opening_hours", - "can_set_sticker_set", - "custom_emoji_sticker_set_name", - "description", - "emoji_status_custom_emoji_id", - "emoji_status_expiration_date", - "has_aggressive_anti_spam_enabled", - "has_hidden_members", - "has_private_forwards", - "has_protected_content", - "has_restricted_voice_and_video_messages", - "has_visible_history", - "invite_link", - "join_by_request", - "join_to_send_messages", - "linked_chat_id", - "location", - "message_auto_delete_time", - "permissions", - "personal_chat", - "photo", - "pinned_message", - "profile_accent_color_id", - "profile_background_custom_emoji_id", - "slow_mode_delay", - "sticker_set_name", - "unrestrict_boost_count", -) +class _ChatBase(TelegramObject): + """Base class for :class:`telegram.Chat` and :class:`telegram.ChatFullInfo`. - -class Chat(TelegramObject): - """This object represents a chat. - - Objects of this class are comparable in terms of equality. Two objects of this class are - considered equal, if their :attr:`id` is equal. - - .. versionchanged:: 20.0 - - * Removed the deprecated methods ``kick_member`` and ``get_members_count``. - * The following are now keyword-only arguments in Bot methods: - ``location``, ``filename``, ``contact``, ``{read, write, connect, pool}_timeout``, - ``api_kwargs``. Use a named argument for those, - and notice that some positional arguments changed position as a result. - - .. versionchanged:: 20.0 - Removed the attribute ``all_members_are_administrators``. As long as Telegram provides - this field for backwards compatibility, it is available through - :attr:`~telegram.TelegramObject.api_kwargs`. - - Args: - id (:obj:`int`): Unique identifier for this chat. This number may be greater than 32 bits - and some programming languages may have difficulty/silent defects in interpreting it. - But it is smaller than 52 bits, so a signed 64-bit integer or double-precision float - type are safe for storing this identifier. - type (:obj:`str`): Type of chat, can be either :attr:`PRIVATE`, :attr:`GROUP`, - :attr:`SUPERGROUP` or :attr:`CHANNEL`. - title (:obj:`str`, optional): Title, for supergroups, channels and group chats. - username (:obj:`str`, optional): Username, for private chats, supergroups and channels if - available. - first_name (:obj:`str`, optional): First name of the other party in a private chat. - last_name (:obj:`str`, optional): Last name of the other party in a private chat. - photo (:class:`telegram.ChatPhoto`, optional): Chat photo. - Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - bio (:obj:`str`, optional): Bio of the other party in a private chat. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_private_forwards (:obj:`bool`, optional): :obj:`True`, if privacy settings of the other - party in the private chat allows to use ``tg://user?id=`` links only in chats - with the user. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 13.9 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - description (:obj:`str`, optional): Description, for groups, supergroups and channel chats. - Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - invite_link (:obj:`str`, optional): Primary invite link, for groups, supergroups and - channel. Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - pinned_message (:class:`telegram.Message`, optional): The most recent pinned message - (by sending date). Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions, - for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between - consecutive messages sent by each unprivileged user. - Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - message_auto_delete_time (:obj:`int`, optional): The time after which all messages sent to - the chat will be automatically deleted; in seconds. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 13.4 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_protected_content (:obj:`bool`, optional): :obj:`True`, if messages from the chat can't - be forwarded to other chats. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 13.9 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_visible_history (:obj:`bool`, optional): :obj:`True`, if new chat members will have - access to old messages; available only to chat administrators. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set. - Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - can_set_sticker_set (:obj:`bool`, optional): :obj:`True`, if the bot can change group the - sticker set. Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - linked_chat_id (:obj:`int`, optional): Unique identifier for the linked chat, i.e. the - discussion group identifier for a channel and vice versa; for supergroups and channel - chats. Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which - the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - join_to_send_messages (:obj:`bool`, optional): :obj:`True`, if users need to join the - supergroup before they can send messages. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - join_by_request (:obj:`bool`, optional): :obj:`True`, if all users directly joining the - supergroup without using an invite link need to be approved by supergroup - administrators. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_restricted_voice_and_video_messages (:obj:`bool`, optional): :obj:`True`, if the - privacy settings of the other party restrict sending voice and video note messages - in the private chat. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - is_forum (:obj:`bool`, optional): :obj:`True`, if the supergroup chat is a forum - (has topics_ enabled). - - .. versionadded:: 20.0 - active_usernames (Sequence[:obj:`str`], optional): If set, the list of all `active chat - usernames `_; for private chats, supergroups and channels. Returned - only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - business_intro (:class:`telegram.BusinessIntro`, optional): For private chats with - business accounts, the intro of the business. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.1 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - business_location (:class:`telegram.BusinessLocation`, optional): For private chats with - business accounts, the location of the business. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.1 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - business_opening_hours (:class:`telegram.BusinessOpeningHours`, optional): For private - chats with business accounts, the opening hours of the business. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.1 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - available_reactions (Sequence[:class:`telegram.ReactionType`], optional): List of available - reactions allowed in the chat. If omitted, then all of - :const:`telegram.constants.ReactionEmoji` are allowed. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - accent_color_id (:obj:`int`, optional): Identifier of the - :class:`accent color ` for the chat name and - backgrounds of the chat photo, reply header, and link preview. See `accent colors`_ - for more details. Returned only in :meth:`telegram.Bot.get_chat`. Always returned in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - background_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of emoji chosen - by the chat for the reply header and link preview background. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - profile_accent_color_id (:obj:`int`, optional): Identifier of the - :class:`accent color ` for the chat's profile - background. See profile `accent colors`_ for more details. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - profile_background_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of - the emoji chosen by the chat for its profile background. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - emoji_status_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of emoji - status of the chat or the other party in a private chat. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - emoji_status_expiration_date (:class:`datetime.datetime`, optional): Expiration date of - emoji status of the chat or the other party in a private chat, in seconds. Returned - only in :meth:`telegram.Bot.get_chat`. - |datetime_localization| - - .. versionadded:: 20.5 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_aggressive_anti_spam_enabled (:obj:`bool`, optional): :obj:`True`, if aggressive - anti-spam checks are enabled in the supergroup. The field is only available to chat - administrators. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_hidden_members (:obj:`bool`, optional): :obj:`True`, if non-administrators can only - get the list of bots and administrators in the chat. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - unrestrict_boost_count (:obj:`int`, optional): For supergroups, the minimum number of - boosts that a non-administrator user needs to add in order to ignore slow mode and chat - permissions. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - custom_emoji_sticker_set_name (:obj:`str`, optional): For supergroups, the name of the - group's custom emoji sticker set. Custom emoji from this set can be used by all users - and bots in the group. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - birthdate (:obj:`telegram.Birthdate`, optional): For private chats, - the date of birth of the user. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.1 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - personal_chat (:obj:`telegram.Chat`, optional): For private chats, the personal channel of - the user. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.1 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - - Attributes: - id (:obj:`int`): Unique identifier for this chat. This number may be greater than 32 bits - and some programming languages may have difficulty/silent defects in interpreting it. - But it is smaller than 52 bits, so a signed 64-bit integer or double-precision float - type are safe for storing this identifier. - type (:obj:`str`): Type of chat, can be either :attr:`PRIVATE`, :attr:`GROUP`, - :attr:`SUPERGROUP` or :attr:`CHANNEL`. - title (:obj:`str`): Optional. Title, for supergroups, channels and group chats. - username (:obj:`str`): Optional. Username, for private chats, supergroups and channels if - available. - first_name (:obj:`str`): Optional. First name of the other party in a private chat. - last_name (:obj:`str`): Optional. Last name of the other party in a private chat. - photo (:class:`telegram.ChatPhoto`): Optional. Chat photo. - Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - bio (:obj:`str`): Optional. Bio of the other party in a private chat. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_private_forwards (:obj:`bool`): Optional. :obj:`True`, if privacy settings of the other - party in the private chat allows to use ``tg://user?id=`` links only in chats - with the user. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 13.9 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats. - Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - invite_link (:obj:`str`): Optional. Primary invite link, for groups, supergroups and - channel. Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - pinned_message (:class:`telegram.Message`): Optional. The most recent pinned message - (by sending date). Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions, - for groups and supergroups. Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between - consecutive messages sent by each unprivileged user. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - message_auto_delete_time (:obj:`int`): Optional. The time after which all messages sent to - the chat will be automatically deleted; in seconds. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 13.4 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_protected_content (:obj:`bool`): Optional. :obj:`True`, if messages from the chat can't - be forwarded to other chats. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 13.9 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_visible_history (:obj:`bool`): Optional. :obj:`True`, if new chat members will have - access to old messages; available only to chat administrators. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set. - Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the - sticker set. Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - linked_chat_id (:obj:`int`): Optional. Unique identifier for the linked chat, i.e. the - discussion group identifier for a channel and vice versa; for supergroups and channel - chats. Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which - the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`. - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - join_to_send_messages (:obj:`bool`): Optional. :obj:`True`, if users need to join - the supergroup before they can send messages. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - join_by_request (:obj:`bool`): Optional. :obj:`True`, if all users directly joining the - supergroup without using an invite link need to be approved by supergroup - administrators. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_restricted_voice_and_video_messages (:obj:`bool`): Optional. :obj:`True`, if the - privacy settings of the other party restrict sending voice and video note messages - in the private chat. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - is_forum (:obj:`bool`): Optional. :obj:`True`, if the supergroup chat is a forum - (has topics_ enabled). - - .. versionadded:: 20.0 - active_usernames (Tuple[:obj:`str`]): Optional. If set, the list of all `active chat - usernames `_; for private chats, supergroups and channels. Returned - only in :meth:`telegram.Bot.get_chat`. - This list is empty if the chat has no active usernames or this chat instance was not - obtained via :meth:`~telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - business_intro (:class:`telegram.BusinessIntro`): Optional. For private chats with - business accounts, the intro of the business. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.1 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - business_location (:class:`telegram.BusinessLocation`): Optional. For private chats with - business accounts, the location of the business. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.1 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - business_opening_hours (:class:`telegram.BusinessOpeningHours`): Optional. For private - chats with business accounts, the opening hours of the business. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.1 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - available_reactions (Tuple[:class:`telegram.ReactionType`]): Optional. List of available - reactions allowed in the chat. If omitted, then all of - :const:`telegram.constants.ReactionEmoji` are allowed. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - accent_color_id (:obj:`int`): Optional. Identifier of the - :class:`accent color ` for the chat name and - backgrounds of the chat photo, reply header, and link preview. See `accent colors`_ - for more details. Returned only in :meth:`telegram.Bot.get_chat`. Always returned in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - background_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of emoji chosen - by the chat for the reply header and link preview background. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - profile_accent_color_id (:obj:`int`): Optional. Identifier of the - :class:`accent color ` for the chat's profile - background. See profile `accent colors`_ for more details. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - profile_background_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of - the emoji chosen by the chat for its profile background. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.8 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - emoji_status_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of emoji - status of the chat or the other party in a private chat. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - emoji_status_expiration_date (:class:`datetime.datetime`): Optional. Expiration date of - emoji status of the chat or the other party in a private chat, in seconds. Returned - only in :meth:`telegram.Bot.get_chat`. - |datetime_localization| - - .. versionadded:: 20.5 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_aggressive_anti_spam_enabled (:obj:`bool`): Optional. :obj:`True`, if aggressive - anti-spam checks are enabled in the supergroup. The field is only available to chat - administrators. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - has_hidden_members (:obj:`bool`): Optional. :obj:`True`, if non-administrators can only - get the list of bots and administrators in the chat. Returned only in - :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 20.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - unrestrict_boost_count (:obj:`int`): Optional. For supergroups, the minimum number of - boosts that a non-administrator user needs to add in order to ignore slow mode and chat - permissions. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - custom_emoji_sticker_set_name (:obj:`str`): Optional. For supergroups, the name of the - group's custom emoji sticker set. Custom emoji from this set can be used by all users - and bots in the group. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.0 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - birthdate (:obj:`telegram.Birthdate`): Optional. For private chats, - the date of birth of the user. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.1 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - personal_chat (:obj:`telegram.Chat`): Optional. For private chats, the personal channel of - the user. Returned only in :meth:`telegram.Bot.get_chat`. - - .. versionadded:: 21.1 - - .. deprecated:: 21.2 - In accordance to Bot API 7.3, this attribute will be moved to - :class:`telegram.ChatFullInfo`. - - .. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups - .. _accent colors: https://core.telegram.org/bots/api#accent-colors + .. versionadded:: NEXT.VERSION """ - __slots__ = ( - "accent_color_id", - "active_usernames", - "available_reactions", - "background_custom_emoji_id", - "bio", - "birthdate", - "business_intro", - "business_location", - "business_opening_hours", - "can_set_sticker_set", - "custom_emoji_sticker_set_name", - "description", - "emoji_status_custom_emoji_id", - "emoji_status_expiration_date", - "first_name", - "has_aggressive_anti_spam_enabled", - "has_hidden_members", - "has_private_forwards", - "has_protected_content", - "has_restricted_voice_and_video_messages", - "has_visible_history", - "id", - "invite_link", - "is_forum", - "join_by_request", - "join_to_send_messages", - "last_name", - "linked_chat_id", - "location", - "message_auto_delete_time", - "permissions", - "personal_chat", - "photo", - "pinned_message", - "profile_accent_color_id", - "profile_background_custom_emoji_id", - "slow_mode_delay", - "sticker_set_name", - "title", - "type", - "unrestrict_boost_count", - "username", - ) + __slots__ = ("first_name", "id", "is_forum", "last_name", "title", "type", "username") + + def __init__( + self, + id: int, + type: str, + title: Optional[str] = None, + username: Optional[str] = None, + first_name: Optional[str] = None, + last_name: Optional[str] = None, + is_forum: Optional[bool] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + # Required + self.id: int = id + self.type: str = enum.get_member(constants.ChatType, type, type) + # Optionals + self.title: Optional[str] = title + self.username: Optional[str] = username + self.first_name: Optional[str] = first_name + self.last_name: Optional[str] = last_name + self.is_forum: Optional[bool] = is_forum + + self._id_attrs = (self.id,) + + self._freeze() + SENDER: Final[str] = constants.ChatType.SENDER """:const:`telegram.constants.ChatType.SENDER` @@ -807,138 +115,11 @@ class Chat(TelegramObject): CHANNEL: Final[str] = constants.ChatType.CHANNEL """:const:`telegram.constants.ChatType.CHANNEL`""" - def __init__( - self, - id: int, - type: str, - title: Optional[str] = None, - username: Optional[str] = None, - first_name: Optional[str] = None, - last_name: Optional[str] = None, - photo: Optional[ChatPhoto] = None, - description: Optional[str] = None, - invite_link: Optional[str] = None, - pinned_message: Optional["Message"] = None, - permissions: Optional[ChatPermissions] = None, - sticker_set_name: Optional[str] = None, - can_set_sticker_set: Optional[bool] = None, - slow_mode_delay: Optional[int] = None, - bio: Optional[str] = None, - linked_chat_id: Optional[int] = None, - location: Optional[ChatLocation] = None, - message_auto_delete_time: Optional[int] = None, - has_private_forwards: Optional[bool] = None, - has_protected_content: Optional[bool] = None, - join_to_send_messages: Optional[bool] = None, - join_by_request: Optional[bool] = None, - has_restricted_voice_and_video_messages: Optional[bool] = None, - is_forum: Optional[bool] = None, - active_usernames: Optional[Sequence[str]] = None, - emoji_status_custom_emoji_id: Optional[str] = None, - emoji_status_expiration_date: Optional[datetime] = None, - has_aggressive_anti_spam_enabled: Optional[bool] = None, - has_hidden_members: Optional[bool] = None, - available_reactions: Optional[Sequence[ReactionType]] = None, - accent_color_id: Optional[int] = None, # required in API 7.3 - Optional for back compat - background_custom_emoji_id: Optional[str] = None, - profile_accent_color_id: Optional[int] = None, - profile_background_custom_emoji_id: Optional[str] = None, - has_visible_history: Optional[bool] = None, - unrestrict_boost_count: Optional[int] = None, - custom_emoji_sticker_set_name: Optional[str] = None, - birthdate: Optional[Birthdate] = None, - personal_chat: Optional["Chat"] = None, - business_intro: Optional["BusinessIntro"] = None, - business_location: Optional["BusinessLocation"] = None, - business_opening_hours: Optional["BusinessOpeningHours"] = None, - *, - api_kwargs: Optional[JSONDict] = None, - ): - super().__init__(api_kwargs=api_kwargs) - # Required - self.id: int = id - self.type: str = enum.get_member(constants.ChatType, type, type) - # Optionals - self.title: Optional[str] = title - self.username: Optional[str] = username - self.first_name: Optional[str] = first_name - self.last_name: Optional[str] = last_name - self.photo: Optional[ChatPhoto] = photo - self.bio: Optional[str] = bio - self.has_private_forwards: Optional[bool] = has_private_forwards - self.description: Optional[str] = description - self.invite_link: Optional[str] = invite_link - self.pinned_message: Optional[Message] = pinned_message - self.permissions: Optional[ChatPermissions] = permissions - self.slow_mode_delay: Optional[int] = slow_mode_delay - self.message_auto_delete_time: Optional[int] = ( - int(message_auto_delete_time) if message_auto_delete_time is not None else None - ) - self.has_protected_content: Optional[bool] = has_protected_content - self.has_visible_history: Optional[bool] = has_visible_history - self.sticker_set_name: Optional[str] = sticker_set_name - self.can_set_sticker_set: Optional[bool] = can_set_sticker_set - self.linked_chat_id: Optional[int] = linked_chat_id - self.location: Optional[ChatLocation] = location - self.join_to_send_messages: Optional[bool] = join_to_send_messages - self.join_by_request: Optional[bool] = join_by_request - self.has_restricted_voice_and_video_messages: Optional[bool] = ( - has_restricted_voice_and_video_messages - ) - self.is_forum: Optional[bool] = is_forum - self.active_usernames: Tuple[str, ...] = parse_sequence_arg(active_usernames) - self.emoji_status_custom_emoji_id: Optional[str] = emoji_status_custom_emoji_id - self.emoji_status_expiration_date: Optional[datetime] = emoji_status_expiration_date - self.has_aggressive_anti_spam_enabled: Optional[bool] = has_aggressive_anti_spam_enabled - self.has_hidden_members: Optional[bool] = has_hidden_members - self.available_reactions: Optional[Tuple[ReactionType, ...]] = parse_sequence_arg( - available_reactions - ) - self.accent_color_id: Optional[int] = accent_color_id - self.background_custom_emoji_id: Optional[str] = background_custom_emoji_id - self.profile_accent_color_id: Optional[int] = profile_accent_color_id - self.profile_background_custom_emoji_id: Optional[str] = profile_background_custom_emoji_id - self.unrestrict_boost_count: Optional[int] = unrestrict_boost_count - self.custom_emoji_sticker_set_name: Optional[str] = custom_emoji_sticker_set_name - self.birthdate: Optional[Birthdate] = birthdate - self.personal_chat: Optional["Chat"] = personal_chat - self.business_intro: Optional["BusinessIntro"] = business_intro - self.business_location: Optional["BusinessLocation"] = business_location - self.business_opening_hours: Optional["BusinessOpeningHours"] = business_opening_hours - - if self.__class__ is Chat: - for arg in _deprecated_attrs: - if (val := object.__getattribute__(self, arg)) is not None and val != (): - warn( - PTBDeprecationWarning( - "21.2", - f"The argument `{arg}` is deprecated and will only be available via " - "`ChatFullInfo` in the future.", - ), - stacklevel=2, - ) - - self._id_attrs = (self.id,) - - self._freeze() - - def __getattribute__(self, name: str) -> Any: - if name in _deprecated_attrs and self.__class__ is Chat: - warn( - PTBDeprecationWarning( - "21.2", - f"The attribute `{name}` is deprecated and will only be accessible via " - "`ChatFullInfo` in the future.", - ), - stacklevel=2, - ) - return super().__getattribute__(name) - @property def effective_name(self) -> Optional[str]: """ - :obj:`str`: Convenience property. Gives :attr:`title` if not :obj:`None`, - else :attr:`full_name` if not :obj:`None`. + :obj:`str`: Convenience property. Gives :attr:`~Chat.title` if not :obj:`None`, + else :attr:`~Chat.full_name` if not :obj:`None`. .. versionadded:: 20.1 """ @@ -951,8 +132,8 @@ class Chat(TelegramObject): @property def full_name(self) -> Optional[str]: """ - :obj:`str`: Convenience property. If :attr:`first_name` is not :obj:`None`, gives - :attr:`first_name` followed by (if available) :attr:`last_name`. + :obj:`str`: Convenience property. If :attr:`~Chat.first_name` is not :obj:`None`, gives + :attr:`~Chat.first_name` followed by (if available) :attr:`~Chat.last_name`. Note: :attr:`full_name` will always be :obj:`None`, if the chat is a (super)group or @@ -968,58 +149,13 @@ class Chat(TelegramObject): @property def link(self) -> Optional[str]: - """:obj:`str`: Convenience property. If the chat has a :attr:`username`, returns a t.me - link of the chat. + """:obj:`str`: Convenience property. If the chat has a :attr:`~Chat.username`, returns a + t.me link of the chat. """ if self.username: return f"https://t.me/{self.username}" return None - @classmethod - def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Chat"]: - """See :meth:`telegram.TelegramObject.de_json`.""" - data = cls._parse_data(data) - - if not data: - return None - - # Get the local timezone from the bot if it has defaults - loc_tzinfo = extract_tzinfo_from_defaults(bot) - - data["emoji_status_expiration_date"] = from_timestamp( - data.get("emoji_status_expiration_date"), tzinfo=loc_tzinfo - ) - - data["photo"] = ChatPhoto.de_json(data.get("photo"), bot) - from telegram import ( # pylint: disable=import-outside-toplevel - BusinessIntro, - BusinessLocation, - BusinessOpeningHours, - Message, - ) - - data["pinned_message"] = Message.de_json(data.get("pinned_message"), bot) - data["permissions"] = ChatPermissions.de_json(data.get("permissions"), bot) - data["location"] = ChatLocation.de_json(data.get("location"), bot) - data["available_reactions"] = ReactionType.de_list(data.get("available_reactions"), bot) - data["birthdate"] = Birthdate.de_json(data.get("birthdate"), bot) - data["personal_chat"] = Chat.de_json(data.get("personal_chat"), bot) - data["business_intro"] = BusinessIntro.de_json(data.get("business_intro"), bot) - data["business_location"] = BusinessLocation.de_json(data.get("business_location"), bot) - data["business_opening_hours"] = BusinessOpeningHours.de_json( - data.get("business_opening_hours"), bot - ) - - api_kwargs = {} - # This is a deprecated field that TG still returns for backwards compatibility - # Let's filter it out to speed up the de-json process - if "all_members_are_administrators" in data: - api_kwargs["all_members_are_administrators"] = data.pop( - "all_members_are_administrators" - ) - - return super()._de_json(data=data, bot=bot, api_kwargs=api_kwargs) - def mention_markdown(self, name: Optional[str] = None) -> str: """ Note: @@ -1030,17 +166,18 @@ class Chat(TelegramObject): .. versionadded:: 20.0 Args: - name (:obj:`str`): The name used as a link for the chat. Defaults to :attr:`full_name`. + name (:obj:`str`): The name used as a link for the chat. Defaults to + :attr:`~Chat.full_name`. Returns: :obj:`str`: The inline mention for the chat as markdown (version 1). Raises: :exc:`TypeError`: If the chat is a private chat and neither the :paramref:`name` - nor the :attr:`first_name` is set, then throw an :exc:`TypeError`. - If the chat is a public chat and neither the :paramref:`name` nor the :attr:`title` - is set, then throw an :exc:`TypeError`. If chat is a private group chat, then - throw an :exc:`TypeError`. + nor the :attr:`~Chat.first_name` is set, then throw an :exc:`TypeError`. + If the chat is a public chat and neither the :paramref:`name` nor the + :attr:`~Chat.title` is set, then throw an :exc:`TypeError`. If chat is a + private group chat, then throw an :exc:`TypeError`. """ if self.type == self.PRIVATE: @@ -1062,17 +199,18 @@ class Chat(TelegramObject): .. versionadded:: 20.0 Args: - name (:obj:`str`): The name used as a link for the chat. Defaults to :attr:`full_name`. + name (:obj:`str`): The name used as a link for the chat. Defaults to + :attr:`~Chat.full_name`. Returns: :obj:`str`: The inline mention for the chat as markdown (version 2). Raises: :exc:`TypeError`: If the chat is a private chat and neither the :paramref:`name` - nor the :attr:`first_name` is set, then throw an :exc:`TypeError`. - If the chat is a public chat and neither the :paramref:`name` nor the :attr:`title` - is set, then throw an :exc:`TypeError`. If chat is a private group chat, then - throw an :exc:`TypeError`. + nor the :attr:`~Chat.first_name` is set, then throw an :exc:`TypeError`. + If the chat is a public chat and neither the :paramref:`name` nor the + :attr:`~Chat.title` is set, then throw an :exc:`TypeError`. If chat is a + private group chat, then throw an :exc:`TypeError`. """ if self.type == self.PRIVATE: @@ -1101,10 +239,10 @@ class Chat(TelegramObject): Raises: :exc:`TypeError`: If the chat is a private chat and neither the :paramref:`name` - nor the :attr:`first_name` is set, then throw an :exc:`TypeError`. - If the chat is a public chat and neither the :paramref:`name` nor the :attr:`title` - is set, then throw an :exc:`TypeError`. If chat is a private group chat, then - throw an :exc:`TypeError`. + nor the :attr:`~Chat.first_name` is set, then throw an :exc:`TypeError`. + If the chat is a public chat and neither the :paramref:`name` nor the + :attr:`~Chat.title` is set, then throw an :exc:`TypeError`. + If chat is a private group chat, then throw an :exc:`TypeError`. """ if self.type == self.PRIVATE: @@ -4074,3 +3212,60 @@ class Chat(TelegramObject): pool_timeout=pool_timeout, api_kwargs=api_kwargs, ) + + +class Chat(_ChatBase): + """This object represents a chat. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`id` is equal. + + .. versionchanged:: 20.0 + + * Removed the deprecated methods ``kick_member`` and ``get_members_count``. + * The following are now keyword-only arguments in Bot methods: + ``location``, ``filename``, ``contact``, ``{read, write, connect, pool}_timeout``, + ``api_kwargs``. Use a named argument for those, + and notice that some positional arguments changed position as a result. + + .. versionchanged:: 20.0 + Removed the attribute ``all_members_are_administrators``. As long as Telegram provides + this field for backwards compatibility, it is available through + :attr:`~telegram.TelegramObject.api_kwargs`. + + .. versionchanged:: NEXT.VERSION + As per Bot API 7.3, most of the arguments and attributes of this class have now moved to + :class:`telegram.ChatFullInfo`. + + Args: + id (:obj:`int`): Unique identifier for this chat. + type (:obj:`str`): Type of chat, can be either :attr:`PRIVATE`, :attr:`GROUP`, + :attr:`SUPERGROUP` or :attr:`CHANNEL`. + title (:obj:`str`, optional): Title, for supergroups, channels and group chats. + username (:obj:`str`, optional): Username, for private chats, supergroups and channels if + available. + first_name (:obj:`str`, optional): First name of the other party in a private chat. + last_name (:obj:`str`, optional): Last name of the other party in a private chat. + is_forum (:obj:`bool`, optional): :obj:`True`, if the supergroup chat is a forum + (has topics_ enabled). + + .. versionadded:: 20.0 + + Attributes: + id (:obj:`int`): Unique identifier for this chat. + type (:obj:`str`): Type of chat, can be either :attr:`PRIVATE`, :attr:`GROUP`, + :attr:`SUPERGROUP` or :attr:`CHANNEL`. + title (:obj:`str`): Optional. Title, for supergroups, channels and group chats. + username (:obj:`str`): Optional. Username, for private chats, supergroups and channels if + available. + first_name (:obj:`str`): Optional. First name of the other party in a private chat. + last_name (:obj:`str`): Optional. Last name of the other party in a private chat. + is_forum (:obj:`bool`): Optional. :obj:`True`, if the supergroup chat is a forum + (has topics_ enabled). + + .. versionadded:: 20.0 + + .. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups + """ + + __slots__ = () diff --git a/telegram/_chatfullinfo.py b/telegram/_chatfullinfo.py index 9b100830b..221b8f623 100644 --- a/telegram/_chatfullinfo.py +++ b/telegram/_chatfullinfo.py @@ -19,58 +19,380 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represents a Telegram ChatFullInfo.""" from datetime import datetime -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._birthdate import Birthdate -from telegram._chat import Chat +from telegram._chat import Chat, _ChatBase from telegram._chatlocation import ChatLocation from telegram._chatpermissions import ChatPermissions from telegram._files.chatphoto import ChatPhoto from telegram._reaction import ReactionType +from telegram._utils.argumentparsing import parse_sequence_arg +from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.types import JSONDict if TYPE_CHECKING: - from telegram import BusinessIntro, BusinessLocation, BusinessOpeningHours, Message + from telegram import Bot, BusinessIntro, BusinessLocation, BusinessOpeningHours, Message -class ChatFullInfo(Chat): +class ChatFullInfo(_ChatBase): """ This object contains full information about a chat. Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`~telegram.Chat.id` is equal. - Caution: - This class is a subclass of :class:`telegram.Chat` and inherits all attributes and methods - for backwards compatibility. In the future, this class will *NOT* inherit from - :class:`telegram.Chat`. - - .. seealso:: - All arguments and attributes can be found in :class:`telegram.Chat`. - .. versionadded:: 21.2 + .. versionchanged:: NEXT.VERSION + Explicit support for all shortcut methods known from :class:`telegram.Chat` on this + object. Previously those were only available because this class inherited from + :class:`telegram.Chat`. + Args: + id (:obj:`int`): Unique identifier for this chat. + type (:obj:`str`): Type of chat, can be either :attr:`PRIVATE`, :attr:`GROUP`, + :attr:`SUPERGROUP` or :attr:`CHANNEL`. + accent_color_id (:obj:`int`, optional): Identifier of the + :class:`accent color ` for the chat name and + backgrounds of the chat photo, reply header, and link preview. See `accent colors`_ + for more details. + + .. versionadded:: 20.8 max_reaction_count (:obj:`int`): The maximum number of reactions that can be set on a message in the chat. .. versionadded:: 21.2 + title (:obj:`str`, optional): Title, for supergroups, channels and group chats. + username (:obj:`str`, optional): Username, for private chats, supergroups and channels if + available. + first_name (:obj:`str`, optional): First name of the other party in a private chat. + last_name (:obj:`str`, optional): Last name of the other party in a private chat. + is_forum (:obj:`bool`, optional): :obj:`True`, if the supergroup chat is a forum + (has topics_ enabled). + + .. versionadded:: 20.0 + photo (:class:`telegram.ChatPhoto`, optional): Chat photo. + active_usernames (Sequence[:obj:`str`], optional): If set, the list of all `active chat + usernames `_; for private chats, supergroups and channels. + + .. versionadded:: 20.0 + birthdate (:obj:`telegram.Birthdate`, optional): For private chats, + the date of birth of the user. + + .. versionadded:: 21.1 + business_intro (:class:`telegram.BusinessIntro`, optional): For private chats with + business accounts, the intro of the business. + + .. versionadded:: 21.1 + business_location (:class:`telegram.BusinessLocation`, optional): For private chats with + business accounts, the location of the business. + + .. versionadded:: 21.1 + business_opening_hours (:class:`telegram.BusinessOpeningHours`, optional): For private + chats with business accounts, the opening hours of the business. + + .. versionadded:: 21.1 + personal_chat (:obj:`telegram.Chat`, optional): For private chats, the personal channel of + the user. + + .. versionadded:: 21.1 + available_reactions (Sequence[:class:`telegram.ReactionType`], optional): List of available + reactions allowed in the chat. If omitted, then all of + :const:`telegram.constants.ReactionEmoji` are allowed. + + .. versionadded:: 20.8 + background_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of emoji chosen + by the chat for the reply header and link preview background. + + .. versionadded:: 20.8 + profile_accent_color_id (:obj:`int`, optional): Identifier of the + :class:`accent color ` for the chat's profile + background. See profile `accent colors`_ for more details. + + .. versionadded:: 20.8 + profile_background_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of + the emoji chosen by the chat for its profile background. + + .. versionadded:: 20.8 + emoji_status_custom_emoji_id (:obj:`str`, optional): Custom emoji identifier of emoji + status of the chat or the other party in a private chat. + + .. versionadded:: 20.0 + emoji_status_expiration_date (:class:`datetime.datetime`, optional): Expiration date of + emoji status of the chat or the other party in a private chat, in seconds. + + |datetime_localization| + + .. versionadded:: 20.5 + bio (:obj:`str`, optional): Bio of the other party in a private chat. + has_private_forwards (:obj:`bool`, optional): :obj:`True`, if privacy settings of the other + party in the private chat allows to use ``tg://user?id=`` links only in chats + with the user. + + .. versionadded:: 13.9 + has_restricted_voice_and_video_messages (:obj:`bool`, optional): :obj:`True`, if the + privacy settings of the other party restrict sending voice and video note messages + in the private chat. + + .. versionadded:: 20.0 + join_to_send_messages (:obj:`bool`, optional): :obj:`True`, if users need to join the + supergroup before they can send messages. + + .. versionadded:: 20.0 + join_by_request (:obj:`bool`, optional): :obj:`True`, if all users directly joining the + supergroup without using an invite link need to be approved by supergroup + administrators. + + .. versionadded:: 20.0 + description (:obj:`str`, optional): Description, for groups, supergroups and channel chats. + invite_link (:obj:`str`, optional): Primary invite link, for groups, supergroups and + channel. + pinned_message (:class:`telegram.Message`, optional): The most recent pinned message + (by sending date). + permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions, + for groups and supergroups. + slow_mode_delay (:obj:`int`, optional): For supergroups, the minimum allowed delay between + consecutive messages sent by each unprivileged user. + unrestrict_boost_count (:obj:`int`, optional): For supergroups, the minimum number of + boosts that a non-administrator user needs to add in order to ignore slow mode and chat + permissions. + + .. versionadded:: 21.0 + message_auto_delete_time (:obj:`int`, optional): The time after which all messages sent to + the chat will be automatically deleted; in seconds. + + .. versionadded:: 13.4 + has_aggressive_anti_spam_enabled (:obj:`bool`, optional): :obj:`True`, if aggressive + anti-spam checks are enabled in the supergroup. The field is only available to chat + administrators. + + .. versionadded:: 20.0 + has_hidden_members (:obj:`bool`, optional): :obj:`True`, if non-administrators can only + get the list of bots and administrators in the chat. + + .. versionadded:: 20.0 + has_protected_content (:obj:`bool`, optional): :obj:`True`, if messages from the chat can't + be forwarded to other chats. + + .. versionadded:: 13.9 + has_visible_history (:obj:`bool`, optional): :obj:`True`, if new chat members will have + access to old messages; available only to chat administrators. + + .. versionadded:: 20.8 + sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set. + can_set_sticker_set (:obj:`bool`, optional): :obj:`True`, if the bot can change group the + sticker set. + custom_emoji_sticker_set_name (:obj:`str`, optional): For supergroups, the name of the + group's custom emoji sticker set. Custom emoji from this set can be used by all users + and bots in the group. + + .. versionadded:: 21.0 + linked_chat_id (:obj:`int`, optional): Unique identifier for the linked chat, i.e. the + discussion group identifier for a channel and vice versa; for supergroups and channel + chats. + location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which + the supergroup is connected. Attributes: + id (:obj:`int`): Unique identifier for this chat. + type (:obj:`str`): Type of chat, can be either :attr:`PRIVATE`, :attr:`GROUP`, + :attr:`SUPERGROUP` or :attr:`CHANNEL`. + accent_color_id (:obj:`int`): Optional. Identifier of the + :class:`accent color ` for the chat name and + backgrounds of the chat photo, reply header, and link preview. See `accent colors`_ + for more details. + + .. versionadded:: 20.8 max_reaction_count (:obj:`int`): The maximum number of reactions that can be set on a message in the chat. .. versionadded:: 21.2 + title (:obj:`str`, optional): Title, for supergroups, channels and group chats. + username (:obj:`str`, optional): Username, for private chats, supergroups and channels if + available. + first_name (:obj:`str`, optional): First name of the other party in a private chat. + last_name (:obj:`str`, optional): Last name of the other party in a private chat. + is_forum (:obj:`bool`, optional): :obj:`True`, if the supergroup chat is a forum + (has topics_ enabled). + + .. versionadded:: 20.0 + photo (:class:`telegram.ChatPhoto`): Optional. Chat photo. + active_usernames (Tuple[:obj:`str`]): Optional. If set, the list of all `active chat + usernames `_; for private chats, supergroups and channels. + + This list is empty if the chat has no active usernames or this chat instance was not + obtained via :meth:`~telegram.Bot.get_chat`. + + .. versionadded:: 20.0 + birthdate (:obj:`telegram.Birthdate`): Optional. For private chats, + the date of birth of the user. + + .. versionadded:: 21.1 + business_intro (:class:`telegram.BusinessIntro`): Optional. For private chats with + business accounts, the intro of the business. + + .. versionadded:: 21.1 + business_location (:class:`telegram.BusinessLocation`): Optional. For private chats with + business accounts, the location of the business. + + .. versionadded:: 21.1 + business_opening_hours (:class:`telegram.BusinessOpeningHours`): Optional. For private + chats with business accounts, the opening hours of the business. + + .. versionadded:: 21.1 + personal_chat (:obj:`telegram.Chat`): Optional. For private chats, the personal channel of + the user. + + .. versionadded:: 21.1 + available_reactions (Tuple[:class:`telegram.ReactionType`]): Optional. List of available + reactions allowed in the chat. If omitted, then all of + :const:`telegram.constants.ReactionEmoji` are allowed. + + .. versionadded:: 20.8 + background_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of emoji chosen + by the chat for the reply header and link preview background. + + .. versionadded:: 20.8 + profile_accent_color_id (:obj:`int`): Optional. Identifier of the + :class:`accent color ` for the chat's profile + background. See profile `accent colors`_ for more details. + + .. versionadded:: 20.8 + profile_background_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of + the emoji chosen by the chat for its profile background. + + .. versionadded:: 20.8 + emoji_status_custom_emoji_id (:obj:`str`): Optional. Custom emoji identifier of emoji + status of the chat or the other party in a private chat. + + .. versionadded:: 20.0 + emoji_status_expiration_date (:class:`datetime.datetime`): Optional. Expiration date of + emoji status of the chat or the other party in a private chat, in seconds. + + |datetime_localization| + + .. versionadded:: 20.5 + bio (:obj:`str`): Optional. Bio of the other party in a private chat. + has_private_forwards (:obj:`bool`): Optional. :obj:`True`, if privacy settings of the other + party in the private chat allows to use ``tg://user?id=`` links only in chats + with the user. + + .. versionadded:: 13.9 + has_restricted_voice_and_video_messages (:obj:`bool`): Optional. :obj:`True`, if the + privacy settings of the other party restrict sending voice and video note messages + in the private chat. + + .. versionadded:: 20.0 + join_to_send_messages (:obj:`bool`): Optional. :obj:`True`, if users need to join + the supergroup before they can send messages. + + .. versionadded:: 20.0 + join_by_request (:obj:`bool`): Optional. :obj:`True`, if all users directly joining the + supergroup without using an invite link need to be approved by supergroup + administrators. + + .. versionadded:: 20.0 + description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats. + invite_link (:obj:`str`): Optional. Primary invite link, for groups, supergroups and + channel. + pinned_message (:class:`telegram.Message`): Optional. The most recent pinned message + (by sending date). + permissions (:class:`telegram.ChatPermissions`): Optional. Default chat member permissions, + for groups and supergroups. + slow_mode_delay (:obj:`int`): Optional. For supergroups, the minimum allowed delay between + consecutive messages sent by each unprivileged user. + unrestrict_boost_count (:obj:`int`): Optional. For supergroups, the minimum number of + boosts that a non-administrator user needs to add in order to ignore slow mode and chat + permissions. + + .. versionadded:: 21.0 + message_auto_delete_time (:obj:`int`): Optional. The time after which all messages sent to + the chat will be automatically deleted; in seconds. + + .. versionadded:: 13.4 + has_aggressive_anti_spam_enabled (:obj:`bool`): Optional. :obj:`True`, if aggressive + anti-spam checks are enabled in the supergroup. The field is only available to chat + administrators. + + .. versionadded:: 20.0 + has_hidden_members (:obj:`bool`): Optional. :obj:`True`, if non-administrators can only + get the list of bots and administrators in the chat. + + .. versionadded:: 20.0 + has_protected_content (:obj:`bool`): Optional. :obj:`True`, if messages from the chat can't + be forwarded to other chats. + + .. versionadded:: 13.9 + has_visible_history (:obj:`bool`): Optional. :obj:`True`, if new chat members will have + access to old messages; available only to chat administrators. + + .. versionadded:: 20.8 + sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set. + can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the + sticker set. + custom_emoji_sticker_set_name (:obj:`str`): Optional. For supergroups, the name of the + group's custom emoji sticker set. Custom emoji from this set can be used by all users + and bots in the group. + + .. versionadded:: 21.0 + linked_chat_id (:obj:`int`): Optional. Unique identifier for the linked chat, i.e. the + discussion group identifier for a channel and vice versa; for supergroups and channel + chats. + location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which + the supergroup is connected. + + .. _accent colors: https://core.telegram.org/bots/api#accent-colors + .. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups """ - __slots__ = ("max_reaction_count",) + __slots__ = ( + "accent_color_id", + "active_usernames", + "available_reactions", + "background_custom_emoji_id", + "bio", + "birthdate", + "business_intro", + "business_location", + "business_opening_hours", + "can_set_sticker_set", + "custom_emoji_sticker_set_name", + "description", + "emoji_status_custom_emoji_id", + "emoji_status_expiration_date", + "has_aggressive_anti_spam_enabled", + "has_hidden_members", + "has_private_forwards", + "has_protected_content", + "has_restricted_voice_and_video_messages", + "has_visible_history", + "invite_link", + "join_by_request", + "join_to_send_messages", + "linked_chat_id", + "location", + "max_reaction_count", + "message_auto_delete_time", + "permissions", + "personal_chat", + "photo", + "pinned_message", + "profile_accent_color_id", + "profile_background_custom_emoji_id", + "slow_mode_delay", + "sticker_set_name", + "unrestrict_boost_count", + ) def __init__( self, id: int, type: str, - accent_color_id: int, # API 7.3 made this argument required - max_reaction_count: int, # NEW arg in api 7.3 and is required + accent_color_id: int, + max_reaction_count: int, title: Optional[str] = None, username: Optional[str] = None, first_name: Optional[str] = None, @@ -120,47 +442,93 @@ class ChatFullInfo(Chat): username=username, first_name=first_name, last_name=last_name, - photo=photo, - description=description, - invite_link=invite_link, - pinned_message=pinned_message, - permissions=permissions, - sticker_set_name=sticker_set_name, - can_set_sticker_set=can_set_sticker_set, - slow_mode_delay=slow_mode_delay, - bio=bio, - linked_chat_id=linked_chat_id, - location=location, - message_auto_delete_time=message_auto_delete_time, - has_private_forwards=has_private_forwards, - has_protected_content=has_protected_content, - join_to_send_messages=join_to_send_messages, - join_by_request=join_by_request, - has_restricted_voice_and_video_messages=has_restricted_voice_and_video_messages, is_forum=is_forum, - active_usernames=active_usernames, - emoji_status_custom_emoji_id=emoji_status_custom_emoji_id, - emoji_status_expiration_date=emoji_status_expiration_date, - has_aggressive_anti_spam_enabled=has_aggressive_anti_spam_enabled, - has_hidden_members=has_hidden_members, - available_reactions=available_reactions, - accent_color_id=accent_color_id, - background_custom_emoji_id=background_custom_emoji_id, - profile_accent_color_id=profile_accent_color_id, - profile_background_custom_emoji_id=profile_background_custom_emoji_id, - has_visible_history=has_visible_history, - unrestrict_boost_count=unrestrict_boost_count, - custom_emoji_sticker_set_name=custom_emoji_sticker_set_name, - birthdate=birthdate, - personal_chat=personal_chat, - business_intro=business_intro, - business_location=business_location, - business_opening_hours=business_opening_hours, api_kwargs=api_kwargs, ) # Required and unique to this class- with self._unfrozen(): self.max_reaction_count: int = max_reaction_count + self.photo: Optional[ChatPhoto] = photo + self.bio: Optional[str] = bio + self.has_private_forwards: Optional[bool] = has_private_forwards + self.description: Optional[str] = description + self.invite_link: Optional[str] = invite_link + self.pinned_message: Optional[Message] = pinned_message + self.permissions: Optional[ChatPermissions] = permissions + self.slow_mode_delay: Optional[int] = slow_mode_delay + self.message_auto_delete_time: Optional[int] = ( + int(message_auto_delete_time) if message_auto_delete_time is not None else None + ) + self.has_protected_content: Optional[bool] = has_protected_content + self.has_visible_history: Optional[bool] = has_visible_history + self.sticker_set_name: Optional[str] = sticker_set_name + self.can_set_sticker_set: Optional[bool] = can_set_sticker_set + self.linked_chat_id: Optional[int] = linked_chat_id + self.location: Optional[ChatLocation] = location + self.join_to_send_messages: Optional[bool] = join_to_send_messages + self.join_by_request: Optional[bool] = join_by_request + self.has_restricted_voice_and_video_messages: Optional[bool] = ( + has_restricted_voice_and_video_messages + ) + self.active_usernames: Tuple[str, ...] = parse_sequence_arg(active_usernames) + self.emoji_status_custom_emoji_id: Optional[str] = emoji_status_custom_emoji_id + self.emoji_status_expiration_date: Optional[datetime] = emoji_status_expiration_date + self.has_aggressive_anti_spam_enabled: Optional[bool] = ( + has_aggressive_anti_spam_enabled + ) + self.has_hidden_members: Optional[bool] = has_hidden_members + self.available_reactions: Optional[Tuple[ReactionType, ...]] = parse_sequence_arg( + available_reactions + ) + self.accent_color_id: Optional[int] = accent_color_id + self.background_custom_emoji_id: Optional[str] = background_custom_emoji_id + self.profile_accent_color_id: Optional[int] = profile_accent_color_id + self.profile_background_custom_emoji_id: Optional[str] = ( + profile_background_custom_emoji_id + ) + self.unrestrict_boost_count: Optional[int] = unrestrict_boost_count + self.custom_emoji_sticker_set_name: Optional[str] = custom_emoji_sticker_set_name + self.birthdate: Optional[Birthdate] = birthdate + self.personal_chat: Optional["Chat"] = personal_chat + self.business_intro: Optional["BusinessIntro"] = business_intro + self.business_location: Optional["BusinessLocation"] = business_location + self.business_opening_hours: Optional["BusinessOpeningHours"] = business_opening_hours - self._freeze() + @classmethod + def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatFullInfo"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + # Get the local timezone from the bot if it has defaults + loc_tzinfo = extract_tzinfo_from_defaults(bot) + + data["emoji_status_expiration_date"] = from_timestamp( + data.get("emoji_status_expiration_date"), tzinfo=loc_tzinfo + ) + + data["photo"] = ChatPhoto.de_json(data.get("photo"), bot) + + from telegram import ( # pylint: disable=import-outside-toplevel + BusinessIntro, + BusinessLocation, + BusinessOpeningHours, + Message, + ) + + data["pinned_message"] = Message.de_json(data.get("pinned_message"), bot) + data["permissions"] = ChatPermissions.de_json(data.get("permissions"), bot) + data["location"] = ChatLocation.de_json(data.get("location"), bot) + data["available_reactions"] = ReactionType.de_list(data.get("available_reactions"), bot) + data["birthdate"] = Birthdate.de_json(data.get("birthdate"), bot) + data["personal_chat"] = Chat.de_json(data.get("personal_chat"), bot) + data["business_intro"] = BusinessIntro.de_json(data.get("business_intro"), bot) + data["business_location"] = BusinessLocation.de_json(data.get("business_location"), bot) + data["business_opening_hours"] = BusinessOpeningHours.de_json( + data.get("business_opening_hours"), bot + ) + + return super().de_json(data=data, bot=bot) diff --git a/telegram/_message.py b/telegram/_message.py index eaea1f3fb..195724d36 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -3331,7 +3331,7 @@ class Message(MaybeInaccessibleMessage): Note: Since the release of Bot API 5.5 it can be impossible to forward messages from some chats. Use the attributes :attr:`telegram.Message.has_protected_content` and - :attr:`telegram.Chat.has_protected_content` to check this. + :attr:`telegram.ChatFullInfo.has_protected_content` to check this. As a workaround, it is still possible to use :meth:`copy`. However, this behaviour is undocumented and might be changed by Telegram. diff --git a/telegram/constants.py b/telegram/constants.py index a2d91885c..06f5bff86 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -168,7 +168,8 @@ ZERO_DATE: Final[datetime.datetime] = datetime.datetime(1970, 1, 1, tzinfo=UTC) class AccentColor(Enum): - """This enum contains the available accent colors for :class:`telegram.Chat.accent_color_id`. + """This enum contains the available accent colors for + :class:`telegram.ChatFullInfo.accent_color_id`. The members of this enum are named tuples with the following attributes: - ``identifier`` (:obj:`int`): The identifier of the accent color. @@ -1959,7 +1960,7 @@ class PollingLimit(IntEnum): class ProfileAccentColor(Enum): """This enum contains the available accent colors for - :class:`telegram.Chat.profile_accent_color_id`. + :class:`telegram.ChatFullInfo.profile_accent_color_id`. The members of this enum are named tuples with the following attributes: - ``identifier`` (:obj:`int`): The identifier of the accent color. diff --git a/tests/test_chat.py b/tests/test_chat.py index 7af7a677c..36c1e80a8 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -17,31 +17,12 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. -import datetime -import warnings import pytest -from telegram import ( - Birthdate, - Bot, - BusinessIntro, - BusinessLocation, - BusinessOpeningHours, - BusinessOpeningHoursInterval, - Chat, - ChatLocation, - ChatPermissions, - Location, - ReactionTypeCustomEmoji, - ReactionTypeEmoji, - User, -) -from telegram._chat import _deprecated_attrs -from telegram._utils.datetime import UTC, to_timestamp +from telegram import Bot, Chat, ChatPermissions, ReactionTypeEmoji, User from telegram.constants import ChatAction, ChatType, ReactionEmoji from telegram.helpers import escape_markdown -from telegram.warnings import PTBDeprecationWarning from tests.auxil.bot_method_checks import ( check_defaults_handling, check_shortcut_call, @@ -57,37 +38,7 @@ def chat(bot): title=TestChatBase.title, type=TestChatBase.type_, username=TestChatBase.username, - sticker_set_name=TestChatBase.sticker_set_name, - can_set_sticker_set=TestChatBase.can_set_sticker_set, - permissions=TestChatBase.permissions, - slow_mode_delay=TestChatBase.slow_mode_delay, - bio=TestChatBase.bio, - linked_chat_id=TestChatBase.linked_chat_id, - location=TestChatBase.location, - has_private_forwards=True, - has_protected_content=True, - has_visible_history=True, - join_to_send_messages=True, - join_by_request=True, - has_restricted_voice_and_video_messages=True, is_forum=True, - active_usernames=TestChatBase.active_usernames, - emoji_status_custom_emoji_id=TestChatBase.emoji_status_custom_emoji_id, - emoji_status_expiration_date=TestChatBase.emoji_status_expiration_date, - has_aggressive_anti_spam_enabled=TestChatBase.has_aggressive_anti_spam_enabled, - has_hidden_members=TestChatBase.has_hidden_members, - available_reactions=TestChatBase.available_reactions, - accent_color_id=TestChatBase.accent_color_id, - background_custom_emoji_id=TestChatBase.background_custom_emoji_id, - profile_accent_color_id=TestChatBase.profile_accent_color_id, - profile_background_custom_emoji_id=TestChatBase.profile_background_custom_emoji_id, - unrestrict_boost_count=TestChatBase.unrestrict_boost_count, - custom_emoji_sticker_set_name=TestChatBase.custom_emoji_sticker_set_name, - business_intro=TestChatBase.business_intro, - business_location=TestChatBase.business_location, - business_opening_hours=TestChatBase.business_opening_hours, - birthdate=Birthdate(1, 1), - personal_chat=TestChatBase.personal_chat, first_name=TestChatBase.first_name, last_name=TestChatBase.last_name, ) @@ -101,48 +52,7 @@ class TestChatBase: title = "ToledosPalaceBot - Group" type_ = "group" username = "username" - all_members_are_administrators = False - sticker_set_name = "stickers" - can_set_sticker_set = False - permissions = ChatPermissions( - can_send_messages=True, - can_change_info=False, - can_invite_users=True, - ) - slow_mode_delay = 30 - bio = "I'm a Barbie Girl in a Barbie World" - linked_chat_id = 11880 - location = ChatLocation(Location(123, 456), "Barbie World") - has_protected_content = True - has_visible_history = True - has_private_forwards = True - join_to_send_messages = True - join_by_request = True - has_restricted_voice_and_video_messages = True is_forum = True - active_usernames = ["These", "Are", "Usernames!"] - emoji_status_custom_emoji_id = "VeryUniqueCustomEmojiID" - emoji_status_expiration_date = datetime.datetime.now(tz=UTC).replace(microsecond=0) - has_aggressive_anti_spam_enabled = True - has_hidden_members = True - available_reactions = [ - ReactionTypeEmoji(ReactionEmoji.THUMBS_DOWN), - ReactionTypeCustomEmoji("custom_emoji_id"), - ] - business_intro = BusinessIntro("Title", "Description", None) - business_location = BusinessLocation("Address", Location(123, 456)) - business_opening_hours = BusinessOpeningHours( - "Country/City", - [BusinessOpeningHoursInterval(opening, opening + 60) for opening in (0, 24 * 60)], - ) - accent_color_id = 1 - background_custom_emoji_id = "background_custom_emoji_id" - profile_accent_color_id = 2 - profile_background_custom_emoji_id = "profile_background_custom_emoji_id" - unrestrict_boost_count = 100 - custom_emoji_sticker_set_name = "custom_emoji_sticker_set_name" - birthdate = Birthdate(1, 1) - personal_chat = Chat(3, "private", "private") first_name = "first" last_name = "last" @@ -159,40 +69,7 @@ class TestChatWithoutRequest(TestChatBase): "title": self.title, "type": self.type_, "username": self.username, - "all_members_are_administrators": self.all_members_are_administrators, - "sticker_set_name": self.sticker_set_name, - "can_set_sticker_set": self.can_set_sticker_set, - "permissions": self.permissions.to_dict(), - "slow_mode_delay": self.slow_mode_delay, - "bio": self.bio, - "business_intro": self.business_intro.to_dict(), - "business_location": self.business_location.to_dict(), - "business_opening_hours": self.business_opening_hours.to_dict(), - "has_protected_content": self.has_protected_content, - "has_visible_history": self.has_visible_history, - "has_private_forwards": self.has_private_forwards, - "linked_chat_id": self.linked_chat_id, - "location": self.location.to_dict(), - "join_to_send_messages": self.join_to_send_messages, - "join_by_request": self.join_by_request, - "has_restricted_voice_and_video_messages": ( - self.has_restricted_voice_and_video_messages - ), "is_forum": self.is_forum, - "active_usernames": self.active_usernames, - "emoji_status_custom_emoji_id": self.emoji_status_custom_emoji_id, - "emoji_status_expiration_date": to_timestamp(self.emoji_status_expiration_date), - "has_aggressive_anti_spam_enabled": self.has_aggressive_anti_spam_enabled, - "has_hidden_members": self.has_hidden_members, - "available_reactions": [reaction.to_dict() for reaction in self.available_reactions], - "accent_color_id": self.accent_color_id, - "background_custom_emoji_id": self.background_custom_emoji_id, - "profile_accent_color_id": self.profile_accent_color_id, - "profile_background_custom_emoji_id": self.profile_background_custom_emoji_id, - "unrestrict_boost_count": self.unrestrict_boost_count, - "custom_emoji_sticker_set_name": self.custom_emoji_sticker_set_name, - "birthdate": self.birthdate.to_dict(), - "personal_chat": self.personal_chat.to_dict(), "first_name": self.first_name, "last_name": self.last_name, } @@ -202,76 +79,10 @@ class TestChatWithoutRequest(TestChatBase): assert chat.title == self.title assert chat.type == self.type_ assert chat.username == self.username - assert chat.sticker_set_name == self.sticker_set_name - assert chat.can_set_sticker_set == self.can_set_sticker_set - assert chat.permissions == self.permissions - assert chat.slow_mode_delay == self.slow_mode_delay - assert chat.bio == self.bio - assert chat.business_intro == self.business_intro - assert chat.business_location == self.business_location - assert chat.business_opening_hours == self.business_opening_hours - assert chat.has_protected_content == self.has_protected_content - assert chat.has_visible_history == self.has_visible_history - assert chat.has_private_forwards == self.has_private_forwards - assert chat.linked_chat_id == self.linked_chat_id - assert chat.location.location == self.location.location - assert chat.location.address == self.location.address - assert chat.join_to_send_messages == self.join_to_send_messages - assert chat.join_by_request == self.join_by_request - assert ( - chat.has_restricted_voice_and_video_messages - == self.has_restricted_voice_and_video_messages - ) - assert chat.api_kwargs == { - "all_members_are_administrators": self.all_members_are_administrators - } assert chat.is_forum == self.is_forum - assert chat.active_usernames == tuple(self.active_usernames) - assert chat.emoji_status_custom_emoji_id == self.emoji_status_custom_emoji_id - assert chat.emoji_status_expiration_date == (self.emoji_status_expiration_date) - assert chat.has_aggressive_anti_spam_enabled == self.has_aggressive_anti_spam_enabled - assert chat.has_hidden_members == self.has_hidden_members - assert chat.available_reactions == tuple(self.available_reactions) - assert chat.accent_color_id == self.accent_color_id - assert chat.background_custom_emoji_id == self.background_custom_emoji_id - assert chat.profile_accent_color_id == self.profile_accent_color_id - assert chat.profile_background_custom_emoji_id == self.profile_background_custom_emoji_id - assert chat.unrestrict_boost_count == self.unrestrict_boost_count - assert chat.custom_emoji_sticker_set_name == self.custom_emoji_sticker_set_name - assert chat.birthdate == self.birthdate - assert chat.personal_chat == self.personal_chat assert chat.first_name == self.first_name assert chat.last_name == self.last_name - def test_de_json_localization(self, bot, raw_bot, tz_bot): - json_dict = { - "id": self.id_, - "type": self.type_, - "emoji_status_expiration_date": to_timestamp(self.emoji_status_expiration_date), - } - chat_bot = Chat.de_json(json_dict, bot) - chat_bot_raw = Chat.de_json(json_dict, raw_bot) - chat_bot_tz = Chat.de_json(json_dict, tz_bot) - - # comparing utcoffsets because comparing tzinfo objects is not reliable - emoji_expire_offset = chat_bot_tz.emoji_status_expiration_date.utcoffset() - emoji_expire_offset_tz = tz_bot.defaults.tzinfo.utcoffset( - chat_bot_tz.emoji_status_expiration_date.replace(tzinfo=None) - ) - - assert chat_bot.emoji_status_expiration_date.tzinfo == UTC - assert chat_bot_raw.emoji_status_expiration_date.tzinfo == UTC - assert emoji_expire_offset_tz == emoji_expire_offset - - def test_always_tuples_attributes(self): - chat = Chat( - id=123, - title="title", - type=Chat.PRIVATE, - ) - assert isinstance(chat.active_usernames, tuple) - assert chat.active_usernames == () - def test_to_dict(self, chat): chat_dict = chat.to_dict() @@ -280,67 +91,10 @@ class TestChatWithoutRequest(TestChatBase): assert chat_dict["title"] == chat.title assert chat_dict["type"] == chat.type assert chat_dict["username"] == chat.username - assert chat_dict["permissions"] == chat.permissions.to_dict() - assert chat_dict["slow_mode_delay"] == chat.slow_mode_delay - assert chat_dict["bio"] == chat.bio - assert chat_dict["business_intro"] == chat.business_intro.to_dict() - assert chat_dict["business_location"] == chat.business_location.to_dict() - assert chat_dict["business_opening_hours"] == chat.business_opening_hours.to_dict() - assert chat_dict["has_private_forwards"] == chat.has_private_forwards - assert chat_dict["has_protected_content"] == chat.has_protected_content - assert chat_dict["has_visible_history"] == chat.has_visible_history - assert chat_dict["linked_chat_id"] == chat.linked_chat_id - assert chat_dict["location"] == chat.location.to_dict() - assert chat_dict["join_to_send_messages"] == chat.join_to_send_messages - assert chat_dict["join_by_request"] == chat.join_by_request - assert ( - chat_dict["has_restricted_voice_and_video_messages"] - == chat.has_restricted_voice_and_video_messages - ) assert chat_dict["is_forum"] == chat.is_forum - assert chat_dict["active_usernames"] == list(chat.active_usernames) - assert chat_dict["emoji_status_custom_emoji_id"] == chat.emoji_status_custom_emoji_id - assert chat_dict["emoji_status_expiration_date"] == to_timestamp( - chat.emoji_status_expiration_date - ) - assert ( - chat_dict["has_aggressive_anti_spam_enabled"] == chat.has_aggressive_anti_spam_enabled - ) - assert chat_dict["has_hidden_members"] == chat.has_hidden_members - assert chat_dict["available_reactions"] == [ - reaction.to_dict() for reaction in chat.available_reactions - ] - assert chat_dict["accent_color_id"] == chat.accent_color_id - assert chat_dict["background_custom_emoji_id"] == chat.background_custom_emoji_id - assert chat_dict["profile_accent_color_id"] == chat.profile_accent_color_id - assert ( - chat_dict["profile_background_custom_emoji_id"] - == chat.profile_background_custom_emoji_id - ) - assert chat_dict["custom_emoji_sticker_set_name"] == chat.custom_emoji_sticker_set_name - assert chat_dict["unrestrict_boost_count"] == chat.unrestrict_boost_count - assert chat_dict["birthdate"] == chat.birthdate.to_dict() - assert chat_dict["personal_chat"] == chat.personal_chat.to_dict() assert chat_dict["first_name"] == chat.first_name assert chat_dict["last_name"] == chat.last_name - def test_deprecated_attributes(self, chat): - for depr_attr in _deprecated_attrs: - with pytest.warns(PTBDeprecationWarning, match="deprecated and will only be accessib"): - getattr(chat, depr_attr) - with warnings.catch_warnings(): # No warning should be raised - warnings.simplefilter("error") - chat.id - chat.first_name - - def test_deprecated_arguments(self): - for depr_attr in _deprecated_attrs: - with pytest.warns(PTBDeprecationWarning, match="deprecated and will only be availabl"): - Chat(1, "type", **{depr_attr: "1"}) - with warnings.catch_warnings(): # No warning should be raised - warnings.simplefilter("error") - Chat(1, "type", first_name="first_name") - def test_enum_init(self): chat = Chat(id=1, type="foo") assert chat.type == "foo" diff --git a/tests/test_chatfullinfo.py b/tests/test_chatfullinfo.py index f42642e4e..b547e4de9 100644 --- a/tests/test_chatfullinfo.py +++ b/tests/test_chatfullinfo.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. import datetime -import warnings import pytest @@ -35,7 +34,6 @@ from telegram import ( ReactionTypeCustomEmoji, ReactionTypeEmoji, ) -from telegram._chat import _deprecated_attrs from telegram._utils.datetime import UTC, to_timestamp from telegram.constants import ReactionEmoji from tests.auxil.slots import mro_slots @@ -44,19 +42,19 @@ from tests.auxil.slots import mro_slots @pytest.fixture(scope="module") def chat_full_info(bot): chat = ChatFullInfo( - TestChatInfoBase.id_, - type=TestChatInfoBase.type_, - accent_color_id=TestChatInfoBase.accent_color_id, - max_reaction_count=TestChatInfoBase.max_reaction_count, - title=TestChatInfoBase.title, - username=TestChatInfoBase.username, - sticker_set_name=TestChatInfoBase.sticker_set_name, - can_set_sticker_set=TestChatInfoBase.can_set_sticker_set, - permissions=TestChatInfoBase.permissions, - slow_mode_delay=TestChatInfoBase.slow_mode_delay, - bio=TestChatInfoBase.bio, - linked_chat_id=TestChatInfoBase.linked_chat_id, - location=TestChatInfoBase.location, + TestChatFullInfoBase.id_, + type=TestChatFullInfoBase.type_, + accent_color_id=TestChatFullInfoBase.accent_color_id, + max_reaction_count=TestChatFullInfoBase.max_reaction_count, + title=TestChatFullInfoBase.title, + username=TestChatFullInfoBase.username, + sticker_set_name=TestChatFullInfoBase.sticker_set_name, + can_set_sticker_set=TestChatFullInfoBase.can_set_sticker_set, + permissions=TestChatFullInfoBase.permissions, + slow_mode_delay=TestChatFullInfoBase.slow_mode_delay, + bio=TestChatFullInfoBase.bio, + linked_chat_id=TestChatFullInfoBase.linked_chat_id, + location=TestChatFullInfoBase.location, has_private_forwards=True, has_protected_content=True, has_visible_history=True, @@ -64,35 +62,37 @@ def chat_full_info(bot): join_by_request=True, has_restricted_voice_and_video_messages=True, is_forum=True, - active_usernames=TestChatInfoBase.active_usernames, - emoji_status_custom_emoji_id=TestChatInfoBase.emoji_status_custom_emoji_id, - emoji_status_expiration_date=TestChatInfoBase.emoji_status_expiration_date, - has_aggressive_anti_spam_enabled=TestChatInfoBase.has_aggressive_anti_spam_enabled, - has_hidden_members=TestChatInfoBase.has_hidden_members, - available_reactions=TestChatInfoBase.available_reactions, - background_custom_emoji_id=TestChatInfoBase.background_custom_emoji_id, - profile_accent_color_id=TestChatInfoBase.profile_accent_color_id, - profile_background_custom_emoji_id=TestChatInfoBase.profile_background_custom_emoji_id, - unrestrict_boost_count=TestChatInfoBase.unrestrict_boost_count, - custom_emoji_sticker_set_name=TestChatInfoBase.custom_emoji_sticker_set_name, - business_intro=TestChatInfoBase.business_intro, - business_location=TestChatInfoBase.business_location, - business_opening_hours=TestChatInfoBase.business_opening_hours, + active_usernames=TestChatFullInfoBase.active_usernames, + emoji_status_custom_emoji_id=TestChatFullInfoBase.emoji_status_custom_emoji_id, + emoji_status_expiration_date=TestChatFullInfoBase.emoji_status_expiration_date, + has_aggressive_anti_spam_enabled=TestChatFullInfoBase.has_aggressive_anti_spam_enabled, + has_hidden_members=TestChatFullInfoBase.has_hidden_members, + available_reactions=TestChatFullInfoBase.available_reactions, + background_custom_emoji_id=TestChatFullInfoBase.background_custom_emoji_id, + profile_accent_color_id=TestChatFullInfoBase.profile_accent_color_id, + profile_background_custom_emoji_id=TestChatFullInfoBase.profile_background_custom_emoji_id, + unrestrict_boost_count=TestChatFullInfoBase.unrestrict_boost_count, + custom_emoji_sticker_set_name=TestChatFullInfoBase.custom_emoji_sticker_set_name, + business_intro=TestChatFullInfoBase.business_intro, + business_location=TestChatFullInfoBase.business_location, + business_opening_hours=TestChatFullInfoBase.business_opening_hours, birthdate=Birthdate(1, 1), - personal_chat=TestChatInfoBase.personal_chat, + personal_chat=TestChatFullInfoBase.personal_chat, + first_name="first_name", + last_name="last_name", ) chat.set_bot(bot) chat._unfreeze() return chat -class TestChatInfoBase: +# Shortcut methods are tested in test_chat.py. +class TestChatFullInfoBase: id_ = -28767330 max_reaction_count = 2 title = "ToledosPalaceBot - Group" type_ = "group" username = "username" - all_members_are_administrators = False sticker_set_name = "stickers" can_set_sticker_set = False permissions = ChatPermissions( @@ -134,13 +134,16 @@ class TestChatInfoBase: custom_emoji_sticker_set_name = "custom_emoji_sticker_set_name" birthdate = Birthdate(1, 1) personal_chat = Chat(3, "private", "private") + first_name = "first_name" + last_name = "last_name" -class TestChatWithoutRequest(TestChatInfoBase): +class TestChatFullInfoWithoutRequest(TestChatFullInfoBase): def test_slot_behaviour(self, chat_full_info): cfi = chat_full_info for attr in cfi.__slots__: assert getattr(cfi, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(cfi)) == len(set(mro_slots(cfi))), "duplicate slot" def test_de_json(self, bot): @@ -151,7 +154,6 @@ class TestChatWithoutRequest(TestChatInfoBase): "accent_color_id": self.accent_color_id, "max_reaction_count": self.max_reaction_count, "username": self.username, - "all_members_are_administrators": self.all_members_are_administrators, "sticker_set_name": self.sticker_set_name, "can_set_sticker_set": self.can_set_sticker_set, "permissions": self.permissions.to_dict(), @@ -184,26 +186,134 @@ class TestChatWithoutRequest(TestChatInfoBase): "custom_emoji_sticker_set_name": self.custom_emoji_sticker_set_name, "birthdate": self.birthdate.to_dict(), "personal_chat": self.personal_chat.to_dict(), + "first_name": self.first_name, + "last_name": self.last_name, } cfi = ChatFullInfo.de_json(json_dict, bot) + assert cfi.id == self.id_ + assert cfi.title == self.title + assert cfi.type == self.type_ + assert cfi.username == self.username + assert cfi.sticker_set_name == self.sticker_set_name + assert cfi.can_set_sticker_set == self.can_set_sticker_set + assert cfi.permissions == self.permissions + assert cfi.slow_mode_delay == self.slow_mode_delay + assert cfi.bio == self.bio + assert cfi.business_intro == self.business_intro + assert cfi.business_location == self.business_location + assert cfi.business_opening_hours == self.business_opening_hours + assert cfi.has_protected_content == self.has_protected_content + assert cfi.has_visible_history == self.has_visible_history + assert cfi.has_private_forwards == self.has_private_forwards + assert cfi.linked_chat_id == self.linked_chat_id + assert cfi.location.location == self.location.location + assert cfi.location.address == self.location.address + assert cfi.join_to_send_messages == self.join_to_send_messages + assert cfi.join_by_request == self.join_by_request + assert ( + cfi.has_restricted_voice_and_video_messages + == self.has_restricted_voice_and_video_messages + ) + assert cfi.is_forum == self.is_forum + assert cfi.active_usernames == tuple(self.active_usernames) + assert cfi.emoji_status_custom_emoji_id == self.emoji_status_custom_emoji_id + assert cfi.emoji_status_expiration_date == (self.emoji_status_expiration_date) + assert cfi.has_aggressive_anti_spam_enabled == self.has_aggressive_anti_spam_enabled + assert cfi.has_hidden_members == self.has_hidden_members + assert cfi.available_reactions == tuple(self.available_reactions) + assert cfi.accent_color_id == self.accent_color_id + assert cfi.background_custom_emoji_id == self.background_custom_emoji_id + assert cfi.profile_accent_color_id == self.profile_accent_color_id + assert cfi.profile_background_custom_emoji_id == self.profile_background_custom_emoji_id + assert cfi.unrestrict_boost_count == self.unrestrict_boost_count + assert cfi.custom_emoji_sticker_set_name == self.custom_emoji_sticker_set_name + assert cfi.birthdate == self.birthdate + assert cfi.personal_chat == self.personal_chat + assert cfi.first_name == self.first_name + assert cfi.last_name == self.last_name assert cfi.max_reaction_count == self.max_reaction_count + def test_de_json_localization(self, bot, raw_bot, tz_bot): + json_dict = { + "id": self.id_, + "type": self.type_, + "accent_color_id": self.accent_color_id, + "max_reaction_count": self.max_reaction_count, + "emoji_status_expiration_date": to_timestamp(self.emoji_status_expiration_date), + } + cfi_bot = ChatFullInfo.de_json(json_dict, bot) + cfi_bot_raw = ChatFullInfo.de_json(json_dict, raw_bot) + cfi_bot_tz = ChatFullInfo.de_json(json_dict, tz_bot) + + # comparing utcoffsets because comparing tzinfo objects is not reliable + emoji_expire_offset = cfi_bot_tz.emoji_status_expiration_date.utcoffset() + emoji_expire_offset_tz = tz_bot.defaults.tzinfo.utcoffset( + cfi_bot_tz.emoji_status_expiration_date.replace(tzinfo=None) + ) + + assert cfi_bot.emoji_status_expiration_date.tzinfo == UTC + assert cfi_bot_raw.emoji_status_expiration_date.tzinfo == UTC + assert emoji_expire_offset_tz == emoji_expire_offset + def test_to_dict(self, chat_full_info): cfi = chat_full_info cfi_dict = cfi.to_dict() assert isinstance(cfi_dict, dict) + assert cfi_dict["id"] == cfi.id + assert cfi_dict["title"] == cfi.title + assert cfi_dict["type"] == cfi.type + assert cfi_dict["username"] == cfi.username + assert cfi_dict["permissions"] == cfi.permissions.to_dict() + assert cfi_dict["slow_mode_delay"] == cfi.slow_mode_delay + assert cfi_dict["bio"] == cfi.bio + assert cfi_dict["business_intro"] == cfi.business_intro.to_dict() + assert cfi_dict["business_location"] == cfi.business_location.to_dict() + assert cfi_dict["business_opening_hours"] == cfi.business_opening_hours.to_dict() + assert cfi_dict["has_private_forwards"] == cfi.has_private_forwards + assert cfi_dict["has_protected_content"] == cfi.has_protected_content + assert cfi_dict["has_visible_history"] == cfi.has_visible_history + assert cfi_dict["linked_chat_id"] == cfi.linked_chat_id + assert cfi_dict["location"] == cfi.location.to_dict() + assert cfi_dict["join_to_send_messages"] == cfi.join_to_send_messages + assert cfi_dict["join_by_request"] == cfi.join_by_request + assert ( + cfi_dict["has_restricted_voice_and_video_messages"] + == cfi.has_restricted_voice_and_video_messages + ) + assert cfi_dict["is_forum"] == cfi.is_forum + assert cfi_dict["active_usernames"] == list(cfi.active_usernames) + assert cfi_dict["emoji_status_custom_emoji_id"] == cfi.emoji_status_custom_emoji_id + assert cfi_dict["emoji_status_expiration_date"] == to_timestamp( + cfi.emoji_status_expiration_date + ) + assert cfi_dict["has_aggressive_anti_spam_enabled"] == cfi.has_aggressive_anti_spam_enabled + assert cfi_dict["has_hidden_members"] == cfi.has_hidden_members + assert cfi_dict["available_reactions"] == [ + reaction.to_dict() for reaction in cfi.available_reactions + ] + assert cfi_dict["accent_color_id"] == cfi.accent_color_id + assert cfi_dict["background_custom_emoji_id"] == cfi.background_custom_emoji_id + assert cfi_dict["profile_accent_color_id"] == cfi.profile_accent_color_id + assert ( + cfi_dict["profile_background_custom_emoji_id"] + == cfi.profile_background_custom_emoji_id + ) + assert cfi_dict["custom_emoji_sticker_set_name"] == cfi.custom_emoji_sticker_set_name + assert cfi_dict["unrestrict_boost_count"] == cfi.unrestrict_boost_count + assert cfi_dict["birthdate"] == cfi.birthdate.to_dict() + assert cfi_dict["personal_chat"] == cfi.personal_chat.to_dict() + assert cfi_dict["first_name"] == cfi.first_name + assert cfi_dict["last_name"] == cfi.last_name + assert cfi_dict["max_reaction_count"] == cfi.max_reaction_count - def test_attr_access_no_warning(self, chat_full_info): - cfi = chat_full_info - for depr_attr in _deprecated_attrs: - with warnings.catch_warnings(): # No warning should be raised - warnings.simplefilter("error") - getattr(cfi, depr_attr) - - def test_cfi_creation_no_warning(self, chat_full_info): - cfi = chat_full_info - with warnings.catch_warnings(): - dict = cfi.to_dict() - ChatFullInfo(**dict) + def test_always_tuples_attributes(self): + cfi = ChatFullInfo( + id=123, + type=Chat.PRIVATE, + accent_color_id=1, + max_reaction_count=2, + ) + assert isinstance(cfi.active_usernames, tuple) + assert cfi.active_usernames == () diff --git a/tests/test_official/exceptions.py b/tests/test_official/exceptions.py index ac043de99..4b44d286b 100644 --- a/tests/test_official/exceptions.py +++ b/tests/test_official/exceptions.py @@ -20,7 +20,6 @@ from telegram import Animation, Audio, Document, PhotoSize, Sticker, Video, VideoNote, Voice -from telegram._chat import _deprecated_attrs from tests.test_official.helpers import _get_params_base IGNORED_OBJECTS = ("ResponseParameters",) @@ -173,9 +172,7 @@ def ignored_param_requirements(object_name: str) -> set[str]: # Arguments that are optional arguments for now for backwards compatibility -BACKWARDS_COMPAT_KWARGS: dict[str, set[str]] = { - "Chat": set(_deprecated_attrs), # removed by bot api 7.3 -} +BACKWARDS_COMPAT_KWARGS: dict[str, set[str]] = {} def backwards_compat_kwargs(object_name: str) -> set[str]: diff --git a/tests/test_telegramobject.py b/tests/test_telegramobject.py index c97db1ada..39f3aaff4 100644 --- a/tests/test_telegramobject.py +++ b/tests/test_telegramobject.py @@ -342,10 +342,14 @@ class TestTelegramObject: chat = (await pp.get_chat_data())[1] assert chat.id == 1 assert chat.type == Chat.PRIVATE - assert chat.api_kwargs == { + api_kwargs_expected = { "all_members_are_administrators": True, "something": "Manually inserted", } + # There are older attrs in Chat's api_kwargs which are present but we don't care about them + for k, v in api_kwargs_expected.items(): + assert chat.api_kwargs[k] == v + with pytest.raises(AttributeError): # removed attribute should not be available as attribute, only though api_kwargs chat.all_members_are_administrators From 2c299bb10974303fa46f9ea0489e5d00fab8676a Mon Sep 17 00:00:00 2001 From: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> Date: Thu, 30 May 2024 21:02:41 +0200 Subject: [PATCH 02/12] Add `setuptools` to `requirements-dev.txt` (#4282) --- requirements-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8dab397cd..9704921e5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,7 @@ pre-commit # needed for pre-commit hooks in the git commit command # For the test suite +setuptools # required for test_meta pytest==8.2.0 pytest-asyncio==0.21.2 # needed because pytest doesn't come with native support for coroutines as tests pytest-xdist==3.6.1 # xdist runs tests in parallel From 57298aa0766fc3e7d6b4fffa1127548de860af8e Mon Sep 17 00:00:00 2001 From: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sat, 1 Jun 2024 21:19:38 +0200 Subject: [PATCH 03/12] Deprecate `python-telegram-bot-raw` (#4270) --- README.rst | 10 ---------- README_RAW.rst | 13 ++++++++++--- telegram/__init__.py | 27 +++++++++++++++++++++++++++ telegram/_utils/datetime.py | 2 +- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index 76743bf02..d9a61b5c9 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,3 @@ -.. - Make sure to apply any changes to this file to README_RAW.rst as well! - .. image:: https://raw.githubusercontent.com/python-telegram-bot/logos/master/logo-text/png/ptb-logo-text_768.png :align: center :target: https://python-telegram-bot.org @@ -79,13 +76,6 @@ In addition to the pure API implementation, this library features a number of hi make the development of bots easy and straightforward. These classes are contained in the ``telegram.ext`` submodule. -A pure API implementation *without* ``telegram.ext`` is available as the standalone package ``python-telegram-bot-raw``. `See here for details. `_ - -Note ----- - -Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conjunction will result in undesired side-effects, so only install *one* of both. - Telegram API support ==================== diff --git a/README_RAW.rst b/README_RAW.rst index 45f636bc2..64f83121e 100644 --- a/README_RAW.rst +++ b/README_RAW.rst @@ -1,6 +1,3 @@ -.. - Make sure to apply any changes to this file to README.rst as well! - .. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-raw-logo-text_768.png?raw=true :align: center :target: https://python-telegram-bot.org @@ -62,6 +59,16 @@ :target: https://telegram.me/pythontelegrambotgroup :alt: Telegram Group +⚠️ Deprecation Notice +===================== + +The ``python-telegram-bot-raw`` library will no longer be updated after NEXT.VERSION. +Please instead use the ``python-telegram-bot`` `library `_. +The change requires no changes in your code and requires no additional dependencies. +For additional information, please see this `channel post `_. + +---- + We have made you a wrapper you can't refuse We have a vibrant community of developers helping each other in our `Telegram group `_. Join us! diff --git a/telegram/__init__.py b/telegram/__init__.py index 5e0f3eaac..1230716e7 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -242,6 +242,7 @@ __all__ = ( "warnings", ) +from pathlib import Path from . import _version, constants, error, helpers, request, warnings from ._birthdate import Birthdate @@ -442,6 +443,7 @@ from ._telegramobject import TelegramObject from ._update import Update from ._user import User from ._userprofilephotos import UserProfilePhotos +from ._utils.warnings import warn from ._videochat import ( VideoChatEnded, VideoChatParticipantsInvited, @@ -475,3 +477,28 @@ __bot_api_version__: str = _version.__bot_api_version__ #: #: .. versionadded:: 20.0 __bot_api_version_info__: constants._BotAPIVersion = _version.__bot_api_version_info__ + + +if not (Path(__file__).parent.resolve().absolute() / "ext").exists(): + _MESSAGE = ( + "Hey. You seem to be using the `python-telegram-bot-raw` library. " + "Please note that this libray has been deprecated and will no longer be updated. " + "Please instead use the `python-telegram-bot` library. The change requires no " + "changes in your code and requires no additional dependencies. For additional " + "information, please see the channel post at " + "https://t.me/pythontelegrambotchannel/145." + ) + + # DeprecationWarning is ignored by default in Python 3.7 and later by default outside + # __main__ modules. We use both warning categories to increase the chance of the user + # seeing the warning. + + warn( + warnings.PTBDeprecationWarning(version="NEXT.VERSION", message=_MESSAGE), + stacklevel=2, + ) + warn( + message=_MESSAGE, + category=warnings.PTBUserWarning, + stacklevel=2, + ) diff --git a/telegram/_utils/datetime.py b/telegram/_utils/datetime.py index 9790e2785..1c0da0854 100644 --- a/telegram/_utils/datetime.py +++ b/telegram/_utils/datetime.py @@ -194,7 +194,7 @@ def extract_tzinfo_from_defaults(bot: "Bot") -> Union[dtm.tzinfo, None]: If the bot has no default values, :obj:`None` is returned. """ # We don't use `ininstance(bot, ExtBot)` here so that this works - # in `python-telegram-bot-raw` as well + # without the job-queue extra dependencies as well if hasattr(bot, "defaults") and bot.defaults: return bot.defaults.tzinfo return None From 078d7752502f543b2b3b34090298eb5ab125a734 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:25:54 +0200 Subject: [PATCH 04/12] Bump `pytest` from 8.2.0 to 8.2.1 (#4272) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9704921e5..63f6432ad 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,7 @@ pre-commit # needed for pre-commit hooks in the git commit command # For the test suite setuptools # required for test_meta -pytest==8.2.0 +pytest==8.2.1 pytest-asyncio==0.21.2 # needed because pytest doesn't come with native support for coroutines as tests pytest-xdist==3.6.1 # xdist runs tests in parallel flaky # Used for flaky tests (flaky decorator) From cf728496e4ffb9b9f9aaa4c3518d9f8898d2b4d9 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Mon, 3 Jun 2024 13:39:31 -0400 Subject: [PATCH 05/12] API 7.4 (#4276, #4278, #4279, #4280, #4286, #4283, #4285) Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> --- README.rst | 4 +- README_RAW.rst | 4 +- docs/source/inclusions/bot_methods.rst | 2 + docs/substitutions/global.rst | 6 + telegram/_bot.py | 226 ++++++++++++++++-- telegram/_callbackquery.py | 5 + telegram/_chat.py | 46 +++- telegram/_files/inputmedia.py | 39 ++- telegram/_inline/inlinekeyboardbutton.py | 35 ++- .../_inline/inlinequeryresultcachedgif.py | 9 + .../inlinequeryresultcachedmpeg4gif.py | 9 + .../_inline/inlinequeryresultcachedphoto.py | 9 + .../_inline/inlinequeryresultcachedvideo.py | 9 + telegram/_inline/inlinequeryresultgif.py | 9 + telegram/_inline/inlinequeryresultmpeg4gif.py | 7 + telegram/_inline/inlinequeryresultphoto.py | 9 + telegram/_inline/inlinequeryresultvideo.py | 9 + .../_inline/inputinvoicemessagecontent.py | 67 +++--- telegram/_keyboardbutton.py | 3 +- telegram/_message.py | 89 ++++++- telegram/_messageentity.py | 75 +++--- telegram/_payment/invoice.py | 6 +- telegram/_payment/precheckoutquery.py | 6 +- telegram/_payment/successfulpayment.py | 6 +- telegram/_user.py | 78 +++++- telegram/constants.py | 71 +++--- telegram/ext/_extbot.py | 73 +++++- telegram/ext/filters.py | 14 ++ tests/_files/test_animation.py | 2 + tests/_files/test_inputmedia.py | 21 ++ tests/_files/test_photo.py | 2 + tests/_files/test_video.py | 2 + .../test_inlinequeryresultcachedgif.py | 10 + .../test_inlinequeryresultcachedmpeg4gif.py | 10 + .../test_inlinequeryresultcachedphoto.py | 10 + .../test_inlinequeryresultcachedvideo.py | 10 + tests/_inline/test_inlinequeryresultgif.py | 7 + .../_inline/test_inlinequeryresultmpeg4gif.py | 9 + tests/_inline/test_inlinequeryresultphoto.py | 7 + tests/_inline/test_inlinequeryresultvideo.py | 7 + tests/_payment/test_invoice.py | 7 +- tests/ext/test_filters.py | 5 + tests/test_bot.py | 24 ++ tests/test_chat.py | 4 +- tests/test_constants.py | 3 +- tests/test_message.py | 37 ++- tests/test_official/exceptions.py | 4 +- tests/test_user.py | 19 +- 48 files changed, 956 insertions(+), 169 deletions(-) diff --git a/README.rst b/README.rst index d9a61b5c9..c3b29aa62 100644 --- a/README.rst +++ b/README.rst @@ -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.3-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-7.4-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API version @@ -79,7 +79,7 @@ make the development of bots easy and straightforward. These classes are contain Telegram API support ==================== -All types and methods of the Telegram Bot API **7.3** are supported. +All types and methods of the Telegram Bot API **7.4** are supported. Installing ========== diff --git a/README_RAW.rst b/README_RAW.rst index 64f83121e..7278f47ae 100644 --- a/README_RAW.rst +++ b/README_RAW.rst @@ -11,7 +11,7 @@ :target: https://pypi.org/project/python-telegram-bot-raw/ :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Bot%20API-7.3-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-7.4-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API version @@ -92,7 +92,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju Telegram API support ==================== -All types and methods of the Telegram Bot API **7.3** are supported. +All types and methods of the Telegram Bot API **7.4** are supported. Installing ========== diff --git a/docs/source/inclusions/bot_methods.rst b/docs/source/inclusions/bot_methods.rst index 9dcfa1982..bece5296e 100644 --- a/docs/source/inclusions/bot_methods.rst +++ b/docs/source/inclusions/bot_methods.rst @@ -369,6 +369,8 @@ - Used for getting basic info about a file * - :meth:`~telegram.Bot.get_me` - Used for getting basic information about the bot + * - :meth:`~telegram.Bot.refund_star_payment` + - Used for refunding a payment in Telegram Stars .. raw:: html diff --git a/docs/substitutions/global.rst b/docs/substitutions/global.rst index 36038e71e..37edc74a4 100644 --- a/docs/substitutions/global.rst +++ b/docs/substitutions/global.rst @@ -81,3 +81,9 @@ .. |non_optional_story_argument| replace:: As of this version, this argument is now required. In accordance with our `stability policy `__, the signature will be kept as optional for now, though they are mandatory and an error will be raised if you don't pass it. .. |business_id_str| replace:: Unique identifier of the business connection on behalf of which the message will be sent. + +.. |message_effect_id| replace:: Unique identifier of the message effect to be added to the message; for private chats only. + +.. |show_cap_above_med| replace:: :obj:`True`, if the caption must be shown above the message media. + +.. |tg_stars| replace:: `Telegram Stars `__ diff --git a/telegram/_bot.py b/telegram/_bot.py index 3ae0ce5f1..94e70fe6c 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -672,6 +672,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): link_preview_options: ODVInput["LinkPreviewOptions"] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -711,7 +712,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): data["disable_notification"] = disable_notification data["protect_content"] = protect_content data["parse_mode"] = parse_mode - data["reply_parameters"] = reply_parameters + + if reply_parameters is not None: + data["reply_parameters"] = reply_parameters if link_preview_options is not None: data["link_preview_options"] = link_preview_options @@ -731,6 +734,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): if business_connection_id is not None: data["business_connection_id"] = business_connection_id + if message_effect_id is not None: + data["message_effect_id"] = message_effect_id + result = await self._post( endpoint, data, @@ -919,6 +925,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -967,6 +974,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -1024,6 +1034,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): parse_mode=parse_mode, link_preview_options=link_preview_options, reply_parameters=reply_parameters, + message_effect_id=message_effect_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, @@ -1272,6 +1283,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): has_spoiler: Optional[bool] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -1335,6 +1348,12 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -1372,6 +1391,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): "chat_id": chat_id, "photo": self._parse_file_input(photo, PhotoSize, filename=filename), "has_spoiler": has_spoiler, + "show_caption_above_media": show_caption_above_media, } return await self._send_message( @@ -1393,6 +1413,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_audio( @@ -1412,6 +1433,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -1484,6 +1506,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -1545,6 +1570,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_document( @@ -1562,6 +1588,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -1633,6 +1660,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -1690,6 +1720,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_sticker( @@ -1703,6 +1734,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): emoji: Optional[str] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -1754,6 +1786,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -1803,6 +1838,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_video( @@ -1824,6 +1860,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -1904,6 +1942,12 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -1946,6 +1990,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): "supports_streaming": supports_streaming, "thumbnail": self._parse_file_input(thumbnail, attach=True) if thumbnail else None, "has_spoiler": has_spoiler, + "show_caption_above_media": show_caption_above_media, } return await self._send_message( @@ -1967,6 +2012,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_video_note( @@ -1982,6 +2028,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -2047,6 +2094,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -2104,6 +2154,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_animation( @@ -2124,6 +2175,8 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -2198,6 +2251,12 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -2239,6 +2298,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): "height": height, "thumbnail": self._parse_file_input(thumbnail, attach=True) if thumbnail else None, "has_spoiler": has_spoiler, + "show_caption_above_media": show_caption_above_media, } return await self._send_message( @@ -2260,6 +2320,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_voice( @@ -2276,6 +2337,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -2344,6 +2406,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -2402,6 +2467,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_media_group( @@ -2415,6 +2481,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -2465,6 +2532,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -2558,6 +2628,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): "message_thread_id": message_thread_id, "reply_parameters": reply_parameters, "business_connection_id": business_connection_id, + "message_effect_id": message_effect_id, } result = await self._post( @@ -2587,6 +2658,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -2643,6 +2715,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -2712,6 +2787,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def edit_message_live_location( @@ -2883,6 +2959,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -2935,6 +3012,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -3015,6 +3095,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_contact( @@ -3030,6 +3111,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -3072,6 +3154,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -3143,6 +3228,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_game( @@ -3155,6 +3241,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -3187,6 +3274,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -3233,6 +3323,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_chat_action( @@ -3954,6 +4045,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): reply_markup: Optional["InlineKeyboardMarkup"] = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence["MessageEntity"]] = None, + show_caption_above_media: Optional[bool] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -3985,6 +4077,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): |sequenceargs| reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): An object for an inline keyboard. + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Returns: :class:`telegram.Message`: On success, if edited message is not an inline message, the @@ -3998,6 +4093,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): "chat_id": chat_id, "message_id": message_id, "inline_message_id": inline_message_id, + "show_caption_above_media": show_caption_above_media, } return await self._send_message( @@ -4822,7 +4918,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): title: str, description: str, payload: str, - provider_token: str, + provider_token: Optional[str], # This arg is now optional as of Bot API 7.4 currency: str, prices: Sequence["LabeledPrice"], start_parameter: Optional[str] = None, @@ -4845,6 +4941,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -4876,12 +4973,19 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): :tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be displayed to the user, use for your internal processes. provider_token (:obj:`str`): Payments provider token, obtained via - `@BotFather `_. + `@BotFather `_. Pass an empty string for payments in + |tg_stars|. + + .. deprecated:: NEXT.VERSION + As of Bot API 7.4, this parameter is now optional and future versions of the + library will make it optional as well. + currency (:obj:`str`): Three-letter ISO 4217 currency code, see `more on currencies - `_. + `_. Pass ``XTR`` for + payment in |tg_stars|. prices (Sequence[:class:`telegram.LabeledPrice`]): Price breakdown, a sequence of components (e.g. product price, tax, discount, delivery cost, delivery tax, - bonus, etc.). + bonus, etc.). Must contain exactly one item for payment in |tg_stars|. .. versionchanged:: 20.0 |sequenceargs| @@ -4890,7 +4994,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): a maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the exp parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the - majority of currencies). Defaults to ``0``. + majority of currencies). Defaults to ``0``. Not supported for payment in |tg_stars| .. versionadded:: 13.5 suggested_tip_amounts (Sequence[:obj:`int`], optional): An array of @@ -4923,19 +5027,20 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): photo_width (:obj:`int`, optional): Photo width. photo_height (:obj:`int`, optional): Photo height. need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full - name to complete the order. + name to complete the order. Ignored for payments in |tg_stars|. need_phone_number (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's - phone number to complete the order. + phone number to complete the order. Ignored for payments in |tg_stars|. need_email (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's email - to complete the order. + to complete the order. Ignored for payments in |tg_stars|. need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the - user's shipping address to complete the order. + user's shipping address to complete the order. Ignored for payments in + |tg_stars|. send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's - phone number should be sent to provider. + phone number should be sent to provider. Ignored for payments in |tg_stars|. send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email - address should be sent to provider. + address should be sent to provider. Ignored for payments in |tg_stars|. is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on - the shipping method. + the shipping method. Ignored for payments in |tg_stars|. disable_notification (:obj:`bool`, optional): |disable_notification| protect_content (:obj:`bool`, optional): |protect_content| @@ -4950,6 +5055,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters| .. versionadded:: 20.8 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -5018,6 +5126,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): connect_timeout=connect_timeout, pool_timeout=pool_timeout, api_kwargs=api_kwargs, + message_effect_id=message_effect_id, ) async def answer_shipping_query( @@ -6839,6 +6948,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. business_connection_id: Optional[str] = None, question_parse_mode: ODVInput[str] = DEFAULT_NONE, question_entities: Optional[Sequence["MessageEntity"]] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -6933,6 +7043,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. :paramref:`question_parse_mode`. .. versionadded:: 21.2 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -6998,6 +7111,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def stop_poll( @@ -7055,6 +7169,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -7101,6 +7216,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. business_connection_id (:obj:`str`, optional): |business_id_str| .. versionadded:: 21.1 + message_effect_id (:obj:`str`, optional): |message_effect_id| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -7148,6 +7266,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def get_my_default_administrator_rights( @@ -7476,6 +7595,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + show_caption_above_media: Optional[bool] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -7519,6 +7639,9 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. reply_parameters (:class:`telegram.ReplyParameters`, optional): |reply_parameters| .. versionadded:: 20.8 + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -7575,6 +7698,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. "reply_markup": reply_markup, "message_thread_id": message_thread_id, "reply_parameters": reply_parameters, + "show_caption_above_media": show_caption_above_media, } result = await self._post( @@ -7743,7 +7867,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. title: str, description: str, payload: str, - provider_token: str, + provider_token: Optional[str], # This arg is now optional as of Bot API 7.4 currency: str, prices: Sequence["LabeledPrice"], max_tip_amount: Optional[int] = None, @@ -7782,12 +7906,19 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. :tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be displayed to the user, use for your internal processes. provider_token (:obj:`str`): Payments provider token, obtained via - `@BotFather `_. + `@BotFather `_. Pass an empty string for payments in + |tg_stars|. + + .. deprecated:: NEXT.VERSION + As of Bot API 7.4, this parameter is now optional and future versions of the + library will make it optional as well. + currency (:obj:`str`): Three-letter ISO 4217 currency code, see `more on currencies - `_. + `_. Pass ``XTR`` for + payments in |tg_stars|. prices (Sequence[:class:`telegram.LabeledPrice`)]: Price breakdown, a sequence of components (e.g. product price, tax, discount, delivery cost, delivery tax, - bonus, etc.). + bonus, etc.). Must contain exactly one item for payments in |tg_stars|. .. versionchanged:: 20.0 |sequenceargs| @@ -7796,7 +7927,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. a maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the exp parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the - majority of currencies). Defaults to ``0``. + majority of currencies). Defaults to ``0``. Not supported for payments in + |tg_stars|. suggested_tip_amounts (Sequence[:obj:`int`], optional): An array of suggested amounts of tips in the *smallest* units of the currency (integer, **not** float/double). At most :tg-const:`telegram.Invoice.MAX_TIP_AMOUNTS` suggested tip @@ -7815,19 +7947,20 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. photo_width (:obj:`int`, optional): Photo width. photo_height (:obj:`int`, optional): Photo height. need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full - name to complete the order. + name to complete the order. Ignored for payments in |tg_stars|. need_phone_number (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's - phone number to complete the order. + phone number to complete the order. Ignored for payments in |tg_stars|. need_email (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's email - address to complete the order. + address to complete the order. Ignored for payments in |tg_stars|. need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the - user's shipping address to complete the order. + user's shipping address to complete the order. Ignored for payments in + |tg_stars|. send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's - phone number should be sent to provider. + phone number should be sent to provider. Ignored for payments in |tg_stars|. send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email - address should be sent to provider. + address should be sent to provider. Ignored for payments in |tg_stars|. is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on - the shipping method. + the shipping method. Ignored for payments in |tg_stars|. Returns: :class:`str`: On success, the created invoice link is returned. @@ -8895,6 +9028,47 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. api_kwargs=api_kwargs, ) + async def refund_star_payment( + self, + user_id: int, + telegram_payment_charge_id: str, + *, + 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, + ) -> bool: + """Refunds a successful payment in |tg_stars|. + + .. versionadded:: NEXT.VERSION + + Args: + user_id (:obj:`int`): User identifier of the user whose payment will be refunded. + telegram_payment_charge_id (:obj:`str`): Telegram payment identifier. + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data: JSONDict = { + "user_id": user_id, + "telegram_payment_charge_id": telegram_payment_charge_id, + } + + return await self._post( + "refundStarPayment", + data, + 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 """See :meth:`telegram.TelegramObject.to_dict`.""" data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name} @@ -9145,3 +9319,5 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. """Alias for :meth:`get_business_connection`""" replaceStickerInSet = replace_sticker_in_set """Alias for :meth:`replace_sticker_in_set`""" + refundStarPayment = refund_star_payment + """Alias for :meth:`refund_star_payment`""" diff --git a/telegram/_callbackquery.py b/telegram/_callbackquery.py index 3df7089c9..af89c784b 100644 --- a/telegram/_callbackquery.py +++ b/telegram/_callbackquery.py @@ -281,6 +281,7 @@ class CallbackQuery(TelegramObject): reply_markup: Optional["InlineKeyboardMarkup"] = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence["MessageEntity"]] = None, + show_caption_above_media: Optional[bool] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -326,6 +327,7 @@ class CallbackQuery(TelegramObject): caption_entities=caption_entities, chat_id=None, message_id=None, + show_caption_above_media=show_caption_above_media, ) return await self._get_message().edit_caption( caption=caption, @@ -337,6 +339,7 @@ class CallbackQuery(TelegramObject): parse_mode=parse_mode, api_kwargs=api_kwargs, caption_entities=caption_entities, + show_caption_above_media=show_caption_above_media, ) async def edit_message_reply_markup( @@ -815,6 +818,7 @@ class CallbackQuery(TelegramObject): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + show_caption_above_media: Optional[bool] = None, *, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, reply_to_message_id: Optional[int] = None, @@ -861,6 +865,7 @@ class CallbackQuery(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, reply_parameters=reply_parameters, + show_caption_above_media=show_caption_above_media, ) MAX_ANSWER_TEXT_LENGTH: Final[int] = ( diff --git a/telegram/_chat.py b/telegram/_chat.py index 8250a8f17..39acf55aa 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -1005,6 +1005,7 @@ class _ChatBase(TelegramObject): link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1045,6 +1046,7 @@ class _ChatBase(TelegramObject): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def delete_message( @@ -1121,6 +1123,7 @@ class _ChatBase(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1162,6 +1165,7 @@ class _ChatBase(TelegramObject): caption_entities=caption_entities, reply_parameters=reply_parameters, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_chat_action( @@ -1214,6 +1218,8 @@ class _ChatBase(TelegramObject): has_spoiler: Optional[bool] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1255,6 +1261,8 @@ class _ChatBase(TelegramObject): api_kwargs=api_kwargs, has_spoiler=has_spoiler, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def send_contact( @@ -1269,6 +1277,7 @@ class _ChatBase(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1309,6 +1318,7 @@ class _ChatBase(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_audio( @@ -1327,6 +1337,7 @@ class _ChatBase(TelegramObject): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1371,6 +1382,7 @@ class _ChatBase(TelegramObject): api_kwargs=api_kwargs, thumbnail=thumbnail, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_document( @@ -1387,6 +1399,7 @@ class _ChatBase(TelegramObject): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1429,6 +1442,7 @@ class _ChatBase(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_dice( @@ -1440,6 +1454,7 @@ class _ChatBase(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1475,6 +1490,7 @@ class _ChatBase(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_game( @@ -1486,6 +1502,7 @@ class _ChatBase(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1521,6 +1538,7 @@ class _ChatBase(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_invoice( @@ -1528,7 +1546,7 @@ class _ChatBase(TelegramObject): title: str, description: str, payload: str, - provider_token: str, + provider_token: Optional[str], currency: str, prices: Sequence["LabeledPrice"], start_parameter: Optional[str] = None, @@ -1551,6 +1569,7 @@ class _ChatBase(TelegramObject): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1615,6 +1634,7 @@ class _ChatBase(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, reply_parameters=reply_parameters, + message_effect_id=message_effect_id, ) async def send_location( @@ -1631,6 +1651,7 @@ class _ChatBase(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1673,6 +1694,7 @@ class _ChatBase(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_animation( @@ -1692,6 +1714,8 @@ class _ChatBase(TelegramObject): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1737,6 +1761,8 @@ class _ChatBase(TelegramObject): has_spoiler=has_spoiler, thumbnail=thumbnail, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def send_sticker( @@ -1749,6 +1775,7 @@ class _ChatBase(TelegramObject): emoji: Optional[str] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1785,6 +1812,7 @@ class _ChatBase(TelegramObject): message_thread_id=message_thread_id, emoji=emoji, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_venue( @@ -1803,6 +1831,7 @@ class _ChatBase(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1847,6 +1876,7 @@ class _ChatBase(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_video( @@ -1867,6 +1897,8 @@ class _ChatBase(TelegramObject): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1913,6 +1945,8 @@ class _ChatBase(TelegramObject): message_thread_id=message_thread_id, has_spoiler=has_spoiler, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def send_video_note( @@ -1927,6 +1961,7 @@ class _ChatBase(TelegramObject): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1967,6 +2002,7 @@ class _ChatBase(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_voice( @@ -1982,6 +2018,7 @@ class _ChatBase(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2023,6 +2060,7 @@ class _ChatBase(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_poll( @@ -2047,6 +2085,7 @@ class _ChatBase(TelegramObject): business_connection_id: Optional[str] = None, question_parse_mode: ODVInput[str] = DEFAULT_NONE, question_entities: Optional[Sequence["MessageEntity"]] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2083,6 +2122,7 @@ class _ChatBase(TelegramObject): write_timeout=write_timeout, connect_timeout=connect_timeout, pool_timeout=pool_timeout, + message_effect_id=message_effect_id, explanation=explanation, explanation_parse_mode=explanation_parse_mode, open_period=open_period, @@ -2109,6 +2149,7 @@ class _ChatBase(TelegramObject): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2149,6 +2190,7 @@ class _ChatBase(TelegramObject): api_kwargs=api_kwargs, protect_content=protect_content, message_thread_id=message_thread_id, + show_caption_above_media=show_caption_above_media, ) async def copy_message( @@ -2163,6 +2205,7 @@ class _ChatBase(TelegramObject): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2203,6 +2246,7 @@ class _ChatBase(TelegramObject): api_kwargs=api_kwargs, protect_content=protect_content, message_thread_id=message_thread_id, + show_caption_above_media=show_caption_above_media, ) async def send_copies( diff --git a/telegram/_files/inputmedia.py b/telegram/_files/inputmedia.py index 163b3a62a..229e73209 100644 --- a/telegram/_files/inputmedia.py +++ b/telegram/_files/inputmedia.py @@ -160,6 +160,9 @@ class InputMediaAnimation(InputMedia): optional): |thumbdocstringnopath| .. versionadded:: 20.2 + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.ANIMATION`. @@ -184,9 +187,19 @@ class InputMediaAnimation(InputMedia): thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase| .. versionadded:: 20.2 + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + + .. versionadded:: NEXT.VERSION """ - __slots__ = ("duration", "has_spoiler", "height", "thumbnail", "width") + __slots__ = ( + "duration", + "has_spoiler", + "height", + "show_caption_above_media", + "thumbnail", + "width", + ) def __init__( self, @@ -200,6 +213,7 @@ class InputMediaAnimation(InputMedia): filename: Optional[str] = None, has_spoiler: Optional[bool] = None, thumbnail: Optional[FileInput] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -229,6 +243,7 @@ class InputMediaAnimation(InputMedia): self.height: Optional[int] = height self.duration: Optional[int] = duration self.has_spoiler: Optional[bool] = has_spoiler + self.show_caption_above_media: Optional[bool] = show_caption_above_media class InputMediaPhoto(InputMedia): @@ -260,6 +275,9 @@ class InputMediaPhoto(InputMedia): with a spoiler animation. .. versionadded:: 20.0 + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.PHOTO`. @@ -278,9 +296,15 @@ class InputMediaPhoto(InputMedia): spoiler animation. .. versionadded:: 20.0 + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + + .. versionadded:: NEXT.VERSION """ - __slots__ = ("has_spoiler",) + __slots__ = ( + "has_spoiler", + "show_caption_above_media", + ) def __init__( self, @@ -290,6 +314,7 @@ class InputMediaPhoto(InputMedia): caption_entities: Optional[Sequence[MessageEntity]] = None, filename: Optional[str] = None, has_spoiler: Optional[bool] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -307,6 +332,7 @@ class InputMediaPhoto(InputMedia): with self._unfrozen(): self.has_spoiler: Optional[bool] = has_spoiler + self.show_caption_above_media: Optional[bool] = show_caption_above_media class InputMediaVideo(InputMedia): @@ -359,6 +385,9 @@ class InputMediaVideo(InputMedia): optional): |thumbdocstringnopath| .. versionadded:: 20.2 + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.VIDEO`. @@ -385,12 +414,16 @@ class InputMediaVideo(InputMedia): thumbnail (:class:`telegram.InputFile`): Optional. |thumbdocstringbase| .. versionadded:: 20.2 + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + + .. versionadded:: NEXT.VERSION """ __slots__ = ( "duration", "has_spoiler", "height", + "show_caption_above_media", "supports_streaming", "thumbnail", "width", @@ -409,6 +442,7 @@ class InputMediaVideo(InputMedia): filename: Optional[str] = None, has_spoiler: Optional[bool] = None, thumbnail: Optional[FileInput] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -439,6 +473,7 @@ class InputMediaVideo(InputMedia): ) self.supports_streaming: Optional[bool] = supports_streaming self.has_spoiler: Optional[bool] = has_spoiler + self.show_caption_above_media: Optional[bool] = show_caption_above_media class InputMediaAudio(InputMedia): diff --git a/telegram/_inline/inlinekeyboardbutton.py b/telegram/_inline/inlinekeyboardbutton.py index 9af5d14ed..d88f222ca 100644 --- a/telegram/_inline/inlinekeyboardbutton.py +++ b/telegram/_inline/inlinekeyboardbutton.py @@ -41,7 +41,8 @@ class InlineKeyboardButton(TelegramObject): :attr:`web_app` and :attr:`pay` are equal. Note: - * You must use exactly one of the optional fields. Mind that :attr:`callback_game` is not + * Exactly one of the optional fields must be used to specify type of the button. + * Mind that :attr:`callback_game` is not working as expected. Putting a game short name in it might, but is not guaranteed to work. * If your bot allows for arbitrary callback data, in keyboards returned in a response @@ -123,11 +124,17 @@ class InlineKeyboardButton(TelegramObject): insert the bot's username and the specified inline query in the input field. Not supported for messages sent on behalf of a Telegram Business account. callback_game (:class:`telegram.CallbackGame`, optional): Description of the game that will - be launched when the user presses the button. This type of button **must** always be - the **first** button in the first row. - pay (:obj:`bool`, optional): Specify :obj:`True`, to send a Pay button. This type of button - **must** always be the **first** button in the first row and can only be used in - invoice messages. + be launched when the user presses the button + + Note: + This type of button **must** always be the first button in the first row. + pay (:obj:`bool`, optional): Specify :obj:`True`, to send a Pay button. + Substrings ``“⭐️”`` and ``“XTR”`` in the buttons's text will be replaced with a + Telegram Star icon. + + Note: + This type of button **must** always be the first button in the first row and can + only be used in invoice messages. switch_inline_query_chosen_chat (:obj:`telegram.SwitchInlineQueryChosenChat`, optional): If set, pressing the button will prompt the user to select one of their chats of the specified type, open that chat and insert the bot's username and the specified inline @@ -186,11 +193,17 @@ class InlineKeyboardButton(TelegramObject): insert the bot's username and the specified inline query in the input field. Not supported for messages sent on behalf of a Telegram Business account. callback_game (:class:`telegram.CallbackGame`): Optional. Description of the game that will - be launched when the user presses the button. This type of button **must** always be - the **first** button in the first row. - pay (:obj:`bool`): Optional. Specify :obj:`True`, to send a Pay button. This type of button - **must** always be the **first** button in the first row and can only be used in - invoice messages. + be launched when the user presses the button. + + Note: + This type of button **must** always be the first button in the first row. + pay (:obj:`bool`): Optional. Specify :obj:`True`, to send a Pay button. + Substrings ``“⭐️”`` and ``“XTR”`` in the buttons's text will be replaced with a + Telegram Star icon. + + Note: + This type of button **must** always be the first button in the first row and can + only be used in invoice messages. switch_inline_query_chosen_chat (:obj:`telegram.SwitchInlineQueryChosenChat`): Optional. If set, pressing the button will prompt the user to select one of their chats of the specified type, open that chat and insert the bot's username and the specified inline diff --git a/telegram/_inline/inlinequeryresultcachedgif.py b/telegram/_inline/inlinequeryresultcachedgif.py index ca7188014..9516accae 100644 --- a/telegram/_inline/inlinequeryresultcachedgif.py +++ b/telegram/_inline/inlinequeryresultcachedgif.py @@ -59,6 +59,9 @@ class InlineQueryResultCachedGif(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the message to be sent instead of the gif. + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.GIF`. @@ -81,6 +84,9 @@ class InlineQueryResultCachedGif(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the message to be sent instead of the gif. + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + + .. versionadded:: NEXT.VERSION """ @@ -91,6 +97,7 @@ class InlineQueryResultCachedGif(InlineQueryResult): "input_message_content", "parse_mode", "reply_markup", + "show_caption_above_media", "title", ) @@ -104,6 +111,7 @@ class InlineQueryResultCachedGif(InlineQueryResult): input_message_content: Optional["InputMessageContent"] = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence[MessageEntity]] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -119,3 +127,4 @@ class InlineQueryResultCachedGif(InlineQueryResult): self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup self.input_message_content: Optional[InputMessageContent] = input_message_content + self.show_caption_above_media: Optional[bool] = show_caption_above_media diff --git a/telegram/_inline/inlinequeryresultcachedmpeg4gif.py b/telegram/_inline/inlinequeryresultcachedmpeg4gif.py index f689734b8..3fa2a8f13 100644 --- a/telegram/_inline/inlinequeryresultcachedmpeg4gif.py +++ b/telegram/_inline/inlinequeryresultcachedmpeg4gif.py @@ -59,6 +59,9 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the message to be sent instead of the MPEG-4 file. + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.MPEG4GIF`. @@ -81,6 +84,9 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the message to be sent instead of the MPEG-4 file. + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + + .. versionadded:: NEXT.VERSION """ @@ -91,6 +97,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): "mpeg4_file_id", "parse_mode", "reply_markup", + "show_caption_above_media", "title", ) @@ -104,6 +111,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): input_message_content: Optional["InputMessageContent"] = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence[MessageEntity]] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -119,3 +127,4 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup self.input_message_content: Optional[InputMessageContent] = input_message_content + self.show_caption_above_media: Optional[bool] = show_caption_above_media diff --git a/telegram/_inline/inlinequeryresultcachedphoto.py b/telegram/_inline/inlinequeryresultcachedphoto.py index be484e280..6c18630ff 100644 --- a/telegram/_inline/inlinequeryresultcachedphoto.py +++ b/telegram/_inline/inlinequeryresultcachedphoto.py @@ -60,6 +60,9 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the message to be sent instead of the photo. + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.PHOTO`. @@ -83,6 +86,9 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the message to be sent instead of the photo. + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + + .. versionadded:: NEXT.VERSION """ @@ -94,6 +100,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): "parse_mode", "photo_file_id", "reply_markup", + "show_caption_above_media", "title", ) @@ -108,6 +115,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): input_message_content: Optional["InputMessageContent"] = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence[MessageEntity]] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -124,3 +132,4 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup self.input_message_content: Optional[InputMessageContent] = input_message_content + self.show_caption_above_media: Optional[bool] = show_caption_above_media diff --git a/telegram/_inline/inlinequeryresultcachedvideo.py b/telegram/_inline/inlinequeryresultcachedvideo.py index e226d0e4f..7ce8c423f 100644 --- a/telegram/_inline/inlinequeryresultcachedvideo.py +++ b/telegram/_inline/inlinequeryresultcachedvideo.py @@ -56,6 +56,9 @@ class InlineQueryResultCachedVideo(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the message to be sent instead of the video. + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.VIDEO`. @@ -79,6 +82,9 @@ class InlineQueryResultCachedVideo(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the message to be sent instead of the video. + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + + .. versionadded:: NEXT.VERSION """ @@ -89,6 +95,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult): "input_message_content", "parse_mode", "reply_markup", + "show_caption_above_media", "title", "video_file_id", ) @@ -104,6 +111,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult): input_message_content: Optional["InputMessageContent"] = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence[MessageEntity]] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -120,3 +128,4 @@ class InlineQueryResultCachedVideo(InlineQueryResult): self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup self.input_message_content: Optional[InputMessageContent] = input_message_content + self.show_caption_above_media: Optional[bool] = show_caption_above_media diff --git a/telegram/_inline/inlinequeryresultgif.py b/telegram/_inline/inlinequeryresultgif.py index 1a889a1bb..ab68f083d 100644 --- a/telegram/_inline/inlinequeryresultgif.py +++ b/telegram/_inline/inlinequeryresultgif.py @@ -78,6 +78,9 @@ class InlineQueryResultGif(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the message to be sent instead of the GIF animation. + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Raises: :class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is @@ -115,6 +118,9 @@ class InlineQueryResultGif(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the message to be sent instead of the GIF animation. + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + + .. versionadded:: NEXT.VERSION """ @@ -128,6 +134,7 @@ class InlineQueryResultGif(InlineQueryResult): "input_message_content", "parse_mode", "reply_markup", + "show_caption_above_media", "thumbnail_mime_type", "thumbnail_url", "title", @@ -148,6 +155,7 @@ class InlineQueryResultGif(InlineQueryResult): parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence[MessageEntity]] = None, thumbnail_mime_type: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -168,3 +176,4 @@ class InlineQueryResultGif(InlineQueryResult): self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup self.input_message_content: Optional[InputMessageContent] = input_message_content self.thumbnail_mime_type: Optional[str] = thumbnail_mime_type + self.show_caption_above_media: Optional[bool] = show_caption_above_media diff --git a/telegram/_inline/inlinequeryresultmpeg4gif.py b/telegram/_inline/inlinequeryresultmpeg4gif.py index a744a0400..6478340f7 100644 --- a/telegram/_inline/inlinequeryresultmpeg4gif.py +++ b/telegram/_inline/inlinequeryresultmpeg4gif.py @@ -80,7 +80,9 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the message to be sent instead of the video animation. + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + .. versionadded:: NEXT.VERSION Raises: :class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is supplied or if both are supplied and are not equal. @@ -118,7 +120,9 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the message to be sent instead of the video animation. + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + .. versionadded:: NEXT.VERSION """ __slots__ = ( @@ -131,6 +135,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult): "mpeg4_width", "parse_mode", "reply_markup", + "show_caption_above_media", "thumbnail_mime_type", "thumbnail_url", "title", @@ -151,6 +156,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult): parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence[MessageEntity]] = None, thumbnail_mime_type: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -171,3 +177,4 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult): self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup self.input_message_content: Optional[InputMessageContent] = input_message_content self.thumbnail_mime_type: Optional[str] = thumbnail_mime_type + self.show_caption_above_media: Optional[bool] = show_caption_above_media diff --git a/telegram/_inline/inlinequeryresultphoto.py b/telegram/_inline/inlinequeryresultphoto.py index 5eef1acf6..1fbb4f4d6 100644 --- a/telegram/_inline/inlinequeryresultphoto.py +++ b/telegram/_inline/inlinequeryresultphoto.py @@ -74,6 +74,9 @@ class InlineQueryResultPhoto(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the message to be sent instead of the photo. + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Raises: :class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is @@ -105,6 +108,9 @@ class InlineQueryResultPhoto(InlineQueryResult): to the message. input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the message to be sent instead of the photo. + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + + .. versionadded:: NEXT.VERSION """ @@ -118,6 +124,7 @@ class InlineQueryResultPhoto(InlineQueryResult): "photo_url", "photo_width", "reply_markup", + "show_caption_above_media", "thumbnail_url", "title", ) @@ -136,6 +143,7 @@ class InlineQueryResultPhoto(InlineQueryResult): input_message_content: Optional["InputMessageContent"] = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence[MessageEntity]] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -155,3 +163,4 @@ class InlineQueryResultPhoto(InlineQueryResult): self.caption_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(caption_entities) self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup self.input_message_content: Optional[InputMessageContent] = input_message_content + self.show_caption_above_media: Optional[bool] = show_caption_above_media diff --git a/telegram/_inline/inlinequeryresultvideo.py b/telegram/_inline/inlinequeryresultvideo.py index 90134c450..2c387b17c 100644 --- a/telegram/_inline/inlinequeryresultvideo.py +++ b/telegram/_inline/inlinequeryresultvideo.py @@ -88,6 +88,9 @@ class InlineQueryResultVideo(InlineQueryResult): message to be sent instead of the video. This field is required if ``InlineQueryResultVideo`` is used to send an HTML-page as a result (e.g., a YouTube video). + show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| + + .. versionadded:: NEXT.VERSION Raises: :class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is @@ -127,6 +130,9 @@ class InlineQueryResultVideo(InlineQueryResult): message to be sent instead of the video. This field is required if ``InlineQueryResultVideo`` is used to send an HTML-page as a result (e.g., a YouTube video). + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + + .. versionadded:: NEXT.VERSION """ @@ -138,6 +144,7 @@ class InlineQueryResultVideo(InlineQueryResult): "mime_type", "parse_mode", "reply_markup", + "show_caption_above_media", "thumbnail_url", "title", "video_duration", @@ -162,6 +169,7 @@ class InlineQueryResultVideo(InlineQueryResult): input_message_content: Optional["InputMessageContent"] = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence[MessageEntity]] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -183,3 +191,4 @@ class InlineQueryResultVideo(InlineQueryResult): self.description: Optional[str] = description self.reply_markup: Optional[InlineKeyboardMarkup] = reply_markup self.input_message_content: Optional[InputMessageContent] = input_message_content + self.show_caption_above_media: Optional[bool] = show_caption_above_media diff --git a/telegram/_inline/inputinvoicemessagecontent.py b/telegram/_inline/inputinvoicemessagecontent.py index 64b9ff93b..d710085fd 100644 --- a/telegram/_inline/inputinvoicemessagecontent.py +++ b/telegram/_inline/inputinvoicemessagecontent.py @@ -49,12 +49,18 @@ class InputInvoiceMessageContent(InputMessageContent): :tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be displayed to the user, use for your internal processes. provider_token (:obj:`str`): Payment provider token, obtained via - `@Botfather `_. + `@Botfather `_. Pass an empty string for payments in + |tg_stars|. + + .. deprecated:: NEXT.VERSION + As of Bot API 7.4, this parameter is now optional and future versions of the + library will make it optional as well. currency (:obj:`str`): Three-letter ISO 4217 currency code, see more on - `currencies `_ + `currencies `_. + Pass ``XTR`` for payments in |tg_stars|. prices (Sequence[:class:`telegram.LabeledPrice`]): Price breakdown, a list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, - etc.) + etc.). Must contain exactly one item for payments in |tg_stars|. .. versionchanged:: 20.0 |sequenceclassargs| @@ -64,7 +70,8 @@ class InputInvoiceMessageContent(InputMessageContent): maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the ``exp`` parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the majority - of currencies). Defaults to ``0``. + of currencies). Defaults to ``0``. Defaults to ``0``. Not supported for payments in + |tg_stars|. suggested_tip_amounts (Sequence[:obj:`int`], optional): An array of suggested amounts of tip in the *smallest* units of the currency (integer, **not** float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be @@ -85,20 +92,20 @@ class InputInvoiceMessageContent(InputMessageContent): photo_size (:obj:`int`, optional): Photo size. photo_width (:obj:`int`, optional): Photo width. photo_height (:obj:`int`, optional): Photo height. - need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full name to - complete the order. + need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full + name to complete the order. Ignored for payments in |tg_stars|. need_phone_number (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's - phone number to complete the order + phone number to complete the order. Ignored for payments in |tg_stars|. need_email (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's email - address to complete the order. - need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's - shipping address to complete the order - send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's phone - number should be sent to provider. - send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email address - should be sent to provider. - is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on the - shipping method. + address to complete the order. Ignored for payments in |tg_stars|. + need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the + user's shipping address to complete the order. Ignored for payments in |tg_stars| + send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's + phone number should be sent to provider. Ignored for payments in |tg_stars|. + send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email + address should be sent to provider. Ignored for payments in |tg_stars|. + is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on + the shipping method. Ignored for payments in |tg_stars|. Attributes: title (:obj:`str`): Product name. :tg-const:`telegram.Invoice.MIN_TITLE_LENGTH`- @@ -111,12 +118,14 @@ class InputInvoiceMessageContent(InputMessageContent): :tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be displayed to the user, use for your internal processes. provider_token (:obj:`str`): Payment provider token, obtained via - `@Botfather `_. + `@Botfather `_. Pass an empty string for payments in `Telegram + Stars `_. currency (:obj:`str`): Three-letter ISO 4217 currency code, see more on - `currencies `_ + `currencies `_. + Pass ``XTR`` for payments in |tg_stars|. prices (Tuple[:class:`telegram.LabeledPrice`]): Price breakdown, a list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, - etc.) + etc.). Must contain exactly one item for payments in |tg_stars|. .. versionchanged:: 20.0 |tupleclassattrs| @@ -126,7 +135,7 @@ class InputInvoiceMessageContent(InputMessageContent): maximum tip of US$ 1.45 ``max_tip_amount`` is ``145``. See the ``exp`` parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the majority - of currencies). Defaults to ``0``. + of currencies). Defaults to ``0``. Not supported for payments in |tg_stars|. suggested_tip_amounts (Tuple[:obj:`int`]): Optional. An array of suggested amounts of tip in the *smallest* units of the currency (integer, **not** float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be @@ -146,19 +155,19 @@ class InputInvoiceMessageContent(InputMessageContent): photo_width (:obj:`int`): Optional. Photo width. photo_height (:obj:`int`): Optional. Photo height. need_name (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's full name to - complete the order. + complete the order. Ignored for payments in |tg_stars|. need_phone_number (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's - phone number to complete the order + phone number to complete the order. Ignored for payments in |tg_stars|. need_email (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's email - address to complete the order. + address to complete the order. Ignored for payments in |tg_stars|. need_shipping_address (:obj:`bool`): Optional. Pass :obj:`True`, if you require the user's - shipping address to complete the order + shipping address to complete the order. Ignored for payments in |tg_stars|. send_phone_number_to_provider (:obj:`bool`): Optional. Pass :obj:`True`, if user's phone - number should be sent to provider. + number should be sent to provider. Ignored for payments in |tg_stars|. send_email_to_provider (:obj:`bool`): Optional. Pass :obj:`True`, if user's email address - should be sent to provider. + should be sent to provider. Ignored for payments in |tg_stars|. is_flexible (:obj:`bool`): Optional. Pass :obj:`True`, if the final price depends on the - shipping method. + shipping method. Ignored for payments in |tg_stars|. """ @@ -190,7 +199,7 @@ class InputInvoiceMessageContent(InputMessageContent): title: str, description: str, payload: str, - provider_token: str, + provider_token: Optional[str], # This arg is now optional since Bot API 7.4 currency: str, prices: Sequence[LabeledPrice], max_tip_amount: Optional[int] = None, @@ -216,7 +225,7 @@ class InputInvoiceMessageContent(InputMessageContent): self.title: str = title self.description: str = description self.payload: str = payload - self.provider_token: str = provider_token + self.provider_token: Optional[str] = provider_token self.currency: str = currency self.prices: Tuple[LabeledPrice, ...] = parse_sequence_arg(prices) # Optionals diff --git a/telegram/_keyboardbutton.py b/telegram/_keyboardbutton.py index ad69a1761..0cb4cd82e 100644 --- a/telegram/_keyboardbutton.py +++ b/telegram/_keyboardbutton.py @@ -32,7 +32,8 @@ if TYPE_CHECKING: class KeyboardButton(TelegramObject): """ - This object represents one button of the reply keyboard. For simple text buttons, :obj:`str` + This object represents one button of the reply keyboard. At most one of the optional fields + must be used to specify type of the button. For simple text buttons, :obj:`str` can be used instead of this object to specify text of the button. Objects of this class are comparable in terms of equality. Two objects of this class are diff --git a/telegram/_message.py b/telegram/_message.py index 195724d36..43b7519b5 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -328,6 +328,11 @@ class Message(MaybeInaccessibleMessage): .. versionadded:: 20.8 + effect_id (:obj:`str`, optional): Unique identifier of the message effect added to the + message. + + ..versionadded:: NEXT.VERSION + caption_entities (Sequence[:class:`telegram.MessageEntity`], optional): For messages with a Caption. Special entities like usernames, URLs, bot commands, etc. that appear in the caption. See :attr:`Message.parse_caption_entity` and :attr:`parse_caption_entities` @@ -337,6 +342,9 @@ class Message(MaybeInaccessibleMessage): .. versionchanged:: 20.0 |sequenceclassargs| + show_caption_above_media (:obj:`bool`, optional): |show_cap_above_med| + + .. versionadded:: NEXT.VERSION audio (:class:`telegram.Audio`, optional): Message is an audio file, information about the file. document (:class:`telegram.Document`, optional): Message is a general file, information @@ -617,6 +625,11 @@ class Message(MaybeInaccessibleMessage): .. versionadded:: 20.8 + effect_id (:obj:`str`): Optional. Unique identifier of the message effect added to the + message. + + ..versionadded:: NEXT.VERSION + caption_entities (Tuple[:class:`telegram.MessageEntity`]): Optional. For messages with a Caption. Special entities like usernames, URLs, bot commands, etc. that appear in the caption. See :attr:`Message.parse_caption_entity` and :attr:`parse_caption_entities` @@ -626,6 +639,9 @@ class Message(MaybeInaccessibleMessage): .. versionchanged:: 20.0 |tupleclassattrs| + show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| + + .. versionadded:: NEXT.VERSION audio (:class:`telegram.Audio`): Optional. Message is an audio file, information about the file. @@ -897,6 +913,7 @@ class Message(MaybeInaccessibleMessage): "dice", "document", "edit_date", + "effect_id", "entities", "external_reply", "forum_topic_closed", @@ -942,6 +959,7 @@ class Message(MaybeInaccessibleMessage): "sender_boost_count", "sender_business_bot", "sender_chat", + "show_caption_above_media", "sticker", "story", "successful_payment", @@ -1044,6 +1062,8 @@ class Message(MaybeInaccessibleMessage): sender_business_bot: Optional[User] = None, is_from_offline: Optional[bool] = None, chat_background_set: Optional[ChatBackground] = None, + effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -1143,6 +1163,8 @@ class Message(MaybeInaccessibleMessage): self.sender_business_bot: Optional[User] = sender_business_bot self.is_from_offline: Optional[bool] = is_from_offline self.chat_background_set: Optional[ChatBackground] = chat_background_set + self.effect_id: Optional[str] = effect_id + self.show_caption_above_media: Optional[bool] = show_caption_above_media self._effective_attachment = DEFAULT_NONE @@ -1634,6 +1656,7 @@ class Message(MaybeInaccessibleMessage): message_thread_id: ODVInput[int] = DEFAULT_NONE, link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1698,6 +1721,7 @@ class Message(MaybeInaccessibleMessage): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_markdown( @@ -1710,6 +1734,7 @@ class Message(MaybeInaccessibleMessage): message_thread_id: ODVInput[int] = DEFAULT_NONE, link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1780,6 +1805,7 @@ class Message(MaybeInaccessibleMessage): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_markdown_v2( @@ -1792,6 +1818,7 @@ class Message(MaybeInaccessibleMessage): message_thread_id: ODVInput[int] = DEFAULT_NONE, link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1858,6 +1885,7 @@ class Message(MaybeInaccessibleMessage): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_html( @@ -1870,6 +1898,7 @@ class Message(MaybeInaccessibleMessage): message_thread_id: ODVInput[int] = DEFAULT_NONE, link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1936,6 +1965,7 @@ class Message(MaybeInaccessibleMessage): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_media_group( @@ -1947,6 +1977,7 @@ class Message(MaybeInaccessibleMessage): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: ODVInput[int] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2013,6 +2044,7 @@ class Message(MaybeInaccessibleMessage): parse_mode=parse_mode, caption_entities=caption_entities, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_photo( @@ -2027,6 +2059,8 @@ class Message(MaybeInaccessibleMessage): message_thread_id: ODVInput[int] = DEFAULT_NONE, has_spoiler: Optional[bool] = None, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2092,6 +2126,8 @@ class Message(MaybeInaccessibleMessage): api_kwargs=api_kwargs, has_spoiler=has_spoiler, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def reply_audio( @@ -2109,6 +2145,7 @@ class Message(MaybeInaccessibleMessage): message_thread_id: ODVInput[int] = DEFAULT_NONE, thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2177,6 +2214,7 @@ class Message(MaybeInaccessibleMessage): api_kwargs=api_kwargs, thumbnail=thumbnail, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_document( @@ -2192,6 +2230,7 @@ class Message(MaybeInaccessibleMessage): message_thread_id: ODVInput[int] = DEFAULT_NONE, thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2258,6 +2297,7 @@ class Message(MaybeInaccessibleMessage): message_thread_id=message_thread_id, thumbnail=thumbnail, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_animation( @@ -2276,6 +2316,8 @@ class Message(MaybeInaccessibleMessage): has_spoiler: Optional[bool] = None, thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2345,6 +2387,8 @@ class Message(MaybeInaccessibleMessage): has_spoiler=has_spoiler, thumbnail=thumbnail, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def reply_sticker( @@ -2356,6 +2400,7 @@ class Message(MaybeInaccessibleMessage): message_thread_id: ODVInput[int] = DEFAULT_NONE, emoji: Optional[str] = None, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2416,6 +2461,7 @@ class Message(MaybeInaccessibleMessage): message_thread_id=message_thread_id, emoji=emoji, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_video( @@ -2435,6 +2481,8 @@ class Message(MaybeInaccessibleMessage): has_spoiler: Optional[bool] = None, thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2505,6 +2553,8 @@ class Message(MaybeInaccessibleMessage): has_spoiler=has_spoiler, thumbnail=thumbnail, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def reply_video_note( @@ -2518,6 +2568,7 @@ class Message(MaybeInaccessibleMessage): message_thread_id: ODVInput[int] = DEFAULT_NONE, thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2582,6 +2633,7 @@ class Message(MaybeInaccessibleMessage): message_thread_id=message_thread_id, thumbnail=thumbnail, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_voice( @@ -2596,6 +2648,7 @@ class Message(MaybeInaccessibleMessage): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: ODVInput[int] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2661,6 +2714,7 @@ class Message(MaybeInaccessibleMessage): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_location( @@ -2676,6 +2730,7 @@ class Message(MaybeInaccessibleMessage): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: ODVInput[int] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2742,6 +2797,7 @@ class Message(MaybeInaccessibleMessage): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_venue( @@ -2759,6 +2815,7 @@ class Message(MaybeInaccessibleMessage): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: ODVInput[int] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2827,6 +2884,7 @@ class Message(MaybeInaccessibleMessage): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_contact( @@ -2840,6 +2898,7 @@ class Message(MaybeInaccessibleMessage): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: ODVInput[int] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2904,6 +2963,7 @@ class Message(MaybeInaccessibleMessage): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_poll( @@ -2927,6 +2987,7 @@ class Message(MaybeInaccessibleMessage): reply_parameters: Optional["ReplyParameters"] = None, question_parse_mode: ODVInput[str] = DEFAULT_NONE, question_entities: Optional[Sequence["MessageEntity"]] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2999,6 +3060,7 @@ class Message(MaybeInaccessibleMessage): business_connection_id=self.business_connection_id, question_parse_mode=question_parse_mode, question_entities=question_entities, + message_effect_id=message_effect_id, ) async def reply_dice( @@ -3009,6 +3071,7 @@ class Message(MaybeInaccessibleMessage): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: ODVInput[int] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3068,6 +3131,7 @@ class Message(MaybeInaccessibleMessage): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_chat_action( @@ -3122,6 +3186,7 @@ class Message(MaybeInaccessibleMessage): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: ODVInput[int] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3183,6 +3248,7 @@ class Message(MaybeInaccessibleMessage): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=self.business_connection_id, + message_effect_id=message_effect_id, ) async def reply_invoice( @@ -3190,7 +3256,7 @@ class Message(MaybeInaccessibleMessage): title: str, description: str, payload: str, - provider_token: str, + provider_token: Optional[str], currency: str, prices: Sequence["LabeledPrice"], start_parameter: Optional[str] = None, @@ -3213,6 +3279,7 @@ class Message(MaybeInaccessibleMessage): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: ODVInput[int] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3302,6 +3369,7 @@ class Message(MaybeInaccessibleMessage): suggested_tip_amounts=suggested_tip_amounts, protect_content=protect_content, message_thread_id=message_thread_id, + message_effect_id=message_effect_id, ) async def forward( @@ -3365,6 +3433,7 @@ class Message(MaybeInaccessibleMessage): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3409,6 +3478,7 @@ class Message(MaybeInaccessibleMessage): api_kwargs=api_kwargs, protect_content=protect_content, message_thread_id=message_thread_id, + show_caption_above_media=show_caption_above_media, ) async def reply_copy( @@ -3423,6 +3493,7 @@ class Message(MaybeInaccessibleMessage): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: ODVInput[int] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3486,6 +3557,7 @@ class Message(MaybeInaccessibleMessage): api_kwargs=api_kwargs, protect_content=protect_content, message_thread_id=message_thread_id, + show_caption_above_media=show_caption_above_media, ) async def edit_text( @@ -3544,6 +3616,7 @@ class Message(MaybeInaccessibleMessage): reply_markup: Optional["InlineKeyboardMarkup"] = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence["MessageEntity"]] = None, + show_caption_above_media: Optional[bool] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -3583,6 +3656,7 @@ class Message(MaybeInaccessibleMessage): api_kwargs=api_kwargs, caption_entities=caption_entities, inline_message_id=None, + show_caption_above_media=show_caption_above_media, ) async def edit_media( @@ -4333,6 +4407,8 @@ class Message(MaybeInaccessibleMessage): insert = f'{escaped_text}' elif entity.type == MessageEntity.BLOCKQUOTE: insert = f"
{escaped_text}
" + elif entity.type == MessageEntity.EXPANDABLE_BLOCKQUOTE: + insert = f"
{escaped_text}
" elif entity.type == MessageEntity.BOLD: insert = f"{escaped_text}" elif entity.type == MessageEntity.ITALIC: @@ -4483,11 +4559,12 @@ class Message(MaybeInaccessibleMessage): ) -> Optional[str]: if version == 1: for entity_type in ( - MessageEntity.UNDERLINE, - MessageEntity.STRIKETHROUGH, - MessageEntity.SPOILER, + MessageEntity.EXPANDABLE_BLOCKQUOTE, MessageEntity.BLOCKQUOTE, MessageEntity.CUSTOM_EMOJI, + MessageEntity.SPOILER, + MessageEntity.STRIKETHROUGH, + MessageEntity.UNDERLINE, ): if any(entity.type == entity_type for entity in entities): name = entity_type.name.title().replace("_", " ") # type:ignore[attr-defined] @@ -4567,8 +4644,10 @@ class Message(MaybeInaccessibleMessage): insert = f"~{escaped_text}~" elif entity.type == MessageEntity.SPOILER: insert = f"||{escaped_text}||" - elif entity.type == MessageEntity.BLOCKQUOTE: + elif entity.type in (MessageEntity.BLOCKQUOTE, MessageEntity.EXPANDABLE_BLOCKQUOTE): insert = ">" + "\n>".join(escaped_text.splitlines()) + if entity.type == MessageEntity.EXPANDABLE_BLOCKQUOTE: + insert = f"{insert}||" elif entity.type == MessageEntity.CUSTOM_EMOJI: # This should never be needed because ids are numeric but the documentation # specifically mentions it so here we are diff --git a/telegram/_messageentity.py b/telegram/_messageentity.py index f6e0ba6ed..ebdcb8e07 100644 --- a/telegram/_messageentity.py +++ b/telegram/_messageentity.py @@ -140,50 +140,55 @@ class MessageEntity(TelegramObject): return super().de_json(data=data, bot=bot) - MENTION: Final[str] = constants.MessageEntityType.MENTION - """:const:`telegram.constants.MessageEntityType.MENTION`""" - HASHTAG: Final[str] = constants.MessageEntityType.HASHTAG - """:const:`telegram.constants.MessageEntityType.HASHTAG`""" - CASHTAG: Final[str] = constants.MessageEntityType.CASHTAG - """:const:`telegram.constants.MessageEntityType.CASHTAG`""" - PHONE_NUMBER: Final[str] = constants.MessageEntityType.PHONE_NUMBER - """:const:`telegram.constants.MessageEntityType.PHONE_NUMBER`""" - BOT_COMMAND: Final[str] = constants.MessageEntityType.BOT_COMMAND - """:const:`telegram.constants.MessageEntityType.BOT_COMMAND`""" - URL: Final[str] = constants.MessageEntityType.URL - """:const:`telegram.constants.MessageEntityType.URL`""" - EMAIL: Final[str] = constants.MessageEntityType.EMAIL - """:const:`telegram.constants.MessageEntityType.EMAIL`""" + ALL_TYPES: Final[List[str]] = list(constants.MessageEntityType) + """List[:obj:`str`]: A list of all available message entity types.""" + BLOCKQUOTE: Final[str] = constants.MessageEntityType.BLOCKQUOTE + """:const:`telegram.constants.MessageEntityType.BLOCKQUOTE` + + .. versionadded:: 20.8 + """ BOLD: Final[str] = constants.MessageEntityType.BOLD """:const:`telegram.constants.MessageEntityType.BOLD`""" - ITALIC: Final[str] = constants.MessageEntityType.ITALIC - """:const:`telegram.constants.MessageEntityType.ITALIC`""" + BOT_COMMAND: Final[str] = constants.MessageEntityType.BOT_COMMAND + """:const:`telegram.constants.MessageEntityType.BOT_COMMAND`""" + CASHTAG: Final[str] = constants.MessageEntityType.CASHTAG + """:const:`telegram.constants.MessageEntityType.CASHTAG`""" CODE: Final[str] = constants.MessageEntityType.CODE """:const:`telegram.constants.MessageEntityType.CODE`""" + CUSTOM_EMOJI: Final[str] = constants.MessageEntityType.CUSTOM_EMOJI + """:const:`telegram.constants.MessageEntityType.CUSTOM_EMOJI` + + .. versionadded:: 20.0 + """ + EMAIL: Final[str] = constants.MessageEntityType.EMAIL + """:const:`telegram.constants.MessageEntityType.EMAIL`""" + EXPANDABLE_BLOCKQUOTE: Final[str] = constants.MessageEntityType.EXPANDABLE_BLOCKQUOTE + """:const:`telegram.constants.MessageEntityType.EXPANDABLE_BLOCKQUOTE` + + .. versionadded:: NEXT.VERSION + """ + HASHTAG: Final[str] = constants.MessageEntityType.HASHTAG + """:const:`telegram.constants.MessageEntityType.HASHTAG`""" + ITALIC: Final[str] = constants.MessageEntityType.ITALIC + """:const:`telegram.constants.MessageEntityType.ITALIC`""" + MENTION: Final[str] = constants.MessageEntityType.MENTION + """:const:`telegram.constants.MessageEntityType.MENTION`""" + PHONE_NUMBER: Final[str] = constants.MessageEntityType.PHONE_NUMBER + """:const:`telegram.constants.MessageEntityType.PHONE_NUMBER`""" PRE: Final[str] = constants.MessageEntityType.PRE """:const:`telegram.constants.MessageEntityType.PRE`""" + SPOILER: Final[str] = constants.MessageEntityType.SPOILER + """:const:`telegram.constants.MessageEntityType.SPOILER` + + .. versionadded:: 13.10 + """ + STRIKETHROUGH: Final[str] = constants.MessageEntityType.STRIKETHROUGH + """:const:`telegram.constants.MessageEntityType.STRIKETHROUGH`""" TEXT_LINK: Final[str] = constants.MessageEntityType.TEXT_LINK """:const:`telegram.constants.MessageEntityType.TEXT_LINK`""" TEXT_MENTION: Final[str] = constants.MessageEntityType.TEXT_MENTION """:const:`telegram.constants.MessageEntityType.TEXT_MENTION`""" UNDERLINE: Final[str] = constants.MessageEntityType.UNDERLINE """:const:`telegram.constants.MessageEntityType.UNDERLINE`""" - STRIKETHROUGH: Final[str] = constants.MessageEntityType.STRIKETHROUGH - """:const:`telegram.constants.MessageEntityType.STRIKETHROUGH`""" - SPOILER: Final[str] = constants.MessageEntityType.SPOILER - """:const:`telegram.constants.MessageEntityType.SPOILER` - - .. versionadded:: 13.10 - """ - CUSTOM_EMOJI: Final[str] = constants.MessageEntityType.CUSTOM_EMOJI - """:const:`telegram.constants.MessageEntityType.CUSTOM_EMOJI` - - .. versionadded:: 20.0 - """ - BLOCKQUOTE: Final[str] = constants.MessageEntityType.BLOCKQUOTE - """:const:`telegram.constants.MessageEntityType.BLOCKQUOTE` - - .. versionadded:: 20.8 - """ - ALL_TYPES: Final[List[str]] = list(constants.MessageEntityType) - """List[:obj:`str`]: A list of all available message entity types.""" + URL: Final[str] = constants.MessageEntityType.URL + """:const:`telegram.constants.MessageEntityType.URL`""" diff --git a/telegram/_payment/invoice.py b/telegram/_payment/invoice.py index 048784246..141f0c8fd 100644 --- a/telegram/_payment/invoice.py +++ b/telegram/_payment/invoice.py @@ -37,7 +37,8 @@ class Invoice(TelegramObject): description (:obj:`str`): Product description. start_parameter (:obj:`str`): Unique bot deep-linking parameter that can be used to generate this invoice. - currency (:obj:`str`): Three-letter ISO 4217 currency code. + currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in + |tg_stars|. total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 pass ``amount = 145``. See the ``exp`` parameter in @@ -50,7 +51,8 @@ class Invoice(TelegramObject): description (:obj:`str`): Product description. start_parameter (:obj:`str`): Unique bot deep-linking parameter that can be used to generate this invoice. - currency (:obj:`str`): Three-letter ISO 4217 currency code. + currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in + |tg_stars|. total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 ``amount`` is ``145``. See the ``exp`` parameter in diff --git a/telegram/_payment/precheckoutquery.py b/telegram/_payment/precheckoutquery.py index 4e7127eea..fb57127fc 100644 --- a/telegram/_payment/precheckoutquery.py +++ b/telegram/_payment/precheckoutquery.py @@ -42,7 +42,8 @@ class PreCheckoutQuery(TelegramObject): Args: id (:obj:`str`): Unique query identifier. from_user (:class:`telegram.User`): User who sent the query. - currency (:obj:`str`): Three-letter ISO 4217 currency code. + currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in + |tg_stars|. total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 pass ``amount = 145``. See the ``exp`` parameter in @@ -57,7 +58,8 @@ class PreCheckoutQuery(TelegramObject): Attributes: id (:obj:`str`): Unique query identifier. from_user (:class:`telegram.User`): User who sent the query. - currency (:obj:`str`): Three-letter ISO 4217 currency code. + currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in + |tg_stars|. total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 ``amount`` is ``145``. See the ``exp`` parameter in diff --git a/telegram/_payment/successfulpayment.py b/telegram/_payment/successfulpayment.py index 737ad841e..a7424feba 100644 --- a/telegram/_payment/successfulpayment.py +++ b/telegram/_payment/successfulpayment.py @@ -36,7 +36,8 @@ class SuccessfulPayment(TelegramObject): :attr:`provider_payment_charge_id` are equal. Args: - currency (:obj:`str`): Three-letter ISO 4217 currency code. + currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in + |tg_stars|. total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 pass ``amount = 145``. See the ``exp`` parameter in @@ -51,7 +52,8 @@ class SuccessfulPayment(TelegramObject): provider_payment_charge_id (:obj:`str`): Provider payment identifier. Attributes: - currency (:obj:`str`): Three-letter ISO 4217 currency code. + currency (:obj:`str`): Three-letter ISO 4217 currency code, or ``XTR`` for payments in + |tg_stars|. total_amount (:obj:`int`): Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 ``amount`` is ``145``. See the ``exp`` parameter in diff --git a/telegram/_user.py b/telegram/_user.py index 17b58f2df..84ab7728c 100644 --- a/telegram/_user.py +++ b/telegram/_user.py @@ -409,6 +409,7 @@ class User(TelegramObject): link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, disable_web_page_preview: Optional[bool] = None, @@ -452,6 +453,7 @@ class User(TelegramObject): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def delete_message( @@ -531,6 +533,8 @@ class User(TelegramObject): has_spoiler: Optional[bool] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -575,6 +579,8 @@ class User(TelegramObject): api_kwargs=api_kwargs, has_spoiler=has_spoiler, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def send_media_group( @@ -587,6 +593,7 @@ class User(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -631,6 +638,7 @@ class User(TelegramObject): parse_mode=parse_mode, caption_entities=caption_entities, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_audio( @@ -649,6 +657,7 @@ class User(TelegramObject): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -696,6 +705,7 @@ class User(TelegramObject): api_kwargs=api_kwargs, thumbnail=thumbnail, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_chat_action( @@ -750,6 +760,7 @@ class User(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -793,6 +804,7 @@ class User(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_dice( @@ -804,6 +816,7 @@ class User(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -842,6 +855,7 @@ class User(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_document( @@ -858,6 +872,7 @@ class User(TelegramObject): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -903,6 +918,7 @@ class User(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_game( @@ -914,6 +930,7 @@ class User(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -952,6 +969,7 @@ class User(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_invoice( @@ -959,7 +977,7 @@ class User(TelegramObject): title: str, description: str, payload: str, - provider_token: str, + provider_token: Optional[str], currency: str, prices: Sequence["LabeledPrice"], start_parameter: Optional[str] = None, @@ -982,6 +1000,7 @@ class User(TelegramObject): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1049,6 +1068,7 @@ class User(TelegramObject): suggested_tip_amounts=suggested_tip_amounts, protect_content=protect_content, message_thread_id=message_thread_id, + message_effect_id=message_effect_id, ) async def send_location( @@ -1065,6 +1085,7 @@ class User(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1110,6 +1131,7 @@ class User(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_animation( @@ -1129,6 +1151,8 @@ class User(TelegramObject): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1177,6 +1201,8 @@ class User(TelegramObject): has_spoiler=has_spoiler, thumbnail=thumbnail, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def send_sticker( @@ -1189,6 +1215,7 @@ class User(TelegramObject): emoji: Optional[str] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1228,6 +1255,7 @@ class User(TelegramObject): message_thread_id=message_thread_id, emoji=emoji, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_video( @@ -1248,6 +1276,8 @@ class User(TelegramObject): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1297,6 +1327,8 @@ class User(TelegramObject): message_thread_id=message_thread_id, has_spoiler=has_spoiler, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def send_venue( @@ -1315,6 +1347,7 @@ class User(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1362,6 +1395,7 @@ class User(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_video_note( @@ -1376,6 +1410,7 @@ class User(TelegramObject): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1419,6 +1454,7 @@ class User(TelegramObject): message_thread_id=message_thread_id, thumbnail=thumbnail, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_voice( @@ -1434,6 +1470,7 @@ class User(TelegramObject): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1478,6 +1515,7 @@ class User(TelegramObject): protect_content=protect_content, message_thread_id=message_thread_id, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_poll( @@ -1502,6 +1540,7 @@ class User(TelegramObject): business_connection_id: Optional[str] = None, question_parse_mode: ODVInput[str] = DEFAULT_NONE, question_entities: Optional[Sequence["MessageEntity"]] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1553,6 +1592,7 @@ class User(TelegramObject): business_connection_id=business_connection_id, question_parse_mode=question_parse_mode, question_entities=question_entities, + message_effect_id=message_effect_id, ) async def send_copy( @@ -1567,6 +1607,7 @@ class User(TelegramObject): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1608,6 +1649,7 @@ class User(TelegramObject): api_kwargs=api_kwargs, protect_content=protect_content, message_thread_id=message_thread_id, + show_caption_above_media=show_caption_above_media, ) async def copy_message( @@ -1622,6 +1664,7 @@ class User(TelegramObject): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -1663,6 +1706,7 @@ class User(TelegramObject): api_kwargs=api_kwargs, protect_content=protect_content, message_thread_id=message_thread_id, + show_caption_above_media=show_caption_above_media, ) async def send_copies( @@ -2101,3 +2145,35 @@ class User(TelegramObject): pool_timeout=pool_timeout, api_kwargs=api_kwargs, ) + + async def refund_star_payment( + self, + telegram_payment_charge_id: str, + *, + 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, + ) -> bool: + """Shortcut for:: + + await bot.refund_star_payment(user_id=update.effective_user.id, *args, **kwargs) + + For the documentation of the arguments, please see + :meth:`telegram.Bot.refund_star_payment`. + + .. versionadded:: NEXT.VERSION + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + """ + return await self.get_bot().refund_star_payment( + user_id=self.id, + telegram_payment_charge_id=telegram_payment_charge_id, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) diff --git a/telegram/constants.py b/telegram/constants.py index 06f5bff86..faf797231 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -146,7 +146,7 @@ class _AccentColor(NamedTuple): #: :data:`telegram.__bot_api_version_info__`. #: #: .. versionadded:: 20.0 -BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=3) +BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=4) #: :obj:`str`: Telegram Bot API #: version supported by this version of `python-telegram-bot`. Also available as #: :data:`telegram.__bot_api_version__`. @@ -1630,48 +1630,53 @@ class MessageEntityType(StringEnum): __slots__ = () - MENTION = "mention" - """:obj:`str`: Message entities representing a mention.""" - HASHTAG = "hashtag" - """:obj:`str`: Message entities representing a hashtag.""" - CASHTAG = "cashtag" - """:obj:`str`: Message entities representing a cashtag.""" - PHONE_NUMBER = "phone_number" - """:obj:`str`: Message entities representing a phone number.""" - BOT_COMMAND = "bot_command" - """:obj:`str`: Message entities representing a bot command.""" - URL = "url" - """:obj:`str`: Message entities representing a url.""" - EMAIL = "email" - """:obj:`str`: Message entities representing a email.""" + BLOCKQUOTE = "blockquote" + """:obj:`str`: Message entities representing a block quotation. + + .. versionadded:: 20.8 + """ BOLD = "bold" """:obj:`str`: Message entities representing bold text.""" - ITALIC = "italic" - """:obj:`str`: Message entities representing italic text.""" + BOT_COMMAND = "bot_command" + """:obj:`str`: Message entities representing a bot command.""" + CASHTAG = "cashtag" + """:obj:`str`: Message entities representing a cashtag.""" CODE = "code" """:obj:`str`: Message entities representing monowidth string.""" + CUSTOM_EMOJI = "custom_emoji" + """:obj:`str`: Message entities representing inline custom emoji stickers. + + .. versionadded:: 20.0 + """ + EMAIL = "email" + """:obj:`str`: Message entities representing a email.""" + EXPANDABLE_BLOCKQUOTE = "expandable_blockquote" + """:obj:`str`: Message entities representing collapsed-by-default block quotation. + + .. versionadded:: NEXT.VERSION + """ + HASHTAG = "hashtag" + """:obj:`str`: Message entities representing a hashtag.""" + ITALIC = "italic" + """:obj:`str`: Message entities representing italic text.""" + MENTION = "mention" + """:obj:`str`: Message entities representing a mention.""" + PHONE_NUMBER = "phone_number" + """:obj:`str`: Message entities representing a phone number.""" PRE = "pre" """:obj:`str`: Message entities representing monowidth block.""" + SPOILER = "spoiler" + """:obj:`str`: Message entities representing spoiler text.""" + STRIKETHROUGH = "strikethrough" + """:obj:`str`: Message entities representing strikethrough text.""" TEXT_LINK = "text_link" """:obj:`str`: Message entities representing clickable text URLs.""" TEXT_MENTION = "text_mention" """:obj:`str`: Message entities representing text mention for users without usernames.""" UNDERLINE = "underline" """:obj:`str`: Message entities representing underline text.""" - STRIKETHROUGH = "strikethrough" - """:obj:`str`: Message entities representing strikethrough text.""" - SPOILER = "spoiler" - """:obj:`str`: Message entities representing spoiler text.""" - CUSTOM_EMOJI = "custom_emoji" - """:obj:`str`: Message entities representing inline custom emoji stickers. - - .. versionadded:: 20.0 - """ - BLOCKQUOTE = "blockquote" - """:obj:`str`: Message entities representing a block quotation. - - .. versionadded:: 20.8 - """ + URL = "url" + """:obj:`str`: Message entities representing a url.""" class MessageLimit(IntEnum): @@ -1801,6 +1806,10 @@ class MessageType(StringEnum): """:obj:`str`: Messages with :attr:`telegram.Message.dice`.""" DOCUMENT = "document" """:obj:`str`: Messages with :attr:`telegram.Message.document`.""" + EFFECT_ID = "effect_id" + """:obj:`str`: Messages with :attr:`telegram.Message.effect_id`. + + .. versionadded:: NEXT.VERSION""" FORUM_TOPIC_CREATED = "forum_topic_created" """:obj:`str`: Messages with :attr:`telegram.Message.forum_topic_created`. diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index 6cefee43c..917e9d8ef 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -593,6 +593,7 @@ class ExtBot(Bot, Generic[RLARGS]): link_preview_options: ODVInput["LinkPreviewOptions"] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -624,6 +625,7 @@ class ExtBot(Bot, Generic[RLARGS]): pool_timeout=pool_timeout, api_kwargs=api_kwargs, business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) if isinstance(result, Message): self._insert_callback_data(result) @@ -798,6 +800,7 @@ class ExtBot(Bot, Generic[RLARGS]): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -828,6 +831,7 @@ 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), + show_caption_above_media=show_caption_above_media, ) async def copy_messages( @@ -1146,7 +1150,7 @@ class ExtBot(Bot, Generic[RLARGS]): title: str, description: str, payload: str, - provider_token: str, + provider_token: Optional[str], currency: str, prices: Sequence["LabeledPrice"], max_tip_amount: Optional[int] = None, @@ -1506,6 +1510,7 @@ class ExtBot(Bot, Generic[RLARGS]): reply_markup: Optional["InlineKeyboardMarkup"] = None, parse_mode: ODVInput[str] = DEFAULT_NONE, caption_entities: Optional[Sequence["MessageEntity"]] = None, + show_caption_above_media: Optional[bool] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -1527,6 +1532,7 @@ 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), + show_caption_above_media=show_caption_above_media, ) async def edit_message_live_location( @@ -2379,6 +2385,8 @@ class ExtBot(Bot, Generic[RLARGS]): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2415,6 +2423,8 @@ class ExtBot(Bot, Generic[RLARGS]): business_connection_id=business_connection_id, pool_timeout=pool_timeout, api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def send_audio( @@ -2434,6 +2444,7 @@ class ExtBot(Bot, Generic[RLARGS]): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2469,6 +2480,7 @@ 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), + message_effect_id=message_effect_id, ) async def send_chat_action( @@ -2510,6 +2522,7 @@ class ExtBot(Bot, Generic[RLARGS]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2541,6 +2554,7 @@ class ExtBot(Bot, Generic[RLARGS]): pool_timeout=pool_timeout, business_connection_id=business_connection_id, api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + message_effect_id=message_effect_id, ) async def send_dice( @@ -2553,6 +2567,7 @@ class ExtBot(Bot, Generic[RLARGS]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2579,6 +2594,7 @@ 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), + message_effect_id=message_effect_id, ) async def send_document( @@ -2596,6 +2612,7 @@ class ExtBot(Bot, Generic[RLARGS]): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2629,6 +2646,7 @@ 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), + message_effect_id=message_effect_id, ) async def send_game( @@ -2641,6 +2659,7 @@ class ExtBot(Bot, Generic[RLARGS]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2667,6 +2686,7 @@ 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), + message_effect_id=message_effect_id, ) async def send_invoice( @@ -2675,7 +2695,7 @@ class ExtBot(Bot, Generic[RLARGS]): title: str, description: str, payload: str, - provider_token: str, + provider_token: Optional[str], currency: str, prices: Sequence["LabeledPrice"], start_parameter: Optional[str] = None, @@ -2698,6 +2718,7 @@ class ExtBot(Bot, Generic[RLARGS]): protect_content: ODVInput[bool] = DEFAULT_NONE, message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2743,6 +2764,7 @@ 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), + message_effect_id=message_effect_id, ) async def send_location( @@ -2760,6 +2782,7 @@ class ExtBot(Bot, Generic[RLARGS]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2793,6 +2816,7 @@ class ExtBot(Bot, Generic[RLARGS]): business_connection_id=business_connection_id, pool_timeout=pool_timeout, api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + message_effect_id=message_effect_id, ) async def send_media_group( @@ -2806,6 +2830,7 @@ class ExtBot(Bot, Generic[RLARGS]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2837,6 +2862,7 @@ class ExtBot(Bot, Generic[RLARGS]): business_connection_id=business_connection_id, parse_mode=parse_mode, caption_entities=caption_entities, + message_effect_id=message_effect_id, ) async def send_message( @@ -2852,6 +2878,7 @@ class ExtBot(Bot, Generic[RLARGS]): link_preview_options: ODVInput["LinkPreviewOptions"] = DEFAULT_NONE, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, disable_web_page_preview: Optional[bool] = None, reply_to_message_id: Optional[int] = None, @@ -2883,6 +2910,7 @@ class ExtBot(Bot, Generic[RLARGS]): pool_timeout=pool_timeout, api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), link_preview_options=link_preview_options, + message_effect_id=message_effect_id, ) async def send_photo( @@ -2899,6 +2927,8 @@ class ExtBot(Bot, Generic[RLARGS]): has_spoiler: Optional[bool] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2931,6 +2961,8 @@ 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), + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def send_poll( @@ -2956,6 +2988,7 @@ class ExtBot(Bot, Generic[RLARGS]): business_connection_id: Optional[str] = None, question_parse_mode: ODVInput[str] = DEFAULT_NONE, question_entities: Optional[Sequence["MessageEntity"]] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -2995,6 +3028,7 @@ class ExtBot(Bot, Generic[RLARGS]): api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), question_parse_mode=question_parse_mode, question_entities=question_entities, + message_effect_id=message_effect_id, ) async def send_sticker( @@ -3008,6 +3042,7 @@ class ExtBot(Bot, Generic[RLARGS]): emoji: Optional[str] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3035,6 +3070,7 @@ class ExtBot(Bot, Generic[RLARGS]): pool_timeout=pool_timeout, emoji=emoji, api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + message_effect_id=message_effect_id, ) async def send_venue( @@ -3054,6 +3090,7 @@ class ExtBot(Bot, Generic[RLARGS]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3089,6 +3126,7 @@ 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), + message_effect_id=message_effect_id, ) async def send_video( @@ -3110,6 +3148,8 @@ class ExtBot(Bot, Generic[RLARGS]): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, + show_caption_above_media: Optional[bool] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3147,6 +3187,8 @@ 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), + message_effect_id=message_effect_id, + show_caption_above_media=show_caption_above_media, ) async def send_video_note( @@ -3162,6 +3204,7 @@ class ExtBot(Bot, Generic[RLARGS]): thumbnail: Optional[FileInput] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3193,6 +3236,7 @@ class ExtBot(Bot, Generic[RLARGS]): pool_timeout=pool_timeout, api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def send_voice( @@ -3209,6 +3253,7 @@ class ExtBot(Bot, Generic[RLARGS]): message_thread_id: Optional[int] = None, reply_parameters: Optional["ReplyParameters"] = None, business_connection_id: Optional[str] = None, + message_effect_id: Optional[str] = None, *, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: ODVInput[bool] = DEFAULT_NONE, @@ -3241,6 +3286,7 @@ class ExtBot(Bot, Generic[RLARGS]): pool_timeout=pool_timeout, api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), business_connection_id=business_connection_id, + message_effect_id=message_effect_id, ) async def set_chat_administrator_custom_title( @@ -4111,6 +4157,28 @@ class ExtBot(Bot, Generic[RLARGS]): api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) + async def refund_star_payment( + self, + user_id: int, + telegram_payment_charge_id: str, + *, + 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, + ) -> bool: + return await super().refund_star_payment( + user_id=user_id, + telegram_payment_charge_id=telegram_payment_charge_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 getMe = get_me sendMessage = send_message @@ -4232,3 +4300,4 @@ class ExtBot(Bot, Generic[RLARGS]): setMessageReaction = set_message_reaction getBusinessConnection = get_business_connection replaceStickerInSet = replace_sticker_in_set + refundStarPayment = refund_star_payment diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index 1fef40a57..24dc982e6 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -46,6 +46,7 @@ __all__ = ( "CHAT", "COMMAND", "CONTACT", + "EFFECT_ID", "FORWARDED", "GAME", "GIVEAWAY", @@ -1338,6 +1339,19 @@ class Document: """Use as ``filters.Document.ZIP``.""" +class _EffectId(MessageFilter): + __slots__ = () + + def filter(self, message: Message) -> bool: + return bool(message.effect_id) + + +EFFECT_ID = _EffectId(name="filters.EFFECT_ID") +"""Messages that contain :attr:`telegram.Message.effect_id`. + +.. versionadded:: NEXT.VERSION""" + + class Entity(MessageFilter): """ Filters messages to only allow those which have a :class:`telegram.MessageEntity` diff --git a/tests/_files/test_animation.py b/tests/_files/test_animation.py index f14504a75..74a17fca4 100644 --- a/tests/_files/test_animation.py +++ b/tests/_files/test_animation.py @@ -233,6 +233,7 @@ class TestAnimationWithRequest(TestAnimationBase): protect_content=True, thumbnail=thumb_file, has_spoiler=True, + show_caption_above_media=True, ) assert isinstance(message.animation, Animation) @@ -246,6 +247,7 @@ class TestAnimationWithRequest(TestAnimationBase): assert message.animation.thumbnail.width == self.width assert message.animation.thumbnail.height == self.height assert message.has_protected_content + assert message.show_caption_above_media try: assert message.has_media_spoiler except AssertionError: diff --git a/tests/_files/test_inputmedia.py b/tests/_files/test_inputmedia.py index 6febe12c8..cce7fdc07 100644 --- a/tests/_files/test_inputmedia.py +++ b/tests/_files/test_inputmedia.py @@ -76,6 +76,7 @@ def input_media_video(class_thumb_file): thumbnail=class_thumb_file, supports_streaming=TestInputMediaVideoBase.supports_streaming, has_spoiler=TestInputMediaVideoBase.has_spoiler, + show_caption_above_media=TestInputMediaVideoBase.show_caption_above_media, ) @@ -87,6 +88,7 @@ def input_media_photo(): parse_mode=TestInputMediaPhotoBase.parse_mode, caption_entities=TestInputMediaPhotoBase.caption_entities, has_spoiler=TestInputMediaPhotoBase.has_spoiler, + show_caption_above_media=TestInputMediaPhotoBase.show_caption_above_media, ) @@ -102,6 +104,7 @@ def input_media_animation(class_thumb_file): thumbnail=class_thumb_file, duration=TestInputMediaAnimationBase.duration, has_spoiler=TestInputMediaAnimationBase.has_spoiler, + show_caption_above_media=TestInputMediaAnimationBase.show_caption_above_media, ) @@ -142,6 +145,7 @@ class TestInputMediaVideoBase: supports_streaming = True caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)] has_spoiler = True + show_caption_above_media = True class TestInputMediaVideoWithoutRequest(TestInputMediaVideoBase): @@ -163,6 +167,7 @@ class TestInputMediaVideoWithoutRequest(TestInputMediaVideoBase): assert input_media_video.supports_streaming == self.supports_streaming assert isinstance(input_media_video.thumbnail, InputFile) assert input_media_video.has_spoiler == self.has_spoiler + assert input_media_video.show_caption_above_media == self.show_caption_above_media def test_caption_entities_always_tuple(self): input_media_video = InputMediaVideo(self.media) @@ -182,6 +187,10 @@ class TestInputMediaVideoWithoutRequest(TestInputMediaVideoBase): ] assert input_media_video_dict["supports_streaming"] == input_media_video.supports_streaming assert input_media_video_dict["has_spoiler"] == input_media_video.has_spoiler + assert ( + input_media_video_dict["show_caption_above_media"] + == input_media_video.show_caption_above_media + ) def test_with_video(self, video): # noqa: F811 # fixture found in test_video @@ -235,6 +244,7 @@ class TestInputMediaPhotoBase: parse_mode = "Markdown" caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)] has_spoiler = True + show_caption_above_media = True class TestInputMediaPhotoWithoutRequest(TestInputMediaPhotoBase): @@ -251,6 +261,7 @@ class TestInputMediaPhotoWithoutRequest(TestInputMediaPhotoBase): assert input_media_photo.parse_mode == self.parse_mode assert input_media_photo.caption_entities == tuple(self.caption_entities) assert input_media_photo.has_spoiler == self.has_spoiler + assert input_media_photo.show_caption_above_media == self.show_caption_above_media def test_caption_entities_always_tuple(self): input_media_photo = InputMediaPhoto(self.media) @@ -266,6 +277,10 @@ class TestInputMediaPhotoWithoutRequest(TestInputMediaPhotoBase): ce.to_dict() for ce in input_media_photo.caption_entities ] assert input_media_photo_dict["has_spoiler"] == input_media_photo.has_spoiler + assert ( + input_media_photo_dict["show_caption_above_media"] + == input_media_photo.show_caption_above_media + ) def test_with_photo(self, photo): # noqa: F811 # fixture found in test_photo @@ -296,6 +311,7 @@ class TestInputMediaAnimationBase: height = 30 duration = 1 has_spoiler = True + show_caption_above_media = True class TestInputMediaAnimationWithoutRequest(TestInputMediaAnimationBase): @@ -313,6 +329,7 @@ class TestInputMediaAnimationWithoutRequest(TestInputMediaAnimationBase): assert input_media_animation.caption_entities == tuple(self.caption_entities) assert isinstance(input_media_animation.thumbnail, InputFile) assert input_media_animation.has_spoiler == self.has_spoiler + assert input_media_animation.show_caption_above_media == self.show_caption_above_media def test_caption_entities_always_tuple(self): input_media_animation = InputMediaAnimation(self.media) @@ -331,6 +348,10 @@ class TestInputMediaAnimationWithoutRequest(TestInputMediaAnimationBase): assert input_media_animation_dict["height"] == input_media_animation.height assert input_media_animation_dict["duration"] == input_media_animation.duration assert input_media_animation_dict["has_spoiler"] == input_media_animation.has_spoiler + assert ( + input_media_animation_dict["show_caption_above_media"] + == input_media_animation.show_caption_above_media + ) def test_with_animation(self, animation): # noqa: F811 # fixture found in test_animation diff --git a/tests/_files/test_photo.py b/tests/_files/test_photo.py index d8be6e814..ac53a048c 100644 --- a/tests/_files/test_photo.py +++ b/tests/_files/test_photo.py @@ -247,6 +247,7 @@ class TestPhotoWithRequest(TestPhotoBase): protect_content=True, parse_mode="Markdown", has_spoiler=True, + show_caption_above_media=True, ) assert isinstance(message.photo[-2], PhotoSize) @@ -264,6 +265,7 @@ class TestPhotoWithRequest(TestPhotoBase): assert message.caption == self.caption.replace("*", "") assert message.has_protected_content assert message.has_media_spoiler + assert message.show_caption_above_media async def test_send_photo_parse_mode_markdown(self, bot, chat_id, photo_file): message = await bot.send_photo( diff --git a/tests/_files/test_video.py b/tests/_files/test_video.py index c7fedbb8c..7f31f9f6e 100644 --- a/tests/_files/test_video.py +++ b/tests/_files/test_video.py @@ -244,6 +244,7 @@ class TestVideoWithRequest(TestVideoBase): parse_mode="Markdown", thumbnail=thumb_file, has_spoiler=True, + show_caption_above_media=True, ) assert isinstance(message.video, Video) @@ -265,6 +266,7 @@ class TestVideoWithRequest(TestVideoBase): assert message.video.file_name == self.file_name assert message.has_protected_content assert message.has_media_spoiler + assert message.show_caption_above_media async def test_get_and_download(self, bot, video, chat_id, tmp_file): new_file = await bot.get_file(video.file_id) diff --git a/tests/_inline/test_inlinequeryresultcachedgif.py b/tests/_inline/test_inlinequeryresultcachedgif.py index d1a3fcdb8..62b908a63 100644 --- a/tests/_inline/test_inlinequeryresultcachedgif.py +++ b/tests/_inline/test_inlinequeryresultcachedgif.py @@ -40,6 +40,7 @@ def inline_query_result_cached_gif(): caption_entities=TestInlineQueryResultCachedGifBase.caption_entities, input_message_content=TestInlineQueryResultCachedGifBase.input_message_content, reply_markup=TestInlineQueryResultCachedGifBase.reply_markup, + show_caption_above_media=TestInlineQueryResultCachedGifBase.show_caption_above_media, ) @@ -53,6 +54,7 @@ class TestInlineQueryResultCachedGifBase: caption_entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)] input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + show_caption_above_media = True class TestInlineQueryResultCachedGifWithoutRequest(TestInlineQueryResultCachedGifBase): @@ -75,6 +77,10 @@ class TestInlineQueryResultCachedGifWithoutRequest(TestInlineQueryResultCachedGi == self.input_message_content.to_dict() ) assert inline_query_result_cached_gif.reply_markup.to_dict() == self.reply_markup.to_dict() + assert ( + inline_query_result_cached_gif.show_caption_above_media + == self.show_caption_above_media + ) def test_caption_entities_always_tuple(self): result = InlineQueryResultCachedGif(self.id_, self.gif_file_id) @@ -110,6 +116,10 @@ class TestInlineQueryResultCachedGifWithoutRequest(TestInlineQueryResultCachedGi inline_query_result_cached_gif_dict["reply_markup"] == inline_query_result_cached_gif.reply_markup.to_dict() ) + assert ( + inline_query_result_cached_gif_dict["show_caption_above_media"] + == inline_query_result_cached_gif.show_caption_above_media + ) def test_equality(self): a = InlineQueryResultCachedGif(self.id_, self.gif_file_id) diff --git a/tests/_inline/test_inlinequeryresultcachedmpeg4gif.py b/tests/_inline/test_inlinequeryresultcachedmpeg4gif.py index 770b9d5c5..3d4b5d3ca 100644 --- a/tests/_inline/test_inlinequeryresultcachedmpeg4gif.py +++ b/tests/_inline/test_inlinequeryresultcachedmpeg4gif.py @@ -40,6 +40,7 @@ def inline_query_result_cached_mpeg4_gif(): caption_entities=TestInlineQueryResultCachedMpeg4GifBase.caption_entities, input_message_content=TestInlineQueryResultCachedMpeg4GifBase.input_message_content, reply_markup=TestInlineQueryResultCachedMpeg4GifBase.reply_markup, + show_caption_above_media=TestInlineQueryResultCachedMpeg4GifBase.show_caption_above_media, ) @@ -53,6 +54,7 @@ class TestInlineQueryResultCachedMpeg4GifBase: caption_entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)] input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + show_caption_above_media = True class TestInlineQueryResultCachedMpeg4GifWithoutRequest(TestInlineQueryResultCachedMpeg4GifBase): @@ -80,6 +82,10 @@ class TestInlineQueryResultCachedMpeg4GifWithoutRequest(TestInlineQueryResultCac inline_query_result_cached_mpeg4_gif.reply_markup.to_dict() == self.reply_markup.to_dict() ) + assert ( + inline_query_result_cached_mpeg4_gif.show_caption_above_media + == self.show_caption_above_media + ) def test_caption_entities_always_tuple(self): result = InlineQueryResultCachedMpeg4Gif(self.id_, self.mpeg4_file_id) @@ -124,6 +130,10 @@ class TestInlineQueryResultCachedMpeg4GifWithoutRequest(TestInlineQueryResultCac inline_query_result_cached_mpeg4_gif_dict["reply_markup"] == inline_query_result_cached_mpeg4_gif.reply_markup.to_dict() ) + assert ( + inline_query_result_cached_mpeg4_gif_dict["show_caption_above_media"] + == inline_query_result_cached_mpeg4_gif.show_caption_above_media + ) def test_equality(self): a = InlineQueryResultCachedMpeg4Gif(self.id_, self.mpeg4_file_id) diff --git a/tests/_inline/test_inlinequeryresultcachedphoto.py b/tests/_inline/test_inlinequeryresultcachedphoto.py index 02b8df156..b5ed6153b 100644 --- a/tests/_inline/test_inlinequeryresultcachedphoto.py +++ b/tests/_inline/test_inlinequeryresultcachedphoto.py @@ -41,6 +41,7 @@ def inline_query_result_cached_photo(): caption_entities=TestInlineQueryResultCachedPhotoBase.caption_entities, input_message_content=TestInlineQueryResultCachedPhotoBase.input_message_content, reply_markup=TestInlineQueryResultCachedPhotoBase.reply_markup, + show_caption_above_media=TestInlineQueryResultCachedPhotoBase.show_caption_above_media, ) @@ -55,6 +56,7 @@ class TestInlineQueryResultCachedPhotoBase: caption_entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)] input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + show_caption_above_media = True class TestInlineQueryResultCachedPhotoWithoutRequest(TestInlineQueryResultCachedPhotoBase): @@ -80,6 +82,10 @@ class TestInlineQueryResultCachedPhotoWithoutRequest(TestInlineQueryResultCached assert ( inline_query_result_cached_photo.reply_markup.to_dict() == self.reply_markup.to_dict() ) + assert ( + inline_query_result_cached_photo.show_caption_above_media + == self.show_caption_above_media + ) def test_caption_entities_always_tuple(self): result = InlineQueryResultCachedPhoto(self.id_, self.photo_file_id) @@ -124,6 +130,10 @@ class TestInlineQueryResultCachedPhotoWithoutRequest(TestInlineQueryResultCached inline_query_result_cached_photo_dict["reply_markup"] == inline_query_result_cached_photo.reply_markup.to_dict() ) + assert ( + inline_query_result_cached_photo_dict["show_caption_above_media"] + == inline_query_result_cached_photo.show_caption_above_media + ) def test_equality(self): a = InlineQueryResultCachedPhoto(self.id_, self.photo_file_id) diff --git a/tests/_inline/test_inlinequeryresultcachedvideo.py b/tests/_inline/test_inlinequeryresultcachedvideo.py index fd108551b..d520b3c7c 100644 --- a/tests/_inline/test_inlinequeryresultcachedvideo.py +++ b/tests/_inline/test_inlinequeryresultcachedvideo.py @@ -41,6 +41,7 @@ def inline_query_result_cached_video(): description=TestInlineQueryResultCachedVideoBase.description, input_message_content=TestInlineQueryResultCachedVideoBase.input_message_content, reply_markup=TestInlineQueryResultCachedVideoBase.reply_markup, + show_caption_above_media=TestInlineQueryResultCachedVideoBase.show_caption_above_media, ) @@ -55,6 +56,7 @@ class TestInlineQueryResultCachedVideoBase: description = "description" input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + show_caption_above_media = True class TestInlineQueryResultCachedVideoWithoutRequest(TestInlineQueryResultCachedVideoBase): @@ -80,6 +82,10 @@ class TestInlineQueryResultCachedVideoWithoutRequest(TestInlineQueryResultCached assert ( inline_query_result_cached_video.reply_markup.to_dict() == self.reply_markup.to_dict() ) + assert ( + inline_query_result_cached_video.show_caption_above_media + == self.show_caption_above_media + ) def test_caption_entities_always_tuple(self): video = InlineQueryResultCachedVideo(self.id_, self.video_file_id, self.title) @@ -125,6 +131,10 @@ class TestInlineQueryResultCachedVideoWithoutRequest(TestInlineQueryResultCached inline_query_result_cached_video_dict["reply_markup"] == inline_query_result_cached_video.reply_markup.to_dict() ) + assert ( + inline_query_result_cached_video_dict["show_caption_above_media"] + == inline_query_result_cached_video.show_caption_above_media + ) def test_equality(self): a = InlineQueryResultCachedVideo(self.id_, self.video_file_id, self.title) diff --git a/tests/_inline/test_inlinequeryresultgif.py b/tests/_inline/test_inlinequeryresultgif.py index abb3a936f..86ac8574a 100644 --- a/tests/_inline/test_inlinequeryresultgif.py +++ b/tests/_inline/test_inlinequeryresultgif.py @@ -45,6 +45,7 @@ def inline_query_result_gif(): input_message_content=TestInlineQueryResultGifBase.input_message_content, reply_markup=TestInlineQueryResultGifBase.reply_markup, thumbnail_mime_type=TestInlineQueryResultGifBase.thumbnail_mime_type, + show_caption_above_media=TestInlineQueryResultGifBase.show_caption_above_media, ) @@ -63,6 +64,7 @@ class TestInlineQueryResultGifBase: caption_entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)] input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + show_caption_above_media = True class TestInlineQueryResultGifWithoutRequest(TestInlineQueryResultGifBase): @@ -94,6 +96,7 @@ class TestInlineQueryResultGifWithoutRequest(TestInlineQueryResultGifBase): == self.input_message_content.to_dict() ) assert inline_query_result_gif.reply_markup.to_dict() == self.reply_markup.to_dict() + assert inline_query_result_gif.show_caption_above_media == self.show_caption_above_media def test_to_dict(self, inline_query_result_gif): inline_query_result_gif_dict = inline_query_result_gif.to_dict() @@ -126,6 +129,10 @@ class TestInlineQueryResultGifWithoutRequest(TestInlineQueryResultGifBase): inline_query_result_gif_dict["reply_markup"] == inline_query_result_gif.reply_markup.to_dict() ) + assert ( + inline_query_result_gif_dict["show_caption_above_media"] + == inline_query_result_gif.show_caption_above_media + ) def test_equality(self): a = InlineQueryResultGif(self.id_, self.gif_url, self.thumbnail_url) diff --git a/tests/_inline/test_inlinequeryresultmpeg4gif.py b/tests/_inline/test_inlinequeryresultmpeg4gif.py index cd211bd4c..2a1bfc2cf 100644 --- a/tests/_inline/test_inlinequeryresultmpeg4gif.py +++ b/tests/_inline/test_inlinequeryresultmpeg4gif.py @@ -45,6 +45,7 @@ def inline_query_result_mpeg4_gif(): input_message_content=TestInlineQueryResultMpeg4GifBase.input_message_content, reply_markup=TestInlineQueryResultMpeg4GifBase.reply_markup, thumbnail_mime_type=TestInlineQueryResultMpeg4GifBase.thumbnail_mime_type, + show_caption_above_media=TestInlineQueryResultMpeg4GifBase.show_caption_above_media, ) @@ -63,6 +64,7 @@ class TestInlineQueryResultMpeg4GifBase: caption_entities = [MessageEntity(MessageEntity.ITALIC, 0, 7)] input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + show_caption_above_media = True class TestInlineQueryResultMpeg4GifWithoutRequest(TestInlineQueryResultMpeg4GifBase): @@ -90,6 +92,9 @@ class TestInlineQueryResultMpeg4GifWithoutRequest(TestInlineQueryResultMpeg4GifB == self.input_message_content.to_dict() ) assert inline_query_result_mpeg4_gif.reply_markup.to_dict() == self.reply_markup.to_dict() + assert ( + inline_query_result_mpeg4_gif.show_caption_above_media == self.show_caption_above_media + ) def test_caption_entities_always_tuple(self): result = InlineQueryResultMpeg4Gif(self.id_, self.mpeg4_url, self.thumbnail_url) @@ -144,6 +149,10 @@ class TestInlineQueryResultMpeg4GifWithoutRequest(TestInlineQueryResultMpeg4GifB inline_query_result_mpeg4_gif_dict["reply_markup"] == inline_query_result_mpeg4_gif.reply_markup.to_dict() ) + assert ( + inline_query_result_mpeg4_gif_dict["show_caption_above_media"] + == inline_query_result_mpeg4_gif.show_caption_above_media + ) def test_equality(self): a = InlineQueryResultMpeg4Gif(self.id_, self.mpeg4_url, self.thumbnail_url) diff --git a/tests/_inline/test_inlinequeryresultphoto.py b/tests/_inline/test_inlinequeryresultphoto.py index 3078353ca..323cf7b71 100644 --- a/tests/_inline/test_inlinequeryresultphoto.py +++ b/tests/_inline/test_inlinequeryresultphoto.py @@ -44,6 +44,7 @@ def inline_query_result_photo(): caption_entities=TestInlineQueryResultPhotoBase.caption_entities, input_message_content=TestInlineQueryResultPhotoBase.input_message_content, reply_markup=TestInlineQueryResultPhotoBase.reply_markup, + show_caption_above_media=TestInlineQueryResultPhotoBase.show_caption_above_media, ) @@ -62,6 +63,7 @@ class TestInlineQueryResultPhotoBase: input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + show_caption_above_media = True class TestInlineQueryResultPhotoWithoutRequest(TestInlineQueryResultPhotoBase): @@ -88,6 +90,7 @@ class TestInlineQueryResultPhotoWithoutRequest(TestInlineQueryResultPhotoBase): == self.input_message_content.to_dict() ) assert inline_query_result_photo.reply_markup.to_dict() == self.reply_markup.to_dict() + assert inline_query_result_photo.show_caption_above_media == self.show_caption_above_media def test_caption_entities_always_tuple(self): result = InlineQueryResultPhoto(self.id_, self.photo_url, self.thumbnail_url) @@ -128,6 +131,10 @@ class TestInlineQueryResultPhotoWithoutRequest(TestInlineQueryResultPhotoBase): inline_query_result_photo_dict["reply_markup"] == inline_query_result_photo.reply_markup.to_dict() ) + assert ( + inline_query_result_photo_dict["show_caption_above_media"] + == inline_query_result_photo.show_caption_above_media + ) def test_equality(self): a = InlineQueryResultPhoto(self.id_, self.photo_url, self.thumbnail_url) diff --git a/tests/_inline/test_inlinequeryresultvideo.py b/tests/_inline/test_inlinequeryresultvideo.py index 40664af19..68709e396 100644 --- a/tests/_inline/test_inlinequeryresultvideo.py +++ b/tests/_inline/test_inlinequeryresultvideo.py @@ -46,6 +46,7 @@ def inline_query_result_video(): description=TestInlineQueryResultVideoBase.description, input_message_content=TestInlineQueryResultVideoBase.input_message_content, reply_markup=TestInlineQueryResultVideoBase.reply_markup, + show_caption_above_media=TestInlineQueryResultVideoBase.show_caption_above_media, ) @@ -65,6 +66,7 @@ class TestInlineQueryResultVideoBase: description = "description" input_message_content = InputTextMessageContent("input_message_content") reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("reply_markup")]]) + show_caption_above_media = True class TestInlineQueryResultVideoWithoutRequest(TestInlineQueryResultVideoBase): @@ -93,6 +95,7 @@ class TestInlineQueryResultVideoWithoutRequest(TestInlineQueryResultVideoBase): == self.input_message_content.to_dict() ) assert inline_query_result_video.reply_markup.to_dict() == self.reply_markup.to_dict() + assert inline_query_result_video.show_caption_above_media == self.show_caption_above_media def test_caption_entities_always_tuple(self): video = InlineQueryResultVideo( @@ -140,6 +143,10 @@ class TestInlineQueryResultVideoWithoutRequest(TestInlineQueryResultVideoBase): inline_query_result_video_dict["reply_markup"] == inline_query_result_video.reply_markup.to_dict() ) + assert ( + inline_query_result_video_dict["show_caption_above_media"] + == inline_query_result_video.show_caption_above_media + ) def test_equality(self): a = InlineQueryResultVideo( diff --git a/tests/_payment/test_invoice.py b/tests/_payment/test_invoice.py index 532fae0a8..65a39aeb3 100644 --- a/tests/_payment/test_invoice.py +++ b/tests/_payment/test_invoice.py @@ -292,13 +292,14 @@ class TestInvoiceWithRequest(TestInvoiceBase): self.title, self.description, self.payload, - provider_token, - self.currency, - self.prices, + "", # using tg stars + "XTR", + [self.prices[0]], allow_sending_without_reply=custom, reply_to_message_id=reply_to_message.message_id, ) assert message.reply_to_message is None + assert message.invoice.currency == "XTR" elif default_bot.defaults.allow_sending_without_reply: message = await default_bot.send_invoice( chat_id, diff --git a/tests/ext/test_filters.py b/tests/ext/test_filters.py index fc88e4284..97d17e2eb 100644 --- a/tests/ext/test_filters.py +++ b/tests/ext/test_filters.py @@ -1107,6 +1107,11 @@ class TestFilters: update.message.game = "test" assert filters.GAME.check_update(update) + def test_filters_effect_id(self, update): + assert not filters.EFFECT_ID.check_update(update) + update.message.effect_id = "test" + assert filters.EFFECT_ID.check_update(update) + def test_entities_filter(self, update, message_entity): update.message.entities = [message_entity] assert filters.Entity(message_entity.type).check_update(update) diff --git a/tests/test_bot.py b/tests/test_bot.py index 047238907..8fa536281 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -2180,6 +2180,16 @@ class TestBotWithoutRequest: monkeypatch.setattr(bot.request, "post", make_assertion) assert await bot.send_message(2, "text", business_connection_id=42) + async def test_message_effect_id_argument(self, bot, monkeypatch): + """We can't test every single method easily, so we just test one. Our linting will catch + any unused args with the others.""" + + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return request_data.parameters.get("message_effect_id") == 42 + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.send_message(2, "text", message_effect_id=42) + async def test_get_business_connection(self, bot, monkeypatch): bci = "42" user = User(1, "first", False) @@ -2200,6 +2210,17 @@ class TestBotWithoutRequest: obj = await bot.get_business_connection(business_connection_id=bci) assert isinstance(obj, BusinessConnection) + async def test_refund_star_payment(self, bot, monkeypatch): + # can't make actual request so we just test that the correct data is passed + async def make_assertion(url, request_data: RequestData, *args, **kwargs): + return ( + request_data.parameters.get("user_id") == 42 + and request_data.parameters.get("telegram_payment_charge_id") == "37" + ) + + monkeypatch.setattr(bot.request, "post", make_assertion) + assert await bot.refund_star_payment(42, "37") + class TestBotWithRequest: """ @@ -2804,9 +2825,11 @@ class TestBotWithRequest: caption="new_caption", chat_id=media_message.chat_id, message_id=media_message.message_id, + show_caption_above_media=False, ) assert message.caption == "new_caption" + assert not message.show_caption_above_media async def test_edit_message_caption_entities(self, bot, media_message): test_string = "Italic Bold Code" @@ -3789,6 +3812,7 @@ class TestBotWithRequest: parse_mode=ParseMode.HTML, reply_to_message_id=media_message.message_id, reply_markup=keyboard, + show_caption_above_media=False, ) # we send a temp message which replies to the returned message id in order to get a # message object diff --git a/tests/test_chat.py b/tests/test_chat.py index 36c1e80a8..a11b40c64 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -711,8 +711,8 @@ class TestChatWithoutRequest(TestChatBase): return from_chat_id and message_id and chat_id assert check_shortcut_signature(Chat.send_copy, Bot.copy_message, ["chat_id"], []) - assert await check_shortcut_call(chat.copy_message, chat.get_bot(), "copy_message") - assert await check_defaults_handling(chat.copy_message, chat.get_bot()) + assert await check_shortcut_call(chat.send_copy, chat.get_bot(), "copy_message") + assert await check_defaults_handling(chat.send_copy, chat.get_bot()) monkeypatch.setattr(chat.get_bot(), "copy_message", make_assertion) assert await chat.send_copy(from_chat_id="test_copy", message_id=42) diff --git a/tests/test_constants.py b/tests/test_constants.py index 98768b806..753688573 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -173,10 +173,9 @@ class TestConstantsWithoutRequest: "is_accessible", "quote", "external_reply", - # attribute is deprecated, no need to add it to MessageType - "user_shared", "via_bot", "is_from_offline", + "show_caption_above_media", } @pytest.mark.parametrize( diff --git a/tests/test_message.py b/tests/test_message.py index 46a7f89b8..c51e3a92a 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -273,6 +273,8 @@ def message(bot): {"sender_business_bot": User(1, "BusinessBot", True)}, {"business_connection_id": "123456789"}, {"chat_background_set": ChatBackground(type=BackgroundTypeChatTheme("ice"))}, + {"effect_id": "123456789"}, + {"show_caption_above_media": True}, ], ids=[ "reply", @@ -342,6 +344,8 @@ def message(bot): "business_connection_id", "is_from_offline", "chat_background_set", + "effect_id", + "show_caption_above_media", ], ) def message_params(bot, request): @@ -399,11 +403,12 @@ class TestMessageBase: {"length": 2, "offset": 150, "type": "custom_emoji", "custom_emoji_id": "1"}, {"length": 34, "offset": 154, "type": "blockquote"}, {"length": 6, "offset": 181, "type": "bold"}, + {"length": 33, "offset": 190, "type": "expandable_blockquote"}, ] test_text_v2 = ( r"Test for trgh nested in italic. Python pre. Spoiled. " - "👍.\nMultiline\nblock quote\nwith nested." + "👍.\nMultiline\nblock quote\nwith nested.\n\nMultiline\nexpandable\nblock quote." ) test_message = Message( message_id=1, @@ -728,7 +733,8 @@ class TestMessageWithoutRequest(TestMessageBase): '
Python pre
. ' 'Spoiled. ' '👍.\n' - "
Multiline\nblock quote\nwith nested.
" + "
Multiline\nblock quote\nwith nested.
\n\n" + "
Multiline\nexpandable\nblock quote.
" ) text_html = self.test_message_v2.text_html assert text_html == test_html_string @@ -749,7 +755,8 @@ class TestMessageWithoutRequest(TestMessageBase): '
Python pre
. ' 'Spoiled. ' '👍.\n' - "
Multiline\nblock quote\nwith nested.
" + "
Multiline\nblock quote\nwith nested.
\n\n" + "
Multiline\nexpandable\nblock quote.
" ) text_html = self.test_message_v2.text_html_urled assert text_html == test_html_string @@ -774,6 +781,9 @@ class TestMessageWithoutRequest(TestMessageBase): ">Multiline\n" ">block quote\n" r">with *nested*\." + "\n\n>Multiline\n" + ">expandable\n" + r">block quote\.||" ) text_markdown = self.test_message_v2.text_markdown_v2 assert text_markdown == test_md_string @@ -830,6 +840,9 @@ class TestMessageWithoutRequest(TestMessageBase): ">Multiline\n" ">block quote\n" r">with *nested*\." + "\n\n>Multiline\n" + ">expandable\n" + r">block quote\.||" ) text_markdown = self.test_message_v2.text_markdown_v2_urled assert text_markdown == test_md_string @@ -946,7 +959,8 @@ class TestMessageWithoutRequest(TestMessageBase): '
Python pre
. ' 'Spoiled. ' '👍.\n' - "
Multiline\nblock quote\nwith nested.
" + "
Multiline\nblock quote\nwith nested.
\n\n" + "
Multiline\nexpandable\nblock quote.
" ) caption_html = self.test_message_v2.caption_html assert caption_html == test_html_string @@ -967,7 +981,8 @@ class TestMessageWithoutRequest(TestMessageBase): '
Python pre
. ' 'Spoiled. ' '👍.\n' - "
Multiline\nblock quote\nwith nested.
" + "
Multiline\nblock quote\nwith nested.
\n\n" + "
Multiline\nexpandable\nblock quote.
" ) caption_html = self.test_message_v2.caption_html_urled assert caption_html == test_html_string @@ -992,6 +1007,9 @@ class TestMessageWithoutRequest(TestMessageBase): ">Multiline\n" ">block quote\n" r">with *nested*\." + "\n\n>Multiline\n" + ">expandable\n" + r">block quote\.||" ) caption_markdown = self.test_message_v2.caption_markdown_v2 assert caption_markdown == test_md_string @@ -1023,6 +1041,9 @@ class TestMessageWithoutRequest(TestMessageBase): ">Multiline\n" ">block quote\n" r">with *nested*\." + "\n\n>Multiline\n" + ">expandable\n" + r">block quote\.||" ) caption_markdown = self.test_message_v2.caption_markdown_v2_urled assert caption_markdown == test_md_string @@ -1484,6 +1505,9 @@ class TestMessageWithoutRequest(TestMessageBase): ">Multiline\n" ">block quote\n" r">with *nested*\." + "\n\n>Multiline\n" + ">expandable\n" + r">block quote\.||" ) async def make_assertion(*_, **kwargs): @@ -1534,7 +1558,8 @@ class TestMessageWithoutRequest(TestMessageBase): '
Python pre
. ' 'Spoiled. ' '👍.\n' - "
Multiline\nblock quote\nwith nested.
" + "
Multiline\nblock quote\nwith nested.
\n\n" + "
Multiline\nexpandable\nblock quote.
" ) async def make_assertion(*_, **kwargs): diff --git a/tests/test_official/exceptions.py b/tests/test_official/exceptions.py index 4b44d286b..91f186ff7 100644 --- a/tests/test_official/exceptions.py +++ b/tests/test_official/exceptions.py @@ -172,7 +172,9 @@ def ignored_param_requirements(object_name: str) -> set[str]: # Arguments that are optional arguments for now for backwards compatibility -BACKWARDS_COMPAT_KWARGS: dict[str, set[str]] = {} +BACKWARDS_COMPAT_KWARGS: dict[str, set[str]] = { + "send_invoice|create_invoice_link|InputInvoiceMessageContent": {"provider_token"} +} def backwards_compat_kwargs(object_name: str) -> set[str]: diff --git a/tests/test_user.py b/tests/test_user.py index 86faa73cd..069365328 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -439,8 +439,8 @@ class TestUserWithoutRequest(TestUserBase): return from_chat_id and message_id and user_id assert check_shortcut_signature(User.send_copy, Bot.copy_message, ["chat_id"], []) - assert await check_shortcut_call(user.copy_message, user.get_bot(), "copy_message") - assert await check_defaults_handling(user.copy_message, user.get_bot()) + assert await check_shortcut_call(user.send_copy, user.get_bot(), "copy_message") + assert await check_defaults_handling(user.send_copy, user.get_bot()) monkeypatch.setattr(user.get_bot(), "copy_message", make_assertion) assert await user.send_copy(from_chat_id="from_chat_id", message_id="message_id") @@ -700,3 +700,18 @@ class TestUserWithoutRequest(TestUserBase): monkeypatch.setattr(user.get_bot(), "forward_messages", make_assertion) assert await user.forward_messages_to(chat_id="test_forwards", message_ids=(42, 43)) + + async def test_instance_method_refund_star_payment(self, monkeypatch, user): + async def make_assertion(*_, **kwargs): + return kwargs["user_id"] == user.id and kwargs["telegram_payment_charge_id"] == 42 + + assert check_shortcut_signature( + user.refund_star_payment, Bot.refund_star_payment, ["user_id"], [] + ) + assert await check_shortcut_call( + user.refund_star_payment, user.get_bot(), "refund_star_payment" + ) + assert await check_defaults_handling(user.refund_star_payment, user.get_bot()) + + monkeypatch.setattr(user.get_bot(), "refund_star_payment", make_assertion) + assert await user.refund_star_payment(telegram_payment_charge_id=42) From 9e70ac8b7aad9848cf6a620d7489413e7621c951 Mon Sep 17 00:00:00 2001 From: Trijeet Modak Date: Thu, 6 Jun 2024 21:01:45 +0530 Subject: [PATCH 06/12] Add Parameter `chat_id` to `ChatMemberHandler` (#4290) --- telegram/ext/_handlers/chatmemberhandler.py | 37 +++++++++---- tests/ext/test_chatmemberhandler.py | 60 +++++++++++++++++++++ 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/telegram/ext/_handlers/chatmemberhandler.py b/telegram/ext/_handlers/chatmemberhandler.py index 87232d842..6d0add00c 100644 --- a/telegram/ext/_handlers/chatmemberhandler.py +++ b/telegram/ext/_handlers/chatmemberhandler.py @@ -21,8 +21,9 @@ from typing import Final, Optional, TypeVar from telegram import Update from telegram._utils.defaultvalue import DEFAULT_TRUE -from telegram._utils.types import DVType +from telegram._utils.types import SCT, DVType from telegram.ext._handlers.basehandler import BaseHandler +from telegram.ext._utils._update_parsing import parse_chat_id from telegram.ext._utils.types import CCT, HandlerCallback RT = TypeVar("RT") @@ -58,6 +59,9 @@ class ChatMemberHandler(BaseHandler[Update, CCT]): :meth:`telegram.ext.Application.process_update`. Defaults to :obj:`True`. .. seealso:: :wiki:`Concurrency` + chat_id (:obj:`int` | Collection[:obj:`int`], optional): Filters chat member updates from + specified chat ID(s) only. + .. versionadded:: NEXT.VERSION Attributes: callback (:term:`coroutine function`): The callback function for this handler. @@ -70,7 +74,10 @@ class ChatMemberHandler(BaseHandler[Update, CCT]): """ - __slots__ = ("chat_member_types",) + __slots__ = ( + "_chat_ids", + "chat_member_types", + ) MY_CHAT_MEMBER: Final[int] = -1 """:obj:`int`: Used as a constant to handle only :attr:`telegram.Update.my_chat_member`.""" CHAT_MEMBER: Final[int] = 0 @@ -84,10 +91,12 @@ class ChatMemberHandler(BaseHandler[Update, CCT]): callback: HandlerCallback[Update, CCT, RT], chat_member_types: int = MY_CHAT_MEMBER, block: DVType[bool] = DEFAULT_TRUE, + chat_id: Optional[SCT[int]] = None, ): super().__init__(callback, block=block) self.chat_member_types: Optional[int] = chat_member_types + self._chat_ids = parse_chat_id(chat_id) def check_update(self, update: object) -> bool: """Determines whether an update should be passed to this handler's :attr:`callback`. @@ -99,12 +108,18 @@ class ChatMemberHandler(BaseHandler[Update, CCT]): :obj:`bool` """ - if isinstance(update, Update): - if not (update.my_chat_member or update.chat_member): - return False - if self.chat_member_types == self.ANY_CHAT_MEMBER: - return True - if self.chat_member_types == self.CHAT_MEMBER: - return bool(update.chat_member) - return bool(update.my_chat_member) - return False + if not isinstance(update, Update): + return False + if not (update.my_chat_member or update.chat_member): + return False + if ( + self._chat_ids + and update.effective_chat + and update.effective_chat.id not in self._chat_ids + ): + return False + if self.chat_member_types == self.ANY_CHAT_MEMBER: + return True + if self.chat_member_types == self.CHAT_MEMBER: + return bool(update.chat_member) + return bool(update.my_chat_member) diff --git a/tests/ext/test_chatmemberhandler.py b/tests/ext/test_chatmemberhandler.py index 9182b2a11..ada95288c 100644 --- a/tests/ext/test_chatmemberhandler.py +++ b/tests/ext/test_chatmemberhandler.py @@ -144,6 +144,66 @@ class TestChatMemberHandler: await app.process_update(chat_member) assert self.test_flag == result_2 + @pytest.mark.parametrize( + argnames=["allowed_types", "chat_id", "expected"], + argvalues=[ + (ChatMemberHandler.MY_CHAT_MEMBER, None, (True, False)), + (ChatMemberHandler.CHAT_MEMBER, None, (False, True)), + (ChatMemberHandler.ANY_CHAT_MEMBER, None, (True, True)), + (ChatMemberHandler.MY_CHAT_MEMBER, 1, (True, False)), + (ChatMemberHandler.CHAT_MEMBER, 1, (False, True)), + (ChatMemberHandler.ANY_CHAT_MEMBER, 1, (True, True)), + (ChatMemberHandler.MY_CHAT_MEMBER, [1], (True, False)), + (ChatMemberHandler.CHAT_MEMBER, [1], (False, True)), + (ChatMemberHandler.ANY_CHAT_MEMBER, [1], (True, True)), + (ChatMemberHandler.MY_CHAT_MEMBER, 2, (False, False)), + (ChatMemberHandler.CHAT_MEMBER, 2, (False, False)), + (ChatMemberHandler.ANY_CHAT_MEMBER, 2, (False, False)), + (ChatMemberHandler.MY_CHAT_MEMBER, [2], (False, False)), + (ChatMemberHandler.CHAT_MEMBER, [2], (False, False)), + (ChatMemberHandler.ANY_CHAT_MEMBER, [2], (False, False)), + ], + ids=[ + "MY_CHAT_MEMBER", + "CHAT_MEMBER", + "ANY_CHAT_MEMBER", + "MY_CHAT_MEMBER, CHAT=1 ", + "CHAT_MEMBER, CHAT=1", + "ANY_CHAT_MEMBER, CHAT=1", + "MY_CHAT_MEMBER, CHAT=[1] ", + "CHAT_MEMBER, CHAT=[1]", + "ANY_CHAT_MEMBER, CHAT=[1]", + "MY_CHAT_MEMBER, CHAT=2 ", + "CHAT_MEMBER, CHAT=2", + "ANY_CHAT_MEMBER, CHAT=2", + "MY_CHAT_MEMBER, CHAT=[2] ", + "CHAT_MEMBER, CHAT=[2]", + "ANY_CHAT_MEMBER, CHAT=[2]", + ], + ) + async def test_chat_member_types_with_chat_id( + self, app, chat_member_updated, chat_member, expected, allowed_types, chat_id + ): + result_1, result_2 = expected + + handler = ChatMemberHandler( + self.callback, chat_member_types=allowed_types, chat_id=chat_id + ) + app.add_handler(handler) + + async with app: + assert handler.check_update(chat_member) == result_1 + await app.process_update(chat_member) + assert self.test_flag == result_1 + + self.test_flag = False + chat_member.my_chat_member = None + chat_member.chat_member = chat_member_updated + + assert handler.check_update(chat_member) == result_2 + await app.process_update(chat_member) + assert self.test_flag == result_2 + def test_other_update_types(self, false_update): handler = ChatMemberHandler(self.callback) assert not handler.check_update(false_update) From 78c945d4857b12b1f19df10d86686e2de25821b6 Mon Sep 17 00:00:00 2001 From: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:13:55 +0200 Subject: [PATCH 07/12] Documentation Improvements (#4264) --- docs/source/conf.py | 8 ++++++-- telegram/_bot.py | 7 ++++--- telegram/_message.py | 12 ++++++------ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index a50e3dbdb..5858a79e2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -20,9 +20,13 @@ author = "Leandro Toledo" # built documents. # # The short X.Y version. -version = "21.2" # telegram.__version__[:3] + +# Import needs to be below the sys.path.insert above +import telegram # noqa: E402 + +version = telegram.__version__ # The full version, including alpha/beta/rc tags. -release = "21.2" # telegram.__version__ +release = telegram.__version__ # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = "6.1.3" diff --git a/telegram/_bot.py b/telegram/_bot.py index 94e70fe6c..93b7f5330 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -7536,7 +7536,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. minutes. Returns: - :obj:`True`: On success + :obj:`True`: On success, :obj:`True` is returned. Raises: :class:`telegram.error.TelegramError` @@ -7567,7 +7567,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. 10 minutes after the bot is launched. Returns: - :obj:`True`: On success + :obj:`True`: On success, :obj:`True` is returned. Raises: :class:`telegram.error.TelegramError` @@ -7664,7 +7664,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. |keyword_only_arg| Returns: - :class:`telegram.MessageId`: On success + :class:`telegram.MessageId`: On success, the :class:`telegram.MessageId` of the sent + message is returned. Raises: :class:`telegram.error.TelegramError` diff --git a/telegram/_message.py b/telegram/_message.py index 43b7519b5..4c74ad440 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -255,8 +255,8 @@ class Message(MaybeInaccessibleMessage): .. versionchanged:: 20.8 * This class is now a subclass of :class:`telegram.MaybeInaccessibleMessage`. - * The :paramref:`pinned_message` now can be either class:`telegram.Message` or - class:`telegram.InaccessibleMessage`. + * The :paramref:`pinned_message` now can be either :class:`telegram.Message` or + :class:`telegram.InaccessibleMessage`. .. versionchanged:: 20.0 @@ -419,8 +419,8 @@ class Message(MaybeInaccessibleMessage): :attr:`reply_to_message` fields even if it is itself a reply. .. versionchanged:: 20.8 - This attribute now is either class:`telegram.Message` or - class:`telegram.InaccessibleMessage`. + This attribute now is either :class:`telegram.Message` or + :class:`telegram.InaccessibleMessage`. invoice (:class:`telegram.Invoice`, optional): Message is an invoice for a payment, information about the invoice. successful_payment (:class:`telegram.SuccessfulPayment`, optional): Message is a service @@ -731,8 +731,8 @@ class Message(MaybeInaccessibleMessage): :attr:`reply_to_message` fields even if it is itself a reply. .. versionchanged:: 20.8 - This attribute now is either class:`telegram.Message` or - class:`telegram.InaccessibleMessage`. + This attribute now is either :class:`telegram.Message` or + :class:`telegram.InaccessibleMessage`. invoice (:class:`telegram.Invoice`): Optional. Message is an invoice for a payment, information about the invoice. successful_payment (:class:`telegram.SuccessfulPayment`): Optional. Message is a service From a9f6afd0152e30f32db69d35211fc05eceb9edfe Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:52:22 +0200 Subject: [PATCH 08/12] Bump version to v21.3 --- CHANGES.rst | 34 +++++++++++++ README_RAW.rst | 2 +- telegram/__init__.py | 2 +- telegram/_bot.py | 50 +++++++++---------- telegram/_chat.py | 4 +- telegram/_chatfullinfo.py | 2 +- telegram/_files/inputmedia.py | 12 ++--- .../_inline/inlinequeryresultcachedgif.py | 4 +- .../inlinequeryresultcachedmpeg4gif.py | 4 +- .../_inline/inlinequeryresultcachedphoto.py | 4 +- .../_inline/inlinequeryresultcachedvideo.py | 4 +- telegram/_inline/inlinequeryresultgif.py | 4 +- telegram/_inline/inlinequeryresultmpeg4gif.py | 4 +- telegram/_inline/inlinequeryresultphoto.py | 4 +- telegram/_inline/inlinequeryresultvideo.py | 4 +- .../_inline/inputinvoicemessagecontent.py | 2 +- telegram/_message.py | 8 +-- telegram/_messageentity.py | 2 +- telegram/_user.py | 2 +- telegram/_version.py | 2 +- telegram/constants.py | 4 +- telegram/ext/_handlers/chatmemberhandler.py | 2 +- telegram/ext/filters.py | 2 +- 23 files changed, 98 insertions(+), 64 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 9a509ab48..79a6f1244 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,40 @@ Changelog ========= +Version 21.3 +============ +*Released 2024-06-07* + +This is the technical changelog for version 21.3. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel `_. + +Major Changes +------------- + +- Full Support for Bot API 7.4 (:pr:`4286`, :pr:`4276` closes :issue:`4275`, :pr:`4285`, :pr:`4283`, :pr:`4280`, :pr:`4278`, :pr:`4279`) +- Deprecate ``python-telegram-bot-raw`` (:pr:`4270`) +- Remove Functionality Deprecated in Bot API 7.3 (:pr:`4266` closes :issue:`4244`) + +New Features +------------ + +- Add Parameter ``chat_id`` to ``ChatMemberHandler`` (:pr:`4290` by `uniquetrij `_ closes :issue:`4287`) + +Documentation Improvements +-------------------------- + +- Documentation Improvements (:pr:`4264` closes :issue:`4240`) + +Internal Changes +---------------- + +- Add ``setuptools`` to ``requirements-dev.txt`` (:pr:`4282`) +- Update Settings for pre-commit.ci (:pr:`4265`) + +Dependency Updates +------------------ + +- Bump ``pytest`` from 8.2.0 to 8.2.1 (:pr:`4272`) + Version 21.2 ============ diff --git a/README_RAW.rst b/README_RAW.rst index 7278f47ae..e82270959 100644 --- a/README_RAW.rst +++ b/README_RAW.rst @@ -62,7 +62,7 @@ ⚠️ Deprecation Notice ===================== -The ``python-telegram-bot-raw`` library will no longer be updated after NEXT.VERSION. +The ``python-telegram-bot-raw`` library will no longer be updated after 21.3. Please instead use the ``python-telegram-bot`` `library `_. The change requires no changes in your code and requires no additional dependencies. For additional information, please see this `channel post `_. diff --git a/telegram/__init__.py b/telegram/__init__.py index 1230716e7..6105c9780 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -494,7 +494,7 @@ if not (Path(__file__).parent.resolve().absolute() / "ext").exists(): # seeing the warning. warn( - warnings.PTBDeprecationWarning(version="NEXT.VERSION", message=_MESSAGE), + warnings.PTBDeprecationWarning(version="21.3", message=_MESSAGE), stacklevel=2, ) warn( diff --git a/telegram/_bot.py b/telegram/_bot.py index 93b7f5330..ebc7817b9 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -976,7 +976,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -1350,10 +1350,10 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -1508,7 +1508,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -1662,7 +1662,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -1788,7 +1788,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -1944,10 +1944,10 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -2096,7 +2096,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -2253,10 +2253,10 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -2408,7 +2408,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -2534,7 +2534,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -2717,7 +2717,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -3014,7 +3014,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -3156,7 +3156,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -3276,7 +3276,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -4079,7 +4079,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): inline keyboard. show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Returns: :class:`telegram.Message`: On success, if edited message is not an inline message, the @@ -4976,7 +4976,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): `@BotFather `_. Pass an empty string for payments in |tg_stars|. - .. deprecated:: NEXT.VERSION + .. deprecated:: 21.3 As of Bot API 7.4, this parameter is now optional and future versions of the library will make it optional as well. @@ -5057,7 +5057,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): .. versionadded:: 20.8 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -7045,7 +7045,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. .. versionadded:: 21.2 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -7218,7 +7218,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. .. versionadded:: 21.1 message_effect_id (:obj:`str`, optional): |message_effect_id| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -7641,7 +7641,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. .. versionadded:: 20.8 show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Keyword Args: allow_sending_without_reply (:obj:`bool`, optional): |allow_sending_without_reply| @@ -7910,7 +7910,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. `@BotFather `_. Pass an empty string for payments in |tg_stars|. - .. deprecated:: NEXT.VERSION + .. deprecated:: 21.3 As of Bot API 7.4, this parameter is now optional and future versions of the library will make it optional as well. @@ -9042,7 +9042,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified. ) -> bool: """Refunds a successful payment in |tg_stars|. - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Args: user_id (:obj:`int`): User identifier of the user whose payment will be refunded. diff --git a/telegram/_chat.py b/telegram/_chat.py index 39acf55aa..b5e2d111f 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -69,7 +69,7 @@ if TYPE_CHECKING: class _ChatBase(TelegramObject): """Base class for :class:`telegram.Chat` and :class:`telegram.ChatFullInfo`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ __slots__ = ("first_name", "id", "is_forum", "last_name", "title", "type", "username") @@ -3277,7 +3277,7 @@ class Chat(_ChatBase): this field for backwards compatibility, it is available through :attr:`~telegram.TelegramObject.api_kwargs`. - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 21.3 As per Bot API 7.3, most of the arguments and attributes of this class have now moved to :class:`telegram.ChatFullInfo`. diff --git a/telegram/_chatfullinfo.py b/telegram/_chatfullinfo.py index 221b8f623..213baed7e 100644 --- a/telegram/_chatfullinfo.py +++ b/telegram/_chatfullinfo.py @@ -44,7 +44,7 @@ class ChatFullInfo(_ChatBase): .. versionadded:: 21.2 - .. versionchanged:: NEXT.VERSION + .. versionchanged:: 21.3 Explicit support for all shortcut methods known from :class:`telegram.Chat` on this object. Previously those were only available because this class inherited from :class:`telegram.Chat`. diff --git a/telegram/_files/inputmedia.py b/telegram/_files/inputmedia.py index 229e73209..0cf5955a4 100644 --- a/telegram/_files/inputmedia.py +++ b/telegram/_files/inputmedia.py @@ -162,7 +162,7 @@ class InputMediaAnimation(InputMedia): .. versionadded:: 20.2 show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.ANIMATION`. @@ -189,7 +189,7 @@ class InputMediaAnimation(InputMedia): .. versionadded:: 20.2 show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ __slots__ = ( @@ -277,7 +277,7 @@ class InputMediaPhoto(InputMedia): .. versionadded:: 20.0 show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.PHOTO`. @@ -298,7 +298,7 @@ class InputMediaPhoto(InputMedia): .. versionadded:: 20.0 show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ __slots__ = ( @@ -387,7 +387,7 @@ class InputMediaVideo(InputMedia): .. versionadded:: 20.2 show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.VIDEO`. @@ -416,7 +416,7 @@ class InputMediaVideo(InputMedia): .. versionadded:: 20.2 show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ __slots__ = ( diff --git a/telegram/_inline/inlinequeryresultcachedgif.py b/telegram/_inline/inlinequeryresultcachedgif.py index 9516accae..9f52347a0 100644 --- a/telegram/_inline/inlinequeryresultcachedgif.py +++ b/telegram/_inline/inlinequeryresultcachedgif.py @@ -61,7 +61,7 @@ class InlineQueryResultCachedGif(InlineQueryResult): message to be sent instead of the gif. show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.GIF`. @@ -86,7 +86,7 @@ class InlineQueryResultCachedGif(InlineQueryResult): message to be sent instead of the gif. show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ diff --git a/telegram/_inline/inlinequeryresultcachedmpeg4gif.py b/telegram/_inline/inlinequeryresultcachedmpeg4gif.py index 3fa2a8f13..f750f4df8 100644 --- a/telegram/_inline/inlinequeryresultcachedmpeg4gif.py +++ b/telegram/_inline/inlinequeryresultcachedmpeg4gif.py @@ -61,7 +61,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): message to be sent instead of the MPEG-4 file. show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.MPEG4GIF`. @@ -86,7 +86,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): message to be sent instead of the MPEG-4 file. show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ diff --git a/telegram/_inline/inlinequeryresultcachedphoto.py b/telegram/_inline/inlinequeryresultcachedphoto.py index 6c18630ff..75f292d2e 100644 --- a/telegram/_inline/inlinequeryresultcachedphoto.py +++ b/telegram/_inline/inlinequeryresultcachedphoto.py @@ -62,7 +62,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): message to be sent instead of the photo. show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.PHOTO`. @@ -88,7 +88,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): message to be sent instead of the photo. show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ diff --git a/telegram/_inline/inlinequeryresultcachedvideo.py b/telegram/_inline/inlinequeryresultcachedvideo.py index 7ce8c423f..99a58eebb 100644 --- a/telegram/_inline/inlinequeryresultcachedvideo.py +++ b/telegram/_inline/inlinequeryresultcachedvideo.py @@ -58,7 +58,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult): message to be sent instead of the video. show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Attributes: type (:obj:`str`): :tg-const:`telegram.constants.InlineQueryResultType.VIDEO`. @@ -84,7 +84,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult): message to be sent instead of the video. show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ diff --git a/telegram/_inline/inlinequeryresultgif.py b/telegram/_inline/inlinequeryresultgif.py index ab68f083d..13e1f253b 100644 --- a/telegram/_inline/inlinequeryresultgif.py +++ b/telegram/_inline/inlinequeryresultgif.py @@ -80,7 +80,7 @@ class InlineQueryResultGif(InlineQueryResult): message to be sent instead of the GIF animation. show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Raises: :class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is @@ -120,7 +120,7 @@ class InlineQueryResultGif(InlineQueryResult): message to be sent instead of the GIF animation. show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ diff --git a/telegram/_inline/inlinequeryresultmpeg4gif.py b/telegram/_inline/inlinequeryresultmpeg4gif.py index 6478340f7..1fff84841 100644 --- a/telegram/_inline/inlinequeryresultmpeg4gif.py +++ b/telegram/_inline/inlinequeryresultmpeg4gif.py @@ -82,7 +82,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult): message to be sent instead of the video animation. show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Raises: :class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is supplied or if both are supplied and are not equal. @@ -122,7 +122,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult): message to be sent instead of the video animation. show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ __slots__ = ( diff --git a/telegram/_inline/inlinequeryresultphoto.py b/telegram/_inline/inlinequeryresultphoto.py index 1fbb4f4d6..637e952d4 100644 --- a/telegram/_inline/inlinequeryresultphoto.py +++ b/telegram/_inline/inlinequeryresultphoto.py @@ -76,7 +76,7 @@ class InlineQueryResultPhoto(InlineQueryResult): message to be sent instead of the photo. show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Raises: :class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is @@ -110,7 +110,7 @@ class InlineQueryResultPhoto(InlineQueryResult): message to be sent instead of the photo. show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ diff --git a/telegram/_inline/inlinequeryresultvideo.py b/telegram/_inline/inlinequeryresultvideo.py index 2c387b17c..90bf4c86d 100644 --- a/telegram/_inline/inlinequeryresultvideo.py +++ b/telegram/_inline/inlinequeryresultvideo.py @@ -90,7 +90,7 @@ class InlineQueryResultVideo(InlineQueryResult): (e.g., a YouTube video). show_caption_above_media (:obj:`bool`, optional): Pass |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Raises: :class:`ValueError`: If neither :paramref:`thumbnail_url` nor :paramref:`thumb_url` is @@ -132,7 +132,7 @@ class InlineQueryResultVideo(InlineQueryResult): (e.g., a YouTube video). show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ diff --git a/telegram/_inline/inputinvoicemessagecontent.py b/telegram/_inline/inputinvoicemessagecontent.py index d710085fd..74ea97de2 100644 --- a/telegram/_inline/inputinvoicemessagecontent.py +++ b/telegram/_inline/inputinvoicemessagecontent.py @@ -52,7 +52,7 @@ class InputInvoiceMessageContent(InputMessageContent): `@Botfather `_. Pass an empty string for payments in |tg_stars|. - .. deprecated:: NEXT.VERSION + .. deprecated:: 21.3 As of Bot API 7.4, this parameter is now optional and future versions of the library will make it optional as well. currency (:obj:`str`): Three-letter ISO 4217 currency code, see more on diff --git a/telegram/_message.py b/telegram/_message.py index 4c74ad440..b0605cd09 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -331,7 +331,7 @@ class Message(MaybeInaccessibleMessage): effect_id (:obj:`str`, optional): Unique identifier of the message effect added to the message. - ..versionadded:: NEXT.VERSION + ..versionadded:: 21.3 caption_entities (Sequence[:class:`telegram.MessageEntity`], optional): For messages with a Caption. Special entities like usernames, URLs, bot commands, etc. that appear in the @@ -344,7 +344,7 @@ class Message(MaybeInaccessibleMessage): show_caption_above_media (:obj:`bool`, optional): |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 audio (:class:`telegram.Audio`, optional): Message is an audio file, information about the file. document (:class:`telegram.Document`, optional): Message is a general file, information @@ -628,7 +628,7 @@ class Message(MaybeInaccessibleMessage): effect_id (:obj:`str`): Optional. Unique identifier of the message effect added to the message. - ..versionadded:: NEXT.VERSION + ..versionadded:: 21.3 caption_entities (Tuple[:class:`telegram.MessageEntity`]): Optional. For messages with a Caption. Special entities like usernames, URLs, bot commands, etc. that appear in the @@ -641,7 +641,7 @@ class Message(MaybeInaccessibleMessage): show_caption_above_media (:obj:`bool`): Optional. |show_cap_above_med| - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 audio (:class:`telegram.Audio`): Optional. Message is an audio file, information about the file. diff --git a/telegram/_messageentity.py b/telegram/_messageentity.py index ebdcb8e07..2f7fb7d61 100644 --- a/telegram/_messageentity.py +++ b/telegram/_messageentity.py @@ -165,7 +165,7 @@ class MessageEntity(TelegramObject): EXPANDABLE_BLOCKQUOTE: Final[str] = constants.MessageEntityType.EXPANDABLE_BLOCKQUOTE """:const:`telegram.constants.MessageEntityType.EXPANDABLE_BLOCKQUOTE` - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ HASHTAG: Final[str] = constants.MessageEntityType.HASHTAG """:const:`telegram.constants.MessageEntityType.HASHTAG`""" diff --git a/telegram/_user.py b/telegram/_user.py index 84ab7728c..7ea769f28 100644 --- a/telegram/_user.py +++ b/telegram/_user.py @@ -2163,7 +2163,7 @@ class User(TelegramObject): For the documentation of the arguments, please see :meth:`telegram.Bot.refund_star_payment`. - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Returns: :obj:`bool`: On success, :obj:`True` is returned. diff --git a/telegram/_version.py b/telegram/_version.py index e1a1bbe79..348495362 100644 --- a/telegram/_version.py +++ b/telegram/_version.py @@ -51,7 +51,7 @@ class Version(NamedTuple): __version_info__: Final[Version] = Version( - major=21, minor=2, micro=0, releaselevel="final", serial=0 + major=21, minor=3, micro=0, releaselevel="final", serial=0 ) __version__: Final[str] = str(__version_info__) diff --git a/telegram/constants.py b/telegram/constants.py index faf797231..5e2c853ba 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -1653,7 +1653,7 @@ class MessageEntityType(StringEnum): EXPANDABLE_BLOCKQUOTE = "expandable_blockquote" """:obj:`str`: Message entities representing collapsed-by-default block quotation. - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 """ HASHTAG = "hashtag" """:obj:`str`: Message entities representing a hashtag.""" @@ -1809,7 +1809,7 @@ class MessageType(StringEnum): EFFECT_ID = "effect_id" """:obj:`str`: Messages with :attr:`telegram.Message.effect_id`. - .. versionadded:: NEXT.VERSION""" + .. versionadded:: 21.3""" FORUM_TOPIC_CREATED = "forum_topic_created" """:obj:`str`: Messages with :attr:`telegram.Message.forum_topic_created`. diff --git a/telegram/ext/_handlers/chatmemberhandler.py b/telegram/ext/_handlers/chatmemberhandler.py index 6d0add00c..592361b64 100644 --- a/telegram/ext/_handlers/chatmemberhandler.py +++ b/telegram/ext/_handlers/chatmemberhandler.py @@ -61,7 +61,7 @@ class ChatMemberHandler(BaseHandler[Update, CCT]): .. seealso:: :wiki:`Concurrency` chat_id (:obj:`int` | Collection[:obj:`int`], optional): Filters chat member updates from specified chat ID(s) only. - .. versionadded:: NEXT.VERSION + .. versionadded:: 21.3 Attributes: callback (:term:`coroutine function`): The callback function for this handler. diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index 24dc982e6..5147574e0 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -1349,7 +1349,7 @@ class _EffectId(MessageFilter): EFFECT_ID = _EffectId(name="filters.EFFECT_ID") """Messages that contain :attr:`telegram.Message.effect_id`. -.. versionadded:: NEXT.VERSION""" +.. versionadded:: 21.3""" class Entity(MessageFilter): From 44e8292838014fefca5ac91d05ea4b77d4cbf7af Mon Sep 17 00:00:00 2001 From: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sat, 15 Jun 2024 10:29:19 +0200 Subject: [PATCH 09/12] Drop `python-telegram-bot-raw` And Switch to `pyproject.toml` Based Packaging (#4288) --- .github/CONTRIBUTING.rst | 11 +- .github/workflows/docs.yml | 2 +- .../pre-commit_dependencies_notifier.yml | 19 -- .github/workflows/readme_notifier.yml | 18 -- .github/workflows/test_official.yml | 5 +- .github/workflows/type_completeness.yml | 3 +- .github/workflows/unit_tests.yml | 11 +- .pre-commit-config.yaml | 2 +- MANIFEST.in | 1 - README.rst | 3 +- README_RAW.rst | 213 ------------------ docs/requirements-docs.txt | 2 +- pyproject.toml | 109 ++++++++- requirements-all.txt | 4 - requirements-dev-all.txt | 5 + requirements-dev.txt | 11 - requirements-opts.txt | 27 --- requirements-unit-tests.txt | 19 ++ requirements.txt | 10 - setup.cfg | 5 +- setup.py | 131 ----------- setup_raw.py | 8 - telegram/__init__.py | 32 +-- telegram/_version.py | 11 +- tests/README.rst | 2 +- tests/test_meta.py | 7 +- 26 files changed, 153 insertions(+), 518 deletions(-) delete mode 100644 .github/workflows/pre-commit_dependencies_notifier.yml delete mode 100644 .github/workflows/readme_notifier.yml delete mode 100644 MANIFEST.in delete mode 100644 README_RAW.rst delete mode 100644 requirements-all.txt create mode 100644 requirements-dev-all.txt delete mode 100644 requirements-dev.txt delete mode 100644 requirements-opts.txt create mode 100644 requirements-unit-tests.txt delete mode 100644 requirements.txt delete mode 100644 setup.py delete mode 100644 setup_raw.py diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst index b5ce59214..635cdb23e 100644 --- a/.github/CONTRIBUTING.rst +++ b/.github/CONTRIBUTING.rst @@ -26,7 +26,7 @@ Setting things up .. code-block:: bash - $ pip install -r requirements-all.txt + $ pip install -r requirements-dev-all.txt 5. Install pre-commit hooks: @@ -210,13 +210,8 @@ doc strings don't have a separate documentation site they generate, instead, the User facing documentation ------------------------- -We use `sphinx`_ to generate static HTML docs. To build them, first make sure you're running Python 3.9 or above and have the required dependencies: - -.. code-block:: bash - - $ pip install -r docs/requirements-docs.txt - -then run the following from the PTB root directory: +We use `sphinx`_ to generate static HTML docs. To build them, first make sure you're running Python 3.9 or above and have the required dependencies installed as explained above. +Then, run the following from the PTB root directory: .. code-block:: bash diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ea1173d69..73e123e17 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -28,7 +28,7 @@ jobs: - name: Install dependencies run: | python -W ignore -m pip install --upgrade pip - python -W ignore -m pip install -r requirements-all.txt + python -W ignore -m pip install -r requirements-dev-all.txt - name: Test autogeneration of admonitions run: pytest -v --tb=short tests/docs/admonition_inserter.py - name: Build docs diff --git a/.github/workflows/pre-commit_dependencies_notifier.yml b/.github/workflows/pre-commit_dependencies_notifier.yml deleted file mode 100644 index 6f6428faf..000000000 --- a/.github/workflows/pre-commit_dependencies_notifier.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Warning maintainers -on: - pull_request_target: - paths: - - requirements.txt - - requirements-opts.txt - - .pre-commit-config.yaml -permissions: - pull-requests: write -jobs: - job: - runs-on: ubuntu-latest - name: about pre-commit and dependency change - steps: - - name: running the check - uses: Poolitzer/notifier-action@master - with: - notify-message: Hey! Looks like you edited the (optional) requirements or the pre-commit hooks. I'm just a friendly reminder to keep the additional dependencies for the hooks in sync with the requirements :) - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/readme_notifier.yml b/.github/workflows/readme_notifier.yml deleted file mode 100644 index 4ec7d4587..000000000 --- a/.github/workflows/readme_notifier.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Warning maintainers -on: - pull_request_target: - paths: - - README.rst - - README_RAW.rst -permissions: - pull-requests: write -jobs: - job: - runs-on: ubuntu-latest - name: about readme change - steps: - - name: running the check - uses: Poolitzer/notifier-action@master - with: - notify-message: Hey! Looks like you edited README.rst or README_RAW.rst. I'm just a friendly reminder to apply relevant changes to both of those files :) - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test_official.yml b/.github/workflows/test_official.yml index 0510d334f..5de11471e 100644 --- a/.github/workflows/test_official.yml +++ b/.github/workflows/test_official.yml @@ -29,9 +29,8 @@ jobs: - name: Install dependencies run: | python -W ignore -m pip install --upgrade pip - python -W ignore -m pip install -r requirements.txt - python -W ignore -m pip install -r requirements-opts.txt - python -W ignore -m pip install -r requirements-dev.txt + python -W ignore -m pip install .[all] + python -W ignore -m pip install -r requirements-unit-tests.txt - name: Compare to official api run: | pytest -v tests/test_official/test_official.py --junit-xml=.test_report_official.xml diff --git a/.github/workflows/type_completeness.yml b/.github/workflows/type_completeness.yml index b2ebd45e3..74087e3e8 100644 --- a/.github/workflows/type_completeness.yml +++ b/.github/workflows/type_completeness.yml @@ -3,8 +3,7 @@ on: pull_request: paths: - telegram/** - - requirements.txt - - requirements-opts.txt + - pyproject.toml push: branches: - master diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index fffe4573d..8e1c7bb06 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -4,9 +4,8 @@ on: paths: - telegram/** - tests/** - - requirements.txt - - requirements-opts.txt - - requirements-dev.txt + - pyproject.toml + - requirements-unit-tests.txt push: branches: - master @@ -35,8 +34,8 @@ jobs: run: | python -W ignore -m pip install --upgrade pip python -W ignore -m pip install -U pytest-cov - python -W ignore -m pip install -r requirements.txt - python -W ignore -m pip install -r requirements-dev.txt + python -W ignore -m pip install . + python -W ignore -m pip install -r requirements-unit-tests.txt python -W ignore -m pip install pytest-xdist[psutil] - name: Test with pytest @@ -65,7 +64,7 @@ jobs: # Test the rest export TEST_WITH_OPT_DEPS='true' - pip install -r requirements-opts.txt + pip install .[all] # `-n auto --dist loadfile` uses pytest-xdist to run each test file on a different CPU # worker. Increasing number of workers has little effect on test duration, but it seems # to increase flakyness, specially on python 3.7 with --dist=loadgroup. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5760c9eac..08c51942a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -# Make sure that the additional_dependencies here match requirements(-opts).txt +# Make sure that the additional_dependencies here match pyproject.toml ci: autofix_prs: false diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 0efb3c7ec..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include LICENSE LICENSE.lesser requirements.txt requirements-opts.txt README_RAW.rst telegram/py.typed diff --git a/README.rst b/README.rst index c3b29aa62..82e272c3f 100644 --- a/README.rst +++ b/README.rst @@ -98,7 +98,8 @@ You can also install ``python-telegram-bot`` from source, though this is usually $ git clone https://github.com/python-telegram-bot/python-telegram-bot $ cd python-telegram-bot - $ python setup.py install + $ pip install build + $ python -m build Verifying Releases ------------------ diff --git a/README_RAW.rst b/README_RAW.rst deleted file mode 100644 index e82270959..000000000 --- a/README_RAW.rst +++ /dev/null @@ -1,213 +0,0 @@ -.. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-raw-logo-text_768.png?raw=true - :align: center - :target: https://python-telegram-bot.org - :alt: python-telegram-bot-raw Logo - -.. image:: https://img.shields.io/pypi/v/python-telegram-bot-raw.svg - :target: https://pypi.org/project/python-telegram-bot-raw/ - :alt: PyPi Package Version - -.. image:: https://img.shields.io/pypi/pyversions/python-telegram-bot-raw.svg - :target: https://pypi.org/project/python-telegram-bot-raw/ - :alt: Supported Python versions - -.. image:: https://img.shields.io/badge/Bot%20API-7.4-blue?logo=telegram - :target: https://core.telegram.org/bots/api-changelog - :alt: Supported Bot API version - -.. image:: https://img.shields.io/pypi/dm/python-telegram-bot-raw - :target: https://pypistats.org/packages/python-telegram-bot-raw - :alt: PyPi Package Monthly Download - -.. image:: https://readthedocs.org/projects/python-telegram-bot/badge/?version=stable - :target: https://docs.python-telegram-bot.org/ - :alt: Documentation Status - -.. image:: https://img.shields.io/pypi/l/python-telegram-bot-raw.svg - :target: https://www.gnu.org/licenses/lgpl-3.0.html - :alt: LGPLv3 License - -.. image:: https://github.com/python-telegram-bot/python-telegram-bot/actions/workflows/unit_tests.yml/badge.svg?branch=master - :target: https://github.com/python-telegram-bot/python-telegram-bot/ - :alt: Github Actions workflow - -.. image:: https://codecov.io/gh/python-telegram-bot/python-telegram-bot/branch/master/graph/badge.svg - :target: https://app.codecov.io/gh/python-telegram-bot/python-telegram-bot - :alt: Code coverage - -.. image:: https://isitmaintained.com/badge/resolution/python-telegram-bot/python-telegram-bot.svg - :target: https://isitmaintained.com/project/python-telegram-bot/python-telegram-bot - :alt: Median time to resolve an issue - -.. image:: https://api.codacy.com/project/badge/Grade/99d901eaa09b44b4819aec05c330c968 - :target: https://app.codacy.com/gh/python-telegram-bot/python-telegram-bot/dashboard - :alt: Code quality: Codacy - -.. image:: https://results.pre-commit.ci/badge/github/python-telegram-bot/python-telegram-bot/master.svg - :target: https://results.pre-commit.ci/latest/github/python-telegram-bot/python-telegram-bot/master - :alt: pre-commit.ci status - -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - :alt: Code Style: Black - -.. image:: https://img.shields.io/badge/Telegram-Channel-blue.svg?logo=telegram - :target: https://t.me/pythontelegrambotchannel - :alt: Telegram Channel - -.. image:: https://img.shields.io/badge/Telegram-Group-blue.svg?logo=telegram - :target: https://telegram.me/pythontelegrambotgroup - :alt: Telegram Group - -⚠️ Deprecation Notice -===================== - -The ``python-telegram-bot-raw`` library will no longer be updated after 21.3. -Please instead use the ``python-telegram-bot`` `library `_. -The change requires no changes in your code and requires no additional dependencies. -For additional information, please see this `channel post `_. - ----- - -We have made you a wrapper you can't refuse - -We have a vibrant community of developers helping each other in our `Telegram group `_. Join us! - -*Stay tuned for library updates and new releases on our* `Telegram Channel `_. - -Introduction -============ - -This library provides a pure Python, asynchronous interface for the -`Telegram Bot API `_. -It's compatible with Python versions **3.8+**. - -``python-telegram-bot-raw`` is part of the `python-telegram-bot `_ ecosystem and provides the pure API functionality extracted from PTB. It therefore does not have independent release schedules, changelogs or documentation. - -Note ----- - -Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conjunction will result in undesired side-effects, so only install *one* of both. - -Telegram API support -==================== - -All types and methods of the Telegram Bot API **7.4** are supported. - -Installing -========== - -You can install or upgrade ``python-telegram-bot`` via - -.. code:: shell - - $ pip install python-telegram-bot-raw --upgrade - -To install a pre-release, use the ``--pre`` `flag `_ in addition. - -You can also install ``python-telegram-bot-raw`` from source, though this is usually not necessary. - -.. code:: shell - - $ git clone https://github.com/python-telegram-bot/python-telegram-bot - $ cd python-telegram-bot - $ python setup_raw.py install - -Note ----- - -Installing the ``.tar.gz`` archive available on PyPi directly via ``pip`` will *not* work as expected, as ``pip`` does not recognize that it should use ``setup_raw.py`` instead of ``setup.py``. - -Verifying Releases ------------------- - -We sign all the releases with a GPG key. -The signatures are uploaded to both the `GitHub releases page `_ and the `PyPI project `_ and end with a suffix ``.asc``. -Please find the public keys `here `_. -The keys are named in the format ``-.gpg`` or ``-current.gpg`` if the key is currently being used for new releases. - -In addition, the GitHub release page also contains the sha1 hashes of the release files in the files with the suffix ``.sha1``. - -This allows you to verify that a release file that you downloaded was indeed provided by the ``python-telegram-bot`` team. - -Dependencies & Their Versions ------------------------------ - -``python-telegram-bot`` tries to use as few 3rd party dependencies as possible. -However, for some features using a 3rd party library is more sane than implementing the functionality again. -As these features are *optional*, the corresponding 3rd party dependencies are not installed by default. -Instead, they are listed as optional dependencies. -This allows to avoid unnecessary dependency conflicts for users who don't need the optional features. - -The only required dependency is `httpx ~= 0.27 `_ for -``telegram.request.HTTPXRequest``, the default networking backend. - -``python-telegram-bot`` is most useful when used along with additional libraries. -To minimize dependency conflicts, we try to be liberal in terms of version requirements on the (optional) dependencies. -On the other hand, we have to ensure stability of ``python-telegram-bot``, which is why we do apply version bounds. -If you encounter dependency conflicts due to these bounds, feel free to reach out. - -Optional Dependencies -##################### - -PTB can be installed with optional dependencies: - -* ``pip install "python-telegram-bot-raw[passport]"`` installs the `cryptography>=39.0.1 `_ library. Use this, if you want to use Telegram Passport related functionality. -* ``pip install "python-telegram-bot-raw[socks]"`` installs `httpx[socks] `_. Use this, if you want to work behind a Socks5 server. -* ``pip install "python-telegram-bot-raw[http2]"`` installs `httpx[http2] `_. Use this, if you want to use HTTP/2. - -To install multiple optional dependencies, separate them by commas, e.g. ``pip install "python-telegram-bot-raw[passport,socks]"``. - -Additionally, the shortcut ``pip install "python-telegram-bot-raw[all]"`` installs all optional dependencies. - -Quick Start -=========== - -Our Wiki contains an `Introduction to the API `_ explaining how the pure Bot API can be accessed via ``python-telegram-bot``. - -Resources -========= - -- The `package documentation `_ is the technical reference for ``python-telegram-bot``. - It contains descriptions of all available classes, modules, methods and arguments as well as the `changelog `_. -- The `wiki `_ is home to number of more elaborate introductions of the different features of ``python-telegram-bot`` and other useful resources that go beyond the technical documentation. -- Our `examples section `_ contains several examples that showcase the different features of both the Bot API and ``python-telegram-bot``. - Even if it is not your approach for learning, please take a look at ``echobot.py``. It is the de facto base for most of the bots out there. - The code for these examples is released to the public domain, so you can start by grabbing the code and building on top of it. -- The `official Telegram Bot API documentation `_ is of course always worth a read. - -Getting help -============ - -If the resources mentioned above don't answer your questions or simply overwhelm you, there are several ways of getting help. - -1. We have a vibrant community of developers helping each other in our `Telegram group `_. Join us! Asking a question here is often the quickest way to get a pointer in the right direction. - -2. Ask questions by opening `a discussion `_. - -3. You can even ask for help on Stack Overflow using the `python-telegram-bot tag `_. - -Concurrency -=========== - -Since v20.0, ``python-telegram-bot`` is built on top of Pythons ``asyncio`` module. -Because ``asyncio`` is in general single-threaded, ``python-telegram-bot`` does currently not aim to be thread-safe. - -Contributing -============ - -Contributions of all sizes are welcome. -Please review our `contribution guidelines `_ to get started. -You can also help by `reporting bugs or feature requests `_. - -Donating -======== -Occasionally we are asked if we accept donations to support the development. -While we appreciate the thought, maintaining PTB is our hobby, and we have almost no running costs for it. We therefore have nothing set up to accept donations. -If you still want to donate, we kindly ask you to donate to another open source project/initiative of your choice instead. - -License -======= - -You may copy, distribute and modify the software provided that modifications are described and licensed for free under `LGPL-3 `_. -Derivatives works (including modifications or anything statically linked to the library) can only be redistributed under LGPL-3, but applications that use the library don't have to be. diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 13bface7d..08fba15d3 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -4,4 +4,4 @@ furo-sphinx-search @ git+https://github.com/harshil21/furo-sphinx-search@v0.2.0. sphinx-paramlinks==0.6.0 sphinxcontrib-mermaid==0.9.2 sphinx-copybutton==0.5.2 -sphinx-inline-tabs==2023.4.21 +sphinx-inline-tabs==2023.4.21 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index b02870776..4f40bd910 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,113 @@ +# PACKAGING +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +dynamic = ["version"] +name = "python-telegram-bot" +description = "We have made you a wrapper you can't refuse" +readme = "README.rst" +requires-python = ">=3.8" +license = "LGPL-3.0-only" +license-files = { paths = ["LICENSE", "LICENSE.dual", "LICENSE.lesser"] } +authors = [ + { name = "Leandro Toledo", email = "devs@python-telegram-bot.org" } +] +keywords = [ + "python", + "telegram", + "bot", + "api", + "wrapper", +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Operating System :: OS Independent", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Communications :: Chat", + "Topic :: Internet", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "httpx ~= 0.27", +] + +[project.urls] +"Homepage" = "https://python-telegram-bot.org" +"Documentation" = "https://docs.python-telegram-bot.org" +"Bug Tracker" = "https://github.com/python-telegram-bot/python-telegram-bot/issues" +"Source Code" = "https://github.com/python-telegram-bot/python-telegram-bot" +"News" = "https://t.me/pythontelegrambotchannel" +"Changelog" = "https://docs.python-telegram-bot.org/en/stable/changelog.html" +"Support" = "https://t.me/pythontelegrambotgroup" + +[project.optional-dependencies] +# Make sure to install those as additional_dependencies in the +# pre-commit hooks for pylint & mypy +# Also update the readme accordingly +# +# When dependencies release new versions and tests succeed, we should try to expand the allowed +# versions and only increase the lower bound if necessary +# +# When adding new groups, make sure to update `ext` and `all` accordingly + +# Optional dependencies for production +all = [ + "python-telegram-bot[ext,http2,passport,socks]", +] +callback-data = [ + # Cachetools doesn't have a strict stability policy. Let's be cautious for now. + "cachetools~=5.3.3", +] +ext = [ + "python-telegram-bot[callback-data,job-queue,rate-limiter,webhooks]", +] +http2 = [ + "httpx[http2]", +] +job-queue = [ + # APS doesn't have a strict stability policy. Let's be cautious for now. + "APScheduler~=3.10.4", + # pytz is required by APS and just needs the lower bound due to #2120 + "pytz>=2018.6", +] +passport = [ + "cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1", +] +rate-limiter = [ + "aiolimiter~=1.1.0", +] +socks = [ + "httpx[socks]", +] +webhooks = [ + # tornado is rather stable, but let's not allow the next major release without prior testing + "tornado~=6.4", +] + + +# HATCH +[tool.hatch.version] +# dynamically evaluates the `__version__` variable in that file +source = "code" +path = "telegram/_version.py" +search-paths = ["telegram"] + +[tool.hatch.build] +packages = ["telegram"] + # BLACK: [tool.black] line-length = 99 -target-version = ['py38', 'py39', 'py310', 'py311'] # ISORT: [tool.isort] # black config @@ -11,7 +117,6 @@ line_length = 99 # RUFF: [tool.ruff] line-length = 99 -target-version = "py38" show-fixes = true [tool.ruff.lint] diff --git a/requirements-all.txt b/requirements-all.txt deleted file mode 100644 index d38ad6691..000000000 --- a/requirements-all.txt +++ /dev/null @@ -1,4 +0,0 @@ --r requirements.txt --r requirements-dev.txt --r requirements-opts.txt --r docs/requirements-docs.txt \ No newline at end of file diff --git a/requirements-dev-all.txt b/requirements-dev-all.txt new file mode 100644 index 000000000..995e067c4 --- /dev/null +++ b/requirements-dev-all.txt @@ -0,0 +1,5 @@ +-e .[all] +# needed for pre-commit hooks in the git commit command +pre-commit +-r requirements-unit-tests.txt +-r docs/requirements-docs.txt diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 63f6432ad..000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,11 +0,0 @@ -pre-commit # needed for pre-commit hooks in the git commit command - -# For the test suite -setuptools # required for test_meta -pytest==8.2.1 -pytest-asyncio==0.21.2 # needed because pytest doesn't come with native support for coroutines as tests -pytest-xdist==3.6.1 # xdist runs tests in parallel -flaky # Used for flaky tests (flaky decorator) -beautifulsoup4 # used in test_official for parsing tg docs - -wheel # required for building the wheels for releases diff --git a/requirements-opts.txt b/requirements-opts.txt deleted file mode 100644 index 05ac0d8c7..000000000 --- a/requirements-opts.txt +++ /dev/null @@ -1,27 +0,0 @@ -# Format: -# package_name==version # req-1, req-2, req-3!ext -# `pip install ptb-raw[req-1/2]` will install `package_name` -# `pip install ptb[req-1/2/3]` will also install `package_name` - -# Make sure to install those as additional_dependencies in the -# pre-commit hooks for pylint & mypy -# Also update the readme accordingly - -# When dependencies release new versions and tests succeed, we should try to expand the allowed -# versions and only increase the lower bound if necessary - -httpx[socks] # socks -httpx[http2] # http2 -cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1 # passport -aiolimiter~=1.1.0 # rate-limiter!ext - -# tornado is rather stable, but let's not allow the next mayor release without prior testing -tornado~=6.4 # webhooks!ext - -# Cachetools and APS don't have a strict stability policy. -# Let's be cautious for now. -cachetools~=5.3.3 # callback-data!ext -APScheduler~=3.10.4 # job-queue!ext - -# pytz is required by APS and just needs the lower bound due to #2120 -pytz>=2018.6 # job-queue!ext diff --git a/requirements-unit-tests.txt b/requirements-unit-tests.txt new file mode 100644 index 000000000..26951dafa --- /dev/null +++ b/requirements-unit-tests.txt @@ -0,0 +1,19 @@ +-e . + +# required for building the wheels for releases +build + +# For the test suite +pytest==8.2.1 + +# needed because pytest doesn't come with native support for coroutines as tests +pytest-asyncio==0.21.2 + +# xdist runs tests in parallel +pytest-xdist==3.6.1 + +# Used for flaky tests (flaky decorator) +flaky + +# used in test_official for parsing tg docs +beautifulsoup4 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 90203e497..000000000 --- a/requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Make sure to install those as additional_dependencies in the -# pre-commit hooks for pylint & mypy -# Also update the readme accordingly - -# When dependencies release new versions and tests succeed, we should try to expand the allowed -# versions and only increase the lower bound if necessary - -# httpx has no stable release yet, but we've had no stability problems since v20.0a0 either -# Since there have been requests to relax the bound a bit, we allow versions < 1.0.0 -httpx ~= 0.27 diff --git a/setup.cfg b/setup.cfg index 278056b06..c24e78bc4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,8 +1,5 @@ -[metadata] -license_files = LICENSE, LICENSE.dual, LICENSE.lesser - [flake8] max-line-length = 99 ignore = W503, W605 extend-ignore = E203, E704 -exclude = setup.py, setup_raw.py docs/source/conf.py +exclude = docs/source/conf.py diff --git a/setup.py b/setup.py deleted file mode 100644 index d62ad39ed..000000000 --- a/setup.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python -"""The setup and build script for the python-telegram-bot library.""" -import subprocess -import sys -from collections import defaultdict -from pathlib import Path -from typing import Any, Dict, List, Tuple - -from setuptools import find_packages, setup - - -def get_requirements() -> List[str]: - """Build the requirements list for this project""" - requirements_list = [] - - with Path("requirements.txt").open(encoding="utf-8") as reqs: - for install in reqs: - if install.startswith("#"): - continue - requirements_list.append(install.strip()) - - return requirements_list - - -def get_packages_requirements(raw: bool = False) -> Tuple[List[str], List[str]]: - """Build the package & requirements list for this project""" - reqs = get_requirements() - - exclude = ["tests*", "docs*"] - if raw: - exclude.append("telegram.ext*") - - packs = find_packages(exclude=exclude) - - return packs, reqs - - -def get_optional_requirements(raw: bool = False) -> Dict[str, List[str]]: - """Build the optional dependencies""" - requirements = defaultdict(list) - - with Path("requirements-opts.txt").open(encoding="utf-8") as reqs: - for line in reqs: - effective_line = line.strip() - if not effective_line or effective_line.startswith("#"): - continue - dependency, names = effective_line.split("#") - dependency = dependency.strip() - for name in names.split(","): - effective_name = name.strip() - if effective_name.endswith("!ext"): - if raw: - continue - effective_name = effective_name[:-4] - requirements["ext"].append(dependency) - requirements[effective_name].append(dependency) - requirements["all"].append(dependency) - - return requirements - - -def get_setup_kwargs(raw: bool = False) -> Dict[str, Any]: - """Builds a dictionary of kwargs for the setup function""" - packages, requirements = get_packages_requirements(raw=raw) - - raw_ext = "-raw" if raw else "" - readme = Path(f'README{"_RAW" if raw else ""}.rst') - - version_file = Path("telegram/_version.py").read_text(encoding="utf-8") - first_part = version_file.split("# SETUP.PY MARKER")[0] - exec(first_part) # pylint: disable=exec-used - - return { - "script_name": f"setup{raw_ext}.py", - "name": f"python-telegram-bot{raw_ext}", - "version": locals()["__version__"], - "author": "Leandro Toledo", - "author_email": "devs@python-telegram-bot.org", - "license": "LGPLv3", - "url": "https://python-telegram-bot.org/", - # Keywords supported by PyPI can be found at - # https://github.com/pypa/warehouse/blob/aafc5185e57e67d43487ce4faa95913dd4573e14/ - # warehouse/templates/packaging/detail.html#L20-L58 - "project_urls": { - "Documentation": "https://docs.python-telegram-bot.org", - "Bug Tracker": "https://github.com/python-telegram-bot/python-telegram-bot/issues", - "Source Code": "https://github.com/python-telegram-bot/python-telegram-bot", - "News": "https://t.me/pythontelegrambotchannel", - "Changelog": "https://docs.python-telegram-bot.org/en/stable/changelog.html", - }, - "download_url": f"https://pypi.org/project/python-telegram-bot{raw_ext}/", - "keywords": "python telegram bot api wrapper", - "description": "We have made you a wrapper you can't refuse", - "long_description": readme.read_text(encoding="utf-8"), - "long_description_content_type": "text/x-rst", - "packages": packages, - "install_requires": requirements, - "extras_require": get_optional_requirements(raw=raw), - "include_package_data": True, - "classifiers": [ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", - "Operating System :: OS Independent", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Communications :: Chat", - "Topic :: Internet", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - ], - "python_requires": ">=3.8", - } - - -def main() -> None: - # If we're building, build ptb-raw as well - if set(sys.argv[1:]) in [{"bdist_wheel"}, {"sdist"}, {"sdist", "bdist_wheel"}]: - args = ["python", "setup_raw.py"] - args.extend(sys.argv[1:]) - subprocess.run(args, check=True, capture_output=True) - - setup(**get_setup_kwargs(raw=False)) - - -if __name__ == "__main__": - main() diff --git a/setup_raw.py b/setup_raw.py deleted file mode 100644 index 0e99fb685..000000000 --- a/setup_raw.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python -"""The setup and build script for the python-telegram-bot-raw library.""" - -from setuptools import setup - -from setup import get_setup_kwargs - -setup(**get_setup_kwargs(raw=True)) diff --git a/telegram/__init__.py b/telegram/__init__.py index 6105c9780..675a60e98 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -242,8 +242,6 @@ __all__ = ( "warnings", ) -from pathlib import Path - from . import _version, constants, error, helpers, request, warnings from ._birthdate import Birthdate from ._bot import Bot @@ -443,7 +441,6 @@ from ._telegramobject import TelegramObject from ._update import Update from ._user import User from ._userprofilephotos import UserProfilePhotos -from ._utils.warnings import warn from ._videochat import ( VideoChatEnded, VideoChatParticipantsInvited, @@ -472,33 +469,8 @@ __version_info__: _version.Version = _version.__version_info__ #: #: .. versionchanged:: 20.0 #: This constant was previously named ``bot_api_version``. -__bot_api_version__: str = _version.__bot_api_version__ +__bot_api_version__: str = constants.BOT_API_VERSION #: :class:`typing.NamedTuple`: Shortcut for :const:`telegram.constants.BOT_API_VERSION_INFO`. #: #: .. versionadded:: 20.0 -__bot_api_version_info__: constants._BotAPIVersion = _version.__bot_api_version_info__ - - -if not (Path(__file__).parent.resolve().absolute() / "ext").exists(): - _MESSAGE = ( - "Hey. You seem to be using the `python-telegram-bot-raw` library. " - "Please note that this libray has been deprecated and will no longer be updated. " - "Please instead use the `python-telegram-bot` library. The change requires no " - "changes in your code and requires no additional dependencies. For additional " - "information, please see the channel post at " - "https://t.me/pythontelegrambotchannel/145." - ) - - # DeprecationWarning is ignored by default in Python 3.7 and later by default outside - # __main__ modules. We use both warning categories to increase the chance of the user - # seeing the warning. - - warn( - warnings.PTBDeprecationWarning(version="21.3", message=_MESSAGE), - stacklevel=2, - ) - warn( - message=_MESSAGE, - category=warnings.PTBUserWarning, - stacklevel=2, - ) +__bot_api_version_info__: constants._BotAPIVersion = constants.BOT_API_VERSION_INFO diff --git a/telegram/_version.py b/telegram/_version.py index 348495362..557a1ab90 100644 --- a/telegram/_version.py +++ b/telegram/_version.py @@ -19,7 +19,7 @@ # pylint: disable=missing-module-docstring from typing import Final, NamedTuple -__all__ = ("__bot_api_version__", "__bot_api_version_info__", "__version__", "__version_info__") +__all__ = ("__version__", "__version_info__") class Version(NamedTuple): @@ -54,12 +54,3 @@ __version_info__: Final[Version] = Version( major=21, minor=3, micro=0, releaselevel="final", serial=0 ) __version__: Final[str] = str(__version_info__) - -# # SETUP.PY MARKER -# Lines above this line will be `exec`-cuted in setup.py. Make sure that this only contains -# std-lib imports! - -from telegram import constants # noqa: E402 # pylint: disable=wrong-import-position - -__bot_api_version__: Final[str] = constants.BOT_API_VERSION -__bot_api_version_info__: Final[constants._BotAPIVersion] = constants.BOT_API_VERSION_INFO diff --git a/tests/README.rst b/tests/README.rst index 753dd6a16..69591953b 100644 --- a/tests/README.rst +++ b/tests/README.rst @@ -4,7 +4,7 @@ Testing in PTB PTB uses `pytest`_ for testing. To run the tests, you need to have pytest installed along with a few other dependencies. You can find the list of dependencies -in the ``requirements-dev.txt`` file in the root of the repository. +in the ``pyproject.toml`` file in the root of the repository. Running tests ============= diff --git a/tests/test_meta.py b/tests/test_meta.py index fd698585d..7b83e7bb9 100644 --- a/tests/test_meta.py +++ b/tests/test_meta.py @@ -35,9 +35,4 @@ def _change_test_dir(request, monkeypatch): @skip_disabled def test_build(): - assert os.system("python setup.py bdist_dumb") == 0 # pragma: no cover - - -@skip_disabled -def test_build_raw(): - assert os.system("python setup_raw.py bdist_dumb") == 0 # pragma: no cover + assert os.system("python -m build") == 0 # pragma: no cover From a83046e1ec9aafc1bbccf84fb6ffad3df4ffa613 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:32:47 -0400 Subject: [PATCH 10/12] Add `mise-en-place` to `.gitignore` (#4300) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index fa3d7aa52..470d2a2aa 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,6 @@ telegram.jpg # virtual env venv* + +# environment manager: +.mise.toml \ No newline at end of file From 5b1e7399a48e802418ed38719d89414198793bdc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:20:47 +0200 Subject: [PATCH 11/12] Bump `pytest` from 8.2.1 to 8.2.2 (#4294) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> --- requirements-unit-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-unit-tests.txt b/requirements-unit-tests.txt index 26951dafa..02f80fe08 100644 --- a/requirements-unit-tests.txt +++ b/requirements-unit-tests.txt @@ -4,7 +4,7 @@ build # For the test suite -pytest==8.2.1 +pytest==8.2.2 # needed because pytest doesn't come with native support for coroutines as tests pytest-asyncio==0.21.2 From 9ce0f498823c142599b6f722be83d212f17c3283 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Tue, 18 Jun 2024 16:25:02 -0400 Subject: [PATCH 12/12] Add Support for Python 3.13 Beta (#4253) Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> --- .github/workflows/type_completeness.yml | 4 +- .github/workflows/unit_tests.yml | 2 +- pyproject.toml | 3 + telegram/_chatfullinfo.py | 8 +-- telegram/_giveaway.py | 2 +- telegram/_inline/inputtextmessagecontent.py | 2 +- telegram/_update.py | 4 +- telegram/ext/_application.py | 69 +++++++++++---------- tests/auxil/pytest_classes.py | 6 +- tests/conftest.py | 6 +- tests/ext/test_application.py | 62 +++++++++++++++--- tests/request/test_request.py | 11 ++-- tests/test_bot.py | 4 +- tests/test_message.py | 6 +- 14 files changed, 123 insertions(+), 66 deletions(-) diff --git a/.github/workflows/type_completeness.yml b/.github/workflows/type_completeness.yml index 74087e3e8..4a98c0b30 100644 --- a/.github/workflows/type_completeness.yml +++ b/.github/workflows/type_completeness.yml @@ -18,12 +18,12 @@ jobs: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: 3.12 cache: 'pip' cache-dependency-path: '**/requirements*.txt' - name: Install Pyright run: | - python -W ignore -m pip install pyright~=1.1.316 + python -W ignore -m pip install pyright~=1.1.367 - name: Get PR Completeness # Must run before base completeness, as base completeness will checkout the base branch # And we can't go back to the PR branch after that in case the PR is coming from a fork diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 8e1c7bb06..214eca12b 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -19,7 +19,7 @@ jobs: runs-on: ${{matrix.os}} strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13.0-beta.2'] os: [ubuntu-latest, windows-latest, macos-latest] fail-fast: False steps: diff --git a/pyproject.toml b/pyproject.toml index 4f40bd910..13ae98395 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ "httpx ~= 0.27", @@ -82,6 +83,8 @@ job-queue = [ ] passport = [ "cryptography!=3.4,!=3.4.1,!=3.4.2,!=3.4.3,>=39.0.1", + # cffi is a dependency of cryptography and added support for python 3.13 in 1.17.0rc1 + "cffi >= 1.17.0rc1; python_version > '3.12'" ] rate-limiter = [ "aiolimiter~=1.1.0", diff --git a/telegram/_chatfullinfo.py b/telegram/_chatfullinfo.py index 213baed7e..3458f6fa6 100644 --- a/telegram/_chatfullinfo.py +++ b/telegram/_chatfullinfo.py @@ -490,10 +490,10 @@ class ChatFullInfo(_ChatBase): self.unrestrict_boost_count: Optional[int] = unrestrict_boost_count self.custom_emoji_sticker_set_name: Optional[str] = custom_emoji_sticker_set_name self.birthdate: Optional[Birthdate] = birthdate - self.personal_chat: Optional["Chat"] = personal_chat - self.business_intro: Optional["BusinessIntro"] = business_intro - self.business_location: Optional["BusinessLocation"] = business_location - self.business_opening_hours: Optional["BusinessOpeningHours"] = business_opening_hours + self.personal_chat: Optional[Chat] = personal_chat + self.business_intro: Optional[BusinessIntro] = business_intro + self.business_location: Optional[BusinessLocation] = business_location + self.business_opening_hours: Optional[BusinessOpeningHours] = business_opening_hours @classmethod def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["ChatFullInfo"]: diff --git a/telegram/_giveaway.py b/telegram/_giveaway.py index 325189803..ed6d4a288 100644 --- a/telegram/_giveaway.py +++ b/telegram/_giveaway.py @@ -313,7 +313,7 @@ class GiveawayCompleted(TelegramObject): self.winner_count: int = winner_count self.unclaimed_prize_count: Optional[int] = unclaimed_prize_count - self.giveaway_message: Optional["Message"] = giveaway_message + self.giveaway_message: Optional[Message] = giveaway_message self._id_attrs = ( self.winner_count, diff --git a/telegram/_inline/inputtextmessagecontent.py b/telegram/_inline/inputtextmessagecontent.py index 0e127ce70..475f9c5bb 100644 --- a/telegram/_inline/inputtextmessagecontent.py +++ b/telegram/_inline/inputtextmessagecontent.py @@ -108,7 +108,7 @@ class InputTextMessageContent(InputMessageContent): # Optionals self.parse_mode: ODVInput[str] = parse_mode self.entities: Tuple[MessageEntity, ...] = parse_sequence_arg(entities) - self.link_preview_options: ODVInput["LinkPreviewOptions"] = parse_lpo_and_dwpp( + self.link_preview_options: ODVInput[LinkPreviewOptions] = parse_lpo_and_dwpp( disable_web_page_preview, link_preview_options ) diff --git a/telegram/_update.py b/telegram/_update.py index 784dea52a..68ff52649 100644 --- a/telegram/_update.py +++ b/telegram/_update.py @@ -446,7 +446,7 @@ class Update(TelegramObject): ) self._effective_user: Optional[User] = None - self._effective_sender: Optional[Union["User", "Chat"]] = None + self._effective_sender: Optional[Union[User, Chat]] = None self._effective_chat: Optional[Chat] = None self._effective_message: Optional[Message] = None @@ -568,7 +568,7 @@ class Update(TelegramObject): if self._effective_sender: return self._effective_sender - sender: Optional[Union["User", "Chat"]] = None + sender: Optional[Union[User, Chat]] = None if message := ( self.message diff --git a/telegram/ext/_application.py b/telegram/ext/_application.py index f5a9d6df4..4f623ed36 100644 --- a/telegram/ext/_application.py +++ b/telegram/ext/_application.py @@ -251,39 +251,44 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AsyncContextManager["Applica """ __slots__ = ( - "__create_task_tasks", - "__update_fetcher_task", - "__update_persistence_event", - "__update_persistence_lock", - "__update_persistence_task", + ( # noqa: RUF005 + "__create_task_tasks", + "__update_fetcher_task", + "__update_persistence_event", + "__update_persistence_lock", + "__update_persistence_task", + "__stop_running_marker", + "_chat_data", + "_chat_ids_to_be_deleted_in_persistence", + "_chat_ids_to_be_updated_in_persistence", + "_conversation_handler_conversations", + "_initialized", + "_job_queue", + "_running", + "_update_processor", + "_user_data", + "_user_ids_to_be_deleted_in_persistence", + "_user_ids_to_be_updated_in_persistence", + "bot", + "bot_data", + "chat_data", + "context_types", + "error_handlers", + "handlers", + "persistence", + "post_init", + "post_shutdown", + "post_stop", + "update_queue", + "updater", + "user_data", + ) # Allowing '__weakref__' creation here since we need it for the JobQueue - # Uncomment if necessary - currently the __weakref__ slot is already created - # in the AsyncContextManager base class - # "__weakref__", - "_chat_data", - "_chat_ids_to_be_deleted_in_persistence", - "_chat_ids_to_be_updated_in_persistence", - "_conversation_handler_conversations", - "_initialized", - "_job_queue", - "_running", - "_update_processor", - "_user_data", - "_user_ids_to_be_deleted_in_persistence", - "_user_ids_to_be_updated_in_persistence", - "bot", - "bot_data", - "chat_data", - "context_types", - "error_handlers", - "handlers", - "persistence", - "post_init", - "post_shutdown", - "post_stop", - "update_queue", - "updater", - "user_data", + # Currently the __weakref__ slot is already created + # in the AsyncContextManager base class for pythons < 3.13 + + ("__weakref__",) + if sys.version_info >= (3, 13) + else () ) def __init__( diff --git a/tests/auxil/pytest_classes.py b/tests/auxil/pytest_classes.py index 5586a8ea0..1b976b02e 100644 --- a/tests/auxil/pytest_classes.py +++ b/tests/auxil/pytest_classes.py @@ -21,7 +21,7 @@ modify behavior of the respective parent classes in order to make them easier to pytest framework. A common change is to allow monkeypatching of the class members by not enforcing slots in the subclasses.""" from telegram import Bot, Message, User -from telegram.ext import Application, ExtBot +from telegram.ext import Application, ExtBot, Updater from tests.auxil.ci_bots import BOT_INFO_PROVIDER from tests.auxil.constants import PRIVATE_KEY from tests.auxil.envvars import TEST_WITH_OPT_DEPS @@ -89,6 +89,10 @@ class PytestMessage(Message): pass +class PytestUpdater(Updater): + pass + + def make_bot(bot_info=None, **kwargs): """ Tests are executed on tg.ext.ExtBot, as that class only extends the functionality of tg.bot diff --git a/tests/conftest.py b/tests/conftest.py index 213bcff4a..a9ef3e686 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,6 +20,7 @@ import asyncio import datetime import logging import sys +from pathlib import Path from typing import Dict, List from uuid import uuid4 @@ -291,6 +292,5 @@ def timezone(tzinfo): @pytest.fixture() -def tmp_file(tmp_path): - with tmp_path / uuid4().hex as file: - yield file +def tmp_file(tmp_path) -> Path: + return tmp_path / uuid4().hex diff --git a/tests/ext/test_application.py b/tests/ext/test_application.py index 714abf853..acfce013a 100644 --- a/tests/ext/test_application.py +++ b/tests/ext/test_application.py @@ -61,7 +61,7 @@ from tests.auxil.asyncio_helpers import call_after from tests.auxil.build_messages import make_message_update from tests.auxil.files import PROJECT_ROOT_PATH from tests.auxil.networking import send_webhook_message -from tests.auxil.pytest_classes import make_bot +from tests.auxil.pytest_classes import PytestApplication, PytestUpdater, make_bot from tests.auxil.slots import mro_slots @@ -1581,7 +1581,13 @@ class TestApplication: async def post_init(app: Application) -> None: events.append("post_init") - app = Application.builder().bot(one_time_bot).post_init(post_init).build() + app = ( + Application.builder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_init(post_init) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "get_updates", get_updates) monkeypatch.setattr( @@ -1624,7 +1630,13 @@ class TestApplication: async def post_shutdown(app: Application) -> None: events.append("post_shutdown") - app = Application.builder().bot(one_time_bot).post_shutdown(post_shutdown).build() + app = ( + Application.builder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_shutdown(post_shutdown) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "get_updates", get_updates) monkeypatch.setattr( @@ -1650,7 +1662,7 @@ class TestApplication: platform.system() == "Windows", reason="Can't send signals without stopping whole process on windows", ) - def test_run_polling_post_stop(self, bot, monkeypatch): + def test_run_polling_post_stop(self, one_time_bot, monkeypatch): events = [] async def get_updates(*args, **kwargs): @@ -1671,7 +1683,13 @@ class TestApplication: async def post_stop(app: Application) -> None: events.append("post_stop") - app = Application.builder().token(bot.token).post_stop(post_stop).build() + app = ( + Application.builder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_stop(post_stop) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "get_updates", get_updates) monkeypatch.setattr(app, "stop", call_after(app.stop, lambda _: events.append("stop"))) @@ -1863,7 +1881,13 @@ class TestApplication: async def post_init(app: Application) -> None: events.append("post_init") - app = Application.builder().bot(one_time_bot).post_init(post_init).build() + app = ( + Application.builder() + .post_init(post_init) + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "set_webhook", set_webhook) monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) @@ -1923,7 +1947,13 @@ class TestApplication: async def post_shutdown(app: Application) -> None: events.append("post_shutdown") - app = Application.builder().bot(one_time_bot).post_shutdown(post_shutdown).build() + app = ( + Application.builder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_shutdown(post_shutdown) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "set_webhook", set_webhook) monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) @@ -1960,7 +1990,7 @@ class TestApplication: platform.system() == "Windows", reason="Can't send signals without stopping whole process on windows", ) - def test_run_webhook_post_stop(self, bot, monkeypatch): + def test_run_webhook_post_stop(self, one_time_bot, monkeypatch): events = [] async def delete_webhook(*args, **kwargs): @@ -1987,7 +2017,13 @@ class TestApplication: async def post_stop(app: Application) -> None: events.append("post_stop") - app = Application.builder().token(bot.token).post_stop(post_stop).build() + app = ( + Application.builder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_stop(post_stop) + .build() + ) app.bot._unfreeze() monkeypatch.setattr(app.bot, "set_webhook", set_webhook) monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) @@ -2480,7 +2516,13 @@ class TestApplication: app.create_task(task(app)) - app = ApplicationBuilder().bot(one_time_bot).post_init(post_init).build() + app = ( + ApplicationBuilder() + .application_class(PytestApplication) + .updater(PytestUpdater(one_time_bot, asyncio.Queue())) + .post_init(post_init) + .build() + ) monkeypatch.setattr(app.bot, "get_updates", get_updates) monkeypatch.setattr(app.bot, "set_webhook", set_webhook) monkeypatch.setattr(app.bot, "delete_webhook", delete_webhook) diff --git a/tests/request/test_request.py b/tests/request/test_request.py index 47e7d2125..ecfb65ece 100644 --- a/tests/request/test_request.py +++ b/tests/request/test_request.py @@ -47,6 +47,7 @@ from telegram.request._httpxrequest import HTTPXRequest from telegram.request._requestparameter import RequestParameter from telegram.warnings import PTBDeprecationWarning from tests.auxil.envvars import TEST_WITH_OPT_DEPS +from tests.auxil.networking import NonchalantHttpxRequest from tests.auxil.slots import mro_slots # We only need mixed_rqs fixture, but it uses the others, so pytest needs us to import them as well @@ -72,7 +73,7 @@ def mocker_factory( @pytest.fixture() async def httpx_request(): - async with HTTPXRequest() as rq: + async with NonchalantHttpxRequest() as rq: yield rq @@ -137,7 +138,7 @@ class TestRequestWithoutRequest: async def shutdown(): self.test_flag.append("stop") - httpx_request = HTTPXRequest() + httpx_request = NonchalantHttpxRequest() monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx_request, "shutdown", shutdown) @@ -154,7 +155,7 @@ class TestRequestWithoutRequest: async def shutdown(): self.test_flag = "stop" - httpx_request = HTTPXRequest() + httpx_request = NonchalantHttpxRequest() monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx_request, "shutdown", shutdown) @@ -545,7 +546,7 @@ class TestHTTPXRequestWithoutRequest: async def aclose(*args): self.test_flag.append("stop") - httpx_request = HTTPXRequest() + httpx_request = NonchalantHttpxRequest() monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx.AsyncClient, "aclose", aclose) @@ -562,7 +563,7 @@ class TestHTTPXRequestWithoutRequest: async def aclose(*args): self.test_flag = "stop" - httpx_request = HTTPXRequest() + httpx_request = NonchalantHttpxRequest() monkeypatch.setattr(httpx_request, "initialize", initialize) monkeypatch.setattr(httpx.AsyncClient, "aclose", aclose) diff --git a/tests/test_bot.py b/tests/test_bot.py index 8fa536281..d22ea96db 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -97,7 +97,7 @@ from tests.auxil.bot_method_checks import check_defaults_handling from tests.auxil.ci_bots import FALLBACKS from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS from tests.auxil.files import data_file -from tests.auxil.networking import expect_bad_request +from tests.auxil.networking import NonchalantHttpxRequest, expect_bad_request from tests.auxil.pytest_classes import PytestBot, PytestExtBot, make_bot from tests.auxil.slots import mro_slots @@ -253,7 +253,7 @@ class TestBotWithoutRequest: async def stop(*args, **kwargs): self.test_flag.append("stop") - temp_bot = PytestBot(token=bot.token) + temp_bot = PytestBot(token=bot.token, request=NonchalantHttpxRequest()) orig_stop = temp_bot.request.shutdown try: diff --git a/tests/test_message.py b/tests/test_message.py index c51e3a92a..075d7089d 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -2624,7 +2624,9 @@ class TestMessageWithoutRequest(TestMessageBase): async def test_default_do_quote( self, bot, message, default_quote, chat_type, expected, monkeypatch ): - message.set_bot(PytestExtBot(token=bot.token, defaults=Defaults(do_quote=default_quote))) + original_bot = message.get_bot() + temp_bot = PytestExtBot(token=bot.token, defaults=Defaults(do_quote=default_quote)) + message.set_bot(temp_bot) async def make_assertion(*_, **kwargs): reply_parameters = kwargs.get("reply_parameters") or ReplyParameters(message_id=False) @@ -2637,7 +2639,7 @@ class TestMessageWithoutRequest(TestMessageBase): message.chat.type = chat_type assert await message.reply_text("test") finally: - message.get_bot()._defaults = None + message.set_bot(original_bot) async def test_edit_forum_topic(self, monkeypatch, message): async def make_assertion(*_, **kwargs):