diff --git a/README.rst b/README.rst index 7bf24728b..b2de996d2 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.7-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-7.8-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API version @@ -81,7 +81,7 @@ After installing_ the library, be sure to check out the section on `working with Telegram API support ~~~~~~~~~~~~~~~~~~~~ -All types and methods of the Telegram Bot API **7.7** are natively supported by this library. +All types and methods of the Telegram Bot API **7.8** are natively supported by this library. In addition, Bot API functionality not yet natively included can still be used as described `in our wiki `_. Notable Features diff --git a/telegram/_bot.py b/telegram/_bot.py index 4d8a778cc..6cb51d2ae 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -6131,6 +6131,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): chat_id: Union[str, int], message_id: int, disable_notification: ODVInput[bool] = DEFAULT_NONE, + business_connection_id: Optional[str] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -6151,6 +6152,10 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): disable_notification (:obj:`bool`, optional): Pass :obj:`True`, if it is not necessary to send a notification to all chat members about the new pinned message. Notifications are always disabled in channels and private chats. + business_connection_id (:obj:`str`, optional): Unique identifier of the business + connection on behalf of which the message will be pinned. + + .. versionadded:: NEXT.VERSION Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -6163,6 +6168,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): "chat_id": chat_id, "message_id": message_id, "disable_notification": disable_notification, + "business_connection_id": business_connection_id, } return await self._post( @@ -6179,6 +6185,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): self, chat_id: Union[str, int], message_id: Optional[int] = None, + business_connection_id: Optional[str] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -6195,8 +6202,13 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): Args: chat_id (:obj:`int` | :obj:`str`): |chat_id_channel| - message_id (:obj:`int`, optional): Identifier of a message to unpin. If not specified, + message_id (:obj:`int`, optional): Identifier of the message to unpin. Required if + :paramref:`business_connection_id` is specified. If not specified, the most recent pinned message (by sending date) will be unpinned. + business_connection_id (:obj:`str`, optional): Unique identifier of the business + connection on behalf of which the message will be unpinned. + + .. versionadded:: NEXT.VERSION Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -6205,7 +6217,11 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]): :class:`telegram.error.TelegramError` """ - data: JSONDict = {"chat_id": chat_id, "message_id": message_id} + data: JSONDict = { + "chat_id": chat_id, + "message_id": message_id, + "business_connection_id": business_connection_id, + } return await self._post( "unpinChatMessage", diff --git a/telegram/_chat.py b/telegram/_chat.py index 200e192c9..02d80c947 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -905,6 +905,7 @@ class _ChatBase(TelegramObject): self, message_id: int, disable_notification: ODVInput[bool] = DEFAULT_NONE, + business_connection_id: Optional[str] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -932,11 +933,13 @@ class _ChatBase(TelegramObject): connect_timeout=connect_timeout, pool_timeout=pool_timeout, api_kwargs=api_kwargs, + business_connection_id=business_connection_id, ) async def unpin_message( self, message_id: Optional[int] = None, + business_connection_id: Optional[str] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -963,6 +966,7 @@ class _ChatBase(TelegramObject): pool_timeout=pool_timeout, api_kwargs=api_kwargs, message_id=message_id, + business_connection_id=business_connection_id, ) async def unpin_all_messages( diff --git a/telegram/_message.py b/telegram/_message.py index d362e42f5..7d077a4d9 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -4106,11 +4106,18 @@ class Message(MaybeInaccessibleMessage): """Shortcut for:: await bot.pin_chat_message( - chat_id=message.chat_id, message_id=message.message_id, *args, **kwargs + chat_id=message.chat_id, + message_id=message.message_id, + business_connection_id=message.business_connection_id, + *args, **kwargs ) For the documentation of the arguments, please see :meth:`telegram.Bot.pin_chat_message`. + .. versionchanged:: NEXT.VERSION + Now also passes :attr:`business_connection_id` to + :meth:`telegram.Bot.pin_chat_message`. + Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -4118,6 +4125,7 @@ class Message(MaybeInaccessibleMessage): return await self.get_bot().pin_chat_message( chat_id=self.chat_id, message_id=self.message_id, + business_connection_id=self.business_connection_id, disable_notification=disable_notification, read_timeout=read_timeout, write_timeout=write_timeout, @@ -4138,11 +4146,18 @@ class Message(MaybeInaccessibleMessage): """Shortcut for:: await bot.unpin_chat_message( - chat_id=message.chat_id, message_id=message.message_id, *args, **kwargs + chat_id=message.chat_id, + message_id=message.message_id, + business_connection_id=message.business_connection_id, + *args, **kwargs ) For the documentation of the arguments, please see :meth:`telegram.Bot.unpin_chat_message`. + .. versionchanged:: NEXT.VERSION + Now also passes :attr:`business_connection_id` to + :meth:`telegram.Bot.pin_chat_message`. + Returns: :obj:`bool`: On success, :obj:`True` is returned. @@ -4150,6 +4165,7 @@ class Message(MaybeInaccessibleMessage): return await self.get_bot().unpin_chat_message( chat_id=self.chat_id, message_id=self.message_id, + business_connection_id=self.business_connection_id, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, diff --git a/telegram/_user.py b/telegram/_user.py index 7ea769f28..50dd66870 100644 --- a/telegram/_user.py +++ b/telegram/_user.py @@ -97,6 +97,10 @@ class User(TelegramObject): :meth:`telegram.Bot.get_me`. .. versionadded:: 21.1 + has_main_web_app (:obj:`bool`, optional): :obj:`True`, if the bot has the main Web App. + Returned only in :meth:`telegram.Bot.get_me`. + + .. versionadded:: NEXT.VERSION Attributes: id (:obj:`int`): Unique identifier for this user or bot. @@ -124,6 +128,11 @@ class User(TelegramObject): :meth:`telegram.Bot.get_me`. .. versionadded:: 21.1 + has_main_web_app (:obj:`bool`) Optional. :obj:`True`, if the bot has the main Web App. + Returned only in :meth:`telegram.Bot.get_me`. + + .. versionadded:: NEXT.VERSION + .. |user_chat_id_note| replace:: This shortcuts build on the assumption that :attr:`User.id` coincides with the :attr:`Chat.id` of the private chat with the user. This has been the case so far, but Telegram does not guarantee that this stays this way. @@ -135,6 +144,7 @@ class User(TelegramObject): "can_join_groups", "can_read_all_group_messages", "first_name", + "has_main_web_app", "id", "is_bot", "is_premium", @@ -158,6 +168,7 @@ class User(TelegramObject): is_premium: Optional[bool] = None, added_to_attachment_menu: Optional[bool] = None, can_connect_to_business: Optional[bool] = None, + has_main_web_app: Optional[bool] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -176,6 +187,7 @@ class User(TelegramObject): self.is_premium: Optional[bool] = is_premium self.added_to_attachment_menu: Optional[bool] = added_to_attachment_menu self.can_connect_to_business: Optional[bool] = can_connect_to_business + self.has_main_web_app: Optional[bool] = has_main_web_app self._id_attrs = (self.id,) @@ -301,6 +313,7 @@ class User(TelegramObject): self, message_id: int, disable_notification: ODVInput[bool] = DEFAULT_NONE, + business_connection_id: Optional[str] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -329,12 +342,14 @@ class User(TelegramObject): write_timeout=write_timeout, connect_timeout=connect_timeout, pool_timeout=pool_timeout, + business_connection_id=business_connection_id, api_kwargs=api_kwargs, ) async def unpin_message( self, message_id: Optional[int] = None, + business_connection_id: Optional[str] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -363,6 +378,7 @@ class User(TelegramObject): pool_timeout=pool_timeout, api_kwargs=api_kwargs, message_id=message_id, + business_connection_id=business_connection_id, ) async def unpin_all_messages( diff --git a/telegram/constants.py b/telegram/constants.py index fb4bc9a19..1aba1f2a9 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -151,7 +151,7 @@ class _AccentColor(NamedTuple): #: :data:`telegram.__bot_api_version_info__`. #: #: .. versionadded:: 20.0 -BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=7) +BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=7, minor=8) #: :obj:`str`: Telegram Bot API #: version supported by this version of `python-telegram-bot`. Also available as #: :data:`telegram.__bot_api_version__`. diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index 7d8d10e49..4d0bef88b 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -2238,6 +2238,7 @@ class ExtBot(Bot, Generic[RLARGS]): chat_id: Union[str, int], message_id: int, disable_notification: ODVInput[bool] = DEFAULT_NONE, + business_connection_id: Optional[str] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2254,6 +2255,7 @@ class ExtBot(Bot, Generic[RLARGS]): write_timeout=write_timeout, connect_timeout=connect_timeout, pool_timeout=pool_timeout, + business_connection_id=business_connection_id, api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) @@ -3739,6 +3741,7 @@ class ExtBot(Bot, Generic[RLARGS]): self, chat_id: Union[str, int], message_id: Optional[int] = None, + business_connection_id: Optional[str] = None, *, read_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE, @@ -3754,6 +3757,7 @@ class ExtBot(Bot, Generic[RLARGS]): write_timeout=write_timeout, connect_timeout=connect_timeout, pool_timeout=pool_timeout, + business_connection_id=business_connection_id, api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) diff --git a/tests/test_bot.py b/tests/test_bot.py index 85232a8c7..e34763592 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -2207,6 +2207,8 @@ class TestBotWithoutRequest: await bot.send_message(2, "text", business_connection_id=42) await bot.stop_poll(chat_id=1, message_id=2, business_connection_id=42) + await bot.pin_chat_message(chat_id=1, message_id=2, business_connection_id=42) + await bot.unpin_chat_message(chat_id=1, 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 diff --git a/tests/test_callbackquery.py b/tests/test_callbackquery.py index 5e41b5993..75c7fc63a 100644 --- a/tests/test_callbackquery.py +++ b/tests/test_callbackquery.py @@ -465,11 +465,14 @@ class TestCallbackQueryWithoutRequest(TestCallbackQueryBase): assert check_shortcut_signature( CallbackQuery.pin_message, Bot.pin_chat_message, - ["message_id", "chat_id"], + ["message_id", "chat_id", "business_connection_id"], [], ) assert await check_shortcut_call( - callback_query.pin_message, callback_query.get_bot(), "pin_chat_message" + callback_query.pin_message, + callback_query.get_bot(), + "pin_chat_message", + ["business_connection_id"], ) assert await check_defaults_handling(callback_query.pin_message, callback_query.get_bot()) @@ -490,14 +493,15 @@ class TestCallbackQueryWithoutRequest(TestCallbackQueryBase): assert check_shortcut_signature( CallbackQuery.unpin_message, Bot.unpin_chat_message, - ["message_id", "chat_id"], + ["message_id", "chat_id", "business_connection_id"], [], ) assert await check_shortcut_call( callback_query.unpin_message, callback_query.get_bot(), "unpin_chat_message", - shortcut_kwargs=["message_id", "chat_id"], + shortcut_kwargs=["message_id"], + skip_params=["business_connection_id"], ) assert await check_defaults_handling( callback_query.unpin_message, callback_query.get_bot() diff --git a/tests/test_message.py b/tests/test_message.py index 9e575a99f..7b4fc0a45 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -2592,9 +2592,17 @@ class TestMessageWithoutRequest(TestMessageBase): return chat_id and message_id assert check_shortcut_signature( - Message.pin, Bot.pin_chat_message, ["chat_id", "message_id"], [] + Message.pin, + Bot.pin_chat_message, + ["chat_id", "message_id", "business_connection_id"], + [], + ) + assert await check_shortcut_call( + message.pin, + message.get_bot(), + "pin_chat_message", + shortcut_kwargs=["chat_id", "message_id", "business_connection_id"], ) - assert await check_shortcut_call(message.pin, message.get_bot(), "pin_chat_message") assert await check_defaults_handling(message.pin, message.get_bot()) monkeypatch.setattr(message.get_bot(), "pin_chat_message", make_assertion) @@ -2607,13 +2615,16 @@ class TestMessageWithoutRequest(TestMessageBase): return chat_id and message_id assert check_shortcut_signature( - Message.unpin, Bot.unpin_chat_message, ["chat_id", "message_id"], [] + Message.unpin, + Bot.unpin_chat_message, + ["chat_id", "message_id", "business_connection_id"], + [], ) assert await check_shortcut_call( message.unpin, message.get_bot(), "unpin_chat_message", - shortcut_kwargs=["chat_id", "message_id"], + shortcut_kwargs=["chat_id", "message_id", "business_connection_id"], ) assert await check_defaults_handling(message.unpin, message.get_bot()) diff --git a/tests/test_user.py b/tests/test_user.py index 069365328..ca5ea2011 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -43,6 +43,7 @@ def json_dict(): "is_premium": TestUserBase.is_premium, "added_to_attachment_menu": TestUserBase.added_to_attachment_menu, "can_connect_to_business": TestUserBase.can_connect_to_business, + "has_main_web_app": TestUserBase.has_main_web_app, } @@ -61,6 +62,7 @@ def user(bot): is_premium=TestUserBase.is_premium, added_to_attachment_menu=TestUserBase.added_to_attachment_menu, can_connect_to_business=TestUserBase.can_connect_to_business, + has_main_web_app=TestUserBase.has_main_web_app, ) user.set_bot(bot) user._unfreeze() @@ -80,6 +82,7 @@ class TestUserBase: is_premium = True added_to_attachment_menu = False can_connect_to_business = True + has_main_web_app = False class TestUserWithoutRequest(TestUserBase): @@ -104,6 +107,7 @@ class TestUserWithoutRequest(TestUserBase): assert user.is_premium == self.is_premium assert user.added_to_attachment_menu == self.added_to_attachment_menu assert user.can_connect_to_business == self.can_connect_to_business + assert user.has_main_web_app == self.has_main_web_app def test_to_dict(self, user): user_dict = user.to_dict() @@ -121,6 +125,7 @@ class TestUserWithoutRequest(TestUserBase): assert user_dict["is_premium"] == user.is_premium assert user_dict["added_to_attachment_menu"] == user.added_to_attachment_menu assert user_dict["can_connect_to_business"] == user.can_connect_to_business + assert user_dict["has_main_web_app"] == user.has_main_web_app def test_equality(self): a = User(self.id_, self.first_name, self.is_bot, self.last_name)