From 8311b2b07cd73dadffcefd5d35dceda1351f5be3 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 1 Nov 2024 23:31:18 +0100 Subject: [PATCH] Add `CopyTextButton` and Integrate into `InlineKeyboardButton` --- docs/source/telegram.at-tree.rst | 1 + docs/source/telegram.copytextbutton.rst | 6 ++ telegram/__init__.py | 2 + telegram/_copytextbutton.py | 55 +++++++++++++++++ telegram/_inline/inlinekeyboardbutton.py | 13 ++++ telegram/constants.py | 12 +++- tests/_inline/test_inlinekeyboardbutton.py | 9 +++ tests/test_copytextbutton.py | 70 ++++++++++++++++++++++ 8 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 docs/source/telegram.copytextbutton.rst create mode 100644 telegram/_copytextbutton.py create mode 100644 tests/test_copytextbutton.py diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index f40223391fc..bdeb7015985 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -29,6 +29,7 @@ Available Types telegram.chat telegram.chatadministratorrights telegram.chatbackground + telegram.copytextbutton telegram.backgroundtype telegram.backgroundtypefill telegram.backgroundtypewallpaper diff --git a/docs/source/telegram.copytextbutton.rst b/docs/source/telegram.copytextbutton.rst new file mode 100644 index 00000000000..7110fbf8b6b --- /dev/null +++ b/docs/source/telegram.copytextbutton.rst @@ -0,0 +1,6 @@ +CopyTextButton +============== + +.. autoclass:: telegram.CopyTextButton + :members: + :show-inheritance: \ No newline at end of file diff --git a/telegram/__init__.py b/telegram/__init__.py index 0ff15a7a9a4..48596c74ffc 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -81,6 +81,7 @@ "ChatShared", "ChosenInlineResult", "Contact", + "CopyTextButton", "Credentials", "DataCredentials", "Dice", @@ -330,6 +331,7 @@ from ._chatmemberupdated import ChatMemberUpdated from ._chatpermissions import ChatPermissions from ._choseninlineresult import ChosenInlineResult +from ._copytextbutton import CopyTextButton from ._dice import Dice from ._files.animation import Animation from ._files.audio import Audio diff --git a/telegram/_copytextbutton.py b/telegram/_copytextbutton.py new file mode 100644 index 00000000000..e3dee813b9a --- /dev/null +++ b/telegram/_copytextbutton.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2024 +# 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 represents a Telegram CopyTextButton.""" +from typing import Optional + +from telegram._telegramobject import TelegramObject +from telegram._utils.types import JSONDict + + +class CopyTextButton(TelegramObject): + """ + This object represents an inline keyboard button that copies specified text to the clipboard. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`text` is equal. + + .. versionadded:: NEXT.VERSION + + Args: + text (:obj:`str`): The text to be copied to the clipboard; + :tg-const:`telegram.constants.InlineKeyboardButtonLimit.MIN_COPY_TEXT`- + :tg-const:`telegram.constants.InlineKeyboardButtonLimit.MAX_COPY_TEXT` characters + + Attributes: + text (:obj:`str`): The text to be copied to the clipboard; + :tg-const:`telegram.constants.InlineKeyboardButtonLimit.MIN_COPY_TEXT`- + :tg-const:`telegram.constants.InlineKeyboardButtonLimit.MAX_COPY_TEXT` characters + + """ + + __slots__ = ("text",) + + def __init__(self, text: str, *, api_kwargs: Optional[JSONDict] = None): + super().__init__(api_kwargs=api_kwargs) + self.text: str = text + + self._id_attrs = (self.text,) + + self._freeze() diff --git a/telegram/_inline/inlinekeyboardbutton.py b/telegram/_inline/inlinekeyboardbutton.py index cff4df66a21..62031af8cd2 100644 --- a/telegram/_inline/inlinekeyboardbutton.py +++ b/telegram/_inline/inlinekeyboardbutton.py @@ -21,6 +21,7 @@ from typing import TYPE_CHECKING, Final, Optional, Union from telegram import constants +from telegram._copytextbutton import CopyTextButton from telegram._games.callbackgame import CallbackGame from telegram._loginurl import LoginUrl from telegram._switchinlinequerychosenchat import SwitchInlineQueryChosenChat @@ -123,6 +124,10 @@ class InlineKeyboardButton(TelegramObject): This offers a quick way for the user to open your bot in inline mode in the same chat - good for selecting something from multiple options. Not supported in channels and for messages sent on behalf of a Telegram Business account. + copy_text (:class:`telegram.CopyTextButton`, optional): Description of the button that + copies the specified text to the clipboard. + + .. versionadded:: NEXT.VERSION callback_game (:class:`telegram.CallbackGame`, optional): Description of the game that will be launched when the user presses the button @@ -192,6 +197,10 @@ class InlineKeyboardButton(TelegramObject): This offers a quick way for the user to open your bot in inline mode in the same chat - good for selecting something from multiple options. Not supported in channels and for messages sent on behalf of a Telegram Business account. + copy_text (:class:`telegram.CopyTextButton`): Optional. Description of the button that + copies the specified text to the clipboard. + + .. versionadded:: NEXT.VERSION callback_game (:class:`telegram.CallbackGame`): Optional. Description of the game that will be launched when the user presses the button. @@ -224,6 +233,7 @@ class InlineKeyboardButton(TelegramObject): __slots__ = ( "callback_data", "callback_game", + "copy_text", "login_url", "pay", "switch_inline_query", @@ -246,6 +256,7 @@ def __init__( login_url: Optional[LoginUrl] = None, web_app: Optional[WebAppInfo] = None, switch_inline_query_chosen_chat: Optional[SwitchInlineQueryChosenChat] = None, + copy_text: Optional[CopyTextButton] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -265,6 +276,7 @@ def __init__( self.switch_inline_query_chosen_chat: Optional[SwitchInlineQueryChosenChat] = ( switch_inline_query_chosen_chat ) + self.copy_text: Optional[CopyTextButton] = copy_text self._id_attrs = () self._set_id_attrs() @@ -299,6 +311,7 @@ def de_json( data["switch_inline_query_chosen_chat"] = SwitchInlineQueryChosenChat.de_json( data.get("switch_inline_query_chosen_chat"), bot ) + data["copy_text"] = CopyTextButton.de_json(data.get("copy_text"), bot) return super().de_json(data=data, bot=bot) diff --git a/telegram/constants.py b/telegram/constants.py index 519e8419165..f1562a6cff2 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -1261,15 +1261,23 @@ class InlineKeyboardButtonLimit(IntEnum): __slots__ = () MIN_CALLBACK_DATA = 1 - """:obj:`int`: Minimum value allowed for + """:obj:`int`: Minimum length allowed for :paramref:`~telegram.InlineKeyboardButton.callback_data` parameter of :class:`telegram.InlineKeyboardButton` """ MAX_CALLBACK_DATA = 64 - """:obj:`int`: Maximum value allowed for + """:obj:`int`: Maximum length allowed for :paramref:`~telegram.InlineKeyboardButton.callback_data` parameter of :class:`telegram.InlineKeyboardButton` """ + MIN_COPY_TEXT = 1 + """:obj:`int`: Minimum length allowed for + :paramref:`~telegram.CopyTextButton.text` parameter of :class:`telegram.CopyTextButton` + """ + MAX_COPY_TEXT = 256 + """:obj:`int`: Maximum length allowed for + :paramref:`~telegram.CopyTextButton.text` parameter of :class:`telegram.CopyTextButton` + """ class InlineKeyboardMarkupLimit(IntEnum): diff --git a/tests/_inline/test_inlinekeyboardbutton.py b/tests/_inline/test_inlinekeyboardbutton.py index 6e406f3ecc3..a71e774898d 100644 --- a/tests/_inline/test_inlinekeyboardbutton.py +++ b/tests/_inline/test_inlinekeyboardbutton.py @@ -21,6 +21,7 @@ from telegram import ( CallbackGame, + CopyTextButton, InlineKeyboardButton, LoginUrl, SwitchInlineQueryChosenChat, @@ -46,6 +47,7 @@ def inline_keyboard_button(): switch_inline_query_chosen_chat=( InlineKeyboardButtonTestBase.switch_inline_query_chosen_chat ), + copy_text=InlineKeyboardButtonTestBase.copy_text, ) @@ -60,6 +62,7 @@ class InlineKeyboardButtonTestBase: 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) + copy_text = CopyTextButton("python-telegram-bot") class TestInlineKeyboardButtonWithoutRequest(InlineKeyboardButtonTestBase): @@ -86,6 +89,7 @@ def test_expected_values(self, inline_keyboard_button): inline_keyboard_button.switch_inline_query_chosen_chat == self.switch_inline_query_chosen_chat ) + assert inline_keyboard_button.copy_text == self.copy_text def test_to_dict(self, inline_keyboard_button): inline_keyboard_button_dict = inline_keyboard_button.to_dict() @@ -115,6 +119,9 @@ def test_to_dict(self, inline_keyboard_button): inline_keyboard_button_dict["switch_inline_query_chosen_chat"] == inline_keyboard_button.switch_inline_query_chosen_chat.to_dict() ) + assert ( + inline_keyboard_button_dict["copy_text"] == inline_keyboard_button.copy_text.to_dict() + ) def test_de_json(self, offline_bot): json_dict = { @@ -128,6 +135,7 @@ def test_de_json(self, offline_bot): "login_url": self.login_url.to_dict(), "pay": self.pay, "switch_inline_query_chosen_chat": self.switch_inline_query_chosen_chat.to_dict(), + "copy_text": self.copy_text.to_dict(), } inline_keyboard_button = InlineKeyboardButton.de_json(json_dict, None) @@ -149,6 +157,7 @@ def test_de_json(self, offline_bot): inline_keyboard_button.switch_inline_query_chosen_chat == self.switch_inline_query_chosen_chat ) + assert inline_keyboard_button.copy_text == self.copy_text none = InlineKeyboardButton.de_json({}, offline_bot) assert none is None diff --git a/tests/test_copytextbutton.py b/tests/test_copytextbutton.py new file mode 100644 index 00000000000..7092456d490 --- /dev/null +++ b/tests/test_copytextbutton.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2024 +# 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 BotCommand, CopyTextButton +from tests.auxil.slots import mro_slots + + +@pytest.fixture(scope="module") +def copy_text_button(): + return CopyTextButton(text=CopyTextButtonTestBase.text) + + +class CopyTextButtonTestBase: + text = "This is some text" + + +class TestCopyTextButtonWithoutRequest(CopyTextButtonTestBase): + def test_slot_behaviour(self, copy_text_button): + for attr in copy_text_button.__slots__: + assert getattr(copy_text_button, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(copy_text_button)) == len( + set(mro_slots(copy_text_button)) + ), "duplicate slot" + + def test_de_json(self, offline_bot): + json_dict = {"text": self.text} + copy_text_button = CopyTextButton.de_json(json_dict, offline_bot) + assert copy_text_button.api_kwargs == {} + + assert copy_text_button.text == self.text + assert CopyTextButton.de_json(None, offline_bot) is None + + def test_to_dict(self, copy_text_button): + copy_text_button_dict = copy_text_button.to_dict() + + assert isinstance(copy_text_button_dict, dict) + assert copy_text_button_dict["text"] == copy_text_button.text + + def test_equality(self): + a = CopyTextButton(self.text) + b = CopyTextButton(self.text) + c = CopyTextButton("text") + d = BotCommand("start", "description") + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d)