From 106cce4aba0d24d440656daeefeee022a0601429 Mon Sep 17 00:00:00 2001 From: kutuzov13 Date: Wed, 10 Apr 2024 13:52:48 +0300 Subject: [PATCH] feat: link in markup --- README.md | 9 ++ pybotx/models/message/markup.py | 17 ++- tests/client/notifications_api/test_markup.py | 114 ++++++++++++++++++ tests/models/test_markup.py | 2 +- 4 files changed, 139 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 53a7b653..d136db2e 100644 --- a/README.md +++ b/README.md @@ -389,6 +389,15 @@ async def bubbles_handler(message: IncomingMessage, bot: Bot) -> None: background_color="#0000FF", new_row=False, ) + + # В кнопку можно добавит ссылку на ресурс, + # для этого нужно добавить url в аргумент `link`, а `command` оставить пустым, + # `alert` добавляется в окно подтверждения при подтверждении перехода по ссылке. + bubbles.add_button( + label="Bubble with link", + alert="", + link="https://example.com" + ) await bot.answer_message( "The time has come to make a choice, Mr. Anderson:", diff --git a/pybotx/models/message/markup.py b/pybotx/models/message/markup.py index f3ab683c..e57a936b 100644 --- a/pybotx/models/message/markup.py +++ b/pybotx/models/message/markup.py @@ -14,8 +14,8 @@ class ButtonTextAlign(Enum): @dataclass class Button: - command: str label: str + command: Missing[str] = Undefined data: Dict[str, Any] = field(default_factory=dict) text_color: Missing[str] = Undefined background_color: Missing[str] = Undefined @@ -25,6 +25,7 @@ class Button: width_ratio: Missing[int] = Undefined alert: Missing[str] = Undefined process_on_client: Missing[bool] = Undefined + link: Missing[str] = Undefined ButtonRow = List[Button] @@ -68,8 +69,8 @@ def add_built_button(self, button: Button, new_row: bool = True) -> None: def add_button( self, - command: str, label: str, + command: Missing[str] = Undefined, data: Optional[Dict[str, Any]] = None, text_color: Missing[str] = Undefined, background_color: Missing[str] = Undefined, @@ -78,8 +79,14 @@ def add_button( width_ratio: Missing[int] = Undefined, alert: Missing[str] = Undefined, process_on_client: Missing[bool] = Undefined, + link: Missing[str] = Undefined, new_row: bool = True, ) -> None: + """Add button. Raise ValueError if link and command are missing.""" + + if link is Undefined and command is Undefined: + raise ValueError("Command arg is required") + button = Button( command=command, label=label, @@ -91,6 +98,7 @@ def add_button( width_ratio=width_ratio, alert=alert, process_on_client=process_on_client, + link=link, ) self.add_built_button(button, new_row=new_row) @@ -118,6 +126,7 @@ class BotXAPIButtonOptions(UnverifiedPayloadBaseModel): show_alert: Missing[Literal[True]] alert_text: Missing[str] handler: Missing[Literal["client"]] + link: Missing[str] class BotXAPIButton(UnverifiedPayloadBaseModel): @@ -140,6 +149,9 @@ def api_button_from_domain(button: Button) -> BotXAPIButton: if button.process_on_client: handler = "client" + if button.link is not Undefined: + handler = "client" + return BotXAPIButton( command=button.command, label=button.label, @@ -153,6 +165,7 @@ def api_button_from_domain(button: Button) -> BotXAPIButton: alert_text=button.alert, show_alert=show_alert, handler=handler, + link=button.link, ), ) diff --git a/tests/client/notifications_api/test_markup.py b/tests/client/notifications_api/test_markup.py index 25ea974e..c8c85d7e 100644 --- a/tests/client/notifications_api/test_markup.py +++ b/tests/client/notifications_api/test_markup.py @@ -374,6 +374,120 @@ async def test__markup__color_and_align( assert endpoint.called +async def test__markup__link( + respx_mock: MockRouter, + host: str, + bot_id: UUID, + bot_account: BotAccountWithSecret, +) -> None: + # - Arrange - + endpoint = respx_mock.post( + f"https://{host}/api/v4/botx/notifications/direct", + headers={"Authorization": "Bearer token", "Content-Type": "application/json"}, + json={ + "group_chat_id": "054af49e-5e18-4dca-ad73-4f96b6de63fa", + "notification": { + "body": "Buttons links:", + "bubble": [ + [ + { + "data": {}, + "label": "Open me", + "opts": { + "silent": True, + "align": "center", + "handler": "client", + "link": "https://example.com", + }, + }, + ], + ], + "keyboard": [ + [ + { + "data": {}, + "label": "Open me", + "opts": { + "silent": True, + "align": "center", + "handler": "client", + "link": "https://example.com", + }, + }, + ], + ], + "status": "ok", + }, + }, + ).mock( + return_value=httpx.Response( + HTTPStatus.ACCEPTED, + json={ + "status": "ok", + "result": {"sync_id": "21a9ec9e-f21f-4406-ac44-1a78d2ccf9e3"}, + }, + ), + ) + + bubbles = BubbleMarkup() + bubbles.add_button( + label="Open me", + silent=True, + link="https://example.com", + ) + + keyboard = KeyboardMarkup() + keyboard.add_button( + label="Open me", + silent=True, + link="https://example.com", + ) + + built_bot = Bot(collectors=[HandlerCollector()], bot_accounts=[bot_account]) + + # - Act - + async with lifespan_wrapper(built_bot) as bot: + task = asyncio.create_task( + bot.send_message( + body="Buttons links:", + bot_id=bot_id, + chat_id=UUID("054af49e-5e18-4dca-ad73-4f96b6de63fa"), + bubbles=bubbles, + keyboard=keyboard, + ), + ) + + await asyncio.sleep(0) # Return control to event loop + + await bot.set_raw_botx_method_result( + { + "status": "ok", + "sync_id": "21a9ec9e-f21f-4406-ac44-1a78d2ccf9e3", + "result": {}, + }, + verify_request=False, + ) + + # - Assert - + assert (await task) == UUID("21a9ec9e-f21f-4406-ac44-1a78d2ccf9e3") + assert endpoint.called + + +def test__markup__bubble_without_command_error_raised() -> None: + # - Arrange - + bubbles = BubbleMarkup() + + # - Act - + with pytest.raises(ValueError) as exc: + bubbles.add_button( + label="label", + silent=True, + ) + + # - Assert - + assert "Command arg is required" in str(exc.value) + + def test__markup__comparison() -> None: # - Arrange - button = Button("/test", "test") diff --git a/tests/models/test_markup.py b/tests/models/test_markup.py index 87ade162..8f8fb999 100644 --- a/tests/models/test_markup.py +++ b/tests/models/test_markup.py @@ -11,5 +11,5 @@ def test__mentions_list_properties__filled() -> None: # - Assert - assert ( bubbles.__repr__() - == "row 1: label1 (command1)\nrow 2: label2 (command2) | label3 (command3)" + == "row 1: command1 (label1)\nrow 2: command2 (label2) | command3 (label3)" )