From 8c3d3e64b4292e9e64522f900fde8ec1a1c4a6ff Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:41:44 +0200 Subject: [PATCH 01/28] Update Bot API Version to 6.6 --- README.rst | 4 ++-- README_RAW.rst | 4 ++-- telegram/constants.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 00b6fa1ec72..1ddad4bfd1a 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ :target: https://pypi.org/project/python-telegram-bot/ :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Bot%20API-6.6-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-6.7-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API versions @@ -93,7 +93,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 **6.6** are supported. +All types and methods of the Telegram Bot API **6.7** are supported. Installing ========== diff --git a/README_RAW.rst b/README_RAW.rst index 29377db2a3b..b8425e4787a 100644 --- a/README_RAW.rst +++ b/README_RAW.rst @@ -14,7 +14,7 @@ :target: https://pypi.org/project/python-telegram-bot-raw/ :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Bot%20API-6.6-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-6.7-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API versions @@ -89,7 +89,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 **6.6** are supported. +All types and methods of the Telegram Bot API **6.7** are supported. Installing ========== diff --git a/telegram/constants.py b/telegram/constants.py index 65569ebe939..a10c7100fdd 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -114,7 +114,7 @@ def __str__(self) -> str: #: :data:`telegram.__bot_api_version_info__`. #: #: .. versionadded:: 20.0 -BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=6) +BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=7) #: :obj:`str`: Telegram Bot API #: version supported by this version of `python-telegram-bot`. Also available as #: :data:`telegram.__bot_api_version__`. From 20ca1044a6af543f0b60f691fbeeade005282db2 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:50:44 +0200 Subject: [PATCH 02/28] Add `BotName` class --- docs/source/telegram.botname.rst | 6 ++++ telegram/__init__.py | 2 ++ telegram/_botname.py | 48 +++++++++++++++++++++++++++ tests/test_botname.py | 56 ++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 docs/source/telegram.botname.rst create mode 100644 telegram/_botname.py create mode 100644 tests/test_botname.py diff --git a/docs/source/telegram.botname.rst b/docs/source/telegram.botname.rst new file mode 100644 index 00000000000..0f78027c7ba --- /dev/null +++ b/docs/source/telegram.botname.rst @@ -0,0 +1,6 @@ +BotName +======= + +.. autoclass:: telegram.BotName + :members: + :show-inheritance: \ No newline at end of file diff --git a/telegram/__init__.py b/telegram/__init__.py index e198098a99a..873063ef89e 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -38,6 +38,7 @@ "BotCommandScopeChatMember", "BotCommandScopeDefault", "BotDescription", + "BotName", "BotShortDescription", "CallbackGame", "CallbackQuery", @@ -204,6 +205,7 @@ BotCommandScopeDefault, ) from ._botdescription import BotDescription, BotShortDescription +from ._botname import BotName from ._callbackquery import CallbackQuery from ._chat import Chat from ._chatadministratorrights import ChatAdministratorRights diff --git a/telegram/_botname.py b/telegram/_botname.py new file mode 100644 index 00000000000..1608c40f3d2 --- /dev/null +++ b/telegram/_botname.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2023 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +"""This module contains an object that represent a Telegram bots name.""" +from telegram._telegramobject import TelegramObject +from telegram._utils.types import JSONDict + + +class BotName(TelegramObject): + """This object represents the bot's name. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`name` is equal. + + .. versionadded:: NEXT.VERSION + + Args: + name (:obj:`str`): The bot's name. + + Attributes: + name (:obj:`str`): The bot's name. + + """ + + __slots__ = ("name",) + + def __init__(self, name: str, *, api_kwargs: JSONDict = None): + super().__init__(api_kwargs=api_kwargs) + self.name = name + + self._id_attrs = (self.name,) + + self._freeze() diff --git a/tests/test_botname.py b/tests/test_botname.py new file mode 100644 index 00000000000..89d2482ed31 --- /dev/null +++ b/tests/test_botname.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2023 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +import pytest + +from telegram import BotName +from tests.auxil.slots import mro_slots + + +@pytest.fixture(scope="module") +def bot_name(bot): + return BotName(TestBotNameBase.name) + + +class TestBotNameBase: + name = "This is a test name" + + +class TestBotNameWithoutRequest(TestBotNameBase): + def test_slot_behaviour(self, bot_name): + for attr in bot_name.__slots__: + assert getattr(bot_name, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(bot_name)) == len(set(mro_slots(bot_name))), "duplicate slot" + + def test_to_dict(self, bot_name): + bot_name_dict = bot_name.to_dict() + + assert isinstance(bot_name_dict, dict) + assert bot_name_dict["name"] == self.name + + def test_equality(self): + a = BotName(self.name) + b = BotName(self.name) + c = BotName("text.com") + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a != c + assert hash(a) != hash(c) From c8a71324953d95fd6a879969e1337268ba8f6dbd Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 21 Apr 2023 15:19:08 +0200 Subject: [PATCH 03/28] Add `{set, get}_my_name` Methods --- docs/source/inclusions/bot_methods.rst | 4 ++ docs/source/telegram.at-tree.rst | 1 + telegram/_bot.py | 89 ++++++++++++++++++++++++++ telegram/constants.py | 16 +++++ telegram/ext/_extbot.py | 45 +++++++++++++ tests/test_bot.py | 39 +++++++++++ 6 files changed, 194 insertions(+) diff --git a/docs/source/inclusions/bot_methods.rst b/docs/source/inclusions/bot_methods.rst index c2d82d50702..36b871c7b73 100644 --- a/docs/source/inclusions/bot_methods.rst +++ b/docs/source/inclusions/bot_methods.rst @@ -196,6 +196,10 @@ - Used for setting the short description of the bot * - :meth:`~telegram.Bot.get_my_short_description` - Used for obtaining the short description of the bot + * - :meth:`~telegram.Bot.set_my_name` + - Used for setting the name of the bot + * - :meth:`~telegram.Bot.get_my_name` + - Used for obtaining the name of the bot .. raw:: html diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index eeb408e967f..37aee71e86b 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -16,6 +16,7 @@ Available Types telegram.botcommandscopechatmember telegram.botcommandscopedefault telegram.botdescription + telegram.botname telegram.botshortdescription telegram.callbackquery telegram.chat diff --git a/telegram/_bot.py b/telegram/_bot.py index 79afc1a11d7..e67eaff8f34 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -56,6 +56,7 @@ from telegram._botcommand import BotCommand from telegram._botcommandscope import BotCommandScope from telegram._botdescription import BotDescription, BotShortDescription +from telegram._botname import BotName from telegram._chat import Chat from telegram._chatadministratorrights import ChatAdministratorRights from telegram._chatinvitelink import ChatInviteLink @@ -8112,6 +8113,90 @@ async def get_my_short_description( bot=self, ) + @_log + async def set_my_name( + self, + name: str = None, + language_code: str = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict = None, + ) -> bool: + """ + Use this method to change the bot's name. + + .. versionadded:: NEXT.VERSION + + Args: + name (:obj:`str`, optional): New bot name; + 0-:tg-const:`telegram.constants.BotNameLimit.MAX_NAME_LENGTH` + characters. Pass an empty string to remove the dedicated name for the given + language. + language_code (:obj:`str`, optional): A two-letter ISO 639-1 language code. If empty, + the name will be applied to all users for whose language there is no + dedicated name. + + Returns: + :obj:`bool`: On success, :obj:`True` is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data: JSONDict = {"name": name, "language_code": language_code} + + return await self._post( + "setMyName", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ) + + @_log + async def get_my_name( + self, + language_code: str = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict = None, + ) -> BotName: + """ + Use this method to get the current bot name for the given user language. + + Args: + language_code (:obj:`str`, optional): A two-letter ISO 639-1 language code or an empty + string. + + Returns: + :class:`telegram.BotName`: On success, the bot name is returned. + + Raises: + :class:`telegram.error.TelegramError` + + """ + data = {"language_code": language_code} + return BotName.de_json( # type: ignore[return-value] + await self._post( + "getMyName", + data, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=api_kwargs, + ), + bot=self, + ) + def to_dict(self, recursive: bool = True) -> JSONDict: # skipcq: PYL-W0613 """See :meth:`telegram.TelegramObject.to_dict`.""" data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name} @@ -8356,3 +8441,7 @@ def __hash__(self) -> int: """Alias for :meth:`set_sticker_keywords`""" setStickerMaskPosition = set_sticker_mask_position """Alias for :meth:`set_sticker_mask_position`""" + setMyName = set_my_name + """Alias for :meth:`set_my_name`""" + getMyName = get_my_name + """Alias for :meth:`get_my_name`""" diff --git a/telegram/constants.py b/telegram/constants.py index a10c7100fdd..f623e7c5f4e 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -37,6 +37,7 @@ "BotCommandLimit", "BotCommandScopeType", "BotDescriptionLimit", + "BotNameLimit", "CallbackQueryLimit", "ChatAction", "ChatID", @@ -209,6 +210,21 @@ class BotDescriptionLimit(IntEnum): """ +class BotNameLimit(IntEnum): + """This enum contains limitations for the methods :meth:`telegram.Bot.set_my_name`. + The enum members of this enumeration are instances of :class:`int` and can be treated as such. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = () + + MAX_NAME_LENGTH = 64 + """:obj:`int`: Maximum length for the parameter :paramref:`~telegram.Bot.set_my_name.name` of + :meth:`telegram.Bot.set_my_name` + """ + + class CallbackQueryLimit(IntEnum): """This enum contains limitations for :class:`telegram.CallbackQuery`/ :meth:`telegram.Bot.answer_callback_query`. The enum members of this enumeration are instances diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index e8f02cb3461..8bd30e74746 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -46,6 +46,7 @@ BotCommand, BotCommandScope, BotDescription, + BotName, BotShortDescription, CallbackQuery, Chat, @@ -3587,6 +3588,48 @@ async def get_my_short_description( api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), ) + async def set_my_name( + self, + name: str = None, + language_code: str = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict = None, + rate_limit_args: RLARGS = None, + ) -> bool: + return await super().set_my_name( + name=name, + language_code=language_code, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + + async def get_my_name( + self, + language_code: str = None, + *, + read_timeout: ODVInput[float] = DEFAULT_NONE, + write_timeout: ODVInput[float] = DEFAULT_NONE, + connect_timeout: ODVInput[float] = DEFAULT_NONE, + pool_timeout: ODVInput[float] = DEFAULT_NONE, + api_kwargs: JSONDict = None, + rate_limit_args: RLARGS = None, + ) -> BotName: + return await super().get_my_name( + language_code=language_code, + read_timeout=read_timeout, + write_timeout=write_timeout, + connect_timeout=connect_timeout, + pool_timeout=pool_timeout, + api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), + ) + async def set_custom_emoji_sticker_set_thumbnail( self, name: str, @@ -3829,3 +3872,5 @@ async def set_sticker_mask_position( setStickerEmojiList = set_sticker_emoji_list setStickerKeywords = set_sticker_keywords setStickerMaskPosition = set_sticker_mask_position + setMyName = set_my_name + getMyName = get_my_name diff --git a/tests/test_bot.py b/tests/test_bot.py index 05ec6731efb..4aec9c8d78a 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -26,6 +26,7 @@ import socket import time from collections import defaultdict +from uuid import uuid4 import pytest @@ -34,6 +35,7 @@ BotCommand, BotCommandScopeChat, BotDescription, + BotName, BotShortDescription, CallbackQuery, Chat, @@ -3307,3 +3309,40 @@ async def test_set_get_my_short_description(self, bot): bot.get_my_short_description("en"), bot.get_my_short_description("de"), ) == 3 * [BotShortDescription("")] + + async def test_set_get_my_name(self, bot): + default_name = uuid4().hex + en_name = uuid4().hex + de_name = uuid4().hex + + # Set the names + assert all( + await asyncio.gather( + bot.set_my_name(default_name), + bot.set_my_name(en_name, language_code="en"), + bot.set_my_name(de_name, language_code="de"), + ) + ) + + # Check that they were set correctly + assert await asyncio.gather( + bot.get_my_name(), bot.get_my_name("en"), bot.get_my_name("de") + ) == [ + BotName(default_name), + BotName(en_name), + BotName(de_name), + ] + + # Delete the names + assert all( + await asyncio.gather( + bot.set_my_name(None), + bot.set_my_name(None, language_code="en"), + bot.set_my_name(None, language_code="de"), + ) + ) + + # Check that they were deleted correctly + assert await asyncio.gather( + bot.get_my_name(), bot.get_my_name("en"), bot.get_my_name("de") + ) == 3 * [BotName("")] From 7129ba6d4580ea3f91840c6d649c28d3061b4256 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 21 Apr 2023 16:02:44 +0200 Subject: [PATCH 04/28] `ChatMemberUpdated.via_chat_folder_invite_link` --- telegram/_chatmemberupdated.py | 11 +++++++++++ tests/test_chatmemberupdated.py | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/telegram/_chatmemberupdated.py b/telegram/_chatmemberupdated.py index 650d7d0b00a..59b30623488 100644 --- a/telegram/_chatmemberupdated.py +++ b/telegram/_chatmemberupdated.py @@ -59,6 +59,10 @@ class ChatMemberUpdated(TelegramObject): new_chat_member (:class:`telegram.ChatMember`): New information about the chat member. invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link, which was used by the user to join the chat. For joining by invite link events only. + via_chat_folder_invite_link (:obj:`bool`, optional): :obj:`True`, if the user joined the + chat via a chat folder invite link + + .. versionadded:: NEXT.VERSION Attributes: chat (:class:`telegram.Chat`): Chat the user belongs to. @@ -72,6 +76,10 @@ class ChatMemberUpdated(TelegramObject): new_chat_member (:class:`telegram.ChatMember`): New information about the chat member. invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link, which was used by the user to join the chat. For joining by invite link events only. + via_chat_folder_invite_link (:obj:`bool`): Optional. :obj:`True`, if the user joined the + chat via a chat folder invite link + + .. versionadded:: NEXT.VERSION """ @@ -82,6 +90,7 @@ class ChatMemberUpdated(TelegramObject): "old_chat_member", "new_chat_member", "invite_link", + "via_chat_folder_invite_link", ) def __init__( @@ -92,6 +101,7 @@ def __init__( old_chat_member: ChatMember, new_chat_member: ChatMember, invite_link: ChatInviteLink = None, + via_chat_folder_invite_link: bool = None, *, api_kwargs: JSONDict = None, ): @@ -102,6 +112,7 @@ def __init__( self.date: datetime.datetime = date self.old_chat_member: ChatMember = old_chat_member self.new_chat_member: ChatMember = new_chat_member + self.via_chat_folder_invite_link: Optional[bool] = via_chat_folder_invite_link # Optionals self.invite_link: Optional[ChatInviteLink] = invite_link diff --git a/tests/test_chatmemberupdated.py b/tests/test_chatmemberupdated.py index de3da135435..f0baf88e038 100644 --- a/tests/test_chatmemberupdated.py +++ b/tests/test_chatmemberupdated.py @@ -79,7 +79,7 @@ def invite_link(user): @pytest.fixture(scope="module") def chat_member_updated(user, chat, old_chat_member, new_chat_member, invite_link, time): - return ChatMemberUpdated(chat, user, time, old_chat_member, new_chat_member, invite_link) + return ChatMemberUpdated(chat, user, time, old_chat_member, new_chat_member, invite_link, True) class TestChatMemberUpdatedBase: @@ -113,6 +113,7 @@ def test_de_json_required_args(self, bot, user, chat, old_chat_member, new_chat_ assert chat_member_updated.old_chat_member == old_chat_member assert chat_member_updated.new_chat_member == new_chat_member assert chat_member_updated.invite_link is None + assert chat_member_updated.via_chat_folder_invite_link is None def test_de_json_all_args( self, bot, user, time, invite_link, chat, old_chat_member, new_chat_member @@ -124,6 +125,7 @@ def test_de_json_all_args( "old_chat_member": old_chat_member.to_dict(), "new_chat_member": new_chat_member.to_dict(), "invite_link": invite_link.to_dict(), + "via_chat_folder_invite_link": True, } chat_member_updated = ChatMemberUpdated.de_json(json_dict, bot) @@ -136,6 +138,7 @@ def test_de_json_all_args( assert chat_member_updated.old_chat_member == old_chat_member assert chat_member_updated.new_chat_member == new_chat_member assert chat_member_updated.invite_link == invite_link + assert chat_member_updated.via_chat_folder_invite_link is True def test_de_json_localization( self, bot, raw_bot, tz_bot, user, chat, old_chat_member, new_chat_member, time, invite_link @@ -178,6 +181,10 @@ def test_to_dict(self, chat_member_updated): == chat_member_updated.new_chat_member.to_dict() ) assert chat_member_updated_dict["invite_link"] == chat_member_updated.invite_link.to_dict() + assert ( + chat_member_updated_dict["via_chat_folder_invite_link"] + == chat_member_updated.via_chat_folder_invite_link + ) def test_equality(self, time, old_chat_member, new_chat_member, invite_link): a = ChatMemberUpdated( From 489dfc97bef2a6fe0854cff43b284725b06069ec Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 21 Apr 2023 16:36:54 +0200 Subject: [PATCH 05/28] Add `InlineQueryResultsButton` --- docs/source/telegram.inline-tree.rst | 1 + .../telegram.inlinequeryresultsbutton.rst | 6 + telegram/__init__.py | 2 + telegram/_bot.py | 9 -- telegram/_inline/inlinequeryresultsbutton.py | 115 ++++++++++++++++++ telegram/constants.py | 33 ++++- tests/test_inlinequeryresultsbutton.py | 80 ++++++++++++ 7 files changed, 235 insertions(+), 11 deletions(-) create mode 100644 docs/source/telegram.inlinequeryresultsbutton.rst create mode 100644 telegram/_inline/inlinequeryresultsbutton.py create mode 100644 tests/test_inlinequeryresultsbutton.py diff --git a/docs/source/telegram.inline-tree.rst b/docs/source/telegram.inline-tree.rst index cda06bc0c93..c0395673f51 100644 --- a/docs/source/telegram.inline-tree.rst +++ b/docs/source/telegram.inline-tree.rst @@ -25,6 +25,7 @@ Inline Mode telegram.inlinequeryresultmpeg4gif telegram.inlinequeryresultphoto telegram.inlinequeryresultvenue + telegram.inlinequeryresultsbutton telegram.inlinequeryresultvideo telegram.inlinequeryresultvoice telegram.inputmessagecontent diff --git a/docs/source/telegram.inlinequeryresultsbutton.rst b/docs/source/telegram.inlinequeryresultsbutton.rst new file mode 100644 index 00000000000..7323a20cc60 --- /dev/null +++ b/docs/source/telegram.inlinequeryresultsbutton.rst @@ -0,0 +1,6 @@ +InlineQueryResultsButton +======================== + +.. autoclass:: telegram.InlineQueryResultsButton + :members: + :show-inheritance: \ No newline at end of file diff --git a/telegram/__init__.py b/telegram/__init__.py index 873063ef89e..a6b1729f307 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -103,6 +103,7 @@ "InlineQueryResultLocation", "InlineQueryResultMpeg4Gif", "InlineQueryResultPhoto", + "InlineQueryResultsButton", "InlineQueryResultVenue", "InlineQueryResultVideo", "InlineQueryResultVoice", @@ -282,6 +283,7 @@ from ._inline.inlinequeryresultlocation import InlineQueryResultLocation from ._inline.inlinequeryresultmpeg4gif import InlineQueryResultMpeg4Gif from ._inline.inlinequeryresultphoto import InlineQueryResultPhoto +from ._inline.inlinequeryresultsbutton import InlineQueryResultsButton from ._inline.inlinequeryresultvenue import InlineQueryResultVenue from ._inline.inlinequeryresultvideo import InlineQueryResultVideo from ._inline.inlinequeryresultvoice import InlineQueryResultVoice diff --git a/telegram/_bot.py b/telegram/_bot.py index e67eaff8f34..66cd286c6e0 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -2803,15 +2803,6 @@ async def answer_inline_query( Use this method to send answers to an inline query. No more than :tg-const:`telegram.InlineQuery.MAX_RESULTS` results per query are allowed. - Example: - An inline bot that sends YouTube videos can ask the user to connect the bot to their - YouTube account to adapt search results accordingly. To do this, it displays a - 'Connect your YouTube account' button above the results, or even before showing any. - The user presses the button, switches to a private chat with the bot and, in doing so, - passes a start parameter that instructs the bot to return an OAuth link. Once done, the - bot can offer a switch_inline button so that the user can easily return to the chat - where they wanted to use the bot's inline capabilities. - Warning: In most use cases :paramref:`current_offset` should not be passed manually. Instead of calling this method directly, use the shortcut :meth:`telegram.InlineQuery.answer` with diff --git a/telegram/_inline/inlinequeryresultsbutton.py b/telegram/_inline/inlinequeryresultsbutton.py new file mode 100644 index 00000000000..8fa6dad05fa --- /dev/null +++ b/telegram/_inline/inlinequeryresultsbutton.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2023 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +# pylint: disable=redefined-builtin +"""This module contains the class that represent a Telegram InlineQueryResultsButton.""" + +from typing import TYPE_CHECKING, ClassVar, Optional + +from telegram import constants +from telegram._telegramobject import TelegramObject +from telegram._utils.types import JSONDict +from telegram._webappinfo import WebAppInfo + +if TYPE_CHECKING: + from telegram import Bot + + +class InlineQueryResultsButton(TelegramObject): + """This object represents a button to be shown above inline query results. You **must** use + exactly one of the optional fields. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`text`, :attr:`web_app` and :attr:`start_parameter` are equal. + + Args: + text (:obj:`str`): Label text on the button. + web_app (:class:`telegram.WebAppInfo`, optional): Description of the + `Web App `_ that will be launched when the + user presses the button. The Web App will be able to switch back to the inline mode + using the method ``web_app_switch_inline_query`` inside the Web App. + start_parameter (:obj:`str`, optional): Deep-linking parameter for the + :guilabel:`/start` message sent to the bot when user presses the switch button. + :tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`- + :tg-const:`telegram.InlineQuery.MAX_SWITCH_PM_TEXT_LENGTH` characters, + only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed. + + Example: + An inline bot that sends YouTube videos can ask the user to connect the bot to + their YouTube account to adapt search results accordingly. To do this, it displays + a 'Connect your YouTube account' button above the results, or even before showing + any. The user presses the button, switches to a private chat with the bot and, in + doing so, passes a start parameter that instructs the bot to return an OAuth link. + Once done, the bot can offer a switch_inline button so that the user can easily + return to the chat where they wanted to use the bot's inline capabilities. + + Attributes: + text (:obj:`str`): Label text on the button. + web_app (:class:`telegram.WebAppInfo`): Optional. Description of the + `Web App `_ that will be launched when the + user presses the button. The Web App will be able to switch back to the inline mode + using the method ``web_app_switch_inline_query`` inside the Web App. + start_parameter (:obj:`str`): Optional. Deep-linking parameter for the + :guilabel:`/start` message sent to the bot when user presses the switch button. + :tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`- + :tg-const:`telegram.InlineQuery.MAX_SWITCH_PM_TEXT_LENGTH` characters, + only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed. + + """ + + __slots__ = ("text", "web_app", "start_parameter") + + def __init__( + self, + text: str, + web_app: WebAppInfo = None, + start_parameter: str = None, + *, + api_kwargs: JSONDict = None, + ): + super().__init__(api_kwargs=api_kwargs) + + # Required + self.text: str = text + + # Optional + self.web_app: Optional[WebAppInfo] = web_app + self.start_parameter: Optional[str] = start_parameter + + self._id_attrs = (self.text, self.web_app, self.start_parameter) + + self._freeze() + + @classmethod + def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["InlineQueryResultsButton"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + if not data: + return None + + data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot) + + return super().de_json(data=data, bot=bot) + + MIN_START_PARAMETER_LENGTH: ClassVar[ + int + ] = constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH + """:const:`telegram.constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH`""" + MAX_START_PARAMETER_LENGTH: ClassVar[ + int + ] = constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH + """:const:`telegram.constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH`""" diff --git a/telegram/constants.py b/telegram/constants.py index f623e7c5f4e..33fe2c758a6 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -58,6 +58,7 @@ "InlineKeyboardMarkupLimit", "InlineQueryLimit", "InlineQueryResultLimit", + "InlineQueryResultsButtonLimit", "InlineQueryResultType", "InputMediaType", "InvoiceLimit", @@ -751,11 +752,19 @@ class InlineQueryLimit(IntEnum): MIN_SWITCH_PM_TEXT_LENGTH = 1 """:obj:`int`: Minimum number of characters in a :obj:`str` passed as the :paramref:`~telegram.Bot.answer_inline_query.switch_pm_parameter` parameter of - :meth:`telegram.Bot.answer_inline_query`.""" + :meth:`telegram.Bot.answer_inline_query`. + + .. deprecated:: NEXT.VERSION + Deprecated in favor of :attr:`InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH`. + """ MAX_SWITCH_PM_TEXT_LENGTH = 64 """:obj:`int`: Maximum number of characters in a :obj:`str` passed as the :paramref:`~telegram.Bot.answer_inline_query.switch_pm_parameter` parameter of - :meth:`telegram.Bot.answer_inline_query`.""" + :meth:`telegram.Bot.answer_inline_query`. + + .. deprecated:: NEXT.VERSION + Deprecated in favor of :attr:`InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH`. + """ class InlineQueryResultLimit(IntEnum): @@ -779,6 +788,26 @@ class InlineQueryResultLimit(IntEnum): """ +class InlineQueryResultsButtonLimit(IntEnum): + """This enum contains limitations for :class:`telegram.InlineQueryResultsButton`. + The enum members of this enumeration are instances of :class:`int` and can be treated as such. + + .. versionadded:: NEXT.VERSION + """ + + __slots__ = () + + MIN_START_PARAMETER_LENGTH = InlineQueryLimit.MIN_SWITCH_PM_TEXT_LENGTH + """:obj:`int`: Minimum number of characters in a :obj:`str` passed as the + :paramref:`~telegram.InlineQueryResultsButton.start_parameter` parameter of + :meth:`telegram.InlineQueryResultsButton`.""" + + MAX_START_PARAMETER_LENGTH = InlineQueryLimit.MAX_SWITCH_PM_TEXT_LENGTH + """:obj:`int`: Maximum number of characters in a :obj:`str` passed as the + :paramref:`~telegram.InlineQueryResultsButton.start_parameter` parameter of + :meth:`telegram.InlineQueryResultsButton`.""" + + class InlineQueryResultType(StringEnum): """This enum contains the available types of :class:`telegram.InlineQueryResult`. The enum members of this enumeration are instances of :class:`str` and can be treated as such. diff --git a/tests/test_inlinequeryresultsbutton.py b/tests/test_inlinequeryresultsbutton.py new file mode 100644 index 00000000000..38b0f207f85 --- /dev/null +++ b/tests/test_inlinequeryresultsbutton.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2023 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +import pytest + +from telegram import InlineQueryResultsButton, WebAppInfo +from tests.auxil.slots import mro_slots + + +@pytest.fixture(scope="module") +def inline_query_results_button(): + return InlineQueryResultsButton( + text=TestInlineQueryResultsButtonBase.text, + start_parameter=TestInlineQueryResultsButtonBase.start_parameter, + web_app=TestInlineQueryResultsButtonBase.web_app, + ) + + +class TestInlineQueryResultsButtonBase: + text = "text" + start_parameter = "start_parameter" + web_app = WebAppInfo(url="https://python-telegram-bot.org") + + +class TestInlineQueryResultsButtonWithoutRequest(TestInlineQueryResultsButtonBase): + def test_slot_behaviour(self, inline_query_results_button): + inst = inline_query_results_button + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_to_dict(self, inline_query_results_button): + inline_query_results_button_dict = inline_query_results_button.to_dict() + assert isinstance(inline_query_results_button_dict, dict) + assert inline_query_results_button_dict["text"] == self.text + assert inline_query_results_button_dict["start_parameter"] == self.start_parameter + assert inline_query_results_button_dict["web_app"] == self.web_app.to_dict() + + def test_de_json(self, bot): + json_dict = { + "text": self.text, + "start_parameter": self.start_parameter, + "web_app": self.web_app.to_dict(), + } + inline_query_results_button = InlineQueryResultsButton.de_json(json_dict, bot) + + assert inline_query_results_button.text == self.text + assert inline_query_results_button.start_parameter == self.start_parameter + assert inline_query_results_button.web_app == self.web_app + + def test_equality(self): + a = InlineQueryResultsButton(self.text, self.start_parameter, self.web_app) + b = InlineQueryResultsButton(self.text, self.start_parameter, self.web_app) + c = InlineQueryResultsButton(self.text, "", self.web_app) + d = InlineQueryResultsButton(self.text, self.start_parameter, None) + + assert a == b + assert hash(a) == hash(b) + assert a is not b + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) From 269e088e4ecfb6d0c463d918e8286a2f9ef6853a Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 21 Apr 2023 17:49:39 +0200 Subject: [PATCH 06/28] Update `Bot.answer_inline_query` --- telegram/_bot.py | 44 +++++++++++++++++++++++-- telegram/ext/_extbot.py | 3 ++ tests/test_bot.py | 71 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 110 insertions(+), 8 deletions(-) diff --git a/telegram/_bot.py b/telegram/_bot.py index 66cd286c6e0..0cfb1265721 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -80,6 +80,7 @@ from telegram._forumtopic import ForumTopic from telegram._games.gamehighscore import GameHighScore from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup +from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton from telegram._menubutton import MenuButton from telegram._message import Message from telegram._messageid import MessageId @@ -2789,8 +2790,15 @@ async def answer_inline_query( cache_time: int = None, is_personal: bool = None, next_offset: str = None, + # Deprecated params since bot api 6.7 + # <---- switch_pm_text: str = None, switch_pm_parameter: str = None, + # ---> + # New params since bot api 6.7 + # <---- + button: InlineQueryResultsButton = None, + # ---> *, current_offset: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -2811,6 +2819,9 @@ async def answer_inline_query( .. seealso:: :wiki:`Working with Files and Media ` + .. |api6_7_depr| replace:: Since Bot API 6.7, this argument is deprecated in favour of + :paramref:`button`. + Args: inline_query_id (:obj:`str`): Unique identifier for the answered query. results (List[:class:`telegram.InlineQueryResult`] | Callable): A list of results for @@ -2831,12 +2842,22 @@ async def answer_inline_query( switch_pm_text (:obj:`str`, optional): If passed, clients will display a button with specified text that switches the user to a private chat with the bot and sends the bot a start message with the parameter :paramref:`switch_pm_parameter`. + + .. deprecated:: NEXT.VERSION + |api6_7_depr| switch_pm_parameter (:obj:`str`, optional): Deep-linking parameter for the :guilabel:`/start` message sent to the bot when user presses the switch button. :tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`- :tg-const:`telegram.InlineQuery.MAX_SWITCH_PM_TEXT_LENGTH` characters, only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed. + .. deprecated:: NEXT.VERSION + |api6_7_depr| + button (:class:`telegram.InlineQueryResultsButton`, optional): A button to be shown + above the inline query results. + + .. versionadded:: NEXT.VERISON + Keyword Args: current_offset (:obj:`str`, optional): The :attr:`telegram.InlineQuery.offset` of the inline query to answer. If passed, PTB will automatically take care of @@ -2850,6 +2871,26 @@ async def answer_inline_query( :class:`telegram.error.TelegramError` """ + if (switch_pm_text or switch_pm_parameter) and button: + raise TypeError( + "Since Bot API 6.7, the parameter `button is mutually exclusive to the deprecated " + "parameters `switch_pm_text` and `switch_pm_parameter`. Please use the new " + "parameter `button`." + ) + + if switch_pm_text and switch_pm_parameter: + warn( + "Since Bot API 6.7, the parameters `switch_pm_text` and `switch_pm_parameter` are " + "deprecated in favour of the new parameter `button`. Please use the new parameter " + "`button` instead.", + category=PTBDeprecationWarning, + stacklevel=4, + ) + button = InlineQueryResultsButton( + text=switch_pm_text, + start_parameter=switch_pm_parameter, + ) + effective_results, next_offset = self._effective_inline_results( results=results, next_offset=next_offset, current_offset=current_offset ) @@ -2865,8 +2906,7 @@ async def answer_inline_query( "next_offset": next_offset, "cache_time": cache_time, "is_personal": is_personal, - "switch_pm_text": switch_pm_text, - "switch_pm_parameter": switch_pm_parameter, + "button": button, } return await self._post( diff --git a/telegram/ext/_extbot.py b/telegram/ext/_extbot.py index 8bd30e74746..f9e9e8054f5 100644 --- a/telegram/ext/_extbot.py +++ b/telegram/ext/_extbot.py @@ -61,6 +61,7 @@ ForumTopic, GameHighScore, InlineKeyboardMarkup, + InlineQueryResultsButton, InputMedia, InputSticker, Location, @@ -785,6 +786,7 @@ async def answer_inline_query( next_offset: str = None, switch_pm_text: str = None, switch_pm_parameter: str = None, + button: InlineQueryResultsButton = None, *, current_offset: str = None, read_timeout: ODVInput[float] = DEFAULT_NONE, @@ -807,6 +809,7 @@ async def answer_inline_query( write_timeout=write_timeout, connect_timeout=connect_timeout, pool_timeout=pool_timeout, + button=button, 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 4aec9c8d78a..387425828a0 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -46,6 +46,7 @@ InlineKeyboardMarkup, InlineQueryResultArticle, InlineQueryResultDocument, + InlineQueryResultsButton, InlineQueryResultVoice, InputFile, InputMessageContent, @@ -78,6 +79,7 @@ from telegram.ext import ExtBot, InvalidCallbackData from telegram.helpers import escape_markdown from telegram.request import BaseRequest, HTTPXRequest, RequestData +from telegram.warnings import PTBDeprecationWarning 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 @@ -656,10 +658,11 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): ) # TODO: Needs improvement. We need incoming inline query to test answer. - async def test_answer_inline_query(self, monkeypatch, bot, raw_bot): + @pytest.mark.parametrize("button_type", ["start", "web_app", "backward_compat"]) + async def test_answer_inline_query(self, monkeypatch, bot, raw_bot, button_type): # For now just test that our internals pass the correct data async def make_assertion(url, request_data: RequestData, *args, **kwargs): - return request_data.parameters == { + expected = { "cache_time": 300, "results": [ { @@ -686,12 +689,22 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): }, ], "next_offset": "42", - "switch_pm_parameter": "start_pm", "inline_query_id": 1234, "is_personal": True, - "switch_pm_text": "switch pm", } + if button_type in ["start", "backward_compat"]: + button_dict = {"text": "button_text", "start_parameter": "start_parameter"} + else: + button_dict = { + "text": "button_text", + "web_app": {"url": "https://python-telegram-bot.org"}, + } + + expected["button"] = button_dict + + return request_data.parameters == expected + results = [ InlineQueryResultArticle("11", "first", InputTextMessageContent("first")), InlineQueryResultArticle("12", "second", InputMessageContentDWPP("second")), @@ -706,6 +719,17 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): ), ] + if button_type == "start": + button = InlineQueryResultsButton( + text="button_text", start_parameter="start_parameter" + ) + elif button_type == "web_app": + button = InlineQueryResultsButton( + text="button_text", web_app=WebAppInfo("https://python-telegram-bot.org") + ) + else: + button = None + copied_results = copy.copy(results) ext_bot = bot for bot in (ext_bot, raw_bot): @@ -718,8 +742,11 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): cache_time=300, is_personal=True, next_offset="42", - switch_pm_text="switch pm", - switch_pm_parameter="start_pm", + switch_pm_text="button_text" if button_type == "backward_compat" else None, + switch_pm_parameter="start_parameter" + if button_type == "backward_compat" + else None, + button=button, ) # 1) @@ -740,6 +767,38 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): monkeypatch.delattr(bot.request, "post") + async def test_answer_inline_query_deprecated_args(self, monkeypatch, bot, recwarn): + async def mock_post(*args, **kwargs): + return True + + monkeypatch.setattr(bot.request, "post", mock_post) + + with pytest.raises( + TypeError, match="6.7, the parameter `button is mutually exclusive to the deprecated" + ): + await bot.answer_inline_query( + inline_query_id="123", + results=[], + button=True, + switch_pm_text="text", + switch_pm_parameter="param", + ) + + recwarn.clear() + assert await bot.answer_inline_query( + inline_query_id="123", + results=[], + switch_pm_text="text", + switch_pm_parameter="parameter", + ) + assert len(recwarn) == 1 + assert recwarn[-1].category is PTBDeprecationWarning + assert str(recwarn[-1].message).startswith( + "Since Bot API 6.7, the parameters `switch_pm_text` and `switch_pm_parameter` are " + "deprecated" + ) + assert recwarn[-1].filename == __file__, "stacklevel is incorrect!" + async def test_answer_inline_query_no_default_parse_mode(self, monkeypatch, bot): async def make_assertion(url, request_data: RequestData, *args, **kwargs): return request_data.parameters == { From 3c974ba2eee17de1877c373fae3dfa413b1ec591 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 21 Apr 2023 22:06:53 +0200 Subject: [PATCH 07/28] Try fixing tests --- tests/test_bot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_bot.py b/tests/test_bot.py index 387425828a0..21bc034fae5 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -3405,3 +3405,6 @@ async def test_set_get_my_name(self, bot): assert await asyncio.gather( bot.get_my_name(), bot.get_my_name("en"), bot.get_my_name("de") ) == 3 * [BotName("")] + + # Reset to original name + assert await bot.set_my_name(bot.first_name) From 0c0bc834414f9bb5ea5a9f5f97f39443f9a2dab9 Mon Sep 17 00:00:00 2001 From: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com> Date: Fri, 21 Apr 2023 23:25:50 +0300 Subject: [PATCH 08/28] add `web_app_name` to `WriteAccessAllowed` --- telegram/_writeaccessallowed.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/telegram/_writeaccessallowed.py b/telegram/_writeaccessallowed.py index bba6ac88ba1..2c9f0101a3c 100644 --- a/telegram/_writeaccessallowed.py +++ b/telegram/_writeaccessallowed.py @@ -17,21 +17,37 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains objects related to the write access allowed service message.""" +from typing import Optional + from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict class WriteAccessAllowed(TelegramObject): """ - This object represents a service message about a user allowing a bot added to the attachment - menu to write messages. Currently holds no information. + This object represents a service message about a user allowing a bot to write messages after + adding the bot to the attachment menu or launching a Web App from a link. .. versionadded:: 20.0 + .. versionchanged:: NEXT.VERSION + Added the optional :attr:`web_app_name` attribute. + + Args: + web_app_name (:obj:`str`, optional): Name of the Web App which was launched from a link. + + .. versionadded:: NEXT.VERSION + + Attributes: + web_app_name (:obj:`str`): Optional. Name of the Web App which was launched from a link. + + .. versionadded:: NEXT.VERSION + """ - __slots__ = () + __slots__ = ("web_app_name",) - def __init__(self, *, api_kwargs: JSONDict = None): + def __init__(self, web_app_name: str = None, *, api_kwargs: JSONDict = None): super().__init__(api_kwargs=api_kwargs) + self.web_app_name: Optional[str] = web_app_name self._freeze() From 9c9cdc01cc6b3725d2ea5404172f9ba622c0f62f Mon Sep 17 00:00:00 2001 From: Harshil Mehta <37377066+harshil21@users.noreply.github.com> Date: Sat, 22 Apr 2023 02:28:53 +0530 Subject: [PATCH 09/28] Add & Integrate SwitchInlineQueryChosenChat --- docs/source/telegram.at-tree.rst | 1 + .../telegram.switchinlinequerychosenchat.rst | 6 ++ telegram/__init__.py | 2 + telegram/_inline/inlinekeyboardbutton.py | 44 +++++++++ telegram/_switchinlinequerychosenchat.py | 99 +++++++++++++++++++ tests/_inline/test_inlinekeyboardbutton.py | 23 ++++- tests/test_switchinlinequerychosenchat.py | 87 ++++++++++++++++ 7 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 docs/source/telegram.switchinlinequerychosenchat.rst create mode 100644 telegram/_switchinlinequerychosenchat.py create mode 100644 tests/test_switchinlinequerychosenchat.py diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index 37aee71e86b..3dc01c6d7e2 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -79,6 +79,7 @@ Available Types telegram.replykeyboardmarkup telegram.replykeyboardremove telegram.sentwebappmessage + telegram.switchinlinequerychosenchat telegram.telegramobject telegram.update telegram.user diff --git a/docs/source/telegram.switchinlinequerychosenchat.rst b/docs/source/telegram.switchinlinequerychosenchat.rst new file mode 100644 index 00000000000..603a56f6ad2 --- /dev/null +++ b/docs/source/telegram.switchinlinequerychosenchat.rst @@ -0,0 +1,6 @@ +SwitchInlineQueryChosenChat +=========================== + +.. autoclass:: telegram.SwitchInlineQueryChosenChat + :members: + :show-inheritance: diff --git a/telegram/__init__.py b/telegram/__init__.py index a6b1729f307..fb3dcadd6df 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -171,6 +171,7 @@ "Sticker", "StickerSet", "SuccessfulPayment", + "SwitchInlineQueryChosenChat", "TelegramObject", "Update", "User", @@ -340,6 +341,7 @@ from ._replykeyboardremove import ReplyKeyboardRemove from ._sentwebappmessage import SentWebAppMessage from ._shared import ChatShared, UserShared +from ._switchinlinequerychosenchat import SwitchInlineQueryChosenChat from ._telegramobject import TelegramObject from ._update import Update from ._user import User diff --git a/telegram/_inline/inlinekeyboardbutton.py b/telegram/_inline/inlinekeyboardbutton.py index 33759e7318e..fc209c4feb6 100644 --- a/telegram/_inline/inlinekeyboardbutton.py +++ b/telegram/_inline/inlinekeyboardbutton.py @@ -23,6 +23,7 @@ from telegram import constants from telegram._games.callbackgame import CallbackGame from telegram._loginurl import LoginUrl +from telegram._switchinlinequerychosenchat import SwitchInlineQueryChosenChat from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict from telegram._webappinfo import WebAppInfo @@ -111,6 +112,10 @@ class InlineKeyboardButton(TelegramObject): in inline mode when they are currently in a private chat with it. Especially useful when combined with ``switch_pm*`` actions - in this case the user will be automatically returned to the chat they switched from, skipping the chat selection screen. + + Tip: + This does the same as :paramref:`switch_inline_query_current_chat`, but for some + reason, Telegram has not deprecated this field. switch_inline_query_current_chat (:obj:`str`, optional): If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input field. Can be empty, in which case only the bot's username will be inserted. This @@ -122,6 +127,20 @@ class InlineKeyboardButton(TelegramObject): 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. + 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 + query in the input field. + + .. versionadded:: NEXT.VERSION + + Tip: + This does the same as :paramref:`switch_inline_query`, but gives more control on + which chats can be selected. + + Caution: + The PTB team has discovered that this field works correctly only if your Telegram + client is released after April 20th 2023. Attributes: text (:obj:`str`): Label text on the button. @@ -154,6 +173,10 @@ class InlineKeyboardButton(TelegramObject): in inline mode when they are currently in a private chat with it. Especially useful when combined with ``switch_pm*`` actions - in this case the user will be automatically returned to the chat they switched from, skipping the chat selection screen. + + Tip: + This does the same as :attr:`switch_inline_query_current_chat`, but for some + reason, Telegram has not deprecated this field. switch_inline_query_current_chat (:obj:`str`): Optional. If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input field. Can be empty, in which case only the bot's username will be inserted. This @@ -165,7 +188,20 @@ class InlineKeyboardButton(TelegramObject): 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. + 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 + query in the input field. + .. versionadded:: NEXT.VERSION + + Tip: + This does the same as :attr:`switch_inline_query`, but gives more control on + which chats can be selected. + + Caution: + The PTB team has discovered that this field works correctly only if your Telegram + client is released after April 20th 2023. """ __slots__ = ( @@ -178,6 +214,7 @@ class InlineKeyboardButton(TelegramObject): "text", "login_url", "web_app", + "switch_inline_query_chosen_chat", ) def __init__( @@ -191,6 +228,7 @@ def __init__( pay: bool = None, login_url: LoginUrl = None, web_app: WebAppInfo = None, + switch_inline_query_chosen_chat: SwitchInlineQueryChosenChat = None, *, api_kwargs: JSONDict = None, ): @@ -207,6 +245,9 @@ def __init__( self.callback_game: Optional[CallbackGame] = callback_game self.pay: Optional[bool] = pay self.web_app: Optional[WebAppInfo] = web_app + self.switch_inline_query_chosen_chat: Optional[ + SwitchInlineQueryChosenChat + ] = switch_inline_query_chosen_chat self._id_attrs = () self._set_id_attrs() @@ -236,6 +277,9 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["InlineKeyboa data["login_url"] = LoginUrl.de_json(data.get("login_url"), bot) data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot) data["callback_game"] = CallbackGame.de_json(data.get("callback_game"), bot) + data["switch_inline_query_chosen_chat"] = SwitchInlineQueryChosenChat.de_json( + data.get("switch_inline_query_chosen_chat"), bot + ) return super().de_json(data=data, bot=bot) diff --git a/telegram/_switchinlinequerychosenchat.py b/telegram/_switchinlinequerychosenchat.py new file mode 100644 index 00000000000..fececa88a45 --- /dev/null +++ b/telegram/_switchinlinequerychosenchat.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2023 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +"""This module contains a class that represents a Telegram SwitchInlineQueryChosenChat.""" + +from telegram._telegramobject import TelegramObject +from telegram._utils.types import JSONDict + + +class SwitchInlineQueryChosenChat(TelegramObject): + """ + This object represents an inline button that switches the current user to inline mode in a + chosen chat, with an optional default inline query. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`query`, :attr:`allow_user_chats`, :attr:`allow_bot_chats`, + :attr:`allow_group_chats` and :attr:`allow_channel_chats` are equal. + + .. versionadded:: NEXT.VERSION + + Caution: + The PTB team has discovered that you must pass at least one of + :paramref:`allow_user_chats`, :paramref:`allow_bot_chats`, :paramref:`allow_group_chats` or + :paramref:`allow_channel_chats` to Telegram. Otherwise, an error will be raised. + + Args: + query (:obj:`str`, optional): The default inline query to be inserted in the input field. + If left empty, only the bot's username will be inserted. + allow_user_chats (:obj:`bool`, optional): Pass :obj:`True`, if private chats with users + can be chosen. + allow_bot_chats (:obj:`bool`, optional): Pass :obj:`True`, if private chats with bots can + be chosen. + allow_group_chats (:obj:`bool`, optional): Pass :obj:`True`, if group and supergroup chats + can be chosen. + allow_channel_chats (:obj:`bool`, optional): Pass :obj:`True`, if channel chats can be + chosen. + + Attributes: + query (:obj:`str`): Optional. The default inline query to be inserted in the input field. + If left empty, only the bot's username will be inserted. + allow_user_chats (:obj:`bool`): Optional. :obj:`True`, if private chats with users can be + chosen. + allow_bot_chats (:obj:`bool`): Optional. :obj:`True`, if private chats with bots can be + chosen. + allow_group_chats (:obj:`bool`): Optional. :obj:`True`, if group and supergroup chats can + be chosen. + allow_channel_chats (:obj:`bool`): Optional. :obj:`True`, if channel chats can be chosen. + + """ + + __slots__ = ( + "query", + "allow_user_chats", + "allow_bot_chats", + "allow_group_chats", + "allow_channel_chats", + ) + + def __init__( + self, + query: str = None, + allow_user_chats: bool = None, + allow_bot_chats: bool = None, + allow_group_chats: bool = None, + allow_channel_chats: bool = None, + *, + api_kwargs: JSONDict = None, + ): + super().__init__(api_kwargs=api_kwargs) + # Optional + self.query = query + self.allow_user_chats = allow_user_chats + self.allow_bot_chats = allow_bot_chats + self.allow_group_chats = allow_group_chats + self.allow_channel_chats = allow_channel_chats + + self._id_attrs = ( + self.query, + self.allow_user_chats, + self.allow_bot_chats, + self.allow_group_chats, + self.allow_channel_chats, + ) + + self._freeze() diff --git a/tests/_inline/test_inlinekeyboardbutton.py b/tests/_inline/test_inlinekeyboardbutton.py index 29675504b81..15c7ef8bf5d 100644 --- a/tests/_inline/test_inlinekeyboardbutton.py +++ b/tests/_inline/test_inlinekeyboardbutton.py @@ -19,7 +19,13 @@ import pytest -from telegram import CallbackGame, InlineKeyboardButton, LoginUrl, WebAppInfo +from telegram import ( + CallbackGame, + InlineKeyboardButton, + LoginUrl, + SwitchInlineQueryChosenChat, + WebAppInfo, +) from tests.auxil.slots import mro_slots @@ -35,6 +41,7 @@ def inline_keyboard_button(): pay=TestInlineKeyboardButtonBase.pay, login_url=TestInlineKeyboardButtonBase.login_url, web_app=TestInlineKeyboardButtonBase.web_app, + switch_inline_query_chosen_chat=TestInlineKeyboardButtonBase.switch_inline_query_chosen_chat, # noqa: E501 ) @@ -48,6 +55,7 @@ class TestInlineKeyboardButtonBase: pay = True login_url = LoginUrl("http://google.com") web_app = WebAppInfo(url="https://example.com") + switch_inline_query_chosen_chat = SwitchInlineQueryChosenChat("a_bot", True, False, True, True) class TestInlineKeyboardButtonWithoutRequest(TestInlineKeyboardButtonBase): @@ -70,6 +78,10 @@ def test_expected_values(self, inline_keyboard_button): assert inline_keyboard_button.pay == self.pay assert inline_keyboard_button.login_url == self.login_url assert inline_keyboard_button.web_app == self.web_app + assert ( + inline_keyboard_button.switch_inline_query_chosen_chat + == self.switch_inline_query_chosen_chat + ) def test_to_dict(self, inline_keyboard_button): inline_keyboard_button_dict = inline_keyboard_button.to_dict() @@ -95,6 +107,10 @@ def test_to_dict(self, inline_keyboard_button): inline_keyboard_button_dict["login_url"] == inline_keyboard_button.login_url.to_dict() ) assert inline_keyboard_button_dict["web_app"] == inline_keyboard_button.web_app.to_dict() + assert ( + inline_keyboard_button_dict["switch_inline_query_chosen_chat"] + == inline_keyboard_button.switch_inline_query_chosen_chat.to_dict() + ) def test_de_json(self, bot): json_dict = { @@ -107,6 +123,7 @@ def test_de_json(self, bot): "web_app": self.web_app.to_dict(), "login_url": self.login_url.to_dict(), "pay": self.pay, + "switch_inline_query_chosen_chat": self.switch_inline_query_chosen_chat.to_dict(), } inline_keyboard_button = InlineKeyboardButton.de_json(json_dict, None) @@ -124,6 +141,10 @@ def test_de_json(self, bot): assert inline_keyboard_button.pay == self.pay assert inline_keyboard_button.login_url == self.login_url assert inline_keyboard_button.web_app == self.web_app + assert ( + inline_keyboard_button.switch_inline_query_chosen_chat + == self.switch_inline_query_chosen_chat + ) none = InlineKeyboardButton.de_json({}, bot) assert none is None diff --git a/tests/test_switchinlinequerychosenchat.py b/tests/test_switchinlinequerychosenchat.py new file mode 100644 index 00000000000..dc610e449dc --- /dev/null +++ b/tests/test_switchinlinequerychosenchat.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2023 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. + +import pytest + +from telegram import SwitchInlineQueryChosenChat +from tests.auxil.slots import mro_slots + + +@pytest.fixture(scope="module") +def switch_inline_query_chosen_chat(): + return SwitchInlineQueryChosenChat( + query=TestSwitchInlineQueryChosenChatBase.query, + allow_user_chats=TestSwitchInlineQueryChosenChatBase.allow_user_chats, + allow_bot_chats=TestSwitchInlineQueryChosenChatBase.allow_bot_chats, + allow_channel_chats=TestSwitchInlineQueryChosenChatBase.allow_channel_chats, + allow_group_chats=TestSwitchInlineQueryChosenChatBase.allow_group_chats, + ) + + +class TestSwitchInlineQueryChosenChatBase: + query = "query" + allow_user_chats = True + allow_bot_chats = True + allow_channel_chats = False + allow_group_chats = True + + +class TestSwitchInlineQueryChosenChat(TestSwitchInlineQueryChosenChatBase): + def test_slot_behaviour(self, switch_inline_query_chosen_chat): + inst = switch_inline_query_chosen_chat + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_expected_values(self, switch_inline_query_chosen_chat): + assert switch_inline_query_chosen_chat.query == self.query + assert switch_inline_query_chosen_chat.allow_user_chats == self.allow_user_chats + assert switch_inline_query_chosen_chat.allow_bot_chats == self.allow_bot_chats + assert switch_inline_query_chosen_chat.allow_channel_chats == self.allow_channel_chats + assert switch_inline_query_chosen_chat.allow_group_chats == self.allow_group_chats + + def test_to_dict(self, switch_inline_query_chosen_chat): + siqcc = switch_inline_query_chosen_chat.to_dict() + + assert isinstance(siqcc, dict) + assert siqcc["query"] == switch_inline_query_chosen_chat.query + assert siqcc["allow_user_chats"] == switch_inline_query_chosen_chat.allow_user_chats + assert siqcc["allow_bot_chats"] == switch_inline_query_chosen_chat.allow_bot_chats + assert siqcc["allow_channel_chats"] == switch_inline_query_chosen_chat.allow_channel_chats + assert siqcc["allow_group_chats"] == switch_inline_query_chosen_chat.allow_group_chats + + def test_equality(self): + siqcc = SwitchInlineQueryChosenChat + a = siqcc(self.query, self.allow_user_chats, self.allow_bot_chats) + b = siqcc(self.query, self.allow_user_chats, self.allow_bot_chats) + c = siqcc(self.query, self.allow_user_chats) + d = siqcc("", self.allow_user_chats, self.allow_bot_chats) + e = siqcc(self.query, self.allow_user_chats, self.allow_bot_chats, self.allow_group_chats) + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) From a1d1369830ba2b3892e801c92561151b0894694b Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 22 Apr 2023 02:52:19 +0530 Subject: [PATCH 10/28] Review: Correct docstring in IKB --- telegram/_inline/inlinekeyboardbutton.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/telegram/_inline/inlinekeyboardbutton.py b/telegram/_inline/inlinekeyboardbutton.py index fc209c4feb6..877a63a9467 100644 --- a/telegram/_inline/inlinekeyboardbutton.py +++ b/telegram/_inline/inlinekeyboardbutton.py @@ -114,7 +114,7 @@ class InlineKeyboardButton(TelegramObject): returned to the chat they switched from, skipping the chat selection screen. Tip: - This does the same as :paramref:`switch_inline_query_current_chat`, but for some + This does the same as :paramref:`switch_inline_query_chosen_chat`, but for some reason, Telegram has not deprecated this field. switch_inline_query_current_chat (:obj:`str`, optional): If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input @@ -135,7 +135,7 @@ class InlineKeyboardButton(TelegramObject): .. versionadded:: NEXT.VERSION Tip: - This does the same as :paramref:`switch_inline_query`, but gives more control on + This is similar to :paramref:`switch_inline_query`, but gives more control on which chats can be selected. Caution: @@ -175,7 +175,7 @@ class InlineKeyboardButton(TelegramObject): returned to the chat they switched from, skipping the chat selection screen. Tip: - This does the same as :attr:`switch_inline_query_current_chat`, but for some + This does the same as :attr:`switch_inline_query_chosen_chat`, but for some reason, Telegram has not deprecated this field. switch_inline_query_current_chat (:obj:`str`): Optional. If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input @@ -196,7 +196,7 @@ class InlineKeyboardButton(TelegramObject): .. versionadded:: NEXT.VERSION Tip: - This does the same as :attr:`switch_inline_query`, but gives more control on + This is similar to :attr:`switch_inline_query`, but gives more control on which chats can be selected. Caution: From 3484dcfa85c051cd25e15b79efa628a43ae0ac9b Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Sat, 22 Apr 2023 02:55:51 +0530 Subject: [PATCH 11/28] Correct again --- telegram/_inline/inlinekeyboardbutton.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telegram/_inline/inlinekeyboardbutton.py b/telegram/_inline/inlinekeyboardbutton.py index 877a63a9467..7592eace752 100644 --- a/telegram/_inline/inlinekeyboardbutton.py +++ b/telegram/_inline/inlinekeyboardbutton.py @@ -114,8 +114,8 @@ class InlineKeyboardButton(TelegramObject): returned to the chat they switched from, skipping the chat selection screen. Tip: - This does the same as :paramref:`switch_inline_query_chosen_chat`, but for some - reason, Telegram has not deprecated this field. + This is similar to the new parameter :paramref:`switch_inline_query_chosen_chat`, + but for some reason, Telegram has not deprecated this parameter. switch_inline_query_current_chat (:obj:`str`, optional): If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input field. Can be empty, in which case only the bot's username will be inserted. This From 7e6818ea2d4f69bca00dde6f35fd9f9eef755689 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sat, 22 Apr 2023 18:13:56 +0200 Subject: [PATCH 12/28] Update Docs --- telegram/_inline/inlinequeryresultsbutton.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/telegram/_inline/inlinequeryresultsbutton.py b/telegram/_inline/inlinequeryresultsbutton.py index 8fa6dad05fa..088de7cc6aa 100644 --- a/telegram/_inline/inlinequeryresultsbutton.py +++ b/telegram/_inline/inlinequeryresultsbutton.py @@ -42,7 +42,9 @@ class InlineQueryResultsButton(TelegramObject): web_app (:class:`telegram.WebAppInfo`, optional): Description of the `Web App `_ that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode - using the method ``web_app_switch_inline_query`` inside the Web App. + using the method + `switchInlineQuery `_ + inside the Web App. start_parameter (:obj:`str`, optional): Deep-linking parameter for the :guilabel:`/start` message sent to the bot when user presses the switch button. :tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`- From bd73413c78c6de553241ad1f43ab829eed47fb05 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sat, 22 Apr 2023 18:21:23 +0200 Subject: [PATCH 13/28] Update Docs --- telegram/_writeaccessallowed.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telegram/_writeaccessallowed.py b/telegram/_writeaccessallowed.py index 2c9f0101a3c..a3270c2efa7 100644 --- a/telegram/_writeaccessallowed.py +++ b/telegram/_writeaccessallowed.py @@ -35,12 +35,12 @@ class WriteAccessAllowed(TelegramObject): Args: web_app_name (:obj:`str`, optional): Name of the Web App which was launched from a link. - .. versionadded:: NEXT.VERSION + .. versionadded:: NEXT.VERSION Attributes: web_app_name (:obj:`str`): Optional. Name of the Web App which was launched from a link. - .. versionadded:: NEXT.VERSION + .. versionadded:: NEXT.VERSION """ From b8ec32f59bb11b68d17009ca623a3a148d40a3b8 Mon Sep 17 00:00:00 2001 From: poolitzer Date: Sat, 22 Apr 2023 21:02:25 +0200 Subject: [PATCH 14/28] Feat: Support for custom emojis --- telegram/_message.py | 69 +++++++++++++-------- telegram/helpers.py | 13 ++-- tests/test_helpers.py | 8 ++- tests/test_message.py | 140 ++++++++++++++++++++++++++++++++++++------ 4 files changed, 180 insertions(+), 50 deletions(-) diff --git a/telegram/_message.py b/telegram/_message.py index 8a962f74bc5..230c7a6db1d 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -578,8 +578,8 @@ class Message(TelegramObject): .. versionadded:: 20.1 - .. |custom_emoji_formatting_note| replace:: Custom emoji entities will currently be ignored - by this function. Instead, the supplied replacement for the emoji will be used. + .. |custom_emoji_formatting_note| replace:: Custom emoji entities will be ignored by this + function. Instead, the supplied replacement for the emoji will be used. """ # fmt: on @@ -3317,6 +3317,10 @@ def _parse_html( insert = f"{escaped_text}" elif entity.type == MessageEntity.SPOILER: insert = f'{escaped_text}' + elif entity.type == MessageEntity.CUSTOM_EMOJI: + insert = ( + f'{escaped_text}' + ) else: insert = escaped_text @@ -3355,12 +3359,12 @@ def text_html(self) -> str: Use this if you want to retrieve the message text with the entities formatted as HTML in the same way the original message was formatted. - Note: - |custom_emoji_formatting_note| - .. versionchanged:: 13.10 Spoiler entities are now formatted as HTML. + .. versionchanged:: NEXT.VERSION + Custom emoji entities are now supported. + Returns: :obj:`str`: Message text with entities formatted as HTML. @@ -3374,12 +3378,12 @@ def text_html_urled(self) -> str: Use this if you want to retrieve the message text with the entities formatted as HTML. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. - Note: - |custom_emoji_formatting_note| - .. versionchanged:: 13.10 Spoiler entities are now formatted as HTML. + .. versionchanged:: NEXT.VERSION + Custom emoji entities are now supported. + Returns: :obj:`str`: Message text with entities formatted as HTML. @@ -3394,12 +3398,12 @@ def caption_html(self) -> str: Use this if you want to retrieve the message caption with the caption entities formatted as HTML in the same way the original message was formatted. - Note: - |custom_emoji_formatting_note| - .. versionchanged:: 13.10 Spoiler entities are now formatted as HTML. + .. versionchanged:: NEXT.VERSION + Custom emoji entities are now supported. + Returns: :obj:`str`: Message caption with caption entities formatted as HTML. """ @@ -3413,12 +3417,12 @@ def caption_html_urled(self) -> str: Use this if you want to retrieve the message caption with the caption entities formatted as HTML. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. - Note: - |custom_emoji_formatting_note| - .. versionchanged:: 13.10 Spoiler entities are now formatted as HTML. + .. versionchanged:: NEXT.VERSION + Custom emoji entities are now supported. + Returns: :obj:`str`: Message caption with caption entities formatted as HTML. """ @@ -3522,6 +3526,19 @@ def _parse_markdown( "Spoiler entities are not supported for Markdown version 1" ) insert = f"||{escaped_text}||" + elif entity.type == MessageEntity.CUSTOM_EMOJI: + if version == 1: + # this ensures compatibility to previous PTB versions + insert = escaped_text + else: + # This should never be needed because ids are numeric but the documentation + # specifically mentions it so here we are + custom_emoji_id = escape_markdown( + entity.custom_emoji_id, + version=version, + entity_type=MessageEntity.CUSTOM_EMOJI, + ) + insert = f"![{escaped_text}](tg://emoji?id={custom_emoji_id})" else: insert = escaped_text @@ -3588,12 +3605,12 @@ def text_markdown_v2(self) -> str: Use this if you want to retrieve the message text with the entities formatted as Markdown in the same way the original message was formatted. - Note: - |custom_emoji_formatting_note| - .. versionchanged:: 13.10 Spoiler entities are now formatted as Markdown V2. + .. versionchanged:: NEXT.VERSION + Custom emoji entities are now supported. + Returns: :obj:`str`: Message text with entities formatted as Markdown. """ @@ -3632,12 +3649,12 @@ def text_markdown_v2_urled(self) -> str: Use this if you want to retrieve the message text with the entities formatted as Markdown. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. - Note: - |custom_emoji_formatting_note| - .. versionchanged:: 13.10 Spoiler entities are now formatted as Markdown V2. + .. versionchanged:: NEXT.VERSION + Custom emoji entities are now supported. + Returns: :obj:`str`: Message text with entities formatted as Markdown. """ @@ -3676,12 +3693,12 @@ def caption_markdown_v2(self) -> str: Use this if you want to retrieve the message caption with the caption entities formatted as Markdown in the same way the original message was formatted. - Note: - |custom_emoji_formatting_note| - .. versionchanged:: 13.10 Spoiler entities are now formatted as Markdown V2. + .. versionchanged:: NEXT.VERSION + Custom emoji entities are now supported. + Returns: :obj:`str`: Message caption with caption entities formatted as Markdown. """ @@ -3722,12 +3739,12 @@ def caption_markdown_v2_urled(self) -> str: Use this if you want to retrieve the message caption with the caption entities formatted as Markdown. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink. - Note: - |custom_emoji_formatting_note| - .. versionchanged:: 13.10 Spoiler entities are now formatted as Markdown V2. + .. versionchanged:: NEXT.VERSION + Custom emoji entities are now supported. + Returns: :obj:`str`: Message caption with caption entities formatted as Markdown. """ diff --git a/telegram/helpers.py b/telegram/helpers.py index 632cd364742..b30023586cd 100644 --- a/telegram/helpers.py +++ b/telegram/helpers.py @@ -44,23 +44,28 @@ def escape_markdown(text: str, version: int = 1, entity_type: str = None) -> str: """Helper function to escape telegram markup symbols. + .. versionchanged:: NEXT.VERSION + Custom emoji entity escaping is now supported. + Args: text (:obj:`str`): The text. version (:obj:`int` | :obj:`str`): Use to specify the version of telegrams Markdown. Either ``1`` or ``2``. Defaults to ``1``. entity_type (:obj:`str`, optional): For the entity types :tg-const:`telegram.MessageEntity.PRE`, :tg-const:`telegram.MessageEntity.CODE` and - the link part of :tg-const:`telegram.MessageEntity.TEXT_LINK`, only certain characters + the link part of :tg-const:`telegram.MessageEntity.TEXT_LINK` and + :tg-const:`telegram.MessageEntity.CUSTOM_EMOJI`, only certain characters need to be escaped in :tg-const:`telegram.constants.ParseMode.MARKDOWN_V2`. - See the official API documentation for details. Only valid in combination with - ``version=2``, will be ignored else. + See the `official API documentation `__ for details. Only valid in + combination with ``version=2``, will be ignored else. """ if int(version) == 1: escape_chars = r"_*`[" elif int(version) == 2: if entity_type in ["pre", "code"]: escape_chars = r"\`" - elif entity_type == "text_link": + elif entity_type in ["text_link", "custom_emoji"]: escape_chars = r"\)" else: escape_chars = r"\_*[]()~`>#+-=|{}.!" diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 8e8da7f3636..9ac642a2276 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -32,8 +32,9 @@ class TestHelpers: ("_italic_", r"\_italic\_"), ("`code`", r"\`code\`"), ("[text_link](https://github.com/)", r"\[text\_link](https://github.com/)"), + ("![👍](tg://emoji?id=1)", r"!\[👍](tg://emoji?id=1)"), ], - ids=["bold", "italic", "code", "text_link"], + ids=["bold", "italic", "code", "text_link", "custom_emoji_id"], ) def test_escape_markdown(self, test_str, expected): assert expected == helpers.escape_markdown(test_str) @@ -68,13 +69,16 @@ def test_escape_markdown_v2_monospaced(self, test_str, expected): test_str, version=2, entity_type=MessageEntity.CODE ) - def test_escape_markdown_v2_text_link(self): + def test_escape_markdown_v2_links(self): test_str = "https://url.containing/funny)cha)\\ra\\)cter\\s" expected_str = "https://url.containing/funny\\)cha\\)\\\\ra\\\\\\)cter\\\\s" assert expected_str == helpers.escape_markdown( test_str, version=2, entity_type=MessageEntity.TEXT_LINK ) + assert expected_str == helpers.escape_markdown( + test_str, version=2, entity_type=MessageEntity.CUSTOM_EMOJI + ) def test_markdown_invalid_version(self): with pytest.raises(ValueError, match="Markdown version must be either"): diff --git a/tests/test_message.py b/tests/test_message.py index a47c085de18..06217dc70a6 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -322,10 +322,11 @@ class TestMessageBase: {"length": 9, "offset": 101, "type": "strikethrough"}, {"length": 10, "offset": 129, "type": "pre", "language": "python"}, {"length": 7, "offset": 141, "type": "spoiler"}, + {"length": 2, "offset": 150, "type": "custom_emoji", "custom_emoji_id": "1"}, ] test_text_v2 = ( r"Test for trgh nested in italic. Python pre. Spoiled." + "http://google.com and bold nested in strk>trgh nested in italic. Python pre. Spoiled. 👍." ) test_message = Message( message_id=1, @@ -513,7 +514,8 @@ def test_text_html_simple(self): r"
`\pre
. http://google.com " "and bold nested in strk>trgh nested in italic. " '
Python pre
. ' - 'Spoiled.' + 'Spoiled. ' + '👍.' ) text_html = self.test_message_v2.text_html assert text_html == test_html_string @@ -532,7 +534,8 @@ def test_text_html_urled(self): r'
`\pre
. http://google.com ' "and bold nested in strk>trgh nested in italic. " '
Python pre
. ' - 'Spoiled.' + 'Spoiled. ' + '👍.' ) text_html = self.test_message_v2.text_html_urled assert text_html == test_html_string @@ -553,7 +556,7 @@ def test_text_markdown_v2_simple(self): "[links](http://github.com/abc\\\\\\)def), " "[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. " r"http://google\.com and _bold *nested in ~strk\>trgh~ nested in* italic_\. " - "```python\nPython pre```\\. ||Spoiled||\\." + "```python\nPython pre```\\. ||Spoiled||\\. ![👍](tg://emoji?id=1)\\." ) text_markdown = self.test_message_v2.text_markdown_v2 assert text_markdown == test_md_string @@ -603,7 +606,8 @@ def test_text_markdown_v2_urled(self): "[links](http://github.com/abc\\\\\\)def), " "[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. " r"[http://google\.com](http://google.com) and _bold *nested in ~strk\>trgh~ " - "nested in* italic_\\. ```python\nPython pre```\\. ||Spoiled||\\." + "nested in* italic_\\. ```python\nPython pre```\\. ||Spoiled||\\. " + "![👍](tg://emoji?id=1)\\." ) text_markdown = self.test_message_v2.text_markdown_v2_urled assert text_markdown == test_md_string @@ -634,17 +638,65 @@ def test_text_markdown_emoji(self): @pytest.mark.parametrize( "type_", argvalues=[ - "text_html", - "text_html_urled", "text_markdown", "text_markdown_urled", + ], + ) + def test_text_custom_emoji_md_v1(self, type_): + text = "Look a custom emoji: 😎" + expected = "Look a custom emoji: 😎" + emoji_entity = MessageEntity( + type=MessageEntity.CUSTOM_EMOJI, + offset=21, + length=2, + custom_emoji_id="5472409228461217725", + ) + message = Message( + 1, + from_user=self.from_user, + date=self.date, + chat=self.chat, + text=text, + entities=[emoji_entity], + ) + assert expected == message[type_] + + @pytest.mark.parametrize( + "type_", + argvalues=[ "text_markdown_v2", "text_markdown_v2_urled", ], ) + def test_text_custom_emoji_md_v2(self, type_): + text = "Look a custom emoji: 😎" + expected = "Look a custom emoji: ![😎](tg://emoji?id=5472409228461217725)" + emoji_entity = MessageEntity( + type=MessageEntity.CUSTOM_EMOJI, + offset=21, + length=2, + custom_emoji_id="5472409228461217725", + ) + message = Message( + 1, + from_user=self.from_user, + date=self.date, + chat=self.chat, + text=text, + entities=[emoji_entity], + ) + assert expected == message[type_] + + @pytest.mark.parametrize( + "type_", + argvalues=[ + "text_html", + "text_html_urled", + ], + ) def test_text_custom_emoji(self, type_): text = "Look a custom emoji: 😎" - expected = "Look a custom emoji: 😎" + expected = 'Look a custom emoji: 😎' emoji_entity = MessageEntity( type=MessageEntity.CUSTOM_EMOJI, offset=21, @@ -670,7 +722,8 @@ def test_caption_html_simple(self): r"
`\pre
. http://google.com " "and bold nested in strk>trgh nested in italic. " '
Python pre
. ' - 'Spoiled.' + 'Spoiled. ' + '👍.' ) caption_html = self.test_message_v2.caption_html assert caption_html == test_html_string @@ -689,7 +742,8 @@ def test_caption_html_urled(self): r'
`\pre
. http://google.com ' "and bold nested in strk>trgh nested in italic. " '
Python pre
. ' - 'Spoiled.' + 'Spoiled. ' + '👍.' ) caption_html = self.test_message_v2.caption_html_urled assert caption_html == test_html_string @@ -710,7 +764,7 @@ def test_caption_markdown_v2_simple(self): "[links](http://github.com/abc\\\\\\)def), " "[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. " r"http://google\.com and _bold *nested in ~strk\>trgh~ nested in* italic_\. " - "```python\nPython pre```\\. ||Spoiled||\\." + "```python\nPython pre```\\. ||Spoiled||\\. ![👍](tg://emoji?id=1)\\." ) caption_markdown = self.test_message_v2.caption_markdown_v2 assert caption_markdown == test_md_string @@ -737,7 +791,8 @@ def test_caption_markdown_v2_urled(self): "[links](http://github.com/abc\\\\\\)def), " "[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. " r"[http://google\.com](http://google.com) and _bold *nested in ~strk\>trgh~ " - "nested in* italic_\\. ```python\nPython pre```\\. ||Spoiled||\\." + "nested in* italic_\\. ```python\nPython pre```\\. ||Spoiled||\\. " + "![👍](tg://emoji?id=1)\\." ) caption_markdown = self.test_message_v2.caption_markdown_v2_urled assert caption_markdown == test_md_string @@ -773,17 +828,65 @@ def test_caption_markdown_emoji(self): @pytest.mark.parametrize( "type_", argvalues=[ - "caption_html", - "caption_html_urled", "caption_markdown", "caption_markdown_urled", + ], + ) + def test_caption_custom_emoji_md_v1(self, type_): + caption = "Look a custom emoji: 😎" + expected = "Look a custom emoji: 😎" + emoji_entity = MessageEntity( + type=MessageEntity.CUSTOM_EMOJI, + offset=21, + length=2, + custom_emoji_id="5472409228461217725", + ) + message = Message( + 1, + from_user=self.from_user, + date=self.date, + chat=self.chat, + caption=caption, + caption_entities=[emoji_entity], + ) + assert expected == message[type_] + + @pytest.mark.parametrize( + "type_", + argvalues=[ "caption_markdown_v2", "caption_markdown_v2_urled", ], ) - def test_caption_custom_emoji(self, type_): + def test_caption_custom_emoji_md_v2(self, type_): caption = "Look a custom emoji: 😎" - expected = "Look a custom emoji: 😎" + expected = "Look a custom emoji: ![😎](tg://emoji?id=5472409228461217725)" + emoji_entity = MessageEntity( + type=MessageEntity.CUSTOM_EMOJI, + offset=21, + length=2, + custom_emoji_id="5472409228461217725", + ) + message = Message( + 1, + from_user=self.from_user, + date=self.date, + chat=self.chat, + caption=caption, + caption_entities=[emoji_entity], + ) + assert expected == message[type_] + + @pytest.mark.parametrize( + "type_", + argvalues=[ + "caption_html", + "caption_html_urled", + ], + ) + def test_caption_custom_emoji_html(self, type_): + caption = "Look a custom emoji: 😎" + expected = 'Look a custom emoji: 😎' emoji_entity = MessageEntity( type=MessageEntity.CUSTOM_EMOJI, offset=21, @@ -955,7 +1058,7 @@ async def test_reply_markdown_v2(self, monkeypatch, message): "[links](http://github.com/abc\\\\\\)def), " "[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. " r"http://google\.com and _bold *nested in ~strk\>trgh~ nested in* italic_\. " - "```python\nPython pre```\\. ||Spoiled||\\." + "```python\nPython pre```\\. ||Spoiled||\\. ![👍](tg://emoji?id=1)\\." ) async def make_assertion(*_, **kwargs): @@ -995,7 +1098,8 @@ async def test_reply_html(self, monkeypatch, message): r"
`\pre
. http://google.com " "and bold nested in strk>trgh nested in italic. " '
Python pre
. ' - 'Spoiled.' + 'Spoiled. ' + '👍.' ) async def make_assertion(*_, **kwargs): From 28127b0c590549bfb0d35e66027e9851d845f45a Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 23 Apr 2023 06:05:01 +0200 Subject: [PATCH 15/28] Fix some tests properly and the name tests temporarily --- telegram/_inline/inlinequery.py | 3 +++ tests/test_bot.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/telegram/_inline/inlinequery.py b/telegram/_inline/inlinequery.py index 379dfb4be6c..ae5abe23847 100644 --- a/telegram/_inline/inlinequery.py +++ b/telegram/_inline/inlinequery.py @@ -23,6 +23,7 @@ from telegram import constants from telegram._files.location import Location +from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton from telegram._telegramobject import TelegramObject from telegram._user import User from telegram._utils.defaultvalue import DEFAULT_NONE @@ -146,6 +147,7 @@ async def answer( next_offset: str = None, switch_pm_text: str = None, switch_pm_parameter: str = None, + button: InlineQueryResultsButton = None, *, current_offset: str = None, auto_pagination: bool = False, @@ -192,6 +194,7 @@ async def answer( next_offset=next_offset, switch_pm_text=switch_pm_text, switch_pm_parameter=switch_pm_parameter, + button=button, read_timeout=read_timeout, write_timeout=write_timeout, connect_timeout=connect_timeout, diff --git a/tests/test_bot.py b/tests/test_bot.py index 21bc034fae5..b11ce4e60ba 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -348,6 +348,9 @@ async def test_get_me_and_properties_not_initialized(self, bot: Bot, attribute): await bot.shutdown() async def test_get_me_and_properties(self, bot): + if dtm.date.today() < dtm.date(2023, 4, 25): + pytest.xfail("Depending on skipped test `test_set_get_my_name`") + get_me_bot = await ExtBot(bot.token).get_me() assert isinstance(get_me_bot, User) @@ -829,10 +832,8 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): }, ], "next_offset": "42", - "switch_pm_parameter": "start_pm", "inline_query_id": 1234, "is_personal": True, - "switch_pm_text": "switch pm", } monkeypatch.setattr(bot.request, "post", make_assertion) @@ -857,8 +858,6 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): cache_time=300, is_personal=True, next_offset="42", - switch_pm_text="switch pm", - switch_pm_parameter="start_pm", ) # make sure that the results were not edited in-place assert results == copied_results @@ -922,10 +921,8 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): }, ], "next_offset": "42", - "switch_pm_parameter": "start_pm", "inline_query_id": 1234, "is_personal": True, - "switch_pm_text": "switch pm", } monkeypatch.setattr(default_bot.request, "post", make_assertion) @@ -950,8 +947,6 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): cache_time=300, is_personal=True, next_offset="42", - switch_pm_text="switch pm", - switch_pm_parameter="start_pm", ) # make sure that the results were not edited in-place assert results == copied_results @@ -3369,6 +3364,11 @@ async def test_set_get_my_short_description(self, bot): bot.get_my_short_description("de"), ) == 3 * [BotShortDescription("")] + # TODO: Remove this once the 24h flood limit is fixed + @pytest.mark.skipif( + dtm.date.today() < dtm.date(2023, 4, 25), + reason="Skipping b/c of 24h flood limit. Waiting for that once will hopefully fix the CI.", + ) async def test_set_get_my_name(self, bot): default_name = uuid4().hex en_name = uuid4().hex From 6d7ec486bcd8f519101d033cdecbd197e56152ae Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 23 Apr 2023 06:09:05 +0200 Subject: [PATCH 16/28] Update `test_official` --- tests/test_official.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_official.py b/tests/test_official.py index 3e829f11b7c..a3cba293aa9 100644 --- a/tests/test_official.py +++ b/tests/test_official.py @@ -154,6 +154,7 @@ def ignored_param_requirements(object_name) -> Set[str]: "thumb_url", }, "InlineQueryResult(Game|Gif|Mpeg4Gif)": {"thumb_mime_type"}, + "answer_inline_query": {"switch_pm_text", "switch_pm_parameter"}, } From 5433627218096f14f0acc7610212845701b361f7 Mon Sep 17 00:00:00 2001 From: Poolitzer Date: Sun, 23 Apr 2023 09:58:01 +0200 Subject: [PATCH 17/28] Fix: Rename test Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> --- tests/test_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_message.py b/tests/test_message.py index 06217dc70a6..6030e5d9a2c 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -694,7 +694,7 @@ def test_text_custom_emoji_md_v2(self, type_): "text_html_urled", ], ) - def test_text_custom_emoji(self, type_): + def test_text_custom_emoji_html(self, type_): text = "Look a custom emoji: 😎" expected = 'Look a custom emoji: 😎' emoji_entity = MessageEntity( From 15040196398cc0aafbd7c0d4ec0ae46b49bad315 Mon Sep 17 00:00:00 2001 From: poolitzer Date: Sun, 23 Apr 2023 09:58:27 +0200 Subject: [PATCH 18/28] Fix: Improve docstring --- telegram/helpers.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/telegram/helpers.py b/telegram/helpers.py index b30023586cd..9bf8ba7309b 100644 --- a/telegram/helpers.py +++ b/telegram/helpers.py @@ -54,11 +54,10 @@ def escape_markdown(text: str, version: int = 1, entity_type: str = None) -> str entity_type (:obj:`str`, optional): For the entity types :tg-const:`telegram.MessageEntity.PRE`, :tg-const:`telegram.MessageEntity.CODE` and the link part of :tg-const:`telegram.MessageEntity.TEXT_LINK` and - :tg-const:`telegram.MessageEntity.CUSTOM_EMOJI`, only certain characters - need to be escaped in :tg-const:`telegram.constants.ParseMode.MARKDOWN_V2`. - See the `official API documentation `__ for details. Only valid in - combination with ``version=2``, will be ignored else. + :tg-const:`telegram.MessageEntity.CUSTOM_EMOJI`, only certain characters need to be + escaped in :tg-const:`telegram.constants.ParseMode.MARKDOWN_V2`. See the `official API + documentation `_ for details. + Only valid in combination with ``version=2``, will be ignored else. """ if int(version) == 1: escape_chars = r"_*`[" From 21357819df9b16ba487e1e11c8fdc47bf350ac44 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sun, 23 Apr 2023 11:22:58 +0200 Subject: [PATCH 19/28] Increase coverage --- tests/test_inlinequeryresultsbutton.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_inlinequeryresultsbutton.py b/tests/test_inlinequeryresultsbutton.py index 38b0f207f85..db87326c556 100644 --- a/tests/test_inlinequeryresultsbutton.py +++ b/tests/test_inlinequeryresultsbutton.py @@ -52,6 +52,9 @@ def test_to_dict(self, inline_query_results_button): assert inline_query_results_button_dict["web_app"] == self.web_app.to_dict() def test_de_json(self, bot): + assert InlineQueryResultsButton.de_json(None, bot) is None + assert InlineQueryResultsButton.de_json({}, bot) is None + json_dict = { "text": self.text, "start_parameter": self.start_parameter, From 8b93d6936a319ccfd53ab1c0f2288f1a72577856 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Mon, 24 Apr 2023 23:18:38 +0530 Subject: [PATCH 20/28] Review: Refine switch_inline_query tip --- telegram/_inline/inlinekeyboardbutton.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram/_inline/inlinekeyboardbutton.py b/telegram/_inline/inlinekeyboardbutton.py index 7592eace752..91613b35823 100644 --- a/telegram/_inline/inlinekeyboardbutton.py +++ b/telegram/_inline/inlinekeyboardbutton.py @@ -115,7 +115,7 @@ class InlineKeyboardButton(TelegramObject): Tip: This is similar to the new parameter :paramref:`switch_inline_query_chosen_chat`, - but for some reason, Telegram has not deprecated this parameter. + but gives no control over which chats can be selected. switch_inline_query_current_chat (:obj:`str`, optional): If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input field. Can be empty, in which case only the bot's username will be inserted. This From cb038d9a768c3b34434a834546352a7929b4be21 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Mon, 24 Apr 2023 21:52:40 +0200 Subject: [PATCH 21/28] Deprecate custom emoji replacement in MD v1 --- telegram/_message.py | 26 ++++++++++++++++++++++++++ tests/test_message.py | 23 +++++++++++++++++++---- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/telegram/_message.py b/telegram/_message.py index 230c7a6db1d..bece9123664 100644 --- a/telegram/_message.py +++ b/telegram/_message.py @@ -59,6 +59,7 @@ from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup +from telegram._utils.warnings import warn from telegram._videochat import ( VideoChatEnded, VideoChatParticipantsInvited, @@ -69,6 +70,7 @@ from telegram._writeaccessallowed import WriteAccessAllowed from telegram.constants import MessageAttachmentType, ParseMode from telegram.helpers import escape_markdown +from telegram.warnings import PTBDeprecationWarning if TYPE_CHECKING: from telegram import ( @@ -580,6 +582,11 @@ class Message(TelegramObject): .. |custom_emoji_formatting_note| replace:: Custom emoji entities will be ignored by this function. Instead, the supplied replacement for the emoji will be used. + + .. |custom_emoji_md1_deprecation| replace:: Since custom emoji entities are not supported by + :attr:`~telegram.constants.ParseMode.MARKDOWN`, this method will raise a + :exc:`ValueError` in future versions instead of falling back to the supplied replacement + for the emoji. """ # fmt: on @@ -3530,6 +3537,13 @@ def _parse_markdown( if version == 1: # this ensures compatibility to previous PTB versions insert = escaped_text + warn( + "Custom emoji entities are not supported for Markdown version 1. " + "Future version of PTB will raise a ValueError instead of falling " + "back to the alternative standard emoji.", + stacklevel=3, + category=PTBDeprecationWarning, + ) else: # This should never be needed because ids are numeric but the documentation # specifically mentions it so here we are @@ -3587,6 +3601,9 @@ def text_markdown(self) -> str: * |custom_emoji_formatting_note| + .. deprecated:: NEXT.VERSION + |custom_emoji_md1_deprecation| + Returns: :obj:`str`: Message text with entities formatted as Markdown. @@ -3631,6 +3648,9 @@ def text_markdown_urled(self) -> str: * |custom_emoji_formatting_note| + .. deprecated:: NEXT.VERSION + |custom_emoji_md1_deprecation| + Returns: :obj:`str`: Message text with entities formatted as Markdown. @@ -3675,6 +3695,9 @@ def caption_markdown(self) -> str: * |custom_emoji_formatting_note| + .. deprecated:: NEXT.VERSION + |custom_emoji_md1_deprecation| + Returns: :obj:`str`: Message caption with caption entities formatted as Markdown. @@ -3721,6 +3744,9 @@ def caption_markdown_urled(self) -> str: * |custom_emoji_formatting_note| + .. deprecated:: NEXT.VERSION + |custom_emoji_md1_deprecation| + Returns: :obj:`str`: Message caption with caption entities formatted as Markdown. diff --git a/tests/test_message.py b/tests/test_message.py index 6030e5d9a2c..86b3c0fa470 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -59,6 +59,7 @@ from telegram._utils.datetime import UTC from telegram.constants import ChatAction, ParseMode from telegram.ext import Defaults +from telegram.warnings import PTBDeprecationWarning from tests._passport.test_passport import RAW_PASSPORT_DATA from tests.auxil.bot_method_checks import ( check_defaults_handling, @@ -642,7 +643,7 @@ def test_text_markdown_emoji(self): "text_markdown_urled", ], ) - def test_text_custom_emoji_md_v1(self, type_): + def test_text_custom_emoji_md_v1(self, type_, recwarn): text = "Look a custom emoji: 😎" expected = "Look a custom emoji: 😎" emoji_entity = MessageEntity( @@ -659,7 +660,14 @@ def test_text_custom_emoji_md_v1(self, type_): text=text, entities=[emoji_entity], ) - assert expected == message[type_] + assert expected == getattr(message, type_) + + assert len(recwarn) == 1 + assert recwarn[0].category is PTBDeprecationWarning + assert str(recwarn[0].message).startswith( + "Custom emoji entities are not supported for Markdown version 1" + ) + assert recwarn[0].filename == __file__ @pytest.mark.parametrize( "type_", @@ -832,7 +840,7 @@ def test_caption_markdown_emoji(self): "caption_markdown_urled", ], ) - def test_caption_custom_emoji_md_v1(self, type_): + def test_caption_custom_emoji_md_v1(self, type_, recwarn): caption = "Look a custom emoji: 😎" expected = "Look a custom emoji: 😎" emoji_entity = MessageEntity( @@ -849,7 +857,14 @@ def test_caption_custom_emoji_md_v1(self, type_): caption=caption, caption_entities=[emoji_entity], ) - assert expected == message[type_] + assert expected == getattr(message, type_) + + assert len(recwarn) == 1 + assert recwarn[0].category is PTBDeprecationWarning + assert str(recwarn[0].message).startswith( + "Custom emoji entities are not supported for Markdown version 1" + ) + assert recwarn[0].filename == __file__ @pytest.mark.parametrize( "type_", From 352047d38ee5165a5af9f86c18b33b8aa0aebf28 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Tue, 25 Apr 2023 22:41:31 +0200 Subject: [PATCH 22/28] Completely skip set_get_my_name test until it's mocked --- tests/test_bot.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/test_bot.py b/tests/test_bot.py index b11ce4e60ba..878c08c805e 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -348,9 +348,6 @@ async def test_get_me_and_properties_not_initialized(self, bot: Bot, attribute): await bot.shutdown() async def test_get_me_and_properties(self, bot): - if dtm.date.today() < dtm.date(2023, 4, 25): - pytest.xfail("Depending on skipped test `test_set_get_my_name`") - get_me_bot = await ExtBot(bot.token).get_me() assert isinstance(get_me_bot, User) @@ -3364,11 +3361,8 @@ async def test_set_get_my_short_description(self, bot): bot.get_my_short_description("de"), ) == 3 * [BotShortDescription("")] - # TODO: Remove this once the 24h flood limit is fixed - @pytest.mark.skipif( - dtm.date.today() < dtm.date(2023, 4, 25), - reason="Skipping b/c of 24h flood limit. Waiting for that once will hopefully fix the CI.", - ) + # TODO: Mock this test + @pytest.mark.skip(reason="Must be converted to mocked test") async def test_set_get_my_name(self, bot): default_name = uuid4().hex en_name = uuid4().hex From f21d8c9eecd7c7e013732506478a8b78b27db150 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Wed, 26 Apr 2023 20:56:30 +0200 Subject: [PATCH 23/28] Add a note on `set_my_name` --- telegram/_bot.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/telegram/_bot.py b/telegram/_bot.py index 0cfb1265721..7e2c75bf663 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -8166,6 +8166,10 @@ async def set_my_name( 0-:tg-const:`telegram.constants.BotNameLimit.MAX_NAME_LENGTH` characters. Pass an empty string to remove the dedicated name for the given language. + + Caution: + If :paramref:`language_code` is not specified, a :paramref:`name` *must* + be specified. language_code (:obj:`str`, optional): A two-letter ISO 639-1 language code. If empty, the name will be applied to all users for whose language there is no dedicated name. From cdadcb71c6861fa5750a8866dc752d5243f933fd Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Wed, 26 Apr 2023 21:16:56 +0200 Subject: [PATCH 24/28] Mock the `set/get_my_name` test --- tests/test_bot.py | 117 +++++++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 43 deletions(-) diff --git a/tests/test_bot.py b/tests/test_bot.py index 878c08c805e..df070c43f39 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -26,7 +26,6 @@ import socket import time from collections import defaultdict -from uuid import uuid4 import pytest @@ -1730,6 +1729,80 @@ async def test_http2_runtime_error(self, recwarn): "instance" in str(recwarn[2].message) ) + async def test_set_get_my_name(self, bot, monkeypatch): + """We only test that we pass the correct values to TG since this endpoint is heavily + rate limited which makes automated tests rather infeasible.""" + default_name = "default_bot_name" + en_name = "en_bot_name" + de_name = "de_bot_name" + + # We predefine the responses that we would TG expect to send us + set_stack = asyncio.Queue() + get_stack = asyncio.Queue() + await set_stack.put({"name": default_name}) + await set_stack.put({"name": en_name, "language_code": "en"}) + await set_stack.put({"name": de_name, "language_code": "de"}) + await get_stack.put({"name": default_name, "language_code": None}) + await get_stack.put({"name": en_name, "language_code": "en"}) + await get_stack.put({"name": de_name, "language_code": "de"}) + + await set_stack.put({"name": default_name}) + await set_stack.put({"language_code": "en"}) + await set_stack.put({"language_code": "de"}) + await get_stack.put({"name": default_name, "language_code": None}) + await get_stack.put({"name": default_name, "language_code": "en"}) + await get_stack.put({"name": default_name, "language_code": "de"}) + + async def post(url, request_data: RequestData, *args, **kwargs): + # The mock-post now just fetches the predefined responses from the queues + if "setMyName" in url: + expected = await set_stack.get() + assert request_data.json_parameters == expected + set_stack.task_done() + return True + + bot_name = await get_stack.get() + if "language_code" in request_data.json_parameters: + assert request_data.json_parameters == {"language_code": bot_name["language_code"]} + else: + assert request_data.json_parameters == {} + get_stack.task_done() + return bot_name + + monkeypatch.setattr(bot.request, "post", post) + + # Set the names + assert all( + await asyncio.gather( + bot.set_my_name(default_name), + bot.set_my_name(en_name, language_code="en"), + bot.set_my_name(de_name, language_code="de"), + ) + ) + + # Check that they were set correctly + assert await asyncio.gather( + bot.get_my_name(), bot.get_my_name("en"), bot.get_my_name("de") + ) == [ + BotName(default_name), + BotName(en_name), + BotName(de_name), + ] + + # Delete the names + assert all( + await asyncio.gather( + bot.set_my_name(default_name), + bot.set_my_name(None, language_code="en"), + bot.set_my_name(None, language_code="de"), + ) + ) + + # Check that they were deleted correctly + assert await asyncio.gather( + bot.get_my_name(), bot.get_my_name("en"), bot.get_my_name("de") + ) == 3 * [BotName(default_name)] + class TestBotWithRequest: """ @@ -3360,45 +3433,3 @@ async def test_set_get_my_short_description(self, bot): bot.get_my_short_description("en"), bot.get_my_short_description("de"), ) == 3 * [BotShortDescription("")] - - # TODO: Mock this test - @pytest.mark.skip(reason="Must be converted to mocked test") - async def test_set_get_my_name(self, bot): - default_name = uuid4().hex - en_name = uuid4().hex - de_name = uuid4().hex - - # Set the names - assert all( - await asyncio.gather( - bot.set_my_name(default_name), - bot.set_my_name(en_name, language_code="en"), - bot.set_my_name(de_name, language_code="de"), - ) - ) - - # Check that they were set correctly - assert await asyncio.gather( - bot.get_my_name(), bot.get_my_name("en"), bot.get_my_name("de") - ) == [ - BotName(default_name), - BotName(en_name), - BotName(de_name), - ] - - # Delete the names - assert all( - await asyncio.gather( - bot.set_my_name(None), - bot.set_my_name(None, language_code="en"), - bot.set_my_name(None, language_code="de"), - ) - ) - - # Check that they were deleted correctly - assert await asyncio.gather( - bot.get_my_name(), bot.get_my_name("en"), bot.get_my_name("de") - ) == 3 * [BotName("")] - - # Reset to original name - assert await bot.set_my_name(bot.first_name) From f78c737abbde5f72f80f61c72dda53660fe84497 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Fri, 5 May 2023 17:05:40 +0530 Subject: [PATCH 25/28] Fix some typos --- docs/source/telegram.inline-tree.rst | 2 +- telegram/_bot.py | 2 +- telegram/_inline/inlinekeyboardbutton.py | 4 ++-- telegram/_inline/inlinequeryresultsbutton.py | 2 +- telegram/_switchinlinequerychosenchat.py | 6 +++--- telegram/_writeaccessallowed.py | 2 -- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/source/telegram.inline-tree.rst b/docs/source/telegram.inline-tree.rst index c0395673f51..7fa52a94b58 100644 --- a/docs/source/telegram.inline-tree.rst +++ b/docs/source/telegram.inline-tree.rst @@ -24,8 +24,8 @@ Inline Mode telegram.inlinequeryresultlocation telegram.inlinequeryresultmpeg4gif telegram.inlinequeryresultphoto - telegram.inlinequeryresultvenue telegram.inlinequeryresultsbutton + telegram.inlinequeryresultvenue telegram.inlinequeryresultvideo telegram.inlinequeryresultvoice telegram.inputmessagecontent diff --git a/telegram/_bot.py b/telegram/_bot.py index 936a722d0a0..8efd98af5ac 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -2879,7 +2879,7 @@ async def answer_inline_query( button (:class:`telegram.InlineQueryResultsButton`, optional): A button to be shown above the inline query results. - .. versionadded:: NEXT.VERISON + .. versionadded:: NEXT.VERSION Keyword Args: current_offset (:obj:`str`, optional): The :attr:`telegram.InlineQuery.offset` of diff --git a/telegram/_inline/inlinekeyboardbutton.py b/telegram/_inline/inlinekeyboardbutton.py index 91613b35823..9626fa1d0c3 100644 --- a/telegram/_inline/inlinekeyboardbutton.py +++ b/telegram/_inline/inlinekeyboardbutton.py @@ -175,8 +175,8 @@ class InlineKeyboardButton(TelegramObject): returned to the chat they switched from, skipping the chat selection screen. Tip: - This does the same as :attr:`switch_inline_query_chosen_chat`, but for some - reason, Telegram has not deprecated this field. + This is similar to the new parameter :paramref:`switch_inline_query_chosen_chat`, + but gives no control over which chats can be selected. switch_inline_query_current_chat (:obj:`str`): Optional. If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input field. Can be empty, in which case only the bot's username will be inserted. This diff --git a/telegram/_inline/inlinequeryresultsbutton.py b/telegram/_inline/inlinequeryresultsbutton.py index 088de7cc6aa..7d19b2ef95c 100644 --- a/telegram/_inline/inlinequeryresultsbutton.py +++ b/telegram/_inline/inlinequeryresultsbutton.py @@ -66,7 +66,7 @@ class InlineQueryResultsButton(TelegramObject): `Web App `_ that will be launched when the user presses the button. The Web App will be able to switch back to the inline mode using the method ``web_app_switch_inline_query`` inside the Web App. - start_parameter (:obj:`str`): Optional. Deep-linking parameter for the + start_parameter (:obj:`str`): Optional. Deep-linking parameter for the :guilabel:`/start` message sent to the bot when user presses the switch button. :tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`- :tg-const:`telegram.InlineQuery.MAX_SWITCH_PM_TEXT_LENGTH` characters, diff --git a/telegram/_switchinlinequerychosenchat.py b/telegram/_switchinlinequerychosenchat.py index fececa88a45..459b30abf75 100644 --- a/telegram/_switchinlinequerychosenchat.py +++ b/telegram/_switchinlinequerychosenchat.py @@ -28,14 +28,14 @@ class SwitchInlineQueryChosenChat(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their :attr:`query`, :attr:`allow_user_chats`, :attr:`allow_bot_chats`, - :attr:`allow_group_chats` and :attr:`allow_channel_chats` are equal. + :attr:`allow_group_chats`, and :attr:`allow_channel_chats` are equal. .. versionadded:: NEXT.VERSION Caution: The PTB team has discovered that you must pass at least one of - :paramref:`allow_user_chats`, :paramref:`allow_bot_chats`, :paramref:`allow_group_chats` or - :paramref:`allow_channel_chats` to Telegram. Otherwise, an error will be raised. + :paramref:`allow_user_chats`, :paramref:`allow_bot_chats`, :paramref:`allow_group_chats`, + or :paramref:`allow_channel_chats` to Telegram. Otherwise, an error will be raised. Args: query (:obj:`str`, optional): The default inline query to be inserted in the input field. diff --git a/telegram/_writeaccessallowed.py b/telegram/_writeaccessallowed.py index a3270c2efa7..faa5c0072e3 100644 --- a/telegram/_writeaccessallowed.py +++ b/telegram/_writeaccessallowed.py @@ -29,8 +29,6 @@ class WriteAccessAllowed(TelegramObject): adding the bot to the attachment menu or launching a Web App from a link. .. versionadded:: 20.0 - .. versionchanged:: NEXT.VERSION - Added the optional :attr:`web_app_name` attribute. Args: web_app_name (:obj:`str`, optional): Name of the Web App which was launched from a link. From 9e85331f232d4610f3652841471e23670ca34e29 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sat, 6 May 2023 19:45:19 +0530 Subject: [PATCH 26/28] update warning test to use bot and extbot --- telegram/_bot.py | 4 ++-- tests/test_bot.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/telegram/_bot.py b/telegram/_bot.py index 8efd98af5ac..b2aa643d62d 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -2902,12 +2902,12 @@ async def answer_inline_query( ) if switch_pm_text and switch_pm_parameter: - warn( + self._warn( "Since Bot API 6.7, the parameters `switch_pm_text` and `switch_pm_parameter` are " "deprecated in favour of the new parameter `button`. Please use the new parameter " "`button` instead.", category=PTBDeprecationWarning, - stacklevel=4, + stacklevel=3, ) button = InlineQueryResultsButton( text=switch_pm_text, diff --git a/tests/test_bot.py b/tests/test_bot.py index aebfa4a862e..7bc003c4889 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -766,10 +766,15 @@ async def make_assertion(url, request_data: RequestData, *args, **kwargs): monkeypatch.delattr(bot.request, "post") - async def test_answer_inline_query_deprecated_args(self, monkeypatch, bot, recwarn): + @pytest.mark.parametrize("bot_class", ["Bot", "ExtBot"]) + async def test_answer_inline_query_deprecated_args( + self, monkeypatch, recwarn, bot_class, bot, raw_bot + ): async def mock_post(*args, **kwargs): return True + bot = raw_bot if bot_class == "Bot" else bot + monkeypatch.setattr(bot.request, "post", mock_post) with pytest.raises( From 9bfd5a1d59b9159d3b51dbfa6a4f3d01b3b0e6df Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 6 May 2023 20:52:58 +0200 Subject: [PATCH 27/28] `pre-commit` autoupdate (#3688) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3c352a2b9ae..1c2269aea81 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: - aiolimiter~=1.0.0 - . # this basically does `pip install -e .` - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.1.1 + rev: v1.2.0 hooks: - id: mypy name: mypy-ptb @@ -65,7 +65,7 @@ repos: - cachetools~=5.3.0 - . # this basically does `pip install -e .` - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.3.2 hooks: - id: pyupgrade files: ^(telegram|examples|tests|docs)/.*\.py$ @@ -80,7 +80,7 @@ repos: - --diff - --check - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.0.261' + rev: 'v0.0.263' hooks: - id: ruff name: ruff From 775deeb02b356ce6759777d780216e0c007877b1 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sat, 6 May 2023 21:38:06 +0200 Subject: [PATCH 28/28] Add a shortcut --- telegram/_botname.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/telegram/_botname.py b/telegram/_botname.py index 1608c40f3d2..05c0610db68 100644 --- a/telegram/_botname.py +++ b/telegram/_botname.py @@ -17,6 +17,9 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. """This module contains an object that represent a Telegram bots name.""" +from typing import ClassVar + +from telegram import constants from telegram._telegramobject import TelegramObject from telegram._utils.types import JSONDict @@ -46,3 +49,6 @@ def __init__(self, name: str, *, api_kwargs: JSONDict = None): self._id_attrs = (self.name,) self._freeze() + + MAX_LENGTH: ClassVar[int] = constants.BotNameLimit.MAX_NAME_LENGTH + """:const:`telegram.constants.BotNameLimit.MAX_NAME_LENGTH`"""