Store read receipts in event model items
This commit is contained in:
parent
d51d266642
commit
a9c316fcf5
|
@ -18,8 +18,8 @@ from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING, Any, Callable, ClassVar, Coroutine, Dict, List, NamedTuple,
|
TYPE_CHECKING, Any, Callable, ClassVar, Coroutine, DefaultDict, Dict, List,
|
||||||
Optional, Set, Tuple, Type, Union,
|
NamedTuple, Optional, Set, Tuple, Type, Union,
|
||||||
)
|
)
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
@ -202,6 +202,15 @@ class MatrixClient(nio.AsyncClient):
|
||||||
self.loaded_once_rooms: Set[str] = set() # {room_id}
|
self.loaded_once_rooms: Set[str] = set() # {room_id}
|
||||||
self.cleared_events_rooms: Set[str] = set() # {room_id}
|
self.cleared_events_rooms: Set[str] = set() # {room_id}
|
||||||
|
|
||||||
|
self.event_to_echo_ids: Dict[str, str] = {}
|
||||||
|
|
||||||
|
# {(room_id, user_id): event_id}
|
||||||
|
self.unassigned_member_last_read_event: Dict[Tuple[str, str], str] = {}
|
||||||
|
|
||||||
|
# {event_id: {user_id: server_timestamp}}
|
||||||
|
self.unassigned_event_last_read_by: DefaultDict[str, Dict[str, int]] =\
|
||||||
|
DefaultDict(dict)
|
||||||
|
|
||||||
# {room_id: event}
|
# {room_id: event}
|
||||||
self.power_level_events: Dict[str, nio.PowerLevelsEvent] = {}
|
self.power_level_events: Dict[str, nio.PowerLevelsEvent] = {}
|
||||||
|
|
||||||
|
@ -1908,8 +1917,20 @@ class MatrixClient(nio.AsyncClient):
|
||||||
|
|
||||||
async def add_member(self, room: nio.MatrixRoom, user_id: str) -> None:
|
async def add_member(self, room: nio.MatrixRoom, user_id: str) -> None:
|
||||||
"""Register/update a room member into our models."""
|
"""Register/update a room member into our models."""
|
||||||
|
|
||||||
|
room_id = room.room_id
|
||||||
|
member_model = self.models[self.user_id, room_id, "members"]
|
||||||
member = room.users[user_id]
|
member = room.users[user_id]
|
||||||
presence = self.backend.presences.get(user_id, None)
|
presence = self.backend.presences.get(user_id, None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
registered = member_model[user_id]
|
||||||
|
except KeyError:
|
||||||
|
last_read_event = self.unassigned_member_last_read_event\
|
||||||
|
.pop((room_id, user_id), "")
|
||||||
|
else:
|
||||||
|
last_read_event = registered.last_read_event
|
||||||
|
|
||||||
member_item = Member(
|
member_item = Member(
|
||||||
id = user_id,
|
id = user_id,
|
||||||
display_name = room.user_name(user_id) # disambiguated
|
display_name = room.user_name(user_id) # disambiguated
|
||||||
|
@ -1918,17 +1939,17 @@ class MatrixClient(nio.AsyncClient):
|
||||||
typing = user_id in room.typing_users,
|
typing = user_id in room.typing_users,
|
||||||
power_level = member.power_level,
|
power_level = member.power_level,
|
||||||
invited = member.invited,
|
invited = member.invited,
|
||||||
|
last_read_event = last_read_event,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Associate presence with member, if it exists
|
# Associate presence with member, if it exists
|
||||||
if presence:
|
if presence:
|
||||||
presence.members[room.room_id] = member_item
|
presence.members[room_id] = member_item
|
||||||
|
|
||||||
# And then update presence fields
|
# And then update presence fields
|
||||||
presence.update_members()
|
presence.update_members()
|
||||||
|
|
||||||
self.models[self.user_id, room.room_id, "members"][user_id] = \
|
member_model[user_id] = member_item
|
||||||
member_item
|
|
||||||
|
|
||||||
|
|
||||||
async def remove_member(self, room: nio.MatrixRoom, user_id: str) -> None:
|
async def remove_member(self, room: nio.MatrixRoom, user_id: str) -> None:
|
||||||
|
@ -2030,6 +2051,17 @@ class MatrixClient(nio.AsyncClient):
|
||||||
if content and "inline_content" not in fields:
|
if content and "inline_content" not in fields:
|
||||||
fields["inline_content"] = HTML.filter(content, inline=True)
|
fields["inline_content"] = HTML.filter(content, inline=True)
|
||||||
|
|
||||||
|
event_model = self.models[self.user_id, room.room_id, "events"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
registered = event_model[event_id or ev.event_id]
|
||||||
|
except KeyError:
|
||||||
|
last_read_by = self.unassigned_event_last_read_by.pop(
|
||||||
|
event_id or ev.event_id, {},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
last_read_by = registered.last_read_by
|
||||||
|
|
||||||
# Create Event ModelItem
|
# Create Event ModelItem
|
||||||
|
|
||||||
item = Event(
|
item = Event(
|
||||||
|
@ -2045,11 +2077,13 @@ class MatrixClient(nio.AsyncClient):
|
||||||
target_name = target_name,
|
target_name = target_name,
|
||||||
target_avatar = target_avatar,
|
target_avatar = target_avatar,
|
||||||
links = Event.parse_links(content),
|
links = Event.parse_links(content),
|
||||||
|
last_read_by = last_read_by,
|
||||||
|
|
||||||
fetch_profile =
|
fetch_profile =
|
||||||
(must_fetch_sender or must_fetch_target)
|
(must_fetch_sender or must_fetch_target)
|
||||||
if override_fetch_profile is None else
|
if override_fetch_profile is None else
|
||||||
override_fetch_profile,
|
override_fetch_profile,
|
||||||
|
|
||||||
**fields,
|
**fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2064,6 +2098,7 @@ class MatrixClient(nio.AsyncClient):
|
||||||
|
|
||||||
if from_us and tx_id and f"echo-{tx_id}" in model:
|
if from_us and tx_id and f"echo-{tx_id}" in model:
|
||||||
item.id = f"echo-{tx_id}"
|
item.id = f"echo-{tx_id}"
|
||||||
|
self.event_to_echo_ids[ev.event_id] = item.id
|
||||||
|
|
||||||
model[item.id] = item
|
model[item.id] = item
|
||||||
await self.set_room_last_event(room.room_id, item)
|
await self.set_room_last_event(room.room_id, item)
|
||||||
|
|
|
@ -242,9 +242,7 @@ class Member(ModelItem):
|
||||||
power_level: int = 0
|
power_level: int = 0
|
||||||
invited: bool = False
|
invited: bool = False
|
||||||
profile_updated: datetime = ZERO_DATE
|
profile_updated: datetime = ZERO_DATE
|
||||||
|
|
||||||
last_read_event: str = ""
|
last_read_event: str = ""
|
||||||
last_read_at: datetime = ZERO_DATE
|
|
||||||
|
|
||||||
presence: Presence.State = Presence.State.offline
|
presence: Presence.State = Presence.State.offline
|
||||||
currently_active: bool = False
|
currently_active: bool = False
|
||||||
|
@ -332,6 +330,9 @@ class Event(ModelItem):
|
||||||
redacter_id: str = ""
|
redacter_id: str = ""
|
||||||
redacter_name: str = ""
|
redacter_name: str = ""
|
||||||
|
|
||||||
|
# {user_id: server_timestamp} - QML can't parse dates from JSONified dicts
|
||||||
|
last_read_by: Dict[str, int] = field(default_factory=dict)
|
||||||
|
|
||||||
is_local_echo: bool = False
|
is_local_echo: bool = False
|
||||||
source: Optional[nio.Event] = None
|
source: Optional[nio.Event] = None
|
||||||
|
|
||||||
|
|
|
@ -730,20 +730,44 @@ class NioCallbacks:
|
||||||
async def onReceiptEvent(
|
async def onReceiptEvent(
|
||||||
self, room: nio.MatrixRoom, ev: nio.ReceiptEvent,
|
self, room: nio.MatrixRoom, ev: nio.ReceiptEvent,
|
||||||
) -> None:
|
) -> None:
|
||||||
model = self.models[self.user_id, room.room_id, "members"]
|
member_model = self.models[self.user_id, room.room_id, "members"]
|
||||||
|
event_model = self.models[self.user_id, room.room_id, "events"]
|
||||||
|
unassigned_mems = self.client.unassigned_member_last_read_event
|
||||||
|
unassigned_evs = self.client.unassigned_event_last_read_by
|
||||||
|
|
||||||
for receipt in ev.receipts:
|
for receipt in ev.receipts:
|
||||||
|
if receipt.user_id in self.client.backend.clients:
|
||||||
|
continue
|
||||||
|
|
||||||
if receipt.receipt_type != "m.read":
|
if receipt.receipt_type != "m.read":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
member = model.get(receipt.user_id)
|
echo_id = self.client.event_to_echo_ids.get(receipt.event_id)
|
||||||
|
read_event = event_model.get(echo_id or receipt.event_id)
|
||||||
|
timestamp = receipt.timestamp
|
||||||
|
|
||||||
if member:
|
if read_event:
|
||||||
timestamp = receipt.timestamp / 1000
|
read_event.last_read_by[receipt.user_id] = timestamp
|
||||||
member.set_fields(
|
read_event.notify_change("last_read_by")
|
||||||
last_read_event = receipt.event_id,
|
else:
|
||||||
last_read_at = datetime.fromtimestamp(timestamp),
|
# We haven't received the read event from the server yet
|
||||||
)
|
unassigned_evs[receipt.event_id][receipt.user_id] = timestamp
|
||||||
|
|
||||||
|
if receipt.user_id not in member_model:
|
||||||
|
# We haven't loaded the member yet (lazy loading), or they left
|
||||||
|
unassigned_mems[room.room_id, receipt.user_id] = \
|
||||||
|
echo_id or receipt.event_id
|
||||||
|
continue
|
||||||
|
|
||||||
|
member = member_model[receipt.user_id]
|
||||||
|
previous_read_event = event_model.get(member.last_read_event)
|
||||||
|
|
||||||
|
if previous_read_event:
|
||||||
|
# Remove the read marker from the previous last read event
|
||||||
|
previous_read_event.last_read_by.pop(receipt.user_id, None)
|
||||||
|
previous_read_event.notify_change("last_read_by")
|
||||||
|
|
||||||
|
member.last_read_event = echo_id or receipt.event_id
|
||||||
|
|
||||||
|
|
||||||
# Presence event callbacks
|
# Presence event callbacks
|
||||||
|
|
Loading…
Reference in New Issue
Block a user