Skip to content

Commit 7d84d01

Browse files
committed
fix: support web message deletion events
1 parent d7dabd3 commit 7d84d01

3 files changed

Lines changed: 99 additions & 4 deletions

File tree

src/pymax/dispatch/resolvers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def resolve_message(frame: InboundFrame) -> EventType | None:
4545

4646
if model.status == MessageStatus.EDITED:
4747
return EventType.MESSAGE_EDIT
48+
if model.status == MessageStatus.REMOVED:
49+
return EventType.MESSAGE_DELETE
4850
else:
4951
return EventType.MESSAGE_NEW
5052
except ValidationError:

src/pymax/types/events/message.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING
3+
from logging import getLogger
4+
from typing import TYPE_CHECKING, Any
45

5-
from pydantic import PrivateAttr
6+
from pydantic import PrivateAttr, model_validator
67

78
from pymax.types.domain import Chat
89
from pymax.types.domain.base import CamelModel
10+
from pymax.types.domain.message import Message
911

1012
if TYPE_CHECKING:
1113
from pymax.api.messages import MessageService
1214

15+
logger = getLogger(__name__)
16+
1317

1418
class MessageDeleteEvent(CamelModel):
1519
"""Событие удаления сообщений.
@@ -25,12 +29,45 @@ class MessageDeleteEvent(CamelModel):
2529
:vartype ttl: bool
2630
"""
2731

28-
chat: Chat
2932
message_ids: list[int]
33+
chat_id: int
34+
chat: Chat | None = None
35+
message: Message | None = None
3036
ttl: bool = False
3137

3238
_actions: MessageService | None = PrivateAttr(default=None)
3339

40+
@model_validator(mode="before")
41+
@classmethod
42+
def normalize_payload(cls, data: Any) -> Any:
43+
# i really hate it cause of stupid web version thats send other type
44+
# of payload (128, expect 142)
45+
# TODO: impl it in the better way maybe
46+
47+
if not isinstance(data, dict):
48+
return data
49+
50+
if "chat" in data: # case opcode == 142
51+
chat = data["chat"]
52+
53+
return {
54+
"chat": chat,
55+
"ttl": data.get("ttl"),
56+
"messageIds": data["messageIds"],
57+
"chatId": chat["id"],
58+
}
59+
if "message" in data: # case opcode == 128
60+
message = data["message"]
61+
return {
62+
"chatId": data["chatId"],
63+
"message": message,
64+
"ttl": data["ttl"],
65+
"messageIds": [message["id"]],
66+
}
67+
68+
logger.warning("Illegal state during MessageDeleteEvent validation. Starting fallback")
69+
return data # stupid fallback but who cares. Still better than KeyError
70+
3471
def bind(self, actions: MessageService) -> MessageDeleteEvent:
3572
"""Привязывает сервис сообщений к событию удаления."""
3673
self._actions = actions

tests/dispatch/test_dispatcher.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ async def on_file(signal, _client):
8686
)
8787
await dispatcher.dispatch(
8888
frame(
89-
{"chat": chat_payload(5), "messageIds": [1, 2]},
89+
{
90+
"chat": chat_payload(5),
91+
"messageIds": [1, 2],
92+
"ttl": False,
93+
},
9094
opcode=Opcode.NOTIF_MSG_DELETE,
9195
cmd=Command.REQUEST,
9296
)
@@ -102,6 +106,58 @@ async def on_file(signal, _client):
102106
]
103107

104108

109+
@pytest.mark.asyncio
110+
async def test_dispatcher_maps_web_removed_message_to_delete_event() -> None:
111+
app = FakeApp()
112+
router: Router[str] = Router()
113+
dispatcher: Dispatcher[str] = Dispatcher(app, router)
114+
dispatcher.bind_client("client")
115+
seen: list[tuple[int, list[int], int | None, bool, bool]] = []
116+
117+
@router.on_message_delete()
118+
async def on_delete(event, _client):
119+
seen.append(
120+
(
121+
event.chat_id,
122+
event.message_ids,
123+
event.message.id if event.message is not None else None,
124+
event.ttl,
125+
event.message is not None and event.message._actions is app.api.messages,
126+
)
127+
)
128+
129+
await dispatcher.dispatch(
130+
frame(
131+
{
132+
"chatId": 0,
133+
"message": {
134+
"id": "116738762887754287",
135+
"time": 1781292158321,
136+
"type": "USER",
137+
"status": "REMOVED",
138+
"text": "deleted",
139+
"attaches": [],
140+
},
141+
"ttl": False,
142+
"unread": 0,
143+
"mark": 1781292158321,
144+
},
145+
opcode=Opcode.NOTIF_MESSAGE,
146+
cmd=Command.REQUEST,
147+
)
148+
)
149+
150+
assert seen == [
151+
(
152+
0,
153+
[116738762887754287],
154+
116738762887754287,
155+
False,
156+
True,
157+
)
158+
]
159+
160+
105161
@pytest.mark.asyncio
106162
async def test_dispatcher_requires_bound_client_for_callbacks() -> None:
107163
dispatcher: Dispatcher[str] = Dispatcher(FakeApp())

0 commit comments

Comments
 (0)