File size: 4,933 Bytes
31c93b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4aaae80
 
31c93b1
 
 
 
 
 
 
 
4aaae80
 
31c93b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
from __future__ import annotations

from dataclasses import dataclass


@dataclass(frozen=True)
class ChatMessage:
    event_id: str
    from_node: str
    to_node: str
    body: str
    attachments: list[dict]
    sent_at: str
    delivered_at: str | None
    read_at: str | None
    client_id: str

    def as_dict(self) -> dict:
        return {
            "event_id": self.event_id,
            "from": self.from_node,
            "to": self.to_node,
            "body": self.body,
            "attachments": self.attachments,
            "sent_at": self.sent_at,
            "delivered_at": self.delivered_at,
            "read_at": self.read_at,
            "client_id": self.client_id,
        }


class ChatView:
    """MaterialisedView from chat.message.* events."""

    def __init__(self, our_node_id: str) -> None:
        self._our_node_id = our_node_id
        self._messages: dict[str, ChatMessage] = {}  # event_id -> ChatMessage
        self._seen_client_ids: set[str] = set()

    def apply(self, event) -> None:
        etype = getattr(event, "event_type", None) or event.get("event_type", "")
        payload = getattr(event, "payload", None) or event.get("payload", {})
        event_id = getattr(event, "event_id", None) or event.get("event_id", "")
        author = getattr(event, "author", None) or event.get("author", "")

        if etype == "chat.message.sent":
            client_id = payload.get("client_id", event_id)
            if client_id in self._seen_client_ids:
                return
            self._seen_client_ids.add(client_id)
            msg = ChatMessage(
                event_id=event_id,
                from_node=author,
                to_node=payload.get("to", ""),
                body=payload.get("body", ""),
                attachments=payload.get("attachments", []),
                sent_at=payload.get("sent_at", ""),
                delivered_at=None,
                read_at=None,
                client_id=client_id,
            )
            self._messages[event_id] = msg

        elif etype == "chat.message.delivered":
            target_id = payload.get("target_event_id", "")
            if target_id in self._messages:
                old = self._messages[target_id]
                self._messages[target_id] = ChatMessage(
                    event_id=old.event_id,
                    from_node=old.from_node,
                    to_node=old.to_node,
                    body=old.body,
                    attachments=old.attachments,
                    sent_at=old.sent_at,
                    delivered_at=payload.get("delivered_at", ""),
                    read_at=old.read_at,
                    client_id=old.client_id,
                )

        elif etype == "chat.message.read":
            target_id = payload.get("target_event_id", "")
            if target_id in self._messages:
                old = self._messages[target_id]
                self._messages[target_id] = ChatMessage(
                    event_id=old.event_id,
                    from_node=old.from_node,
                    to_node=old.to_node,
                    body=old.body,
                    attachments=old.attachments,
                    sent_at=old.sent_at,
                    delivered_at=old.delivered_at,
                    read_at=payload.get("read_at", ""),
                    client_id=old.client_id,
                )

    def messages_with(self, peer_node_id: str) -> list[ChatMessage]:
        return [
            m
            for m in self._messages.values()
            if m.from_node == peer_node_id or m.to_node == peer_node_id
        ]

    def all_messages(self) -> list[ChatMessage]:
        return sorted(self._messages.values(), key=lambda m: m.sent_at)

    def unread_count(self, peer: str) -> int:
        return sum(
            1
            for m in self._messages.values()
            if m.to_node == self._our_node_id and m.from_node == peer and m.read_at is None
        )

    def snapshot_state(self) -> dict:
        return {
            "messages": {eid: m.as_dict() for eid, m in self._messages.items()},
            "seen_client_ids": list(self._seen_client_ids),
        }

    def restore_state(self, state: dict) -> None:
        self._messages = {}
        for eid, md in state.get("messages", {}).items():
            self._messages[eid] = ChatMessage(
                event_id=md["event_id"],
                from_node=md["from"],
                to_node=md["to"],
                body=md["body"],
                attachments=md.get("attachments", []),
                sent_at=md["sent_at"],
                delivered_at=md.get("delivered_at"),
                read_at=md.get("read_at"),
                client_id=md.get("client_id", eid),
            )
        self._seen_client_ids = set(state.get("seen_client_ids", []))

    def reset(self) -> None:
        self._messages.clear()
        self._seen_client_ids.clear()