diff --git a/lib/src/room.dart b/lib/src/room.dart index ef796dd5..5f0b1b65 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -24,6 +24,7 @@ import 'package:collection/collection.dart'; import 'package:html_unescape/html_unescape.dart'; import 'package:matrix/src/utils/crypto/crypto.dart'; import 'package:matrix/src/utils/space_child.dart'; +import 'package:matrix/widget.dart'; import '../matrix.dart'; import 'utils/markdown.dart'; @@ -369,6 +370,17 @@ class Room { : []; } + /// Returns all present Widgets in the room. + List get widgets => { + ...states['m.widget'] ?? {}, + ...states['im.vector.modular.widgets'] ?? {}, + } + .values + .map( + (e) => MatrixWidget.fromJson(e.content, this), + ) + .toList(); + /// Your current client instance. final Client client; @@ -501,7 +513,7 @@ class Room { /// Returns true if this room is unread bool get isUnread => notificationCount > 0 || markedUnread; - @Deprecated('Use [markUnread] instead') + @Deprecated('Use [markUnread] instead') Future setUnread(bool unread) => markUnread(unread); /// Sets an unread flag manually for this room. This changes the local account diff --git a/lib/widget.dart b/lib/widget.dart new file mode 100644 index 00000000..d72074a8 --- /dev/null +++ b/lib/widget.dart @@ -0,0 +1,65 @@ +import 'package:matrix/src/room.dart'; + +class MatrixWidget { + final Room room; + final String creatorUserId; + final Map data; + final String id; + final String name; + final String type; + final String url; + final bool waitForIframeLoad; + + MatrixWidget({ + required this.room, + required this.creatorUserId, + required this.data, + required this.id, + required this.name, + required this.type, + + /// use [buildWidgetUrl] instead + required this.url, + required this.waitForIframeLoad, + }); + + factory MatrixWidget.fromJson(Map json, Room room) => + MatrixWidget( + room: room, + creatorUserId: json['creatorUserId'], + data: json['data'], + id: json['id'], + name: json['name'], + type: json['type'], + url: json['url'], + waitForIframeLoad: json['waitForIframeLoad'], + ); + + Future buildWidgetUrl() async { + // See https://github.com/matrix-org/matrix-doc/issues/1236 for a + // description, specifically the section + // `What does the other stuff in content mean?` + final userProfile = await room.client.ownProfile; + var parsedUri = url; + + // a key-value map with the strings to be replaced + final replaceMap = { + r'$matrix_user_id': userProfile.userId, + r'$matrix_room_id': room.id, + r'$matrix_display_name': userProfile.displayName ?? '', + r'$matrix_avatar_url': userProfile.avatarUrl?.toString() ?? '', + // removing potentially dangerous keys containing anything but + // `[a-zA-Z0-9_-]` as well as non string values + ...Map.from(data) + ..removeWhere((key, value) => + !RegExp(r'^[\w-]+$').hasMatch(key) || !value is String) + ..map((key, value) => MapEntry('\$key', value)), + }; + + replaceMap.forEach((key, value) { + parsedUri = parsedUri.replaceAll(key, Uri.encodeComponent(value)); + }); + + return Uri.parse(parsedUri); + } +} diff --git a/test/room_test.dart b/test/room_test.dart index 5999c205..bed29e6f 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -921,6 +921,10 @@ void main() { expect(room.getState('m.room.message') != null, true); }); + test('Widgets', () { + expect(room.widgets.isEmpty, true); + }); + test('Spaces', () async { expect(room.isSpace, false); room.states['m.room.create'] = {