atagen

macltok

public
Star0
Watch0

Commit 068bfa92dd36

tarball

atagen <boss@atagen.co>2026-10-25 13:54 UTC

1 file changed+81-12
context: give state rows the typed content the live timeline shows

Context and inbox rows labelled every state event as a generic state change, so a join, a rename or the room's creation read as "sent a state event". Member events now go through ruma's membership_change, and the room name, topic, avatar, encryption, create and tombstone events get their own content as they do in the live timeline.
Modified file: src/normalise/observed.rs+81 additions−12 deletions
@@ -2,8 +2,12 @@ use ruma::{
22 OwnedEventId, UserId,
33 events::{
44 poll::{start::PollStartEventContent, unstable_start::UnstablePollStartEventContent},
5- room::message::{
6- MessageType, Relation, RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
5+ room::{
6+ member::{MembershipChange as RumaMembershipChange, OriginalSyncRoomMemberEvent},
7+ message::{
8+ MessageType, Relation, RoomMessageEventContent,
9+ RoomMessageEventContentWithoutRelation,
10+ },
711 },
812 sticker::StickerEventContent,
913 },
@@ -19,8 +23,8 @@ use super::{
1923 timeline::formatted_body,
2024 };
2125 use crate::{
22- Message, MessageMentions, ReplyDetails, ReplyReference, SendState, Sender, TimelineContent,
23- TimelineEvent, TimelineItem, TimelineItemId, UtdCause,
26+ Change, MediaId, MembershipChange, Message, MessageMentions, ReplyDetails, ReplyReference,
27+ SendState, Sender, TimelineContent, TimelineEvent, TimelineItem, TimelineItemId, UtdCause,
2428 };
2529
2630 pub(crate) fn normalise_observed(
@@ -148,14 +152,11 @@ pub(crate) fn normalise_observed(
148152 None => (TimelineContent::Other { event_type }, None, None),
149153 }
150154 } else if let Some(state_key) = value.get("state_key").and_then(Value::as_str) {
151- (
152- TimelineContent::OtherState {
153- event_type,
154- state_key: state_key.to_owned(),
155- },
156- None,
157- None,
158- )
155+ let content = state_content(value, &event_type).unwrap_or(TimelineContent::OtherState {
156+ event_type,
157+ state_key: state_key.to_owned(),
158+ });
159+ (content, None, None)
159160 } else {
160161 (TimelineContent::Other { event_type }, None, None)
161162 };
@@ -269,3 +270,71 @@ fn generic_text(value: &Value) -> Option<String> {
269270 _ => None,
270271 }
271272 }
273+
274+/// the typed state rows the live timeline gives these events
275+fn state_content(value: &Value, event_type: &str) -> Option<TimelineContent> {
276+ let content = value.get("content")?;
277+ let text = |key: &str| {
278+ content
279+ .get(key)
280+ .and_then(Value::as_str)
281+ .filter(|text| !text.is_empty())
282+ .map(str::to_owned)
283+ };
284+ Some(match event_type {
285+ "m.room.member" => {
286+ let event: OriginalSyncRoomMemberEvent = serde_json::from_value(value.clone()).ok()?;
287+ let user_id = event.state_key.clone();
288+ match event.membership_change() {
289+ RumaMembershipChange::ProfileChanged {
290+ displayname_change,
291+ avatar_url_change,
292+ } => TimelineContent::Profile {
293+ user_id,
294+ display_name: displayname_change.map(|change| Change {
295+ old: change.old.map(str::to_owned),
296+ new: change.new.map(str::to_owned),
297+ }),
298+ avatar: avatar_url_change.map(|change| Change {
299+ old: change.old.map(MediaId::plain),
300+ new: change.new.map(MediaId::plain),
301+ }),
302+ },
303+ change => TimelineContent::Membership {
304+ user_id,
305+ display_name: event.content.displayname.clone(),
306+ change: membership_change(&change),
307+ },
308+ }
309+ }
310+ "m.room.name" => TimelineContent::RoomName(text("name")),
311+ "m.room.topic" => TimelineContent::RoomTopic(text("topic")),
312+ "m.room.avatar" => TimelineContent::RoomAvatar(text("url").map(|url| MediaId::plain(&url))),
313+ "m.room.encryption" => TimelineContent::EncryptionEnabled,
314+ "m.room.create" => TimelineContent::RoomCreated,
315+ "m.room.tombstone" => TimelineContent::RoomUpgraded {
316+ successor: serde_json::from_value(content.get("replacement_room")?.clone()).ok()?,
317+ },
318+ _ => return None,
319+ })
320+}
321+
322+const fn membership_change(change: &RumaMembershipChange<'_>) -> MembershipChange {
323+ match change {
324+ RumaMembershipChange::Joined => MembershipChange::Joined,
325+ RumaMembershipChange::Left => MembershipChange::Left,
326+ RumaMembershipChange::Banned => MembershipChange::Banned,
327+ RumaMembershipChange::Unbanned => MembershipChange::Unbanned,
328+ RumaMembershipChange::Kicked => MembershipChange::Kicked,
329+ RumaMembershipChange::KickedAndBanned => MembershipChange::KickedAndBanned,
330+ RumaMembershipChange::Invited => MembershipChange::Invited,
331+ RumaMembershipChange::InvitationAccepted => MembershipChange::InvitationAccepted,
332+ RumaMembershipChange::InvitationRejected => MembershipChange::InvitationRejected,
333+ RumaMembershipChange::InvitationRevoked => MembershipChange::InvitationRevoked,
334+ RumaMembershipChange::Knocked => MembershipChange::Knocked,
335+ RumaMembershipChange::KnockAccepted => MembershipChange::KnockAccepted,
336+ RumaMembershipChange::KnockRetracted => MembershipChange::KnockRetracted,
337+ RumaMembershipChange::KnockDenied => MembershipChange::KnockDenied,
338+ _ => MembershipChange::Other,
339+ }
340+}