From 7605b1de31c32e32514ab493669b8c3bb827a62c Mon Sep 17 00:00:00 2001 From: td Date: Mon, 21 Oct 2024 13:58:33 +0200 Subject: [PATCH] feat: v1.12 spec endpoints support (BREAKING CHANGE) --- lib/matrix_api_lite/generated/api.dart | 98 +++++++------ lib/matrix_api_lite/generated/model.dart | 174 ++++++++++++++++++++--- lib/src/client.dart | 17 ++- lib/src/room.dart | 11 +- 4 files changed, 227 insertions(+), 73 deletions(-) diff --git a/lib/matrix_api_lite/generated/api.dart b/lib/matrix_api_lite/generated/api.dart index c7c026cd..859c594d 100644 --- a/lib/matrix_api_lite/generated/api.dart +++ b/lib/matrix_api_lite/generated/api.dart @@ -119,7 +119,7 @@ class Api { /// /// Clients, both authenticated and unauthenticated, might wish to hide user interface which exposes /// this feature if the server is not offering it. Authenticated clients can check for support on - /// a per-user basis with the `m.get_login_token` [capability](https://spec.matrix.org/unstable/client-server-api/#capabilities-negotiation), + /// a per-user basis with the [`m.get_login_token`](https://spec.matrix.org/unstable/client-server-api/#mget_login_token-capability) capability, /// while unauthenticated clients can detect server support by looking for an `m.login.token` login /// flow with `get_login_token: true` on [`GET /login`](https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3login). /// @@ -1982,6 +1982,9 @@ class Api { /// [serverName] The servers to attempt to join the room through. One of the servers /// must be participating in the room. /// + /// [via] The servers to attempt to join the room through. One of the servers + /// must be participating in the room. + /// /// [reason] Optional reason to be included as the `reason` on the subsequent /// membership event. /// @@ -1993,12 +1996,14 @@ class Api { /// The joined room ID. Future joinRoom(String roomIdOrAlias, {List? serverName, + List? via, String? reason, ThirdPartySigned? thirdPartySigned}) async { final requestUri = Uri( path: '_matrix/client/v3/join/${Uri.encodeComponent(roomIdOrAlias)}', queryParameters: { if (serverName != null) 'server_name': serverName, + if (via != null) 'via': via, }); final request = Request('POST', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; @@ -2286,17 +2291,21 @@ class Api { /// [serverName] The servers to attempt to knock on the room through. One of the servers /// must be participating in the room. /// + /// [via] The servers to attempt to knock on the room through. One of the servers + /// must be participating in the room. + /// /// [reason] Optional reason to be included as the `reason` on the subsequent /// membership event. /// /// returns `room_id`: /// The knocked room ID. Future knockRoom(String roomIdOrAlias, - {List? serverName, String? reason}) async { + {List? serverName, List? via, String? reason}) async { final requestUri = Uri( path: '_matrix/client/v3/knock/${Uri.encodeComponent(roomIdOrAlias)}', queryParameters: { if (serverName != null) 'server_name': serverName, + if (via != null) 'via': via, }); final request = Request('POST', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; @@ -2523,8 +2532,7 @@ class Api { /// Get the combined profile information for this user. This API may be used /// to fetch the user's own profile information or other users; either - /// locally or on remote homeservers. This API may return keys which are not - /// limited to `displayname` or `avatar_url`. + /// locally or on remote homeservers. /// /// [userId] The user whose profile information to get. Future getUserProfile(String userId) async { @@ -2734,10 +2742,8 @@ class Api { : null)(json['pushers']); } - /// Retrieve all push rulesets for this user. Clients can "drill-down" on - /// the rulesets by suffixing a `scope` to this path e.g. - /// `/pushrules/global/`. This will return a subset of this data under the - /// specified key e.g. the `global` key. + /// Retrieve all push rulesets for this user. Currently the only push ruleset + /// defined is `global`. /// /// returns `global`: /// The global ruleset. @@ -2753,20 +2759,30 @@ class Api { return PushRuleSet.fromJson(json['global'] as Map); } + /// Retrieve all push rules for this user. + Future getPushRulesGlobal() async { + final requestUri = Uri(path: '_matrix/client/v3/pushrules/global/'); + final request = Request('GET', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + final response = await httpClient.send(request); + final responseBody = await response.stream.toBytes(); + if (response.statusCode != 200) unexpectedResponse(response, responseBody); + final responseString = utf8.decode(responseBody); + final json = jsonDecode(responseString); + return GetPushRulesGlobalResponse.fromJson(json as Map); + } + /// This endpoint removes the push rule defined in the path. /// - /// [scope] `global` to specify global rules. - /// /// [kind] The kind of rule /// /// /// [ruleId] The identifier for the rule. /// - Future deletePushRule( - String scope, PushRuleKind kind, String ruleId) async { + Future deletePushRule(PushRuleKind kind, String ruleId) async { final requestUri = Uri( path: - '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}'); + '_matrix/client/v3/pushrules/global/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}'); final request = Request('DELETE', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; final response = await httpClient.send(request); @@ -2779,18 +2795,15 @@ class Api { /// Retrieve a single specified push rule. /// - /// [scope] `global` to specify global rules. - /// /// [kind] The kind of rule /// /// /// [ruleId] The identifier for the rule. /// - Future getPushRule( - String scope, PushRuleKind kind, String ruleId) async { + Future getPushRule(PushRuleKind kind, String ruleId) async { final requestUri = Uri( path: - '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}'); + '_matrix/client/v3/pushrules/global/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}'); final request = Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; final response = await httpClient.send(request); @@ -2818,8 +2831,6 @@ class Api { /// /// When creating push rules, they MUST be enabled by default. /// - /// [scope] `global` to specify global rules. - /// /// [kind] The kind of rule /// /// @@ -2844,14 +2855,14 @@ class Api { /// /// [pattern] Only applicable to `content` rules. The glob-style pattern to match against. Future setPushRule( - String scope, PushRuleKind kind, String ruleId, List actions, + PushRuleKind kind, String ruleId, List actions, {String? before, String? after, List? conditions, String? pattern}) async { final requestUri = Uri( path: - '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}', + '_matrix/client/v3/pushrules/global/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}', queryParameters: { if (before != null) 'before': before, if (after != null) 'after': after, @@ -2875,9 +2886,6 @@ class Api { /// This endpoint get the actions for the specified push rule. /// - /// [scope] Either `global` or `device/` to specify global - /// rules or device rules for the given `profile_tag`. - /// /// [kind] The kind of rule /// /// @@ -2887,10 +2895,10 @@ class Api { /// returns `actions`: /// The action(s) to perform for this rule. Future> getPushRuleActions( - String scope, PushRuleKind kind, String ruleId) async { + PushRuleKind kind, String ruleId) async { final requestUri = Uri( path: - '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/actions'); + '_matrix/client/v3/pushrules/global/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/actions'); final request = Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; final response = await httpClient.send(request); @@ -2904,8 +2912,6 @@ class Api { /// This endpoint allows clients to change the actions of a push rule. /// This can be used to change the actions of builtin rules. /// - /// [scope] `global` to specify global rules. - /// /// [kind] The kind of rule /// /// @@ -2913,11 +2919,11 @@ class Api { /// /// /// [actions] The action(s) to perform for this rule. - Future setPushRuleActions(String scope, PushRuleKind kind, - String ruleId, List actions) async { + Future setPushRuleActions( + PushRuleKind kind, String ruleId, List actions) async { final requestUri = Uri( path: - '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/actions'); + '_matrix/client/v3/pushrules/global/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/actions'); final request = Request('PUT', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; request.headers['content-type'] = 'application/json'; @@ -2934,9 +2940,6 @@ class Api { /// This endpoint gets whether the specified push rule is enabled. /// - /// [scope] Either `global` or `device/` to specify global - /// rules or device rules for the given `profile_tag`. - /// /// [kind] The kind of rule /// /// @@ -2945,11 +2948,10 @@ class Api { /// /// returns `enabled`: /// Whether the push rule is enabled or not. - Future isPushRuleEnabled( - String scope, PushRuleKind kind, String ruleId) async { + Future isPushRuleEnabled(PushRuleKind kind, String ruleId) async { final requestUri = Uri( path: - '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/enabled'); + '_matrix/client/v3/pushrules/global/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/enabled'); final request = Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; final response = await httpClient.send(request); @@ -2962,8 +2964,6 @@ class Api { /// This endpoint allows clients to enable or disable the specified push rule. /// - /// [scope] `global` to specify global rules. - /// /// [kind] The kind of rule /// /// @@ -2972,10 +2972,10 @@ class Api { /// /// [enabled] Whether the push rule is enabled or not. Future setPushRuleEnabled( - String scope, PushRuleKind kind, String ruleId, bool enabled) async { + PushRuleKind kind, String ruleId, bool enabled) async { final requestUri = Uri( path: - '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/enabled'); + '_matrix/client/v3/pushrules/global/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/enabled'); final request = Request('PUT', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; request.headers['content-type'] = 'application/json'; @@ -5386,7 +5386,12 @@ class Api { /// /// [body] /// - /// [contentType] The content type of the file being uploaded + /// [contentType] **Optional.** The content type of the file being uploaded. + /// + /// Clients SHOULD always supply this header. + /// + /// Defaults to `application/octet-stream` if it is not set. + /// /// /// returns `content_uri`: /// The [`mxc://` URI](https://spec.matrix.org/unstable/client-server-api/#matrix-content-mxc-uris) to the uploaded content. @@ -5422,7 +5427,12 @@ class Api { /// /// [body] /// - /// [contentType] The content type of the file being uploaded + /// [contentType] **Optional.** The content type of the file being uploaded. + /// + /// Clients SHOULD always supply this header. + /// + /// Defaults to `application/octet-stream` if it is not set. + /// Future> uploadContentToMXC( String serverName, String mediaId, Uint8List body, {String? filename, String? contentType}) async { diff --git a/lib/matrix_api_lite/generated/model.dart b/lib/matrix_api_lite/generated/model.dart index ac11a296..33ff746c 100644 --- a/lib/matrix_api_lite/generated/model.dart +++ b/lib/matrix_api_lite/generated/model.dart @@ -87,7 +87,7 @@ class DiscoveryInformation { additionalProperties = Map.fromEntries(json.entries .where( (e) => !['m.homeserver', 'm.identity_server'].contains(e.key)) - .map((e) => MapEntry(e.key, e.value as Map))); + .map((e) => MapEntry(e.key, e.value))); Map toJson() { final mIdentityServer = this.mIdentityServer; return { @@ -104,7 +104,7 @@ class DiscoveryInformation { /// Used by clients to discover identity server information. IdentityServerInformation? mIdentityServer; - Map> additionalProperties; + Map additionalProperties; @dart.override bool operator ==(Object other) => @@ -1354,24 +1354,24 @@ class WhoIsInfo { /// @_NameSource('spec') -class ChangePasswordCapability { - ChangePasswordCapability({ +class BooleanCapability { + BooleanCapability({ required this.enabled, }); - ChangePasswordCapability.fromJson(Map json) + BooleanCapability.fromJson(Map json) : enabled = json['enabled'] as bool; Map toJson() => { 'enabled': enabled, }; - /// True if the user can change their password, false otherwise. + /// True if the user can perform the action, false otherwise. bool enabled; @dart.override bool operator ==(Object other) => identical(this, other) || - (other is ChangePasswordCapability && + (other is BooleanCapability && other.runtimeType == runtimeType && other.enabled == enabled); @@ -1428,51 +1428,99 @@ class RoomVersionsCapability { @_NameSource('spec') class Capabilities { Capabilities({ + this.m3pidChanges, this.mChangePassword, + this.mGetLoginToken, this.mRoomVersions, + this.mSetAvatarUrl, + this.mSetDisplayname, this.additionalProperties = const {}, }); Capabilities.fromJson(Map json) - : mChangePassword = ((v) => v != null - ? ChangePasswordCapability.fromJson(v as Map) + : m3pidChanges = ((v) => v != null + ? BooleanCapability.fromJson(v as Map) + : null)(json['m.3pid_changes']), + mChangePassword = ((v) => v != null + ? BooleanCapability.fromJson(v as Map) : null)(json['m.change_password']), + mGetLoginToken = ((v) => v != null + ? BooleanCapability.fromJson(v as Map) + : null)(json['m.get_login_token']), mRoomVersions = ((v) => v != null ? RoomVersionsCapability.fromJson(v as Map) : null)(json['m.room_versions']), + mSetAvatarUrl = ((v) => v != null + ? BooleanCapability.fromJson(v as Map) + : null)(json['m.set_avatar_url']), + mSetDisplayname = ((v) => v != null + ? BooleanCapability.fromJson(v as Map) + : null)(json['m.set_displayname']), additionalProperties = Map.fromEntries(json.entries - .where((e) => - !['m.change_password', 'm.room_versions'].contains(e.key)) - .map((e) => MapEntry(e.key, e.value as Map))); + .where((e) => ![ + 'm.3pid_changes', + 'm.change_password', + 'm.get_login_token', + 'm.room_versions', + 'm.set_avatar_url', + 'm.set_displayname' + ].contains(e.key)) + .map((e) => MapEntry(e.key, e.value))); Map toJson() { + final m3pidChanges = this.m3pidChanges; final mChangePassword = this.mChangePassword; + final mGetLoginToken = this.mGetLoginToken; final mRoomVersions = this.mRoomVersions; + final mSetAvatarUrl = this.mSetAvatarUrl; + final mSetDisplayname = this.mSetDisplayname; return { ...additionalProperties, + if (m3pidChanges != null) 'm.3pid_changes': m3pidChanges.toJson(), if (mChangePassword != null) 'm.change_password': mChangePassword.toJson(), + if (mGetLoginToken != null) 'm.get_login_token': mGetLoginToken.toJson(), if (mRoomVersions != null) 'm.room_versions': mRoomVersions.toJson(), + if (mSetAvatarUrl != null) 'm.set_avatar_url': mSetAvatarUrl.toJson(), + if (mSetDisplayname != null) + 'm.set_displayname': mSetDisplayname.toJson(), }; } + /// Capability to indicate if the user can change 3PID associations on their account. + BooleanCapability? m3pidChanges; + /// Capability to indicate if the user can change their password. - ChangePasswordCapability? mChangePassword; + BooleanCapability? mChangePassword; + + /// Capability to indicate if the user can generate tokens to log further clients into their account. + BooleanCapability? mGetLoginToken; /// The room versions the server supports. RoomVersionsCapability? mRoomVersions; - Map> additionalProperties; + /// Capability to indicate if the user can change their avatar. + BooleanCapability? mSetAvatarUrl; + + /// Capability to indicate if the user can change their display name. + BooleanCapability? mSetDisplayname; + + Map additionalProperties; @dart.override bool operator ==(Object other) => identical(this, other) || (other is Capabilities && other.runtimeType == runtimeType && + other.m3pidChanges == m3pidChanges && other.mChangePassword == mChangePassword && - other.mRoomVersions == mRoomVersions); + other.mGetLoginToken == mGetLoginToken && + other.mRoomVersions == mRoomVersions && + other.mSetAvatarUrl == mSetAvatarUrl && + other.mSetDisplayname == mSetDisplayname); @dart.override - int get hashCode => Object.hash(mChangePassword, mRoomVersions); + int get hashCode => Object.hash(m3pidChanges, mChangePassword, mGetLoginToken, + mRoomVersions, mSetAvatarUrl, mSetDisplayname); } /// @@ -3006,6 +3054,90 @@ class PushRuleSet { int get hashCode => Object.hash(content, override, room, sender, underride); } +/// +@_NameSource('generated') +class GetPushRulesGlobalResponse { + GetPushRulesGlobalResponse({ + this.content, + this.override, + this.room, + this.sender, + this.underride, + }); + + GetPushRulesGlobalResponse.fromJson(Map json) + : content = ((v) => v != null + ? (v as List) + .map((v) => PushRule.fromJson(v as Map)) + .toList() + : null)(json['content']), + override = ((v) => v != null + ? (v as List) + .map((v) => PushRule.fromJson(v as Map)) + .toList() + : null)(json['override']), + room = ((v) => v != null + ? (v as List) + .map((v) => PushRule.fromJson(v as Map)) + .toList() + : null)(json['room']), + sender = ((v) => v != null + ? (v as List) + .map((v) => PushRule.fromJson(v as Map)) + .toList() + : null)(json['sender']), + underride = ((v) => v != null + ? (v as List) + .map((v) => PushRule.fromJson(v as Map)) + .toList() + : null)(json['underride']); + Map toJson() { + final content = this.content; + final override = this.override; + final room = this.room; + final sender = this.sender; + final underride = this.underride; + return { + if (content != null) 'content': content.map((v) => v.toJson()).toList(), + if (override != null) + 'override': override.map((v) => v.toJson()).toList(), + if (room != null) 'room': room.map((v) => v.toJson()).toList(), + if (sender != null) 'sender': sender.map((v) => v.toJson()).toList(), + if (underride != null) + 'underride': underride.map((v) => v.toJson()).toList(), + }; + } + + /// + List? content; + + /// + List? override; + + /// + List? room; + + /// + List? sender; + + /// + List? underride; + + @dart.override + bool operator ==(Object other) => + identical(this, other) || + (other is GetPushRulesGlobalResponse && + other.runtimeType == runtimeType && + other.content == content && + other.override == override && + other.room == room && + other.sender == sender && + other.underride == underride); + + @dart.override + int get hashCode => Object.hash(content, override, room, sender, underride); +} + /// @_NameSource('rule override generated') @EnhancedEnum() @@ -4734,7 +4866,7 @@ class FieldType { 'regexp': regexp, }; - /// An placeholder serving as a valid example of the field value. + /// A placeholder serving as a valid example of the field value. String placeholder; /// A regular expression for validation of a field's value. This may be relatively @@ -4836,9 +4968,9 @@ class Protocol { 'user_fields': userFields.map((v) => v).toList(), }; - /// The type definitions for the fields defined in the `user_fields` and - /// `location_fields`. Each entry in those arrays MUST have an entry here. The - /// `string` key for this object is field name itself. + /// The type definitions for the fields defined in `user_fields` and + /// `location_fields`. Each entry in those arrays MUST have an entry here. + /// The `string` key for this object is the field name itself. /// /// May be an empty object if no fields are defined. Map fieldTypes; @@ -4904,7 +5036,7 @@ class ThirdPartyUser { /// The protocol ID that the third-party location is a part of. String protocol; - /// A Matrix User ID represting a third-party user. + /// A Matrix User ID representing a third-party user. String userid; @dart.override diff --git a/lib/src/client.dart b/lib/src/client.dart index fc6df426..1357ff19 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -3369,7 +3369,6 @@ class Client extends MatrixApi { Future setMuteAllPushNotifications(bool muted) async { await setPushRuleEnabled( - 'global', PushRuleKind.override, '.m.rule.master', muted, @@ -3377,6 +3376,22 @@ class Client extends MatrixApi { return; } + /// preference is always given to via over serverName, irrespective of what field + /// you are trying to use + @override + Future joinRoom(String roomIdOrAlias, + {List? serverName, + List? via, + String? reason, + ThirdPartySigned? thirdPartySigned}) => + super.joinRoom( + roomIdOrAlias, + serverName: via ?? serverName, + via: via ?? serverName, + reason: reason, + thirdPartySigned: thirdPartySigned, + ); + /// Changes the password. You should either set oldPasswort or another authentication flow. @override Future changePassword( diff --git a/lib/src/room.dart b/lib/src/room.dart index bdef55e4..95392b68 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -2067,24 +2067,22 @@ class Room { // All push notifications should be sent to the user case PushRuleState.notify: if (pushRuleState == PushRuleState.dontNotify) { - await client.deletePushRule('global', PushRuleKind.override, id); + await client.deletePushRule(PushRuleKind.override, id); } else if (pushRuleState == PushRuleState.mentionsOnly) { - await client.deletePushRule('global', PushRuleKind.room, id); + await client.deletePushRule(PushRuleKind.room, id); } break; // Only when someone mentions the user, a push notification should be sent case PushRuleState.mentionsOnly: if (pushRuleState == PushRuleState.dontNotify) { - await client.deletePushRule('global', PushRuleKind.override, id); + await client.deletePushRule(PushRuleKind.override, id); await client.setPushRule( - 'global', PushRuleKind.room, id, [PushRuleAction.dontNotify], ); } else if (pushRuleState == PushRuleState.notify) { await client.setPushRule( - 'global', PushRuleKind.room, id, [PushRuleAction.dontNotify], @@ -2094,10 +2092,9 @@ class Room { // No push notification should be ever sent for this room. case PushRuleState.dontNotify: if (pushRuleState == PushRuleState.mentionsOnly) { - await client.deletePushRule('global', PushRuleKind.room, id); + await client.deletePushRule(PushRuleKind.room, id); } await client.setPushRule( - 'global', PushRuleKind.override, id, [PushRuleAction.dontNotify],