From 35b352e8c120d65b9ed613fd0e88b66929b47100 Mon Sep 17 00:00:00 2001 From: Krille Date: Thu, 8 May 2025 09:59:20 +0200 Subject: [PATCH] feat: Support fallback for threads in Event.getReplyEvent() --- lib/src/event.dart | 34 ++++++++++++++++++++++++++++------ test/event_test.dart | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/lib/src/event.dart b/lib/src/event.dart index 885e28ba..87e23d30 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -481,13 +481,35 @@ class Event extends MatrixEvent { Future redactEvent({String? reason, String? txid}) async => await room.redactEvent(eventId, reason: reason, txid: txid); - /// Searches for the reply event in the given timeline. + /// Searches for the reply event in the given timeline. Also returns the + /// event fallback if the relationship type is `m.thread`. + /// https://spec.matrix.org/v1.14/client-server-api/#fallback-for-unthreaded-clients Future getReplyEvent(Timeline timeline) async { - if (relationshipType != RelationshipTypes.reply) return null; - final relationshipEventId = this.relationshipEventId; - return relationshipEventId == null - ? null - : await timeline.getEventById(relationshipEventId); + switch (relationshipType) { + case RelationshipTypes.reply: + final relationshipEventId = this.relationshipEventId; + return relationshipEventId == null + ? null + : await timeline.getEventById(relationshipEventId); + + case RelationshipTypes.thread: + final relationshipContent = + content.tryGetMap('m.relates_to'); + if (relationshipContent == null) return null; + final String? relationshipEventId; + if (relationshipContent.tryGet('is_falling_back') == true) { + relationshipEventId = relationshipContent + .tryGetMap('m.in_reply_to') + ?.tryGet('event_id'); + } else { + relationshipEventId = this.relationshipEventId; + } + return relationshipEventId == null + ? null + : await timeline.getEventById(relationshipEventId); + default: + return null; + } } /// If this event is encrypted and the decryption was not successful because diff --git a/test/event_test.dart b/test/event_test.dart index 0978a8bb..8c1d4998 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -2862,5 +2862,49 @@ void main() { matrixEvent.toJson(), ); }); + + test('getReplyEvent fallback', () async { + final event = Event.fromJson( + { + 'content': { + 'msgtype': 'text', + 'body': 'Hello world', + 'm.relates_to': { + 'rel_type': 'm.thread', + 'event_id': '\$root', + 'm.in_reply_to': {'event_id': '\$target'}, + 'is_falling_back': true, + }, + }, + 'event_id': '\$143273582443PhrSn:example.org', + 'origin_server_ts': 1432735824653, + 'room_id': room.id, + 'sender': '@example:example.org', + 'type': 'm.room.message', + 'unsigned': {'age': 1234}, + 'redacts': 'abcd', + 'prev_content': { + 'foo': 'bar', + }, + }, + room, + ); + expect(event.relationshipType, RelationshipTypes.thread); + expect(event.relationshipEventId, '\$root'); + final targetEvent = Event( + eventId: '\$target', + senderId: '@example:example.org', + type: 'm.room.message', + content: { + 'msgtype': 'text', + 'body': 'Hello world', + }, + originServerTs: DateTime.now(), + room: room, + ); + final timeline = + Timeline(room: room, chunk: TimelineChunk(events: [targetEvent])); + expect(await event.getReplyEvent(timeline), targetEvent); + }); }); }