Spaces:
Running on Zero
Running on Zero
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()
|