diff --git a/pybotx/__init__.py b/pybotx/__init__.py index 0c662e45..7df455c8 100644 --- a/pybotx/__init__.py +++ b/pybotx/__init__.py @@ -110,6 +110,7 @@ from pybotx.models.system_events.cts_login import CTSLoginEvent from pybotx.models.system_events.cts_logout import CTSLogoutEvent from pybotx.models.system_events.deleted_from_chat import DeletedFromChatEvent +from pybotx.models.system_events.event_edit import EventEdit from pybotx.models.system_events.internal_bot_notification import ( InternalBotNotificationEvent, ) @@ -142,6 +143,7 @@ "ButtonTextAlign", "CTSLoginEvent", "CTSLogoutEvent", + "EventEdit", "CallbackNotReceivedError", "CallbackRepoProto", "CantUpdatePersonalChatError", diff --git a/pybotx/bot/handler.py b/pybotx/bot/handler.py index 318ad006..f2eedc99 100644 --- a/pybotx/bot/handler.py +++ b/pybotx/bot/handler.py @@ -10,6 +10,7 @@ from pybotx.models.system_events.cts_login import CTSLoginEvent from pybotx.models.system_events.cts_logout import CTSLogoutEvent from pybotx.models.system_events.deleted_from_chat import DeletedFromChatEvent +from pybotx.models.system_events.event_edit import EventEdit from pybotx.models.system_events.internal_bot_notification import ( InternalBotNotificationEvent, ) @@ -32,6 +33,7 @@ HandlerFunc[CTSLogoutEvent], HandlerFunc[InternalBotNotificationEvent], HandlerFunc[SmartAppEvent], + HandlerFunc[EventEdit], ] VisibleFunc = Callable[[StatusRecipient, "Bot"], Awaitable[bool]] diff --git a/pybotx/bot/handler_collector.py b/pybotx/bot/handler_collector.py index d0d6c72e..42ce36d5 100644 --- a/pybotx/bot/handler_collector.py +++ b/pybotx/bot/handler_collector.py @@ -39,6 +39,7 @@ from pybotx.models.system_events.cts_login import CTSLoginEvent from pybotx.models.system_events.cts_logout import CTSLogoutEvent from pybotx.models.system_events.deleted_from_chat import DeletedFromChatEvent +from pybotx.models.system_events.event_edit import EventEdit from pybotx.models.system_events.internal_bot_notification import ( InternalBotNotificationEvent, ) @@ -270,6 +271,16 @@ def cts_logout( return handler_func + def event_edit( + self, + handler_func: HandlerFunc[EventEdit], + ) -> HandlerFunc[EventEdit]: + """Decorate `event edit` event handler.""" + + self._system_event(EventEdit, handler_func) + + return handler_func + def smartapp_event( self, handler_func: HandlerFunc[SmartAppEvent], diff --git a/pybotx/models/commands.py b/pybotx/models/commands.py index d2459eab..7173428c 100644 --- a/pybotx/models/commands.py +++ b/pybotx/models/commands.py @@ -15,6 +15,7 @@ BotAPIDeletedFromChat, DeletedFromChatEvent, ) +from pybotx.models.system_events.event_edit import BotAPIEventEdit, EventEdit from pybotx.models.system_events.internal_bot_notification import ( BotAPIInternalBotNotification, InternalBotNotificationEvent, @@ -38,6 +39,7 @@ BotAPILeftFromChat, BotAPICTSLogin, BotAPICTSLogout, + BotAPIEventEdit, ] BotAPICommand = Union[BotAPIIncomingMessage, BotAPISystemEvent] @@ -51,5 +53,6 @@ LeftFromChatEvent, CTSLoginEvent, CTSLogoutEvent, + EventEdit, ] BotCommand = Union[IncomingMessage, SystemEvent] diff --git a/pybotx/models/enums.py b/pybotx/models/enums.py index bf7751dd..e5cac18e 100644 --- a/pybotx/models/enums.py +++ b/pybotx/models/enums.py @@ -100,6 +100,7 @@ class BotAPISystemEventTypes(StrEnum): INTERNAL_BOT_NOTIFICATION = "system:internal_bot_notification" LEFT_FROM_CHAT = "system:left_from_chat" SMARTAPP_EVENT = "system:smartapp_event" + EVENT_EDIT = "system:event_edit" class BotAPIClientPlatforms(Enum): diff --git a/pybotx/models/system_events/event_edit.py b/pybotx/models/system_events/event_edit.py new file mode 100644 index 00000000..f81cf7f1 --- /dev/null +++ b/pybotx/models/system_events/event_edit.py @@ -0,0 +1,49 @@ +from dataclasses import dataclass +from typing import Any, Dict, Literal, Optional + +from pydantic import Field + +from pybotx.models.api_base import VerifiedPayloadBaseModel +from pybotx.models.base_command import ( + BaseBotAPIContext, + BotAPIBaseCommand, + BotAPIBaseSystemEventPayload, + BotCommandBase, +) +from pybotx.models.bot_account import BotAccount +from pybotx.models.enums import BotAPISystemEventTypes + + +@dataclass +class EventEdit(BotCommandBase): + """Event `system:event_edit`. + + Attributes: + body: updated message body. + """ + + body: Optional[str] + + +class BotAPIEventEditData(VerifiedPayloadBaseModel): + body: Optional[str] + + +class BotAPIEventEditPayload(BotAPIBaseSystemEventPayload): + body: Literal[BotAPISystemEventTypes.EVENT_EDIT] + data: BotAPIEventEditData + + +class BotAPIEventEdit(BotAPIBaseCommand): + payload: BotAPIEventEditPayload = Field(..., alias="command") + sender: BaseBotAPIContext = Field(..., alias="from") + + def to_domain(self, raw_command: Dict[str, Any]) -> EventEdit: + return EventEdit( + bot=BotAccount( + id=self.bot_id, + host=self.sender.host, + ), + raw_command=raw_command, + body=self.payload.data.body, + ) diff --git a/pyproject.toml b/pyproject.toml index aade9513..4f031633 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pybotx" -version = "0.61.2" +version = "0.61.3" description = "A python library for interacting with eXpress BotX API" authors = [ "Sidnev Nikolay ", diff --git a/tests/system_events/test_event_edit.py b/tests/system_events/test_event_edit.py new file mode 100644 index 00000000..9f2e3cf5 --- /dev/null +++ b/tests/system_events/test_event_edit.py @@ -0,0 +1,85 @@ +from typing import Optional +from uuid import UUID + +import pytest + +from pybotx import ( + Bot, + BotAccount, + BotAccountWithSecret, + EventEdit, + HandlerCollector, + lifespan_wrapper, +) + +pytestmark = [ + pytest.mark.asyncio, + pytest.mark.mock_authorization, + pytest.mark.usefixtures("respx_mock"), +] + + +async def test__event_edit__succeed( + bot_account: BotAccountWithSecret, +) -> None: + # - Arrange - + payload = { + "sync_id": "a465f0f3-1354-491c-8f11-f400164295cb", + "command": { + "body": "system:event_edit", + "data": {"body": "Edited"}, + "command_type": "system", + "metadata": {}, + }, + "async_files": [], + "attachments": [], + "entities": [], + "from": { + "user_huid": None, + "group_chat_id": None, + "ad_login": None, + "ad_domain": None, + "username": None, + "chat_type": None, + "manufacturer": None, + "device": None, + "device_software": None, + "device_meta": {}, + "platform": None, + "platform_package_id": None, + "is_admin": None, + "is_creator": None, + "app_version": None, + "locale": "en", + "host": "cts.example.com", + }, + "bot_id": "24348246-6791-4ac0-9d86-b948cd6a0e46", + "proto_version": 4, + "source_sync_id": None, + } + + collector = HandlerCollector() + event_edit: Optional[EventEdit] = None + + @collector.event_edit + async def event_edit_handler(event: EventEdit, bot: Bot) -> None: + nonlocal event_edit + event_edit = event + # Drop `raw_command` from asserting + event_edit.raw_command = None + + built_bot = Bot(collectors=[collector], bot_accounts=[bot_account]) + + # - Act - + async with lifespan_wrapper(built_bot) as bot: + bot.async_execute_raw_bot_command(payload) + + # - Assert - + assert event_edit == EventEdit( + bot=BotAccount( + id=UUID("24348246-6791-4ac0-9d86-b948cd6a0e46"), + host="cts.example.com", + ), + raw_command=None, + body="Edited", + )