diff --git a/lib/matrix.dart b/lib/matrix.dart index 3187ecbf..6b271890 100644 --- a/lib/matrix.dart +++ b/lib/matrix.dart @@ -73,6 +73,7 @@ export 'src/utils/sync_update_extension.dart'; export 'src/utils/to_device_event.dart'; export 'src/utils/uia_request.dart'; export 'src/utils/uri_extension.dart'; +export 'src/models/login_type.dart'; export 'msc_extensions/extension_recent_emoji/recent_emoji.dart'; export 'msc_extensions/msc_3935_cute_events/msc_3935_cute_events.dart'; diff --git a/lib/matrix_api_lite/generated/api.dart b/lib/matrix_api_lite/generated/api.dart index 1e244738..c7c026cd 100644 --- a/lib/matrix_api_lite/generated/api.dart +++ b/lib/matrix_api_lite/generated/api.dart @@ -12,6 +12,8 @@ import 'package:matrix/matrix_api_lite/model/matrix_event.dart'; import 'package:matrix/matrix_api_lite/model/matrix_keys.dart'; import 'package:matrix/matrix_api_lite/model/sync_update.dart'; +// ignore_for_file: provide_deprecation_message + class Api { Client httpClient; Uri? baseUri; @@ -45,6 +47,326 @@ class Api { return DiscoveryInformation.fromJson(json as Map); } + /// Gets server admin contact and support page of the domain. + /// + /// Like the [well-known discovery URI](https://spec.matrix.org/unstable/client-server-api/#well-known-uri), + /// this should be accessed with the hostname of the homeserver by making a + /// GET request to `https://hostname/.well-known/matrix/support`. + /// + /// Note that this endpoint is not necessarily handled by the homeserver. + /// It may be served by another webserver, used for discovering support + /// information for the homeserver. + Future getWellknownSupport() async { + final requestUri = Uri(path: '.well-known/matrix/support'); + final request = Request('GET', baseUri!.resolveUri(requestUri)); + 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 GetWellknownSupportResponse.fromJson(json as Map); + } + + /// This API asks the homeserver to call the + /// [`/_matrix/app/v1/ping`](#post_matrixappv1ping) endpoint on the + /// application service to ensure that the homeserver can communicate + /// with the application service. + /// + /// This API requires the use of an application service access token (`as_token`) + /// instead of a typical client's access token. This API cannot be invoked by + /// users who are not identified as application services. Additionally, the + /// appservice ID in the path must be the same as the appservice whose `as_token` + /// is being used. + /// + /// [appserviceId] The appservice ID of the appservice to ping. This must be the same + /// as the appservice whose `as_token` is being used to authenticate the + /// request. + /// + /// [transactionId] An optional transaction ID that is passed through to the `/_matrix/app/v1/ping` call. + /// + /// returns `duration_ms`: + /// The duration in milliseconds that the + /// [`/_matrix/app/v1/ping`](#post_matrixappv1ping) + /// request took from the homeserver's point of view. + Future pingAppservice(String appserviceId, + {String? transactionId}) async { + final requestUri = Uri( + path: + '_matrix/client/v1/appservice/${Uri.encodeComponent(appserviceId)}/ping'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (transactionId != null) 'transaction_id': transactionId, + })); + 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 json['duration_ms'] as int; + } + + /// Optional endpoint - the server is not required to implement this endpoint if it does not + /// intend to use or support this functionality. + /// + /// This API endpoint uses the [User-Interactive Authentication API](https://spec.matrix.org/unstable/client-server-api/#user-interactive-authentication-api). + /// + /// An already-authenticated client can call this endpoint to generate a single-use, time-limited, + /// token for an unauthenticated client to log in with, becoming logged in as the same user which + /// called this endpoint. The unauthenticated client uses the generated token in a `m.login.token` + /// login flow with the homeserver. + /// + /// 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), + /// 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). + /// + /// In v1.7 of the specification, transmission of the generated token to an unauthenticated client is + /// left as an implementation detail. Future MSCs such as [MSC3906](https://github.com/matrix-org/matrix-spec-proposals/pull/3906) + /// might standardise a way to transmit the token between clients. + /// + /// The generated token MUST only be valid for a single login, enforced by the server. Clients which + /// intend to log in multiple devices must generate a token for each. + /// + /// With other User-Interactive Authentication (UIA)-supporting endpoints, servers sometimes do not re-prompt + /// for verification if the session recently passed UIA. For this endpoint, servers MUST always re-prompt + /// the user for verification to ensure explicit consent is gained for each additional client. + /// + /// Servers are encouraged to apply stricter than normal rate limiting to this endpoint, such as maximum + /// of 1 request per minute. + /// + /// [auth] Additional authentication information for the user-interactive authentication API. + Future generateLoginToken( + {AuthenticationData? auth}) async { + final requestUri = Uri(path: '_matrix/client/v1/login/get_token'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (auth != null) 'auth': auth.toJson(), + })); + 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 GenerateLoginTokenResponse.fromJson(json as Map); + } + + /// This endpoint allows clients to retrieve the configuration of the content + /// repository, such as upload limitations. + /// Clients SHOULD use this as a guide when using content repository endpoints. + /// All values are intentionally left optional. Clients SHOULD follow + /// the advice given in the field description when the field is not available. + /// + /// {{% boxes/note %}} + /// Both clients and server administrators should be aware that proxies + /// between the client and the server may affect the apparent behaviour of content + /// repository APIs, for example, proxies may enforce a lower upload size limit + /// than is advertised by the server on this endpoint. + /// {{% /boxes/note %}} + Future getConfigAuthed() async { + final requestUri = Uri(path: '_matrix/client/v1/media/config'); + 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 MediaConfig.fromJson(json as Map); + } + + /// {{% boxes/note %}} + /// Clients SHOULD NOT generate or use URLs which supply the access token in + /// the query string. These URLs may be copied by users verbatim and provided + /// in a chat message to another user, disclosing the sender's access token. + /// {{% /boxes/note %}} + /// + /// Clients MAY be redirected using the 307/308 responses below to download + /// the request object. This is typical when the homeserver uses a Content + /// Delivery Network (CDN). + /// + /// [serverName] The server name from the `mxc://` URI (the authority component). + /// + /// + /// [mediaId] The media ID from the `mxc://` URI (the path component). + /// + /// + /// [timeoutMs] The maximum number of milliseconds that the client is willing to wait to + /// start receiving data, in the case that the content has not yet been + /// uploaded. The default value is 20000 (20 seconds). The content + /// repository SHOULD impose a maximum value for this parameter. The + /// content repository MAY respond before the timeout. + /// + Future getContentAuthed(String serverName, String mediaId, + {int? timeoutMs}) async { + final requestUri = Uri( + path: + '_matrix/client/v1/media/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}', + queryParameters: { + if (timeoutMs != null) 'timeout_ms': timeoutMs.toString(), + }); + 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); + return FileResponse( + contentType: response.headers['content-type'], data: responseBody); + } + + /// This will download content from the content repository (same as + /// the previous endpoint) but replaces the target file name with the one + /// provided by the caller. + /// + /// {{% boxes/note %}} + /// Clients SHOULD NOT generate or use URLs which supply the access token in + /// the query string. These URLs may be copied by users verbatim and provided + /// in a chat message to another user, disclosing the sender's access token. + /// {{% /boxes/note %}} + /// + /// Clients MAY be redirected using the 307/308 responses below to download + /// the request object. This is typical when the homeserver uses a Content + /// Delivery Network (CDN). + /// + /// [serverName] The server name from the `mxc://` URI (the authority component). + /// + /// + /// [mediaId] The media ID from the `mxc://` URI (the path component). + /// + /// + /// [fileName] A filename to give in the `Content-Disposition` header. + /// + /// [timeoutMs] The maximum number of milliseconds that the client is willing to wait to + /// start receiving data, in the case that the content has not yet been + /// uploaded. The default value is 20000 (20 seconds). The content + /// repository SHOULD impose a maximum value for this parameter. The + /// content repository MAY respond before the timeout. + /// + Future getContentOverrideNameAuthed( + String serverName, String mediaId, String fileName, + {int? timeoutMs}) async { + final requestUri = Uri( + path: + '_matrix/client/v1/media/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}/${Uri.encodeComponent(fileName)}', + queryParameters: { + if (timeoutMs != null) 'timeout_ms': timeoutMs.toString(), + }); + 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); + return FileResponse( + contentType: response.headers['content-type'], data: responseBody); + } + + /// Get information about a URL for the client. Typically this is called when a + /// client sees a URL in a message and wants to render a preview for the user. + /// + /// {{% boxes/note %}} + /// Clients should consider avoiding this endpoint for URLs posted in encrypted + /// rooms. Encrypted rooms often contain more sensitive information the users + /// do not want to share with the homeserver, and this can mean that the URLs + /// being shared should also not be shared with the homeserver. + /// {{% /boxes/note %}} + /// + /// [url] The URL to get a preview of. + /// + /// [ts] The preferred point in time to return a preview for. The server may + /// return a newer version if it does not have the requested version + /// available. + Future getUrlPreviewAuthed(Uri url, {int? ts}) async { + final requestUri = + Uri(path: '_matrix/client/v1/media/preview_url', queryParameters: { + 'url': url.toString(), + if (ts != null) 'ts': ts.toString(), + }); + 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 PreviewForUrl.fromJson(json as Map); + } + + /// Download a thumbnail of content from the content repository. + /// See the [Thumbnails](https://spec.matrix.org/unstable/client-server-api/#thumbnails) section for more information. + /// + /// {{% boxes/note %}} + /// Clients SHOULD NOT generate or use URLs which supply the access token in + /// the query string. These URLs may be copied by users verbatim and provided + /// in a chat message to another user, disclosing the sender's access token. + /// {{% /boxes/note %}} + /// + /// Clients MAY be redirected using the 307/308 responses below to download + /// the request object. This is typical when the homeserver uses a Content + /// Delivery Network (CDN). + /// + /// [serverName] The server name from the `mxc://` URI (the authority component). + /// + /// + /// [mediaId] The media ID from the `mxc://` URI (the path component). + /// + /// + /// [width] The *desired* width of the thumbnail. The actual thumbnail may be + /// larger than the size specified. + /// + /// [height] The *desired* height of the thumbnail. The actual thumbnail may be + /// larger than the size specified. + /// + /// [method] The desired resizing method. See the [Thumbnails](https://spec.matrix.org/unstable/client-server-api/#thumbnails) + /// section for more information. + /// + /// [timeoutMs] The maximum number of milliseconds that the client is willing to wait to + /// start receiving data, in the case that the content has not yet been + /// uploaded. The default value is 20000 (20 seconds). The content + /// repository SHOULD impose a maximum value for this parameter. The + /// content repository MAY respond before the timeout. + /// + /// + /// [animated] Indicates preference for an animated thumbnail from the server, if possible. Animated + /// thumbnails typically use the content types `image/gif`, `image/png` (with APNG format), + /// `image/apng`, and `image/webp` instead of the common static `image/png` or `image/jpeg` + /// content types. + /// + /// When `true`, the server SHOULD return an animated thumbnail if possible and supported. + /// When `false`, the server MUST NOT return an animated thumbnail. For example, returning a + /// static `image/png` or `image/jpeg` thumbnail. When not provided, the server SHOULD NOT + /// return an animated thumbnail. + /// + /// Servers SHOULD prefer to return `image/webp` thumbnails when supporting animation. + /// + /// When `true` and the media cannot be animated, such as in the case of a JPEG or PDF, the + /// server SHOULD behave as though `animated` is `false`. + /// + Future getContentThumbnailAuthed( + String serverName, String mediaId, int width, int height, + {Method? method, int? timeoutMs, bool? animated}) async { + final requestUri = Uri( + path: + '_matrix/client/v1/media/thumbnail/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}', + queryParameters: { + 'width': width.toString(), + 'height': height.toString(), + if (method != null) 'method': method.name, + if (timeoutMs != null) 'timeout_ms': timeoutMs.toString(), + if (animated != null) 'animated': animated.toString(), + }); + 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); + return FileResponse( + contentType: response.headers['content-type'], data: responseBody); + } + /// Queries the server to determine if a given registration token is still /// valid at the time of request. This is a point-in-time check where the /// token might still expire by the time it is used. @@ -157,9 +479,26 @@ class Api { /// will be returned in chronological order starting at `from`. If it /// is set to `b`, events will be returned in *reverse* chronological /// order, again starting at `from`. + /// + /// [recurse] Whether to additionally include events which only relate indirectly to the + /// given event, i.e. events related to the given event via two or more direct relationships. + /// + /// If set to `false`, only events which have a direct relation with the given + /// event will be included. + /// + /// If set to `true`, events which have an indirect relation with the given event + /// will be included additionally up to a certain depth level. Homeservers SHOULD traverse + /// at least 3 levels of relationships. Implementations MAY perform more but MUST be careful + /// to not infinitely recurse. + /// + /// The default value is `false`. Future getRelatingEvents( String roomId, String eventId, - {String? from, String? to, int? limit, Direction? dir}) async { + {String? from, + String? to, + int? limit, + Direction? dir, + bool? recurse}) async { final requestUri = Uri( path: '_matrix/client/v1/rooms/${Uri.encodeComponent(roomId)}/relations/${Uri.encodeComponent(eventId)}', @@ -168,6 +507,7 @@ class Api { if (to != null) 'to': to, if (limit != null) 'limit': limit.toString(), if (dir != null) 'dir': dir.name, + if (recurse != null) 'recurse': recurse.toString(), }); final request = Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; @@ -218,9 +558,26 @@ class Api { /// will be returned in chronological order starting at `from`. If it /// is set to `b`, events will be returned in *reverse* chronological /// order, again starting at `from`. + /// + /// [recurse] Whether to additionally include events which only relate indirectly to the + /// given event, i.e. events related to the given event via two or more direct relationships. + /// + /// If set to `false`, only events which have a direct relation with the given + /// event will be included. + /// + /// If set to `true`, events which have an indirect relation with the given event + /// will be included additionally up to a certain depth level. Homeservers SHOULD traverse + /// at least 3 levels of relationships. Implementations MAY perform more but MUST be careful + /// to not infinitely recurse. + /// + /// The default value is `false`. Future getRelatingEventsWithRelType( String roomId, String eventId, String relType, - {String? from, String? to, int? limit, Direction? dir}) async { + {String? from, + String? to, + int? limit, + Direction? dir, + bool? recurse}) async { final requestUri = Uri( path: '_matrix/client/v1/rooms/${Uri.encodeComponent(roomId)}/relations/${Uri.encodeComponent(eventId)}/${Uri.encodeComponent(relType)}', @@ -229,6 +586,7 @@ class Api { if (to != null) 'to': to, if (limit != null) 'limit': limit.toString(), if (dir != null) 'dir': dir.name, + if (recurse != null) 'recurse': recurse.toString(), }); final request = Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; @@ -285,10 +643,27 @@ class Api { /// will be returned in chronological order starting at `from`. If it /// is set to `b`, events will be returned in *reverse* chronological /// order, again starting at `from`. + /// + /// [recurse] Whether to additionally include events which only relate indirectly to the + /// given event, i.e. events related to the given event via two or more direct relationships. + /// + /// If set to `false`, only events which have a direct relation with the given + /// event will be included. + /// + /// If set to `true`, events which have an indirect relation with the given event + /// will be included additionally up to a certain depth level. Homeservers SHOULD traverse + /// at least 3 levels of relationships. Implementations MAY perform more but MUST be careful + /// to not infinitely recurse. + /// + /// The default value is `false`. Future getRelatingEventsWithRelTypeAndEventType( String roomId, String eventId, String relType, String eventType, - {String? from, String? to, int? limit, Direction? dir}) async { + {String? from, + String? to, + int? limit, + Direction? dir, + bool? recurse}) async { final requestUri = Uri( path: '_matrix/client/v1/rooms/${Uri.encodeComponent(roomId)}/relations/${Uri.encodeComponent(eventId)}/${Uri.encodeComponent(relType)}/${Uri.encodeComponent(eventType)}', @@ -297,6 +672,7 @@ class Api { if (to != null) 'to': to, if (limit != null) 'limit': limit.toString(), if (dir != null) 'dir': dir.name, + if (recurse != null) 'recurse': recurse.toString(), }); final request = Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; @@ -309,8 +685,10 @@ class Api { json as Map); } - /// Paginates over the thread roots in a room, ordered by the `latest_event` of each thread root - /// in its bundle. + /// This API is used to paginate through the list of the thread roots in a given room. + /// + /// Optionally, the returned list may be filtered according to whether the requesting + /// user has participated in the thread. /// /// [roomId] The room ID where the thread roots are located. /// @@ -394,10 +772,10 @@ class Api { return GetEventByTimestampResponse.fromJson(json as Map); } - /// Gets a list of the third party identifiers that the homeserver has + /// Gets a list of the third-party identifiers that the homeserver has /// associated with the user's account. /// - /// This is *not* the same as the list of third party identifiers bound to + /// This is *not* the same as the list of third-party identifiers bound to /// the user's Matrix ID in identity servers. /// /// Identifiers in this list may be used by the homeserver as, for example, @@ -433,7 +811,7 @@ class Api { /// This results in this endpoint being an equivalent to `/3pid/bind` rather /// than dual-purpose. /// - /// [threePidCreds] The third party credentials to associate with the account. + /// [threePidCreds] The third-party credentials to associate with the account. /// /// returns `submit_url`: /// An optional field containing a URL where the client must @@ -448,7 +826,7 @@ class Api { /// verification will happen without the client's involvement /// provided the homeserver advertises this specification version /// in the `/versions` response (ie: r0.5.0). - @Deprecated('message') + @deprecated Future post3PIDs(ThreePidCredentials threePidCreds) async { final requestUri = Uri(path: '_matrix/client/v3/account/3pid'); final request = Request('POST', baseUri!.resolveUri(requestUri)); @@ -534,21 +912,21 @@ class Api { return ignore(json); } - /// Removes a third party identifier from the user's account. This might not + /// Removes a third-party identifier from the user's account. This might not /// cause an unbind of the identifier from the identity server. /// /// Unlike other endpoints, this endpoint does not take an `id_access_token` /// parameter because the homeserver is expected to sign the request to the /// identity server instead. /// - /// [address] The third party address being removed. + /// [address] The third-party address being removed. /// /// [idServer] The identity server to unbind from. If not provided, the homeserver /// MUST use the `id_server` the identifier was added through. If the /// homeserver does not know the original `id_server`, it MUST return /// a `id_server_unbind_result` of `no-support`. /// - /// [medium] The medium of the third party identifier being removed. + /// [medium] The medium of the third-party identifier being removed. /// /// returns `id_server_unbind_result`: /// An indicator as to whether or not the homeserver was able to unbind @@ -711,21 +1089,21 @@ class Api { return RequestTokenResponse.fromJson(json as Map); } - /// Removes a user's third party identifier from the provided identity server + /// Removes a user's third-party identifier from the provided identity server /// without removing it from the homeserver. /// /// Unlike other endpoints, this endpoint does not take an `id_access_token` /// parameter because the homeserver is expected to sign the request to the /// identity server instead. /// - /// [address] The third party address being removed. + /// [address] The third-party address being removed. /// /// [idServer] The identity server to unbind from. If not provided, the homeserver /// MUST use the `id_server` the identifier was added through. If the /// homeserver does not know the original `id_server`, it MUST return /// a `id_server_unbind_result` of `no-support`. /// - /// [medium] The medium of the third party identifier being removed. + /// [medium] The medium of the third-party identifier being removed. /// /// returns `id_server_unbind_result`: /// An indicator as to whether or not the identity server was able to unbind @@ -771,6 +1149,23 @@ class Api { /// /// [auth] Additional authentication information for the user-interactive authentication API. /// + /// [erase] Whether the user would like their content to be erased as + /// much as possible from the server. + /// + /// Erasure means that any users (or servers) which join the + /// room after the erasure request are served redacted copies of + /// the events sent by this account. Users which had visibility + /// on those events prior to the erasure are still able to see + /// unredacted copies. No redactions are sent and the erasure + /// request is not shared over federation, so other servers + /// might still serve unredacted copies. + /// + /// The server should additionally erase any non-event data + /// associated with the user, such as [account data](https://spec.matrix.org/unstable/client-server-api/#client-config) + /// and [contact 3PIDs](https://spec.matrix.org/unstable/client-server-api/#adding-account-administrative-contact-information). + /// + /// Defaults to `false` if not present. + /// /// [idServer] The identity server to unbind all of the user's 3PIDs from. /// If not provided, the homeserver MUST use the `id_server` /// that was originally use to bind each identifier. If the @@ -788,13 +1183,16 @@ class Api { /// must be `success` if the homeserver has no identifiers to unbind /// for the user. Future deactivateAccount( - {AuthenticationData? auth, String? idServer}) async { + {AuthenticationData? auth, bool? erase, String? idServer}) async { final requestUri = Uri(path: '_matrix/client/v3/account/deactivate'); final request = Request('POST', baseUri!.resolveUri(requestUri)); - request.headers['authorization'] = 'Bearer ${bearerToken!}'; + if (bearerToken != null) { + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + } request.headers['content-type'] = 'application/json'; request.bodyBytes = utf8.encode(jsonEncode({ if (auth != null) 'auth': auth.toJson(), + if (erase != null) 'erase': erase, if (idServer != null) 'id_server': idServer, })); final response = await httpClient.send(request); @@ -833,7 +1231,9 @@ class Api { {AuthenticationData? auth, bool? logoutDevices}) async { final requestUri = Uri(path: '_matrix/client/v3/account/password'); final request = Request('POST', baseUri!.resolveUri(requestUri)); - request.headers['authorization'] = 'Bearer ${bearerToken!}'; + if (bearerToken != null) { + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + } request.headers['content-type'] = 'application/json'; request.bodyBytes = utf8.encode(jsonEncode({ if (auth != null) 'auth': auth.toJson(), @@ -1111,7 +1511,7 @@ class Api { /// [invite] A list of user IDs to invite to the room. This will tell the /// server to invite everyone in the list to the newly created room. /// - /// [invite3pid] A list of objects representing third party IDs to invite into + /// [invite3pid] A list of objects representing third-party IDs to invite into /// the room. /// /// [isDirect] This flag makes the server set the `is_direct` flag on the @@ -1132,7 +1532,7 @@ class Api { /// based on a preset. /// /// If unspecified, the server should use the `visibility` to determine - /// which preset to use. A visbility of `public` equates to a preset of + /// which preset to use. A visibility of `public` equates to a preset of /// `public_chat` and `private` visibility equates to a preset of /// `private_chat`. /// @@ -1493,7 +1893,7 @@ class Api { /// request to this API or from the initial sync API. /// /// [timeout] The maximum time in milliseconds to wait for an event. - @Deprecated('message') + @deprecated Future getEvents({String? from, int? timeout}) async { final requestUri = Uri(path: '_matrix/client/v3/events', queryParameters: { if (from != null) 'from': from, @@ -1552,7 +1952,7 @@ class Api { /// or the [/rooms/{roomId}/context/{eventId](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3roomsroomidcontexteventid) API. /// /// [eventId] The event ID to get. - @Deprecated('message') + @deprecated Future getOneEvent(String eventId) async { final requestUri = Uri(path: '_matrix/client/v3/events/${Uri.encodeComponent(eventId)}'); @@ -1598,8 +1998,7 @@ class Api { final requestUri = Uri( path: '_matrix/client/v3/join/${Uri.encodeComponent(roomIdOrAlias)}', queryParameters: { - if (serverName != null) - 'server_name': serverName.map((v) => v).toList(), + if (serverName != null) 'server_name': serverName, }); final request = Request('POST', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; @@ -1700,6 +2099,19 @@ class Api { /// /// This API endpoint uses the [User-Interactive Authentication API](https://spec.matrix.org/unstable/client-server-api/#user-interactive-authentication-api). /// + /// User-Interactive Authentication MUST be performed, except in these cases: + /// - there is no existing cross-signing master key uploaded to the homeserver, OR + /// - there is an existing cross-signing master key and it exactly matches the + /// cross-signing master key provided in the request body. If there are any additional + /// keys provided in the request (self-signing key, user-signing key) they MUST also + /// match the existing keys stored on the server. In other words, the request contains + /// no new keys. + /// + /// This allows clients to freely upload one set of keys, but not modify/overwrite keys if + /// they already exist. Allowing clients to upload the same set of keys more than once + /// makes this endpoint idempotent in the case where the response is lost over the network, + /// which would otherwise cause a UIA challenge upon retry. + /// /// [auth] Additional authentication information for the /// user-interactive authentication API. /// @@ -1746,13 +2158,8 @@ class Api { /// /// [timeout] The time (in milliseconds) to wait when downloading keys from /// remote servers. 10 seconds is the recommended default. - /// - /// [token] If the client is fetching keys as a result of a device update received - /// in a sync request, this should be the 'since' token of that sync request, - /// or any later sync token. This allows the server to ensure its response - /// contains the keys advertised by the notification in that sync. Future queryKeys(Map> deviceKeys, - {int? timeout, String? token}) async { + {int? timeout}) async { final requestUri = Uri(path: '_matrix/client/v3/keys/query'); final request = Request('POST', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; @@ -1761,7 +2168,6 @@ class Api { 'device_keys': deviceKeys.map((k, v) => MapEntry(k, v.map((v) => v).toList())), if (timeout != null) 'timeout': timeout, - if (token != null) 'token': token, })); final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); @@ -1771,10 +2177,14 @@ class Api { return QueryKeysResponse.fromJson(json as Map); } - /// Publishes cross-signing signatures for the user. The request body is a - /// map from user ID to key ID to signed JSON object. + /// Publishes cross-signing signatures for the user. /// - /// [signatures] The signatures to be published. + /// The signed JSON object must match the key previously uploaded or + /// retrieved for the given key ID, with the exception of the `signatures` + /// property, which contains the new signature(s) to add. + /// + /// [body] A map from user ID to key ID to signed JSON objects containing the + /// signatures to be published. /// /// returns `failures`: /// A map from user ID to key ID to an error for any signatures @@ -1782,13 +2192,13 @@ class Api { /// be set to `M_INVALID_SIGNATURE`. Future>>?> uploadCrossSigningSignatures( - Map>> signatures) async { + Map>> body) async { final requestUri = Uri(path: '_matrix/client/v3/keys/signatures/upload'); final request = Request('POST', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; request.headers['content-type'] = 'application/json'; - request.bodyBytes = utf8.encode(jsonEncode(signatures - .map((k, v) => MapEntry(k, v.map((k, v) => MapEntry(k, v)))))); + request.bodyBytes = utf8.encode(jsonEncode( + body.map((k, v) => MapEntry(k, v.map((k, v) => MapEntry(k, v)))))); final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); if (response.statusCode != 200) unexpectedResponse(response, responseBody); @@ -1802,6 +2212,59 @@ class Api { : null)(json['failures']); } + /// Publishes end-to-end encryption keys for the device. + /// + /// [deviceKeys] Identity keys for the device. May be absent if no new + /// identity keys are required. + /// + /// [fallbackKeys] The public key which should be used if the device's one-time keys + /// are exhausted. The fallback key is not deleted once used, but should + /// be replaced when additional one-time keys are being uploaded. The + /// server will notify the client of the fallback key being used through + /// `/sync`. + /// + /// There can only be at most one key per algorithm uploaded, and the server + /// will only persist one key per algorithm. + /// + /// When uploading a signed key, an additional `fallback: true` key should + /// be included to denote that the key is a fallback key. + /// + /// May be absent if a new fallback key is not required. + /// + /// [oneTimeKeys] One-time public keys for "pre-key" messages. The names of + /// the properties should be in the format + /// `:`. The format of the key is determined + /// by the [key algorithm](https://spec.matrix.org/unstable/client-server-api/#key-algorithms). + /// + /// May be absent if no new one-time keys are required. + /// + /// returns `one_time_key_counts`: + /// For each key algorithm, the number of unclaimed one-time keys + /// of that type currently held on the server for this device. + /// If an algorithm is not listed, the count for that algorithm + /// is to be assumed zero. + Future> uploadKeys( + {MatrixDeviceKeys? deviceKeys, + Map? fallbackKeys, + Map? oneTimeKeys}) async { + final requestUri = Uri(path: '_matrix/client/v3/keys/upload'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (deviceKeys != null) 'device_keys': deviceKeys.toJson(), + if (fallbackKeys != null) 'fallback_keys': fallbackKeys, + if (oneTimeKeys != null) 'one_time_keys': oneTimeKeys, + })); + 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 (json['one_time_key_counts'] as Map) + .map((k, v) => MapEntry(k, v as int)); + } + /// *Note that this API takes either a room ID or alias, unlike other membership APIs.* /// /// This API "knocks" on the room to ask for permission to join, if the user @@ -1833,8 +2296,7 @@ class Api { final requestUri = Uri( path: '_matrix/client/v3/knock/${Uri.encodeComponent(roomIdOrAlias)}', queryParameters: { - if (serverName != null) - 'server_name': serverName.map((v) => v).toList(), + if (serverName != null) 'server_name': serverName, }); final request = Request('POST', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; @@ -1881,7 +2343,7 @@ class Api { /// invalidate any access token previously associated with that device. See /// [Relationship between access tokens and devices](https://spec.matrix.org/unstable/client-server-api/#relationship-between-access-tokens-and-devices). /// - /// [address] Third party identifier for the user. Deprecated in favour of `identifier`. + /// [address] Third-party identifier for the user. Deprecated in favour of `identifier`. /// /// [deviceId] ID of the client device. If this does not correspond to a /// known client device, a new device will be created. The given @@ -1895,7 +2357,7 @@ class Api { /// [initialDeviceDisplayName] A display name to assign to the newly-created device. Ignored /// if `device_id` corresponds to a known device. /// - /// [medium] When logging in using a third party identifier, the medium of the identifier. Must be 'email'. Deprecated in favour of `identifier`. + /// [medium] When logging in using a third-party identifier, the medium of the identifier. Must be 'email'. Deprecated in favour of `identifier`. /// /// [password] Required when `type` is `m.login.password`. The user's /// password. @@ -1906,8 +2368,12 @@ class Api { /// /// [type] The login type being used. /// + /// This must be a type returned in one of the flows of the + /// response of the [`GET /login`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3login) + /// endpoint, like `m.login.password` or `m.login.token`. + /// /// [user] The fully qualified user ID or just local part of the user ID, to log in. Deprecated in favour of `identifier`. - Future login(LoginType type, + Future login(String type, {String? address, String? deviceId, AuthenticationIdentifier? identifier, @@ -1930,7 +2396,7 @@ class Api { if (password != null) 'password': password, if (refreshToken != null) 'refresh_token': refreshToken, if (token != null) 'token': token, - 'type': type.name, + 'type': type, if (user != null) 'user': user, })); final response = await httpClient.send(request); @@ -2177,7 +2643,7 @@ class Api { /// is supplied, rather than via an explicit flag. /// /// [server] The server to fetch the public room lists from. Defaults to the - /// local server. + /// local server. Case sensitive. Future getPublicRooms( {int? limit, String? since, String? server}) async { final requestUri = @@ -2201,7 +2667,7 @@ class Api { /// of joined members, with the largest rooms first. /// /// [server] The server to fetch the public room lists from. Defaults to the - /// local server. + /// local server. Case sensitive. /// /// [filter] Filter to apply to the results. /// @@ -2215,7 +2681,7 @@ class Api { /// of pagination is specified solely by which token is supplied, /// rather than via an explicit flag. /// - /// [thirdPartyInstanceId] The specific third party network/protocol to request from the + /// [thirdPartyInstanceId] The specific third-party network/protocol to request from the /// homeserver. Can only be used if `include_all_networks` is false. Future queryPublicRooms( {String? server, @@ -2578,8 +3044,8 @@ class Api { /// The server SHOULD register an account with a User ID based on the /// `username` provided, if any. Note that the grammar of Matrix User ID /// localparts is restricted, so the server MUST either map the provided - /// `username` onto a `user_id` in a logical manner, or reject - /// `username`\s which do not comply to the grammar, with + /// `username` onto a `user_id` in a logical manner, or reject any + /// `username` which does not comply to the grammar with /// `M_INVALID_USERNAME`. /// /// Matrix clients MUST NOT assume that localpart of the registered @@ -2853,9 +3319,9 @@ class Api { /// /// [version] The backup in which to store the keys. Must be the current backup. /// - /// [backupData] The backup data. + /// [body] The backup data. Future putRoomKeys( - String version, RoomKeys backupData) async { + String version, RoomKeys body) async { final requestUri = Uri(path: '_matrix/client/v3/room_keys/keys', queryParameters: { 'version': version, @@ -2863,7 +3329,7 @@ class Api { final request = Request('PUT', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; request.headers['content-type'] = 'application/json'; - request.bodyBytes = utf8.encode(jsonEncode(backupData.toJson())); + request.bodyBytes = utf8.encode(jsonEncode(body.toJson())); final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); if (response.statusCode != 200) unexpectedResponse(response, responseBody); @@ -2922,9 +3388,9 @@ class Api { /// /// [version] The backup in which to store the keys. Must be the current backup. /// - /// [backupData] The backup data + /// [body] The backup data Future putRoomKeysByRoomId( - String roomId, String version, RoomKeyBackup backupData) async { + String roomId, String version, RoomKeyBackup body) async { final requestUri = Uri( path: '_matrix/client/v3/room_keys/keys/${Uri.encodeComponent(roomId)}', queryParameters: { @@ -2933,7 +3399,7 @@ class Api { final request = Request('PUT', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; request.headers['content-type'] = 'application/json'; - request.bodyBytes = utf8.encode(jsonEncode(backupData.toJson())); + request.bodyBytes = utf8.encode(jsonEncode(body.toJson())); final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); if (response.statusCode != 200) unexpectedResponse(response, responseBody); @@ -3000,9 +3466,9 @@ class Api { /// /// [version] The backup in which to store the key. Must be the current backup. /// - /// [data] The key data. + /// [body] The key data. Future putRoomKeyBySessionId(String roomId, - String sessionId, String version, KeyBackupData data) async { + String sessionId, String version, KeyBackupData body) async { final requestUri = Uri( path: '_matrix/client/v3/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}', @@ -3012,7 +3478,7 @@ class Api { final request = Request('PUT', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; request.headers['content-type'] = 'application/json'; - request.bodyBytes = utf8.encode(jsonEncode(data.toJson())); + request.bodyBytes = utf8.encode(jsonEncode(body.toJson())); final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); if (response.statusCode != 200) unexpectedResponse(response, responseBody); @@ -3288,9 +3754,9 @@ class Api { /// *Note that there are two forms of this API, which are documented separately. /// This version of the API does not require that the inviter know the Matrix - /// identifier of the invitee, and instead relies on third party identifiers. + /// identifier of the invitee, and instead relies on third-party identifiers. /// The homeserver uses an identity server to perform the mapping from - /// third party identifier to a Matrix identifier. The other is documented in the* + /// third-party identifier to a Matrix identifier. The other is documented in the* /// [joining rooms section](https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3roomsroomidinvite). /// /// This API invites a user to participate in a particular room. @@ -3301,18 +3767,18 @@ class Api { /// join that room. /// /// If the identity server did know the Matrix user identifier for the - /// third party identifier, the homeserver will append a `m.room.member` + /// third-party identifier, the homeserver will append a `m.room.member` /// event to the room. /// /// If the identity server does not know a Matrix user identifier for the - /// passed third party identifier, the homeserver will issue an invitation - /// which can be accepted upon providing proof of ownership of the third + /// passed third-party identifier, the homeserver will issue an invitation + /// which can be accepted upon providing proof of ownership of the third- /// party identifier. This is achieved by the identity server generating a /// token, which it gives to the inviting homeserver. The homeserver will /// add an `m.room.third_party_invite` event into the graph for the room, /// containing that token. /// - /// When the invitee binds the invited third party identifier to a Matrix + /// When the invitee binds the invited third-party identifier to a Matrix /// user ID, the identity server will give the user a list of pending /// invitations, each containing: /// @@ -3329,13 +3795,13 @@ class Api { /// /// [roomId] The room identifier (not alias) to which to invite the user. /// - /// [address] The invitee's third party identifier. + /// [address] The invitee's third-party identifier. /// /// [idAccessToken] An access token previously registered with the identity server. Servers /// can treat this as optional to distinguish between r0.5-compatible clients /// and this specification version. /// - /// [idServer] The hostname+port of the identity server which should be used for third party identifier lookups. + /// [idServer] The hostname+port of the identity server which should be used for third-party identifier lookups. /// /// [medium] The kind of address being passed in the address field, for example /// `email` (see [the list of recognised values](https://spec.matrix.org/unstable/appendices/#3pid-types)). @@ -3363,7 +3829,7 @@ class Api { /// *Note that there are two forms of this API, which are documented separately. /// This version of the API requires that the inviter knows the Matrix /// identifier of the invitee. The other is documented in the - /// [third party invites](https://spec.matrix.org/unstable/client-server-api/#third-party-invites) section.* + /// [third-party invites](https://spec.matrix.org/unstable/client-server-api/#third-party-invites) section.* /// /// This API invites a user to participate in a particular room. /// They do not start participating in the room until they actually join the @@ -3743,7 +4209,14 @@ class Api { } /// Reports an event as inappropriate to the server, which may then notify - /// the appropriate people. + /// the appropriate people. The caller must be joined to the room to report + /// it. + /// + /// It might be possible for clients to deduce whether an event exists by + /// timing the response, as only a report for an event that does exist + /// will require the homeserver to check whether a user is joined to + /// the room. To combat this, homeserver implementations should add + /// a random delay when generating a response. /// /// [roomId] The room in which the event being reported is located. /// @@ -4134,7 +4607,7 @@ class Api { return SyncUpdate.fromJson(json as Map); } - /// Retrieve an array of third party network locations from a Matrix room + /// Retrieve an array of third-party network locations from a Matrix room /// alias. /// /// [alias] The Matrix room alias to look up. @@ -4158,23 +4631,23 @@ class Api { /// Requesting this endpoint with a valid protocol name results in a list /// of successful mapping results in a JSON array. Each result contains /// objects to represent the Matrix room or rooms that represent a portal - /// to this third party network. Each has the Matrix room alias string, - /// an identifier for the particular third party network protocol, and an + /// to this third-party network. Each has the Matrix room alias string, + /// an identifier for the particular third-party network protocol, and an /// object containing the network-specific fields that comprise this /// identifier. It should attempt to canonicalise the identifier as much /// as reasonably possible given the network type. /// - /// [protocol] The protocol used to communicate to the third party network. + /// [protocol] The protocol used to communicate to the third-party network. /// - /// [searchFields] One or more custom fields to help identify the third party + /// [fields] One or more custom fields to help identify the third-party /// location. Future> queryLocationByProtocol(String protocol, - {String? searchFields}) async { + {Map? fields}) async { final requestUri = Uri( path: '_matrix/client/v3/thirdparty/location/${Uri.encodeComponent(protocol)}', queryParameters: { - if (searchFields != null) 'searchFields': searchFields, + if (fields != null) ...fields, }); final request = Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; @@ -4188,7 +4661,7 @@ class Api { .toList(); } - /// Fetches the metadata from the homeserver about a particular third party protocol. + /// Fetches the metadata from the homeserver about a particular third-party protocol. /// /// [protocol] The name of the protocol. Future getProtocolMetadata(String protocol) async { @@ -4221,7 +4694,7 @@ class Api { (k, v) => MapEntry(k, Protocol.fromJson(v as Map))); } - /// Retrieve an array of third party users from a Matrix User ID. + /// Retrieve an array of third-party users from a Matrix User ID. /// /// [userid] The Matrix User ID to look up. Future> queryUserByID(String userid) async { @@ -4241,19 +4714,19 @@ class Api { .toList(); } - /// Retrieve a Matrix User ID linked to a user on the third party service, given + /// Retrieve a Matrix User ID linked to a user on the third-party service, given /// a set of user parameters. /// /// [protocol] The name of the protocol. /// /// [fields] One or more custom fields that are passed to the AS to help identify the user. Future> queryUserByProtocol(String protocol, - {String? fields}) async { + {Map? fields}) async { final requestUri = Uri( path: '_matrix/client/v3/thirdparty/user/${Uri.encodeComponent(protocol)}', queryParameters: { - if (fields != null) 'fields...': fields, + if (fields != null) ...fields, }); final request = Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; @@ -4301,16 +4774,16 @@ class Api { /// [type] The event type of the account data to set. Custom types should be /// namespaced to avoid clashes. /// - /// [content] The content of the account data. + /// [body] The content of the account data. Future setAccountData( - String userId, String type, Map content) async { + String userId, String type, Map body) async { final requestUri = Uri( path: '_matrix/client/v3/user/${Uri.encodeComponent(userId)}/account_data/${Uri.encodeComponent(type)}'); final request = Request('PUT', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; request.headers['content-type'] = 'application/json'; - request.bodyBytes = utf8.encode(jsonEncode(content)); + request.bodyBytes = utf8.encode(jsonEncode(body)); final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); if (response.statusCode != 200) unexpectedResponse(response, responseBody); @@ -4325,20 +4798,20 @@ class Api { /// /// [userId] The id of the user uploading the filter. The access token must be authorized to make requests for this user id. /// - /// [filter] The filter to upload. + /// [body] The filter to upload. /// /// returns `filter_id`: /// The ID of the filter that was created. Cannot start /// with a `{` as this character is used to determine /// if the filter provided is inline JSON or a previously /// declared filter by homeservers on some APIs. - Future defineFilter(String userId, Filter filter) async { + Future defineFilter(String userId, Filter body) async { final requestUri = Uri( path: '_matrix/client/v3/user/${Uri.encodeComponent(userId)}/filter'); final request = Request('POST', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; request.headers['content-type'] = 'application/json'; - request.bodyBytes = utf8.encode(jsonEncode(filter.toJson())); + request.bodyBytes = utf8.encode(jsonEncode(body.toJson())); final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); if (response.statusCode != 200) unexpectedResponse(response, responseBody); @@ -4433,16 +4906,16 @@ class Api { /// [type] The event type of the account data to set. Custom types should be /// namespaced to avoid clashes. /// - /// [content] The content of the account data. + /// [body] The content of the account data. Future setAccountDataPerRoom(String userId, String roomId, String type, - Map content) async { + Map body) async { final requestUri = Uri( path: '_matrix/client/v3/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/account_data/${Uri.encodeComponent(type)}'); final request = Request('PUT', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; request.headers['content-type'] = 'application/json'; - request.bodyBytes = utf8.encode(jsonEncode(content)); + request.bodyBytes = utf8.encode(jsonEncode(body)); final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); if (response.statusCode != 200) unexpectedResponse(response, responseBody); @@ -4508,21 +4981,16 @@ class Api { /// /// [tag] The tag to add. /// - /// [order] A number in a range `[0,1]` describing a relative - /// position of the room under the given tag. - Future setRoomTag(String userId, String roomId, String tag, - {double? order, - Map additionalProperties = const {}}) async { + /// [body] Extra data for the tag, e.g. ordering. + Future setRoomTag( + String userId, String roomId, String tag, Tag body) async { final requestUri = Uri( path: '_matrix/client/v3/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/tags/${Uri.encodeComponent(tag)}'); final request = Request('PUT', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; request.headers['content-type'] = 'application/json'; - request.bodyBytes = utf8.encode(jsonEncode({ - ...additionalProperties, - if (order != null) 'order': order, - })); + request.bodyBytes = utf8.encode(jsonEncode(body.toJson())); final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); if (response.statusCode != 200) unexpectedResponse(response, responseBody); @@ -4591,13 +5059,20 @@ class Api { /// which has not yet landed in the spec. For example, a feature currently /// undergoing the proposal process may appear here and eventually be taken /// off this list once the feature lands in the spec and the server deems it - /// reasonable to do so. Servers may wish to keep advertising features here - /// after they've been released into the spec to give clients a chance to - /// upgrade appropriately. Additionally, clients should avoid using unstable - /// features in their stable releases. + /// reasonable to do so. Servers can choose to enable some features only for + /// some users, so clients should include authentication in the request to + /// get all the features available for the logged-in user. If no + /// authentication is provided, the server should only return the features + /// available to all users. Servers may wish to keep advertising features + /// here after they've been released into the spec to give clients a chance + /// to upgrade appropriately. Additionally, clients should avoid using + /// unstable features in their stable releases. Future getVersions() async { final requestUri = Uri(path: '_matrix/client/versions'); final request = Request('GET', baseUri!.resolveUri(requestUri)); + if (bearerToken != null) { + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + } final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); if (response.statusCode != 200) unexpectedResponse(response, responseBody); @@ -4606,6 +5081,41 @@ class Api { return GetVersionsResponse.fromJson(json as Map); } + /// Creates a new `mxc://` URI, independently of the content being uploaded. The content must be provided later + /// via [`PUT /_matrix/media/v3/upload/{serverName}/{mediaId}`](https://spec.matrix.org/unstable/client-server-api/#put_matrixmediav3uploadservernamemediaid). + /// + /// The server may optionally enforce a maximum age for unused IDs, + /// and delete media IDs when the client doesn't start the upload in time, + /// or when the upload was interrupted and not resumed in time. The server + /// should include the maximum POSIX millisecond timestamp to complete the + /// upload in the `unused_expires_at` field in the response JSON. The + /// recommended default expiration is 24 hours which should be enough time + /// to accommodate users on poor connection who find a better connection to + /// complete the upload. + /// + /// As well as limiting the rate of requests to create `mxc://` URIs, the server + /// should limit the number of concurrent *pending media uploads* a given + /// user can have. A pending media upload is a created `mxc://` URI where (a) + /// the media has not yet been uploaded, and (b) has not yet expired (the + /// `unused_expires_at` timestamp has not yet passed). In both cases, the + /// server should respond with an HTTP 429 error with an errcode of + /// `M_LIMIT_EXCEEDED`. + Future createContent() async { + final requestUri = Uri(path: '_matrix/media/v1/create'); + final request = Request('POST', 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 CreateContentResponse.fromJson(json as Map); + } + + /// {{% boxes/note %}} + /// Replaced by [`GET /_matrix/client/v1/media/config`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv1mediaconfig). + /// {{% /boxes/note %}} + /// /// This endpoint allows clients to retrieve the configuration of the content /// repository, such as upload limitations. /// Clients SHOULD use this as a guide when using content repository endpoints. @@ -4616,7 +5126,8 @@ class Api { /// between the client and the server may affect the apparent behaviour of content /// repository APIs, for example, proxies may enforce a lower upload size limit /// than is advertised by the server on this endpoint. - Future getConfig() async { + @deprecated + Future getConfig() async { final requestUri = Uri(path: '_matrix/media/v3/config'); final request = Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; @@ -4625,31 +5136,56 @@ class Api { if (response.statusCode != 200) unexpectedResponse(response, responseBody); final responseString = utf8.decode(responseBody); final json = jsonDecode(responseString); - return ServerConfig.fromJson(json as Map); + return MediaConfig.fromJson(json as Map); } + /// {{% boxes/note %}} + /// Replaced by [`GET /_matrix/client/v1/media/download/{serverName}/{mediaId}`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv1mediadownloadservernamemediaid) + /// (requires authentication). + /// {{% /boxes/note %}} + /// + /// {{% boxes/warning %}} + /// {{< changed-in v="1.11" >}} This endpoint MAY return `404 M_NOT_FOUND` + /// for media which exists, but is after the server froze unauthenticated + /// media access. See [Client Behaviour](https://spec.matrix.org/unstable/client-server-api/#content-repo-client-behaviour) for more + /// information. + /// {{% /boxes/warning %}} + /// + /// [serverName] The server name from the `mxc://` URI (the authority component). /// /// - /// [serverName] The server name from the `mxc://` URI (the authoritory component) + /// [mediaId] The media ID from the `mxc://` URI (the path component). /// /// - /// [mediaId] The media ID from the `mxc://` URI (the path component) + /// [allowRemote] Indicates to the server that it should not attempt to fetch the media if + /// it is deemed remote. This is to prevent routing loops where the server + /// contacts itself. + /// + /// Defaults to `true` if not provided. + /// + /// [timeoutMs] The maximum number of milliseconds that the client is willing to wait to + /// start receiving data, in the case that the content has not yet been + /// uploaded. The default value is 20000 (20 seconds). The content + /// repository SHOULD impose a maximum value for this parameter. The + /// content repository MAY respond before the timeout. /// /// - /// [allowRemote] Indicates to the server that it should not attempt to fetch the media if it is deemed - /// remote. This is to prevent routing loops where the server contacts itself. Defaults to - /// true if not provided. + /// [allowRedirect] Indicates to the server that it may return a 307 or 308 redirect + /// response that points at the relevant media content. When not explicitly + /// set to `true` the server must return the media content itself. /// + @deprecated Future getContent(String serverName, String mediaId, - {bool? allowRemote}) async { + {bool? allowRemote, int? timeoutMs, bool? allowRedirect}) async { final requestUri = Uri( path: '_matrix/media/v3/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}', queryParameters: { if (allowRemote != null) 'allow_remote': allowRemote.toString(), + if (timeoutMs != null) 'timeout_ms': timeoutMs.toString(), + if (allowRedirect != null) 'allow_redirect': allowRedirect.toString(), }); 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); @@ -4657,33 +5193,60 @@ class Api { contentType: response.headers['content-type'], data: responseBody); } + /// {{% boxes/note %}} + /// Replaced by [`GET /_matrix/client/v1/media/download/{serverName}/{mediaId}/{fileName}`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv1mediadownloadservernamemediaidfilename) + /// (requires authentication). + /// {{% /boxes/note %}} + /// /// This will download content from the content repository (same as /// the previous endpoint) but replace the target file name with the one /// provided by the caller. /// - /// [serverName] The server name from the `mxc://` URI (the authoritory component) + /// {{% boxes/warning %}} + /// {{< changed-in v="1.11" >}} This endpoint MAY return `404 M_NOT_FOUND` + /// for media which exists, but is after the server froze unauthenticated + /// media access. See [Client Behaviour](https://spec.matrix.org/unstable/client-server-api/#content-repo-client-behaviour) for more + /// information. + /// {{% /boxes/warning %}} + /// + /// [serverName] The server name from the `mxc://` URI (the authority component). /// /// - /// [mediaId] The media ID from the `mxc://` URI (the path component) + /// [mediaId] The media ID from the `mxc://` URI (the path component). /// /// /// [fileName] A filename to give in the `Content-Disposition` header. /// - /// [allowRemote] Indicates to the server that it should not attempt to fetch the media if it is deemed - /// remote. This is to prevent routing loops where the server contacts itself. Defaults to - /// true if not provided. + /// [allowRemote] Indicates to the server that it should not attempt to fetch the media if + /// it is deemed remote. This is to prevent routing loops where the server + /// contacts itself. /// + /// Defaults to `true` if not provided. + /// + /// [timeoutMs] The maximum number of milliseconds that the client is willing to wait to + /// start receiving data, in the case that the content has not yet been + /// uploaded. The default value is 20000 (20 seconds). The content + /// repository SHOULD impose a maximum value for this parameter. The + /// content repository MAY respond before the timeout. + /// + /// + /// [allowRedirect] Indicates to the server that it may return a 307 or 308 redirect + /// response that points at the relevant media content. When not explicitly + /// set to `true` the server must return the media content itself. + /// + @deprecated Future getContentOverrideName( String serverName, String mediaId, String fileName, - {bool? allowRemote}) async { + {bool? allowRemote, int? timeoutMs, bool? allowRedirect}) async { final requestUri = Uri( path: '_matrix/media/v3/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}/${Uri.encodeComponent(fileName)}', queryParameters: { if (allowRemote != null) 'allow_remote': allowRemote.toString(), + if (timeoutMs != null) 'timeout_ms': timeoutMs.toString(), + if (allowRedirect != null) 'allow_redirect': allowRedirect.toString(), }); 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); @@ -4691,6 +5254,10 @@ class Api { contentType: response.headers['content-type'], data: responseBody); } + /// {{% boxes/note %}} + /// Replaced by [`GET /_matrix/client/v1/media/preview_url`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv1mediapreview_url). + /// {{% /boxes/note %}} + /// /// Get information about a URL for the client. Typically this is called when a /// client sees a URL in a message and wants to render a preview for the user. /// @@ -4705,7 +5272,8 @@ class Api { /// [ts] The preferred point in time to return a preview for. The server may /// return a newer version if it does not have the requested version /// available. - Future getUrlPreview(Uri url, {int? ts}) async { + @deprecated + Future getUrlPreview(Uri url, {int? ts}) async { final requestUri = Uri(path: '_matrix/media/v3/preview_url', queryParameters: { 'url': url.toString(), @@ -4718,16 +5286,28 @@ class Api { if (response.statusCode != 200) unexpectedResponse(response, responseBody); final responseString = utf8.decode(responseBody); final json = jsonDecode(responseString); - return GetUrlPreviewResponse.fromJson(json as Map); + return PreviewForUrl.fromJson(json as Map); } + /// {{% boxes/note %}} + /// Replaced by [`GET /_matrix/client/v1/media/thumbnail/{serverName}/{mediaId}`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv1mediathumbnailservernamemediaid) + /// (requires authentication). + /// {{% /boxes/note %}} + /// /// Download a thumbnail of content from the content repository. /// See the [Thumbnails](https://spec.matrix.org/unstable/client-server-api/#thumbnails) section for more information. /// - /// [serverName] The server name from the `mxc://` URI (the authoritory component) + /// {{% boxes/warning %}} + /// {{< changed-in v="1.11" >}} This endpoint MAY return `404 M_NOT_FOUND` + /// for media which exists, but is after the server froze unauthenticated + /// media access. See [Client Behaviour](https://spec.matrix.org/unstable/client-server-api/#content-repo-client-behaviour) for more + /// information. + /// {{% /boxes/warning %}} + /// + /// [serverName] The server name from the `mxc://` URI (the authority component). /// /// - /// [mediaId] The media ID from the `mxc://` URI (the path component) + /// [mediaId] The media ID from the `mxc://` URI (the path component). /// /// /// [width] The *desired* width of the thumbnail. The actual thumbnail may be @@ -4739,12 +5319,47 @@ class Api { /// [method] The desired resizing method. See the [Thumbnails](https://spec.matrix.org/unstable/client-server-api/#thumbnails) /// section for more information. /// - /// [allowRemote] Indicates to the server that it should not attempt to fetch - /// the media if it is deemed remote. This is to prevent routing loops - /// where the server contacts itself. Defaults to true if not provided. + /// [allowRemote] Indicates to the server that it should not attempt to fetch the media if + /// it is deemed remote. This is to prevent routing loops where the server + /// contacts itself. + /// + /// Defaults to `true` if not provided. + /// + /// [timeoutMs] The maximum number of milliseconds that the client is willing to wait to + /// start receiving data, in the case that the content has not yet been + /// uploaded. The default value is 20000 (20 seconds). The content + /// repository SHOULD impose a maximum value for this parameter. The + /// content repository MAY respond before the timeout. + /// + /// + /// [allowRedirect] Indicates to the server that it may return a 307 or 308 redirect + /// response that points at the relevant media content. When not explicitly + /// set to `true` the server must return the media content itself. + /// + /// + /// [animated] Indicates preference for an animated thumbnail from the server, if possible. Animated + /// thumbnails typically use the content types `image/gif`, `image/png` (with APNG format), + /// `image/apng`, and `image/webp` instead of the common static `image/png` or `image/jpeg` + /// content types. + /// + /// When `true`, the server SHOULD return an animated thumbnail if possible and supported. + /// When `false`, the server MUST NOT return an animated thumbnail. For example, returning a + /// static `image/png` or `image/jpeg` thumbnail. When not provided, the server SHOULD NOT + /// return an animated thumbnail. + /// + /// Servers SHOULD prefer to return `image/webp` thumbnails when supporting animation. + /// + /// When `true` and the media cannot be animated, such as in the case of a JPEG or PDF, the + /// server SHOULD behave as though `animated` is `false`. + /// + @deprecated Future getContentThumbnail( String serverName, String mediaId, int width, int height, - {Method? method, bool? allowRemote}) async { + {Method? method, + bool? allowRemote, + int? timeoutMs, + bool? allowRedirect, + bool? animated}) async { final requestUri = Uri( path: '_matrix/media/v3/thumbnail/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}', @@ -4753,9 +5368,11 @@ class Api { 'height': height.toString(), if (method != null) 'method': method.name, if (allowRemote != null) 'allow_remote': allowRemote.toString(), + if (timeoutMs != null) 'timeout_ms': timeoutMs.toString(), + if (allowRedirect != null) 'allow_redirect': allowRedirect.toString(), + if (animated != null) 'animated': animated.toString(), }); 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); @@ -4767,13 +5384,13 @@ class Api { /// /// [filename] The name of the file being uploaded /// - /// [content] The content to be uploaded. + /// [body] /// /// [contentType] The content type of the file being uploaded /// /// returns `content_uri`: - /// The [MXC URI](https://spec.matrix.org/unstable/client-server-api/#matrix-content-mxc-uris) to the uploaded content. - Future uploadContent(Uint8List content, + /// The [`mxc://` URI](https://spec.matrix.org/unstable/client-server-api/#matrix-content-mxc-uris) to the uploaded content. + Future uploadContent(Uint8List body, {String? filename, String? contentType}) async { final requestUri = Uri(path: '_matrix/media/v3/upload', queryParameters: { if (filename != null) 'filename': filename, @@ -4781,12 +5398,49 @@ class Api { final request = Request('POST', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; if (contentType != null) request.headers['content-type'] = contentType; - request.bodyBytes = content; + request.bodyBytes = body; 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 Uri.parse(json['content_uri'] as String); + return ((json['content_uri'] as String).startsWith('mxc://') + ? Uri.parse(json['content_uri'] as String) + : throw Exception('Uri not an mxc URI')); + } + + /// This endpoint permits uploading content to an `mxc://` URI that was created + /// earlier via [POST /_matrix/media/v1/create](https://spec.matrix.org/unstable/client-server-api/#post_matrixmediav1create). + /// + /// [serverName] The server name from the `mxc://` URI returned by `POST /_matrix/media/v1/create` (the authority component). + /// + /// + /// [mediaId] The media ID from the `mxc://` URI returned by `POST /_matrix/media/v1/create` (the path component). + /// + /// + /// [filename] The name of the file being uploaded + /// + /// [body] + /// + /// [contentType] The content type of the file being uploaded + Future> uploadContentToMXC( + String serverName, String mediaId, Uint8List body, + {String? filename, String? contentType}) async { + final requestUri = Uri( + path: + '_matrix/media/v3/upload/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}', + queryParameters: { + if (filename != null) 'filename': filename, + }); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + if (contentType != null) request.headers['content-type'] = contentType; + request.bodyBytes = body; + 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 json as Map; } } diff --git a/lib/matrix_api_lite/generated/model.dart b/lib/matrix_api_lite/generated/model.dart index e2a35622..36f70fb0 100644 --- a/lib/matrix_api_lite/generated/model.dart +++ b/lib/matrix_api_lite/generated/model.dart @@ -84,6 +84,195 @@ class DiscoveryInformation { Map> additionalProperties; } +/// +@_NameSource('generated') +@EnhancedEnum() +enum Role { + @EnhancedEnumValue(name: 'm.role.admin') + mRoleAdmin, + @EnhancedEnumValue(name: 'm.role.security') + mRoleSecurity +} + +/// A way to contact the server administrator. +@_NameSource('spec') +class Contact { + Contact({ + this.emailAddress, + this.matrixId, + required this.role, + }); + + Contact.fromJson(Map json) + : emailAddress = + ((v) => v != null ? v as String : null)(json['email_address']), + matrixId = ((v) => v != null ? v as String : null)(json['matrix_id']), + role = Role.values.fromString(json['role'] as String)!; + Map toJson() { + final emailAddress = this.emailAddress; + final matrixId = this.matrixId; + return { + if (emailAddress != null) 'email_address': emailAddress, + if (matrixId != null) 'matrix_id': matrixId, + 'role': role.name, + }; + } + + /// An email address to reach the administrator. + /// + /// At least one of `matrix_id` or `email_address` is + /// required. + String? emailAddress; + + /// A [Matrix User ID](https://spec.matrix.org/unstable/appendices/#user-identifiers) + /// representing the administrator. + /// + /// It could be an account registered on a different + /// homeserver so the administrator can be contacted + /// when the homeserver is down. + /// + /// At least one of `matrix_id` or `email_address` is + /// required. + String? matrixId; + + /// An informal description of what the contact methods + /// are used for. + /// + /// `m.role.admin` is a catch-all role for any queries + /// and `m.role.security` is intended for sensitive + /// requests. + /// + /// Unspecified roles are permitted through the use of + /// [Namespaced Identifiers](https://spec.matrix.org/unstable/appendices/#common-namespaced-identifier-grammar). + Role role; +} + +/// +@_NameSource('generated') +class GetWellknownSupportResponse { + GetWellknownSupportResponse({ + this.contacts, + this.supportPage, + }); + + GetWellknownSupportResponse.fromJson(Map json) + : contacts = ((v) => v != null + ? (v as List) + .map((v) => Contact.fromJson(v as Map)) + .toList() + : null)(json['contacts']), + supportPage = + ((v) => v != null ? v as String : null)(json['support_page']); + Map toJson() { + final contacts = this.contacts; + final supportPage = this.supportPage; + return { + if (contacts != null) + 'contacts': contacts.map((v) => v.toJson()).toList(), + if (supportPage != null) 'support_page': supportPage, + }; + } + + /// Ways to contact the server administrator. + /// + /// At least one of `contacts` or `support_page` is required. + /// If only `contacts` is set, it must contain at least one + /// item. + List? contacts; + + /// The URL of a page to give users help specific to the + /// homeserver, like extra login/registration steps. + /// + /// At least one of `contacts` or `support_page` is required. + String? supportPage; +} + +/// +@_NameSource('generated') +class GenerateLoginTokenResponse { + GenerateLoginTokenResponse({ + required this.expiresInMs, + required this.loginToken, + }); + + GenerateLoginTokenResponse.fromJson(Map json) + : expiresInMs = json['expires_in_ms'] as int, + loginToken = json['login_token'] as String; + Map toJson() => { + 'expires_in_ms': expiresInMs, + 'login_token': loginToken, + }; + + /// The time remaining in milliseconds until the homeserver will no longer accept the token. `120000` + /// (2 minutes) is recommended as a default. + int expiresInMs; + + /// The login token for the `m.login.token` login flow. + String loginToken; +} + +/// +@_NameSource('rule override generated') +class MediaConfig { + MediaConfig({ + this.mUploadSize, + }); + + MediaConfig.fromJson(Map json) + : mUploadSize = + ((v) => v != null ? v as int : null)(json['m.upload.size']); + Map toJson() { + final mUploadSize = this.mUploadSize; + return { + if (mUploadSize != null) 'm.upload.size': mUploadSize, + }; + } + + /// The maximum size an upload can be in bytes. + /// Clients SHOULD use this as a guide when uploading content. + /// If not listed or null, the size limit should be treated as unknown. + int? mUploadSize; +} + +/// +@_NameSource('rule override generated') +class PreviewForUrl { + PreviewForUrl({ + this.matrixImageSize, + this.ogImage, + }); + + PreviewForUrl.fromJson(Map json) + : matrixImageSize = + ((v) => v != null ? v as int : null)(json['matrix:image:size']), + ogImage = ((v) => + v != null ? Uri.parse(v as String) : null)(json['og:image']); + Map toJson() { + final matrixImageSize = this.matrixImageSize; + final ogImage = this.ogImage; + return { + if (matrixImageSize != null) 'matrix:image:size': matrixImageSize, + if (ogImage != null) 'og:image': ogImage.toString(), + }; + } + + /// The byte-size of the image. Omitted if there is no image attached. + int? matrixImageSize; + + /// An [`mxc://` URI](https://spec.matrix.org/unstable/client-server-api/#matrix-content-mxc-uris) to the image. Omitted if there is no image. + Uri? ogImage; +} + +/// +@_NameSource('generated') +@EnhancedEnum() +enum Method { + @EnhancedEnumValue(name: 'crop') + crop, + @EnhancedEnumValue(name: 'scale') + scale +} + /// @_NameSource('spec') class PublicRoomsChunk { @@ -170,13 +359,13 @@ class PublicRoomsChunk { /// @_NameSource('spec') -class ChildRoomsChunk { - ChildRoomsChunk({ +class SpaceHierarchyRoomsChunk { + SpaceHierarchyRoomsChunk({ required this.childrenState, this.roomType, }); - ChildRoomsChunk.fromJson(Map json) + SpaceHierarchyRoomsChunk.fromJson(Map json) : childrenState = (json['children_state'] as List) .map((v) => ChildrenState.fromJson(v as Map)) .toList(), @@ -201,7 +390,7 @@ class ChildRoomsChunk { /// @_NameSource('rule override generated') -class SpaceRoomsChunk implements PublicRoomsChunk, ChildRoomsChunk { +class SpaceRoomsChunk implements PublicRoomsChunk, SpaceHierarchyRoomsChunk { SpaceRoomsChunk({ this.avatarUrl, this.canonicalAlias, @@ -352,6 +541,7 @@ class GetRelatingEventsResponse { required this.chunk, this.nextBatch, this.prevBatch, + this.recursionDepth, }); GetRelatingEventsResponse.fromJson(Map json) @@ -359,91 +549,18 @@ class GetRelatingEventsResponse { .map((v) => MatrixEvent.fromJson(v as Map)) .toList(), nextBatch = ((v) => v != null ? v as String : null)(json['next_batch']), - prevBatch = ((v) => v != null ? v as String : null)(json['prev_batch']); - Map toJson() { - final nextBatch = this.nextBatch; - final prevBatch = this.prevBatch; - return { - 'chunk': chunk.map((v) => v.toJson()).toList(), - if (nextBatch != null) 'next_batch': nextBatch, - if (prevBatch != null) 'prev_batch': prevBatch, - }; - } - - /// The child events of the requested event, ordered topologically most-recent first. - List chunk; - - /// An opaque string representing a pagination token. The absence of this token - /// means there are no more results to fetch and the client should stop paginating. - String? nextBatch; - - /// An opaque string representing a pagination token. The absence of this token - /// means this is the start of the result set, i.e. this is the first batch/page. - String? prevBatch; -} - -/// -@_NameSource('generated') -class GetRelatingEventsWithRelTypeResponse { - GetRelatingEventsWithRelTypeResponse({ - required this.chunk, - this.nextBatch, - this.prevBatch, - }); - - GetRelatingEventsWithRelTypeResponse.fromJson(Map json) - : chunk = (json['chunk'] as List) - .map((v) => MatrixEvent.fromJson(v as Map)) - .toList(), - nextBatch = ((v) => v != null ? v as String : null)(json['next_batch']), - prevBatch = ((v) => v != null ? v as String : null)(json['prev_batch']); - Map toJson() { - final nextBatch = this.nextBatch; - final prevBatch = this.prevBatch; - return { - 'chunk': chunk.map((v) => v.toJson()).toList(), - if (nextBatch != null) 'next_batch': nextBatch, - if (prevBatch != null) 'prev_batch': prevBatch, - }; - } - - /// The child events of the requested event, ordered topologically - /// most-recent first. The events returned will match the `relType` - /// supplied in the URL. - List chunk; - - /// An opaque string representing a pagination token. The absence of this token - /// means there are no more results to fetch and the client should stop paginating. - String? nextBatch; - - /// An opaque string representing a pagination token. The absence of this token - /// means this is the start of the result set, i.e. this is the first batch/page. - String? prevBatch; -} - -/// -@_NameSource('generated') -class GetRelatingEventsWithRelTypeAndEventTypeResponse { - GetRelatingEventsWithRelTypeAndEventTypeResponse({ - required this.chunk, - this.nextBatch, - this.prevBatch, - }); - - GetRelatingEventsWithRelTypeAndEventTypeResponse.fromJson( - Map json) - : chunk = (json['chunk'] as List) - .map((v) => MatrixEvent.fromJson(v as Map)) - .toList(), - nextBatch = ((v) => v != null ? v as String : null)(json['next_batch']), - prevBatch = ((v) => v != null ? v as String : null)(json['prev_batch']); + prevBatch = ((v) => v != null ? v as String : null)(json['prev_batch']), + recursionDepth = + ((v) => v != null ? v as int : null)(json['recursion_depth']); Map toJson() { final nextBatch = this.nextBatch; final prevBatch = this.prevBatch; + final recursionDepth = this.recursionDepth; return { 'chunk': chunk.map((v) => v.toJson()).toList(), if (nextBatch != null) 'next_batch': nextBatch, if (prevBatch != null) 'prev_batch': prevBatch, + if (recursionDepth != null) 'recursion_depth': recursionDepth, }; } @@ -459,6 +576,110 @@ class GetRelatingEventsWithRelTypeAndEventTypeResponse { /// An opaque string representing a pagination token. The absence of this token /// means this is the start of the result set, i.e. this is the first batch/page. String? prevBatch; + + /// If the `recurse` parameter was supplied by the client, this response field is + /// mandatory and gives the actual depth to which the server recursed. If the client + /// did not specify the `recurse` parameter, this field must be absent. + int? recursionDepth; +} + +/// +@_NameSource('generated') +class GetRelatingEventsWithRelTypeResponse { + GetRelatingEventsWithRelTypeResponse({ + required this.chunk, + this.nextBatch, + this.prevBatch, + this.recursionDepth, + }); + + GetRelatingEventsWithRelTypeResponse.fromJson(Map json) + : chunk = (json['chunk'] as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList(), + nextBatch = ((v) => v != null ? v as String : null)(json['next_batch']), + prevBatch = ((v) => v != null ? v as String : null)(json['prev_batch']), + recursionDepth = + ((v) => v != null ? v as int : null)(json['recursion_depth']); + Map toJson() { + final nextBatch = this.nextBatch; + final prevBatch = this.prevBatch; + final recursionDepth = this.recursionDepth; + return { + 'chunk': chunk.map((v) => v.toJson()).toList(), + if (nextBatch != null) 'next_batch': nextBatch, + if (prevBatch != null) 'prev_batch': prevBatch, + if (recursionDepth != null) 'recursion_depth': recursionDepth, + }; + } + + /// The child events of the requested event, ordered topologically most-recent + /// first. The events returned will match the `relType` and `eventType` supplied + /// in the URL. + List chunk; + + /// An opaque string representing a pagination token. The absence of this token + /// means there are no more results to fetch and the client should stop paginating. + String? nextBatch; + + /// An opaque string representing a pagination token. The absence of this token + /// means this is the start of the result set, i.e. this is the first batch/page. + String? prevBatch; + + /// If the `recurse` parameter was supplied by the client, this response field is + /// mandatory and gives the actual depth to which the server recursed. If the client + /// did not specify the `recurse` parameter, this field must be absent. + int? recursionDepth; +} + +/// +@_NameSource('generated') +class GetRelatingEventsWithRelTypeAndEventTypeResponse { + GetRelatingEventsWithRelTypeAndEventTypeResponse({ + required this.chunk, + this.nextBatch, + this.prevBatch, + this.recursionDepth, + }); + + GetRelatingEventsWithRelTypeAndEventTypeResponse.fromJson( + Map json) + : chunk = (json['chunk'] as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList(), + nextBatch = ((v) => v != null ? v as String : null)(json['next_batch']), + prevBatch = ((v) => v != null ? v as String : null)(json['prev_batch']), + recursionDepth = + ((v) => v != null ? v as int : null)(json['recursion_depth']); + Map toJson() { + final nextBatch = this.nextBatch; + final prevBatch = this.prevBatch; + final recursionDepth = this.recursionDepth; + return { + 'chunk': chunk.map((v) => v.toJson()).toList(), + if (nextBatch != null) 'next_batch': nextBatch, + if (prevBatch != null) 'prev_batch': prevBatch, + if (recursionDepth != null) 'recursion_depth': recursionDepth, + }; + } + + /// The child events of the requested event, ordered topologically most-recent + /// first. The events returned will match the `relType` and `eventType` supplied + /// in the URL. + List chunk; + + /// An opaque string representing a pagination token. The absence of this token + /// means there are no more results to fetch and the client should stop paginating. + String? nextBatch; + + /// An opaque string representing a pagination token. The absence of this token + /// means this is the start of the result set, i.e. this is the first batch/page. + String? prevBatch; + + /// If the `recurse` parameter was supplied by the client, this response field is + /// mandatory and gives the actual depth to which the server recursed. If the client + /// did not specify the `recurse` parameter, this field must be absent. + int? recursionDepth; } /// @@ -492,8 +713,8 @@ class GetThreadRootsResponse { }; } - /// The thread roots, ordered by the `latest_event` in each event's aggregation bundle. All events - /// returned include bundled [aggregations](https://spec.matrix.org/unstable/client-server-api/#aggregations). + /// The thread roots, ordered by the `latest_event` in each event's aggregated children. All events + /// returned include bundled [aggregations](https://spec.matrix.org/unstable/client-server-api/#aggregations-of-child-events). /// /// If the thread root event was sent by an [ignored user](https://spec.matrix.org/unstable/client-server-api/#ignoring-users), the /// event is returned redacted to the caller. This is to simulate the same behaviour of a client doing @@ -564,13 +785,13 @@ class ThirdPartyIdentifier { 'validated_at': validatedAt, }; - /// The timestamp, in milliseconds, when the homeserver associated the third party identifier with the user. + /// The timestamp, in milliseconds, when the homeserver associated the third-party identifier with the user. int addedAt; - /// The third party identifier address. + /// The third-party identifier address. String address; - /// The medium of the third party identifier. + /// The medium of the third-party identifier. ThirdPartyIdentifierMedium medium; /// The timestamp, in milliseconds, when the identifier was @@ -961,7 +1182,7 @@ class Invite3pid { 'medium': medium, }; - /// The invitee's third party identifier. + /// The invitee's third-party identifier. String address; /// An access token previously registered with the identity server. Servers @@ -969,7 +1190,7 @@ class Invite3pid { /// and this specification version. String idAccessToken; - /// The hostname+port of the identity server which should be used for third party identifier lookups. + /// The hostname+port of the identity server which should be used for third-party identifier lookups. String idServer; /// The kind of address being passed in the address field, for example `email` @@ -1155,7 +1376,7 @@ class PeekEventsResponse { } /// A signature of an `m.third_party_invite` token to prove that this user -/// owns a third party identity which has been invited to the room. +/// owns a third-party identity which has been invited to the room. @_NameSource('spec') class ThirdPartySigned { ThirdPartySigned({ @@ -1374,33 +1595,33 @@ class QueryKeysResponse { @_NameSource('spec') class LoginFlow { LoginFlow({ - this.type, + this.getLoginToken, + required this.type, }); LoginFlow.fromJson(Map json) - : type = ((v) => v != null ? v as String : null)(json['type']); + : getLoginToken = + ((v) => v != null ? v as bool : null)(json['get_login_token']), + type = json['type'] as String; Map toJson() { - final type = this.type; + final getLoginToken = this.getLoginToken; return { - if (type != null) 'type': type, + if (getLoginToken != null) 'get_login_token': getLoginToken, + 'type': type, }; } + /// If `type` is `m.login.token`, an optional field to indicate + /// to the unauthenticated client that the homeserver supports + /// the [`POST /login/get_token`](https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv1loginget_token) + /// endpoint. Note that supporting the endpoint does not + /// necessarily indicate that the user attempting to log in will + /// be able to generate such a token. + bool? getLoginToken; + /// The login type. This is supplied as the `type` when /// logging in. - String? type; -} - -/// -@_NameSource('rule override generated') -@EnhancedEnum() -enum LoginType { - @EnhancedEnumValue(name: 'm.login.password') - mLoginPassword, - @EnhancedEnumValue(name: 'm.login.token') - mLoginToken, - @EnhancedEnumValue(name: 'org.matrix.login.jwt') - mLoginJWT + String type; } /// @@ -1713,7 +1934,9 @@ class PublicRoomQueryFilter { : genericSearchTerm = ((v) => v != null ? v as String : null)(json['generic_search_term']), roomTypes = ((v) => v != null - ? (v as List).map((v) => v as String).toList() + ? (v as List) + .map((v) => ((v) => v != null ? v as String : null)(v)) + .toList() : null)(json['room_types']); Map toJson() { final genericSearchTerm = this.genericSearchTerm; @@ -1732,7 +1955,7 @@ class PublicRoomQueryFilter { /// for. To include rooms without a room type, specify `null` within this /// list. When not specified, all applicable rooms (regardless of type) /// are returned. - List? roomTypes; + List? roomTypes; } /// A list of the rooms on the server. @@ -1929,22 +2152,26 @@ class PushCondition { this.key, required this.kind, this.pattern, + this.value, }); PushCondition.fromJson(Map json) : is$ = ((v) => v != null ? v as String : null)(json['is']), key = ((v) => v != null ? v as String : null)(json['key']), kind = json['kind'] as String, - pattern = ((v) => v != null ? v as String : null)(json['pattern']); + pattern = ((v) => v != null ? v as String : null)(json['pattern']), + value = ((v) => v != null ? v as Object? : null)(json['value']); Map toJson() { final is$ = this.is$; final key = this.key; final pattern = this.pattern; + final value = this.value; return { if (is$ != null) 'is': is$, if (key != null) 'key': key, 'kind': kind, if (pattern != null) 'pattern': pattern, + if (value != null) 'value': value, }; } @@ -1954,8 +2181,8 @@ class PushCondition { /// so forth. If no prefix is present, this parameter defaults to ==. String? is$; - /// Required for `event_match` conditions. The dot-separated field of the - /// event to match. + /// Required for `event_match`, `event_property_is` and `event_property_contains` + /// conditions. The dot-separated field of the event to match. /// /// Required for `sender_notification_permission` conditions. The field in /// the power level event the user needs a minimum power level for. Fields @@ -1963,13 +2190,18 @@ class PushCondition { /// event's `content`. String? key; - /// The kind of condition to apply. See [conditions](https://spec.matrix.org/unstable/client-server-api/#conditions) for + /// The kind of condition to apply. See [conditions](https://spec.matrix.org/unstable/client-server-api/#conditions-1) for /// more information on the allowed kinds and how they work. String kind; - /// Required for `event_match` conditions. The glob-style pattern to - /// match against. + /// Required for `event_match` conditions. The [glob-style pattern](https://spec.matrix.org/unstable/appendices#glob-style-matching) + /// to match against. String? pattern; + + /// Required for `event_property_is` and `event_property_contains` conditions. + /// A non-compound [canonical JSON](https://spec.matrix.org/unstable/appendices#canonical-json) value to match + /// against. + Object? value; } /// @@ -2023,8 +2255,8 @@ class PushRule { /// Whether the push rule is enabled or not. bool enabled; - /// The glob-style pattern to match against. Only applicable to `content` - /// rules. + /// The [glob-style pattern](https://spec.matrix.org/unstable/appendices#glob-style-matching) to match against. + /// Only applicable to `content` rules. String? pattern; /// The ID of this rule. @@ -2542,7 +2774,7 @@ class RoomMember { }; } - /// The mxc avatar url of the user this object is representing. + /// The avatar of the user this object is representing, as an [`mxc://` URI](https://spec.matrix.org/unstable/client-server-api/#matrix-content-mxc-uris). Uri? avatarUrl; /// The display name of the user this object is representing. @@ -2724,7 +2956,11 @@ class EventFilter { }; } - /// The maximum number of events to return. + /// The maximum number of events to return, must be an integer greater than 0. + /// + /// Servers should apply a default value, and impose a maximum value to avoid + /// resource exhaustion. + /// int? limit; /// A list of sender IDs to exclude. If this list is absent then no senders are excluded. A matching sender will be excluded even if it is listed in the `'senders'` filter. @@ -2887,7 +3123,11 @@ class SearchFilter implements EventFilter, RoomEventFilter { }; } - /// The maximum number of events to return. + /// The maximum number of events to return, must be an integer greater than 0. + /// + /// Servers should apply a default value, and impose a maximum value to avoid + /// resource exhaustion. + /// @override int? limit; @@ -3245,7 +3485,7 @@ class SearchResultsEventContext { /// The historic profile information of the /// users that sent the events returned. /// - /// The `string` key is the user ID for which + /// The key is the user ID for which /// the profile belongs to. Map? profileInfo; @@ -3374,7 +3614,7 @@ class ResultRoomEvents { /// This is included if the request had the /// `include_state` key set with a value of `true`. /// - /// The `string` key is the room ID for which the `State + /// The key is the room ID for which the `State /// Event` array belongs to. Map>? state; } @@ -3441,10 +3681,10 @@ class Location { /// An alias for a matrix room. String alias; - /// Information used to identify this third party location. + /// Information used to identify this third-party location. Map fields; - /// The protocol ID that the third party location is a part of. + /// The protocol ID that the third-party location is a part of. String protocol; } @@ -3549,7 +3789,7 @@ class Protocol { /// May be an empty object if no fields are defined. Map fieldTypes; - /// A content URI representing an icon for the third party protocol. + /// A content URI representing an icon for the third-party protocol. String icon; /// A list of objects representing independent instances of configuration. @@ -3557,13 +3797,13 @@ class Protocol { /// same application service. List instances; - /// Fields which may be used to identify a third party location. These should be + /// Fields which may be used to identify a third-party location. These should be /// ordered to suggest the way that entities may be grouped, where higher /// groupings are ordered first. For example, the name of a network should be /// searched before the name of a channel. List locationFields; - /// Fields which may be used to identify a third party user. These should be + /// Fields which may be used to identify a third-party user. These should be /// ordered to suggest the way that entities may be grouped, where higher /// groupings are ordered first. For example, the name of a network should be /// searched before the nickname of a user. @@ -3589,13 +3829,13 @@ class ThirdPartyUser { 'userid': userid, }; - /// Information used to identify this third party location. + /// Information used to identify this third-party location. Map fields; - /// The protocol ID that the third party location is a part of. + /// 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 represting a third-party user. String userid; } @@ -3684,7 +3924,11 @@ class StateFilter implements EventFilter, RoomEventFilter { }; } - /// The maximum number of events to return. + /// The maximum number of events to return, must be an integer greater than 0. + /// + /// Servers should apply a default value, and impose a maximum value to avoid + /// resource exhaustion. + /// @override int? limit; @@ -3857,7 +4101,7 @@ class Filter { /// The user account data that isn't associated with rooms to include. EventFilter? accountData; - /// List of event fields to include. If this list is absent then all fields are included. The entries may include '.' characters to indicate sub-fields. So ['content.body'] will include the 'body' field of the 'content' object. A literal '.' character in a field name may be escaped using a '\\'. A server may include more fields than were requested. + /// List of event fields to include. If this list is absent then all fields are included. The entries are [dot-separated paths for each property](https://spec.matrix.org/unstable/appendices#dot-separated-property-paths) to include. So ['content.body'] will include the 'body' field of the 'content' object. A server may include more fields than were requested. List? eventFields; /// The format to use for events. 'client' will return the events in a format suitable for clients. 'federation' will return the raw event as received over federation. The default is 'client'. @@ -3963,7 +4207,7 @@ class Profile { }; } - /// The avatar url, as an MXC, if one exists. + /// The avatar url, as an [`mxc://` URI](https://spec.matrix.org/unstable/client-server-api/#matrix-content-mxc-uris), if one exists. Uri? avatarUrl; /// The display name of the user, if one exists. @@ -4065,63 +4309,32 @@ class GetVersionsResponse { } /// -@_NameSource('rule override generated') -class ServerConfig { - ServerConfig({ - this.mUploadSize, +@_NameSource('generated') +class CreateContentResponse { + CreateContentResponse({ + required this.contentUri, + this.unusedExpiresAt, }); - ServerConfig.fromJson(Map json) - : mUploadSize = - ((v) => v != null ? v as int : null)(json['m.upload.size']); + CreateContentResponse.fromJson(Map json) + : contentUri = ((json['content_uri'] as String).startsWith('mxc://') + ? Uri.parse(json['content_uri'] as String) + : throw Exception('Uri not an mxc URI')), + unusedExpiresAt = + ((v) => v != null ? v as int : null)(json['unused_expires_at']); Map toJson() { - final mUploadSize = this.mUploadSize; + final unusedExpiresAt = this.unusedExpiresAt; return { - if (mUploadSize != null) 'm.upload.size': mUploadSize, + 'content_uri': contentUri.toString(), + if (unusedExpiresAt != null) 'unused_expires_at': unusedExpiresAt, }; } - /// The maximum size an upload can be in bytes. - /// Clients SHOULD use this as a guide when uploading content. - /// If not listed or null, the size limit should be treated as unknown. - int? mUploadSize; -} - -/// -@_NameSource('generated') -class GetUrlPreviewResponse { - GetUrlPreviewResponse({ - this.matrixImageSize, - this.ogImage, - }); - - GetUrlPreviewResponse.fromJson(Map json) - : matrixImageSize = - ((v) => v != null ? v as int : null)(json['matrix:image:size']), - ogImage = ((v) => - v != null ? Uri.parse(v as String) : null)(json['og:image']); - Map toJson() { - final matrixImageSize = this.matrixImageSize; - final ogImage = this.ogImage; - return { - if (matrixImageSize != null) 'matrix:image:size': matrixImageSize, - if (ogImage != null) 'og:image': ogImage.toString(), - }; - } - - /// The byte-size of the image. Omitted if there is no image attached. - int? matrixImageSize; - - /// An [MXC URI](https://spec.matrix.org/unstable/client-server-api/#matrix-content-mxc-uris) to the image. Omitted if there is no image. - Uri? ogImage; -} - -/// -@_NameSource('generated') -@EnhancedEnum() -enum Method { - @EnhancedEnumValue(name: 'crop') - crop, - @EnhancedEnumValue(name: 'scale') - scale + /// The [`mxc://` URI](https://spec.matrix.org/unstable/client-server-api/#matrix-content-mxc-uris) at + /// which the content will be available, once it is uploaded. + Uri contentUri; + + /// The timestamp (in milliseconds since the unix epoch) when the + /// generated media id will expire, if media is not uploaded. + int? unusedExpiresAt; } diff --git a/lib/matrix_api_lite/generated/model.g.dart b/lib/matrix_api_lite/generated/model.g.dart index 94a747cd..4b17c9ea 100644 --- a/lib/matrix_api_lite/generated/model.g.dart +++ b/lib/matrix_api_lite/generated/model.g.dart @@ -6,6 +6,88 @@ part of 'model.dart'; // EnhancedEnumGenerator // ************************************************************************** +extension RoleFromStringExtension on Iterable { + Role? fromString(String val) { + final override = { + 'm.role.admin': Role.mRoleAdmin, + 'm.role.security': Role.mRoleSecurity, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension RoleEnhancedEnum on Role { + @override +// ignore: override_on_non_overriding_member + String get name => { + Role.mRoleAdmin: 'm.role.admin', + Role.mRoleSecurity: 'm.role.security', + }[this]!; + bool get isMRoleAdmin => this == Role.mRoleAdmin; + bool get isMRoleSecurity => this == Role.mRoleSecurity; + T when({ + required T Function() mRoleAdmin, + required T Function() mRoleSecurity, + }) => + { + Role.mRoleAdmin: mRoleAdmin, + Role.mRoleSecurity: mRoleSecurity, + }[this]!(); + T maybeWhen({ + T? Function()? mRoleAdmin, + T? Function()? mRoleSecurity, + required T Function() orElse, + }) => + { + Role.mRoleAdmin: mRoleAdmin, + Role.mRoleSecurity: mRoleSecurity, + }[this] + ?.call() ?? + orElse(); +} + +extension MethodFromStringExtension on Iterable { + Method? fromString(String val) { + final override = { + 'crop': Method.crop, + 'scale': Method.scale, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension MethodEnhancedEnum on Method { + @override +// ignore: override_on_non_overriding_member + String get name => { + Method.crop: 'crop', + Method.scale: 'scale', + }[this]!; + bool get isCrop => this == Method.crop; + bool get isScale => this == Method.scale; + T when({ + required T Function() crop, + required T Function() scale, + }) => + { + Method.crop: crop, + Method.scale: scale, + }[this]!(); + T maybeWhen({ + T? Function()? crop, + T? Function()? scale, + required T Function() orElse, + }) => + { + Method.crop: crop, + Method.scale: scale, + }[this] + ?.call() ?? + orElse(); +} + extension DirectionFromStringExtension on Iterable { Direction? fromString(String val) { final override = { @@ -303,54 +385,6 @@ extension VisibilityEnhancedEnum on Visibility { orElse(); } -extension LoginTypeFromStringExtension on Iterable { - LoginType? fromString(String val) { - final override = { - 'm.login.password': LoginType.mLoginPassword, - 'm.login.token': LoginType.mLoginToken, - 'org.matrix.login.jwt': LoginType.mLoginJWT, - }[val]; -// ignore: unnecessary_this - return this.contains(override) ? override : null; - } -} - -extension LoginTypeEnhancedEnum on LoginType { - @override -// ignore: override_on_non_overriding_member - String get name => { - LoginType.mLoginPassword: 'm.login.password', - LoginType.mLoginToken: 'm.login.token', - LoginType.mLoginJWT: 'org.matrix.login.jwt', - }[this]!; - bool get isMLoginPassword => this == LoginType.mLoginPassword; - bool get isMLoginToken => this == LoginType.mLoginToken; - bool get isMLoginJWT => this == LoginType.mLoginJWT; - T when({ - required T Function() mLoginPassword, - required T Function() mLoginToken, - required T Function() mLoginJWT, - }) => - { - LoginType.mLoginPassword: mLoginPassword, - LoginType.mLoginToken: mLoginToken, - LoginType.mLoginJWT: mLoginJWT, - }[this]!(); - T maybeWhen({ - T? Function()? mLoginPassword, - T? Function()? mLoginToken, - T? Function()? mLoginJWT, - required T Function() orElse, - }) => - { - LoginType.mLoginPassword: mLoginPassword, - LoginType.mLoginToken: mLoginToken, - LoginType.mLoginJWT: mLoginJWT, - }[this] - ?.call() ?? - orElse(); -} - extension PresenceTypeFromStringExtension on Iterable { PresenceType? fromString(String val) { final override = { @@ -821,44 +855,3 @@ extension EventFormatEnhancedEnum on EventFormat { ?.call() ?? orElse(); } - -extension MethodFromStringExtension on Iterable { - Method? fromString(String val) { - final override = { - 'crop': Method.crop, - 'scale': Method.scale, - }[val]; -// ignore: unnecessary_this - return this.contains(override) ? override : null; - } -} - -extension MethodEnhancedEnum on Method { - @override -// ignore: override_on_non_overriding_member - String get name => { - Method.crop: 'crop', - Method.scale: 'scale', - }[this]!; - bool get isCrop => this == Method.crop; - bool get isScale => this == Method.scale; - T when({ - required T Function() crop, - required T Function() scale, - }) => - { - Method.crop: crop, - Method.scale: scale, - }[this]!(); - T maybeWhen({ - T? Function()? crop, - T? Function()? scale, - required T Function() orElse, - }) => - { - Method.crop: crop, - Method.scale: scale, - }[this] - ?.call() ?? - orElse(); -} diff --git a/lib/matrix_api_lite/matrix_api.dart b/lib/matrix_api_lite/matrix_api.dart index 2cbaacaa..d6da5c87 100644 --- a/lib/matrix_api_lite/matrix_api.dart +++ b/lib/matrix_api_lite/matrix_api.dart @@ -150,27 +150,6 @@ class MatrixApi extends Api { return jsonResp!; } - /// Publishes end-to-end encryption keys for the device. - /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-query - Future> uploadKeys( - {MatrixDeviceKeys? deviceKeys, - Map? oneTimeKeys, - Map? fallbackKeys}) async { - final response = await request( - RequestType.POST, - '/client/v3/keys/upload', - data: { - if (deviceKeys != null) 'device_keys': deviceKeys.toJson(), - if (oneTimeKeys != null) 'one_time_keys': oneTimeKeys, - if (fallbackKeys != null) ...{ - 'fallback_keys': fallbackKeys, - 'org.matrix.msc2732.fallback_keys': fallbackKeys, - }, - }, - ); - return Map.from(response['one_time_key_counts'] as Map); - } - /// This endpoint allows the creation, modification and deletion of pushers /// for this user ID. The behaviour of this endpoint varies depending on the /// values in the JSON body. diff --git a/lib/msc_extensions/msc_2835_uia_login/msc_2835_uia_login.dart b/lib/msc_extensions/msc_2835_uia_login/msc_2835_uia_login.dart index 631a303d..00a00f90 100644 --- a/lib/msc_extensions/msc_2835_uia_login/msc_2835_uia_login.dart +++ b/lib/msc_extensions/msc_2835_uia_login/msc_2835_uia_login.dart @@ -12,7 +12,7 @@ extension UiaLogin on Client { /// Set `pathVersion` to `r0` if you need to use the previous /// version of the login endpoint. Future uiaLogin( - LoginType type, { + String type, { String? address, String? deviceId, AuthenticationIdentifier? identifier, @@ -37,10 +37,7 @@ extension UiaLogin on Client { if (medium != null) 'medium': medium, if (password != null) 'password': password, if (token != null) 'token': token, - 'type': { - LoginType.mLoginPassword: 'm.login.password', - LoginType.mLoginToken: 'm.login.token' - }[type]!, + 'type': type, if (user != null) 'user': user, if (auth != null) 'auth': auth.toJson(), if (refreshToken != null) 'refresh_token': refreshToken, diff --git a/lib/src/client.dart b/lib/src/client.dart index d842fe5d..c96f0680 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -538,7 +538,7 @@ class Client extends MatrixApi { final loginTypes = await getLoginFlows() ?? []; if (!loginTypes.any((f) => supportedLoginTypes.contains(f.type))) { throw BadServerLoginTypesException( - loginTypes.map((f) => f.type ?? '').toSet(), supportedLoginTypes); + loginTypes.map((f) => f.type).toSet(), supportedLoginTypes); } return (wellKnown, versions, loginTypes); @@ -628,7 +628,7 @@ class Client extends MatrixApi { /// older server versions. @override Future login( - LoginType type, { + String type, { AuthenticationIdentifier? identifier, String? password, String? token, @@ -1219,7 +1219,7 @@ class Client extends MatrixApi { versionsResponse.unstableFeatures?['org.matrix.msc3916.stable'] == true; } - final _serverConfigCache = AsyncCache(const Duration(hours: 1)); + final _serverConfigCache = AsyncCache(const Duration(hours: 1)); /// This endpoint allows clients to retrieve the configuration of the content /// repository, such as upload limitations. @@ -1232,27 +1232,11 @@ class Client extends MatrixApi { /// repository APIs, for example, proxies may enforce a lower upload size limit /// than is advertised by the server on this endpoint. @override - Future getConfig() => - _serverConfigCache.fetch(() => _getAuthenticatedConfig()); - - // TODO: remove once we are able to autogen this - Future _getAuthenticatedConfig() async { - String path; - if (await authenticatedMediaSupported()) { - path = '_matrix/client/v1/media/config'; - } else { - path = '_matrix/media/v3/config'; - } - final requestUri = Uri(path: path); - final request = http.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 ServerConfig.fromJson(json as Map); - } + Future getConfig() => + _serverConfigCache.fetch(() async => (await authenticatedMediaSupported()) + ? getConfigAuthed() + // ignore: deprecated_member_use_from_same_package + : super.getConfig()); /// /// @@ -1262,124 +1246,126 @@ class Client extends MatrixApi { /// [mediaId] The media ID from the `mxc://` URI (the path component) /// /// - /// [allowRemote] Indicates to the server that it should not attempt to fetch the media if it is deemed - /// remote. This is to prevent routing loops where the server contacts itself. Defaults to - /// true if not provided. + /// [allowRemote] Indicates to the server that it should not attempt to fetch the media if + /// it is deemed remote. This is to prevent routing loops where the server + /// contacts itself. + /// + /// Defaults to `true` if not provided. + /// + /// [timeoutMs] The maximum number of milliseconds that the client is willing to wait to + /// start receiving data, in the case that the content has not yet been + /// uploaded. The default value is 20000 (20 seconds). The content + /// repository SHOULD impose a maximum value for this parameter. The + /// content repository MAY respond before the timeout. + /// + /// + /// [allowRedirect] Indicates to the server that it may return a 307 or 308 redirect + /// response that points at the relevant media content. When not explicitly + /// set to `true` the server must return the media content itself. /// @override - // TODO: remove once we are able to autogen this - Future getContent(String serverName, String mediaId, - {bool? allowRemote}) async { - String path; - - if (await authenticatedMediaSupported()) { - path = - '_matrix/client/v1/media/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}'; - } else { - path = - '_matrix/media/v3/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}'; - } - final requestUri = Uri(path: path, queryParameters: { - if (allowRemote != null && !await authenticatedMediaSupported()) - // removed with msc3916, so just to be explicit - 'allow_remote': allowRemote.toString(), - }); - final request = http.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); - return FileResponse( - contentType: response.headers['content-type'], data: responseBody); + Future getContent( + String serverName, + String mediaId, { + bool? allowRemote, + int? timeoutMs, + bool? allowRedirect, + }) async { + return (await authenticatedMediaSupported()) + ? getContentAuthed( + serverName, + mediaId, + timeoutMs: timeoutMs, + ) + // ignore: deprecated_member_use_from_same_package + : super.getContent( + serverName, + mediaId, + allowRemote: allowRemote, + timeoutMs: timeoutMs, + allowRedirect: allowRedirect, + ); } /// This will download content from the content repository (same as /// the previous endpoint) but replace the target file name with the one /// provided by the caller. /// - /// [serverName] The server name from the `mxc://` URI (the authoritory component) + /// {{% boxes/warning %}} + /// {{< changed-in v="1.11" >}} This endpoint MAY return `404 M_NOT_FOUND` + /// for media which exists, but is after the server froze unauthenticated + /// media access. See [Client Behaviour](https://spec.matrix.org/unstable/client-server-api/#content-repo-client-behaviour) for more + /// information. + /// {{% /boxes/warning %}} + /// + /// [serverName] The server name from the `mxc://` URI (the authority component). /// /// - /// [mediaId] The media ID from the `mxc://` URI (the path component) + /// [mediaId] The media ID from the `mxc://` URI (the path component). /// /// /// [fileName] A filename to give in the `Content-Disposition` header. /// - /// [allowRemote] Indicates to the server that it should not attempt to fetch the media if it is deemed - /// remote. This is to prevent routing loops where the server contacts itself. Defaults to - /// true if not provided. + /// [allowRemote] Indicates to the server that it should not attempt to fetch the media if + /// it is deemed remote. This is to prevent routing loops where the server + /// contacts itself. /// + /// Defaults to `true` if not provided. + /// + /// [timeoutMs] The maximum number of milliseconds that the client is willing to wait to + /// start receiving data, in the case that the content has not yet been + /// uploaded. The default value is 20000 (20 seconds). The content + /// repository SHOULD impose a maximum value for this parameter. The + /// content repository MAY respond before the timeout. + /// + /// + /// [allowRedirect] Indicates to the server that it may return a 307 or 308 redirect + /// response that points at the relevant media content. When not explicitly + /// set to `true` the server must return the media content itself. @override - // TODO: remove once we are able to autogen this Future getContentOverrideName( - String serverName, String mediaId, String fileName, - {bool? allowRemote}) async { - String path; - if (await authenticatedMediaSupported()) { - path = - '_matrix/client/v1/media/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}/${Uri.encodeComponent(fileName)}'; - } else { - path = - '_matrix/media/v3/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}/${Uri.encodeComponent(fileName)}'; - } - final requestUri = Uri(path: path, queryParameters: { - if (allowRemote != null && !await authenticatedMediaSupported()) - // removed with msc3916, so just to be explicit - 'allow_remote': allowRemote.toString(), - }); - final request = http.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); - return FileResponse( - contentType: response.headers['content-type'], data: responseBody); - } - - /// Get information about a URL for the client. Typically this is called when a - /// client sees a URL in a message and wants to render a preview for the user. - /// - /// **Note:** - /// Clients should consider avoiding this endpoint for URLs posted in encrypted - /// rooms. Encrypted rooms often contain more sensitive information the users - /// do not want to share with the homeserver, and this can mean that the URLs - /// being shared should also not be shared with the homeserver. - /// - /// [url] The URL to get a preview of. - /// - /// [ts] The preferred point in time to return a preview for. The server may - /// return a newer version if it does not have the requested version - /// available. - @override - // TODO: remove once we are able to autogen this - Future getUrlPreview(Uri url, {int? ts}) async { - String path; - if (await authenticatedMediaSupported()) { - path = '_matrix/client/v1/media/preview_url'; - } else { - path = '_matrix/media/v3/preview_url'; - } - final requestUri = Uri(path: path, queryParameters: { - 'url': url.toString(), - if (ts != null) 'ts': ts.toString(), - }); - final request = http.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 GetUrlPreviewResponse.fromJson(json as Map); + String serverName, + String mediaId, + String fileName, { + bool? allowRemote, + int? timeoutMs, + bool? allowRedirect, + }) async { + return (await authenticatedMediaSupported()) + ? getContentOverrideNameAuthed( + serverName, + mediaId, + fileName, + timeoutMs: timeoutMs, + ) + // ignore: deprecated_member_use_from_same_package + : super.getContentOverrideName( + serverName, + mediaId, + fileName, + allowRemote: allowRemote, + timeoutMs: timeoutMs, + allowRedirect: allowRedirect, + ); } /// Download a thumbnail of content from the content repository. /// See the [Thumbnails](https://spec.matrix.org/unstable/client-server-api/#thumbnails) section for more information. /// - /// [serverName] The server name from the `mxc://` URI (the authoritory component) + /// {{% boxes/note %}} + /// Clients SHOULD NOT generate or use URLs which supply the access token in + /// the query string. These URLs may be copied by users verbatim and provided + /// in a chat message to another user, disclosing the sender's access token. + /// {{% /boxes/note %}} + /// + /// Clients MAY be redirected using the 307/308 responses below to download + /// the request object. This is typical when the homeserver uses a Content + /// Delivery Network (CDN). + /// + /// [serverName] The server name from the `mxc://` URI (the authority component). /// /// - /// [mediaId] The media ID from the `mxc://` URI (the path component) + /// [mediaId] The media ID from the `mxc://` URI (the path component). /// /// /// [width] The *desired* width of the thumbnail. The actual thumbnail may be @@ -1391,39 +1377,82 @@ class Client extends MatrixApi { /// [method] The desired resizing method. See the [Thumbnails](https://spec.matrix.org/unstable/client-server-api/#thumbnails) /// section for more information. /// - /// [allowRemote] Indicates to the server that it should not attempt to fetch - /// the media if it is deemed remote. This is to prevent routing loops - /// where the server contacts itself. Defaults to true if not provided. + /// [timeoutMs] The maximum number of milliseconds that the client is willing to wait to + /// start receiving data, in the case that the content has not yet been + /// uploaded. The default value is 20000 (20 seconds). The content + /// repository SHOULD impose a maximum value for this parameter. The + /// content repository MAY respond before the timeout. + /// + /// + /// [animated] Indicates preference for an animated thumbnail from the server, if possible. Animated + /// thumbnails typically use the content types `image/gif`, `image/png` (with APNG format), + /// `image/apng`, and `image/webp` instead of the common static `image/png` or `image/jpeg` + /// content types. + /// + /// When `true`, the server SHOULD return an animated thumbnail if possible and supported. + /// When `false`, the server MUST NOT return an animated thumbnail. For example, returning a + /// static `image/png` or `image/jpeg` thumbnail. When not provided, the server SHOULD NOT + /// return an animated thumbnail. + /// + /// Servers SHOULD prefer to return `image/webp` thumbnails when supporting animation. + /// + /// When `true` and the media cannot be animated, such as in the case of a JPEG or PDF, the + /// server SHOULD behave as though `animated` is `false`. @override - // TODO: remove once we are able to autogen this Future getContentThumbnail( - String serverName, String mediaId, int width, int height, - {Method? method, bool? allowRemote}) async { - String path; - if (await authenticatedMediaSupported()) { - path = - '_matrix/client/v1/media/thumbnail/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}'; - } else { - path = - '_matrix/media/v3/thumbnail/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}'; - } + String serverName, + String mediaId, + int width, + int height, { + Method? method, + bool? allowRemote, + int? timeoutMs, + bool? allowRedirect, + bool? animated, + }) async { + return (await authenticatedMediaSupported()) + ? getContentThumbnailAuthed( + serverName, + mediaId, + width, + height, + method: method, + timeoutMs: timeoutMs, + animated: animated, + ) + // ignore: deprecated_member_use_from_same_package + : super.getContentThumbnail( + serverName, + mediaId, + width, + height, + method: method, + timeoutMs: timeoutMs, + animated: animated, + ); + } - final requestUri = Uri(path: path, queryParameters: { - 'width': width.toString(), - 'height': height.toString(), - if (method != null) 'method': method.name, - if (allowRemote != null && !await authenticatedMediaSupported()) - // removed with msc3916, so just to be explicit - 'allow_remote': allowRemote.toString(), - }); - - final request = http.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); - return FileResponse( - contentType: response.headers['content-type'], data: responseBody); + /// Get information about a URL for the client. Typically this is called when a + /// client sees a URL in a message and wants to render a preview for the user. + /// + /// {{% boxes/note %}} + /// Clients should consider avoiding this endpoint for URLs posted in encrypted + /// rooms. Encrypted rooms often contain more sensitive information the users + /// do not want to share with the homeserver, and this can mean that the URLs + /// being shared should also not be shared with the homeserver. + /// {{% /boxes/note %}} + /// + /// [url] The URL to get a preview of. + /// + /// [ts] The preferred point in time to return a preview for. The server may + /// return a newer version if it does not have the requested version + /// available. + @override + Future getUrlPreview(Uri url, {int? ts}) async { + return (await authenticatedMediaSupported()) + ? getUrlPreviewAuthed(url, ts: ts) + // ignore: deprecated_member_use_from_same_package + : super.getUrlPreview(url, ts: ts); } /// Uploads a file and automatically caches it in the database, if it is small enough @@ -1614,7 +1643,7 @@ class Client extends MatrixApi { final CachedStreamController onKeyVerificationRequest = CachedStreamController(); - /// When the library calls an endpoint that needs UIA the `UiaRequest` is passed down this screen. + /// When the library calls an endpoint that needs UIA the `UiaRequest` is passed down this stream. /// The client can open a UIA prompt based on this. final CachedStreamController onUiaRequest = CachedStreamController(); @@ -3348,10 +3377,12 @@ class Client extends MatrixApi { /// Changes the password. You should either set oldPasswort or another authentication flow. @override - Future changePassword(String newPassword, - {String? oldPassword, - AuthenticationData? auth, - bool? logoutDevices}) async { + Future changePassword( + String newPassword, { + String? oldPassword, + AuthenticationData? auth, + bool? logoutDevices, + }) async { final userID = this.userID; try { if (oldPassword != null && userID != null) { diff --git a/lib/src/models/login_type.dart b/lib/src/models/login_type.dart new file mode 100644 index 00000000..be2dfd7f --- /dev/null +++ b/lib/src/models/login_type.dart @@ -0,0 +1,4 @@ +abstract class LoginType { + static const mLoginPassword = 'm.login.password'; + static const mLoginToken = 'm.login.token'; +} diff --git a/lib/src/room.dart b/lib/src/room.dart index d76617a3..9e5ee931 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -450,11 +450,12 @@ class Room { /// Add a tag to the room. Future addTag(String tag, {double? order}) => client.setRoomTag( - client.userID!, - id, - tag, + client.userID!, + id, + tag, + Tag( order: order, - ); + )); /// Removes a tag from the room. Future removeTag(String tag) => client.deleteRoomTag( @@ -469,8 +470,9 @@ class Room { static Tag _tryTagFromJson(Object o) { if (o is Map) { return Tag( - order: o.tryGet('order', TryGet.silent)?.toDouble(), - additionalProperties: Map.from(o)..remove('order')); + order: o.tryGet('order', TryGet.silent)?.toDouble(), + additionalProperties: Map.from(o)..remove('order'), + ); } return Tag(); }