Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pybotx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,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_delete import EventDeleted
from pybotx.models.system_events.event_edit import EventEdit
from pybotx.models.system_events.internal_bot_notification import (
InternalBotNotificationEvent,
Expand Down Expand Up @@ -206,6 +207,7 @@
"DeletedFromChatEvent",
"Document",
"EditMessage",
"EventDeleted",
"EventEdit",
"EventNotFoundError",
"File",
Expand Down
10 changes: 1 addition & 9 deletions pybotx/bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,7 @@
from contextlib import asynccontextmanager
from datetime import datetime
from types import SimpleNamespace
from typing import (
Any,
Dict,
List,
Optional,
Set,
Tuple,
Union,
)
from typing import Any, Dict, List, Optional, Set, Tuple, Union
from uuid import UUID

import aiofiles
Expand Down
12 changes: 3 additions & 9 deletions pybotx/bot/handler.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
from dataclasses import dataclass
from functools import partial
from typing import (
TYPE_CHECKING,
Awaitable,
Callable,
List,
Literal,
TypeVar,
Union,
)
from typing import TYPE_CHECKING, Awaitable, Callable, List, Literal, TypeVar, Union

from pybotx.models.commands import BotCommand
from pybotx.models.message.incoming_message import IncomingMessage
Expand All @@ -23,6 +15,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_delete import EventDeleted
from pybotx.models.system_events.event_edit import EventEdit
from pybotx.models.system_events.internal_bot_notification import (
InternalBotNotificationEvent,
Expand Down Expand Up @@ -53,6 +46,7 @@
HandlerFunc[CTSLogoutEvent],
HandlerFunc[InternalBotNotificationEvent],
HandlerFunc[SmartAppEvent],
HandlerFunc[EventDeleted],
HandlerFunc[EventEdit],
HandlerFunc[JoinToChatEvent],
HandlerFunc[ConferenceChangedEvent],
Expand Down
9 changes: 9 additions & 0 deletions pybotx/bot/handler_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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_delete import EventDeleted
from pybotx.models.system_events.event_edit import EventEdit
from pybotx.models.system_events.internal_bot_notification import (
InternalBotNotificationEvent,
Expand Down Expand Up @@ -316,6 +317,14 @@ def event_edit(
self._system_event(EventEdit, handler_func)
return handler_func

def event_deleted(
self,
handler_func: HandlerFunc[EventDeleted],
) -> HandlerFunc[EventDeleted]:
"""Decorate `event deleted` event handler."""
self._system_event(EventDeleted, handler_func)
return handler_func

def conference_changed(
self,
handler_func: HandlerFunc[ConferenceChangedEvent],
Expand Down
3 changes: 3 additions & 0 deletions pybotx/models/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
BotAPIDeletedFromChat,
DeletedFromChatEvent,
)
from pybotx.models.system_events.event_delete import BotAPIEventDeleted, EventDeleted
from pybotx.models.system_events.event_edit import BotAPIEventEdit, EventEdit
from pybotx.models.system_events.internal_bot_notification import (
BotAPIInternalBotNotification,
Expand Down Expand Up @@ -60,6 +61,7 @@
BotAPILeftFromChat,
BotAPICTSLogin,
BotAPICTSLogout,
BotAPIEventDeleted,
BotAPIEventEdit,
BotAPIJoinToChat,
BotAPIConferenceChanged,
Expand All @@ -79,6 +81,7 @@
LeftFromChatEvent,
CTSLoginEvent,
CTSLogoutEvent,
EventDeleted,
EventEdit,
JoinToChatEvent,
ConferenceChangedEvent,
Expand Down
1 change: 1 addition & 0 deletions pybotx/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ class BotAPISystemEventTypes(StrEnum):
INTERNAL_BOT_NOTIFICATION = "system:internal_bot_notification"
LEFT_FROM_CHAT = "system:left_from_chat"
SMARTAPP_EVENT = "system:smartapp_event"
EVENT_DELETED = "system:event_deleted"
EVENT_EDIT = "system:event_edit"
JOIN_TO_CHAT = "system:user_joined_to_chat"
CONFERENCE_CHANGED = "system:conference_changed"
Expand Down
63 changes: 63 additions & 0 deletions pybotx/models/system_events/event_delete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional
from uuid import UUID

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 EventDeleted(BotCommandBase):
"""Event `system:event_deleted`.

Attributes:
deleted_at: Delete message date and time.
group_chat_id: Delete message group chat id.
meta: Delete message meta.
sync_ids: Delete message sync ids.
"""

deleted_at: datetime
group_chat_id: UUID
sync_ids: List[UUID]
meta: Optional[Dict[str, Any]]


class BotAPIEventDeletedData(VerifiedPayloadBaseModel):
deleted_at: datetime
group_chat_id: UUID
sync_ids: List[UUID]
meta: Optional[Dict[str, Any]]


class BotAPIEventDeletedPayload(BotAPIBaseSystemEventPayload):
body: Literal[BotAPISystemEventTypes.EVENT_DELETED]
data: BotAPIEventDeletedData


class BotAPIEventDeleted(BotAPIBaseCommand):
payload: BotAPIEventDeletedPayload = Field(..., alias="command")
bot: BaseBotAPIContext = Field(..., alias="from")

def to_domain(self, raw_command: Dict[str, Any]) -> EventDeleted:
return EventDeleted(
bot=BotAccount(
id=self.bot_id,
host=self.bot.host,
),
raw_command=raw_command,
deleted_at=self.payload.data.deleted_at,
group_chat_id=self.payload.data.group_chat_id,
meta=self.payload.data.meta,
sync_ids=self.payload.data.sync_ids,
)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pybotx"
version = "0.75.2"
version = "0.75.3"
description = "A python library for interacting with eXpress BotX API"
authors = [
"Sidnev Nikolay <nsidnev@ccsteam.ru>",
Expand Down
10 changes: 10 additions & 0 deletions tests/system_events/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,13 @@ class ConferenceChangedDataFactory(DictFactory):
operation = "change_conference_info"
sip_number = 12345678
start_at = "2025-04-15T11:00:39.634000Z"


class DeleteEventFactory(DictFactory):
deleted_at = "2025-09-23T09:04:49.787078Z"
group_chat_id = "30dc1980-643a-00ad-37fc-7cc10d74e935"
meta = {
"deleted_by": "fbc84c63-e432-4ff1-99bd-c3275f053866",
"deleted_by_admin": True,
}
sync_ids = ["36c4f12a-7082-599a-9842-cd146730e179"]
72 changes: 72 additions & 0 deletions tests/system_events/test_event_delete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from datetime import datetime
from typing import Any, Callable, Dict, Optional
from uuid import UUID

import pytest
from deepdiff import DeepDiff

from pybotx import (
Bot,
BotAccount,
BotAccountWithSecret,
EventDeleted,
HandlerCollector,
lifespan_wrapper,
)
from tests.system_events.factories import DeleteEventFactory

pytestmark = [
pytest.mark.asyncio,
pytest.mark.mock_authorization,
pytest.mark.usefixtures("respx_mock"),
]


async def test__event_delete__succeed(
bot_account: BotAccountWithSecret,
bot_id: UUID,
host: str,
datetime_formatter: Callable[[str], datetime],
api_incoming_message_factory: Callable[..., Dict[str, Any]],
) -> None:
# - Arrange -
event_deleted_data = DeleteEventFactory.create()

payload = api_incoming_message_factory(
body="system:event_deleted",
command_type="system",
data=event_deleted_data,
bot_id=bot_id,
host=host,
)

collector = HandlerCollector()
event_deleted: Optional[EventDeleted] = None

@collector.event_deleted
async def event_deleted_handler(event: EventDeleted, _: Bot) -> None:
nonlocal event_deleted
event_deleted = event
# Drop `raw_command` from asserting
event_deleted.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, verify_request=False)

# - Assert -
diff = DeepDiff(
event_deleted,
EventDeleted(
bot=BotAccount(id=bot_id, host=host),
raw_command=None,
deleted_at=datetime_formatter(event_deleted_data["deleted_at"]),
meta=event_deleted_data["meta"],
group_chat_id=UUID(event_deleted_data["group_chat_id"]),
sync_ids=[UUID(uuid_str) for uuid_str in event_deleted_data["sync_ids"]],
),
)

assert diff == {}, diff
Loading