diff --git a/lib/fake_matrix_api.dart b/lib/fake_matrix_api.dart new file mode 100644 index 00000000..006d0862 --- /dev/null +++ b/lib/fake_matrix_api.dart @@ -0,0 +1,2406 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'dart:convert'; +import 'dart:core'; +import 'dart:math'; + +import 'package:http/http.dart'; +import 'package:http/testing.dart'; + +import 'package:matrix_api_lite/matrix_api_lite.dart'; + +Map decodeJson(dynamic data) { + if (data is String) { + return json.decode(data) as Map; + } + if (data is Map && data.isEmpty) { + return {}; + } + return data as Map; +} + +T? tryCast(dynamic object) => object is T ? object : null; + +/// A mock http client for testing purposes. +class FakeMatrixApi extends MockClient { + static final calledEndpoints = >{}; + static int eventCounter = 0; + + FakeMatrixApi() + : super((request) async { + // Collect data from Request + var action = request.url.path; + if (request.url.path.contains('/_matrix')) { + action = + '${request.url.path.split('/_matrix').last}?${request.url.query}'; + } + + if (action.endsWith('?')) { + action = action.substring(0, action.length - 1); + } + if (action.endsWith('/')) { + action = action.substring(0, action.length - 1); + } + final method = request.method; + final dynamic data = + method == 'GET' ? request.url.queryParameters : request.body; + dynamic res = {}; + var statusCode = 200; + + //print('\$method request to $action with Data: $data'); + + // Sync requests with timeout + if (data is Map && data['timeout'] is String) { + await Future.delayed(Duration(seconds: 5)); + } + + if (request.url.origin != 'https://fakeserver.notexisting') { + return Response( + 'Not found...', 404); + } + + // Call API + if (!calledEndpoints.containsKey(action)) { + calledEndpoints[action] = []; + } + calledEndpoints[action]!.add(data); + if (api.containsKey(method) && api[method]!.containsKey(action)) { + res = api[method]![action]?.call(data); + if (res is Map && res.containsKey('errcode')) { + statusCode = 405; + } + } else if (method == 'PUT' && + action.contains('/client/v3/sendToDevice/')) { + res = {}; + } else if (method == 'GET' && + action.contains('/client/v3/rooms/') && + action.contains('/state/m.room.member/')) { + res = {'displayname': ''}; + } else if (method == 'PUT' && + action.contains( + '/client/v3/rooms/!1234%3AfakeServer.notExisting/send/')) { + res = {'event_id': '\$event${FakeMatrixApi.eventCounter++}'}; + } else if (action.contains('/client/v3/sync')) { + res = { + 'next_batch': DateTime.now().millisecondsSinceEpoch.toString + }; + } else { + res = { + 'errcode': 'M_UNRECOGNIZED', + 'error': 'Unrecognized request' + }; + statusCode = 405; + } + + return Response.bytes(utf8.encode(json.encode(res)), statusCode); + }); + + static Map messagesResponse = { + 'start': 't47429-4392820_219380_26003_2265', + 'end': 't47409-4357353_219380_26003_2265', + 'chunk': [ + { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '3143273582443PhrSn:example.org', + 'room_id': '!1234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + { + 'content': {'name': 'The room name'}, + 'type': 'm.room.name', + 'event_id': '2143273582443PhrSn:example.org', + 'room_id': '!1234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'body': 'Gangnam Style', + 'url': 'mxc://example.org/a526eYUSFFxlgbQYZmo442', + 'info': { + 'thumbnail_url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe', + 'thumbnail_info': { + 'mimetype': 'image/jpeg', + 'size': 46144, + 'w': 300, + 'h': 300 + }, + 'w': 480, + 'h': 320, + 'duration': 2140786, + 'size': 1563685, + 'mimetype': 'video/mp4' + }, + 'msgtype': 'm.video' + }, + 'type': 'm.room.message', + 'event_id': '1143273582443PhrSn:example.org', + 'room_id': '!1234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + ], + 'state': >[], + }; + + static Map syncResponse = { + 'next_batch': Random().nextDouble().toString(), + 'rooms': { + 'join': { + '!726s6s6q:example.com': { + 'summary': { + 'm.heroes': ['@alice:example.com', '@bob:example.com'], + 'm.joined_member_count': 2, + 'm.invited_member_count': 0 + }, + 'unread_notifications': { + 'highlight_count': 2, + 'notification_count': 2, + }, + 'state': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.room.member', + 'state_key': '@alice:example.com', + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid', + }, + 'origin_server_ts': 1417731086795, + 'event_id': '66697273743031:example.com' + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.canonical_alias', + 'content': { + 'alias': '#famedlyContactDiscovery:fakeServer.notExisting' + }, + 'state_key': '', + 'origin_server_ts': 1417731086796, + 'event_id': '66697273743032:example.com' + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.encryption', + 'state_key': '', + 'content': {'algorithm': AlgorithmTypes.megolmV1AesSha2}, + 'origin_server_ts': 1417731086795, + 'event_id': '666972737430353:example.com' + }, + { + 'content': { + 'pinned': ['1234:bla'] + }, + 'type': 'm.room.pinned_events', + 'event_id': '21432735824443PhrSn:example.org', + 'room_id': '!1234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + ] + }, + 'timeline': { + 'events': [ + { + 'sender': '@bob:example.com', + 'type': 'm.room.member', + 'state_key': '@bob:example.com', + 'content': {'membership': 'join'}, + 'prev_content': {'membership': 'invite'}, + 'origin_server_ts': 1417731086795, + 'event_id': '7365636s6r6432:example.com', + 'unsigned': {'foo': 'bar'} + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.message', + 'content': {'body': 'I am a fish', 'msgtype': 'm.text'}, + 'origin_server_ts': 1417731086797, + 'event_id': '74686972643033:example.com' + } + ], + 'limited': true, + 'prev_batch': 't34-23535_0_0' + }, + 'ephemeral': { + 'events': [ + { + 'type': 'm.typing', + 'content': { + 'user_ids': ['@alice:example.com'] + } + }, + { + 'content': { + '7365636s6r6432:example.com': { + 'm.read': { + '@alice:example.com': {'ts': 1436451550453} + } + } + }, + 'room_id': '!726s6s6q:example.com', + 'type': 'm.receipt' + } + ] + }, + 'account_data': { + 'events': [ + { + 'type': 'm.tag', + 'content': { + 'tags': { + 'work': {'order': 1} + } + } + }, + { + 'type': 'org.example.custom.room.config', + 'content': {'custom_config_key': 'custom_config_value'} + } + ] + } + } + }, + 'invite': { + '!696r7674:example.com': { + 'invite_state': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.room.name', + 'state_key': '', + 'content': {'name': 'My Room Name'} + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.member', + 'state_key': '@bob:example.com', + 'content': {'membership': 'invite'} + } + ] + } + } + }, + 'leave': { + '!726s6s6f:example.com': { + 'state': { + 'events': [ + { + 'sender': '@charley:example.com', + 'type': 'm.room.name', + 'state_key': '', + 'content': {'name': 'left room'}, + 'origin_server_ts': 1417731086795, + 'event_id': '66697273743031:example.com' + }, + ] + }, + 'timeline': { + 'events': [ + { + 'sender': '@bob:example.com', + 'type': 'm.room.message', + 'content': {'text': 'Hallo'}, + 'origin_server_ts': 1417731086795, + 'event_id': '7365636s6r64300:example.com', + 'unsigned': {'foo': 'bar'} + }, + ], + 'limited': true, + 'prev_batch': 't34-23535_0_0' + }, + 'account_data': { + 'events': [ + { + 'type': 'm.tag', + 'content': { + 'tags': { + 'work': {'order': 1} + } + } + }, + { + 'type': 'org.example.custom.room.config', + 'content': {'custom_config_key': 'custom_config_value'} + } + ] + } + } + }, + }, + 'presence': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.presence', + 'content': {'presence': 'online'} + } + ] + }, + 'account_data': { + 'events': [ + { + 'content': { + 'global': { + 'content': [ + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight'} + ], + 'default': true, + 'enabled': true, + 'pattern': 'alice', + 'rule_id': '.m.rule.contains_user_name' + } + ], + 'override': [ + { + 'actions': ['dont_notify'], + 'conditions': >[], + 'default': true, + 'enabled': false, + 'rule_id': '.m.rule.master' + }, + { + 'actions': ['dont_notify'], + 'conditions': [ + { + 'key': 'content.msgtype', + 'kind': 'event_match', + 'pattern': 'm.notice' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.suppress_notices' + } + ], + 'room': [ + { + 'actions': ['dont_notify'], + 'conditions': [ + { + 'key': 'room_id', + 'kind': 'event_match', + 'pattern': '!localpart:server.abc', + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '!localpart:server.abc' + } + ], + 'sender': >[], + 'underride': [ + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'ring'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.call.invite' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.call' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight'} + ], + 'conditions': [ + {'kind': 'contains_display_name'} + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.contains_display_name' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + {'is': '2', 'kind': 'room_member_count'}, + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.message' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.room_one_to_one' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.member' + }, + { + 'key': 'content.membership', + 'kind': 'event_match', + 'pattern': 'invite' + }, + { + 'key': 'state_key', + 'kind': 'event_match', + 'pattern': '@alice:example.com' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.invite_for_me' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.member' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.member_event' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.message' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.message' + } + ] + } + }, + 'type': 'm.push_rules' + }, + { + 'type': 'org.example.custom.config', + 'content': {'custom_config_key': 'custom_config_value'} + }, + { + 'content': { + '@bob:example.com': [ + '!726s6s6q:example.com', + '!hgfedcba:example.com' + ] + }, + 'type': 'm.direct' + }, + { + 'type': EventTypes.SecretStorageDefaultKey, + 'content': {'key': '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3'} + }, + { + 'type': 'm.secret_storage.key.0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3', + 'content': { + 'algorithm': AlgorithmTypes.secretStorageV1AesHmcSha2, + 'passphrase': { + 'algorithm': AlgorithmTypes.pbkdf2, + 'iterations': 500000, + 'salt': 'F4jJ80mr0Fc8mRwU9JgA3lQDyjPuZXQL' + }, + 'iv': 'HjbTgIoQH2pI7jQo19NUzA==', + 'mac': 'QbJjQzDnAggU0cM4RBnDxw2XyarRGjdahcKukP9xVlk=' + } + }, + { + 'type': 'm.cross_signing.master', + 'content': { + 'encrypted': { + '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3': { + 'iv': 'eIb2IITxtmcq+1TrT8D5eQ==', + 'ciphertext': + 'lWRTPo5qxf4LAVwVPzGHOyMcP181n7bb9/B0lvkLDC2Oy4DvAL0eLx2x3bY=', + 'mac': 'Ynx89tIxPkx0o6ljMgxszww17JOgB4tg4etmNnMC9XI=' + } + } + } + }, + { + 'type': EventTypes.CrossSigningSelfSigning, + 'content': { + 'encrypted': { + '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3': { + 'iv': 'YqU2XIjYulYZl+bkZtGgVw==', + 'ciphertext': + 'kM2TSoy/jR/4d357ZoRPbpPypxQl6XRLo3FsEXz+f7vIOp82GeRp28RYb3k=', + 'mac': 'F+DZa5tAFmWsYSryw5EuEpzTmmABRab4GETkM85bGGo=' + } + } + } + }, + { + 'type': EventTypes.CrossSigningUserSigning, + 'content': { + 'encrypted': { + '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3': { + 'iv': 'D7AM3LXFu7ZlyGOkR+OeqQ==', + 'ciphertext': + 'bYA2+OMgsO6QB1E31aY+ESAWrT0fUBTXqajy4qmL7bVDSZY4Uj64EXNbHuA=', + 'mac': 'j2UtyPo/UBSoiaQCWfzCiRZXp3IRt0ZZujuXgUMjnw4=' + } + } + } + }, + { + 'type': EventTypes.MegolmBackup, + 'content': { + 'encrypted': { + '0FajDWYaM6wQ4O60OZnLvwZfsBNu4Bu3': { + 'iv': 'cL/0MJZaiEd3fNU+I9oJrw==', + 'ciphertext': + 'WL73Pzdk5wZdaaSpaeRH0uZYKcxkuV8IS6Qa2FEfA1+vMeRLuHcWlXbMX0w=', + 'mac': '+xozp909S6oDX8KRV8D8ZFVRyh7eEYQpPP76f+DOsnw=' + } + } + } + } + ] + }, + 'to_device': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.new_device', + 'content': { + 'device_id': 'XYZABCDE', + 'rooms': ['!726s6s6q:example.com'] + } + }, +// { +// 'sender': '@othertest:fakeServer.notExisting', +// 'content': { +// 'algorithm': AlgorithmTypes.megolmV1AesSha2, +// 'room_id': '!726s6s6q:example.com', +// 'session_id': 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU', +// 'session_key': +// 'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw' +// }, +// 'type': 'm.room_key' +// }, + { + // this is the commented out m.room_key event - only encrypted + 'sender': '@othertest:fakeServer.notExisting', + 'content': { + 'algorithm': AlgorithmTypes.olmV1Curve25519AesSha2, + 'sender_key': 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg', + 'ciphertext': { + '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk': { + 'type': 0, + 'body': + 'Awogyh7K4iLUQjcOxIfi7q7LhBBqv9w0mQ6JI9+U9tv7iF4SIHC6xb5YFWf9voRnmDBbd+0vxD/xDlVNRDlPIKliLGkYGiAkEbtlo+fng4ELtO4gSLKVbcFn7tZwZCEUE8H2miBsCCKABgMKIFrKDJwB7gM3lXPt9yVoh6gQksafKt7VFCNRN5KLKqsDEAAi0AX5EfTV7jJ1ZWAbxftjoSN6kCVIxzGclbyg1HjchmNCX7nxNCHWl+q5ZgqHYZVu2n2mCVmIaKD0kvoEZeY3tV1Itb6zf67BLaU0qgW/QzHCHg5a44tNLjucvL2mumHjIG8k0BY2uh+52HeiMCvSOvtDwHg7nzCASGdqPVCj9Kzw6z7F6nL4e3mYim8zvJd7f+mD9z3ARrypUOLGkTGYbB2PQOovf0Do8WzcaRzfaUCnuu/YVZWKK7DPgG8uhw/TjR6XtraAKZysF+4DJYMG9SQWx558r6s7Z5EUOF5CU2M35w1t1Xxllb3vrS83dtf9LPCrBhLsEBeYEUBE2+bTBfl0BDKqLiB0Cc0N0ixOcHIt6e40wAvW622/gMgHlpNSx8xG12u0s6h6EMWdCXXLWd9fy2q6glFUHvA67A35q7O+M8DVml7Y9xG55Y3DHkMDc9cwgwFkBDCAYQe6pQF1nlKytcVCGREpBs/gq69gHAStMQ8WEg38Lf8u8eBr2DFexrN4U+QAk+S//P3fJgf0bQx/Eosx4fvWSz9En41iC+ADCsWQpMbwHn4JWvtAbn3oW0XmL/OgThTkJMLiCymduYAa1Hnt7a3tP0KTL2/x11F02ggQHL28cCjq5W4zUGjWjl5wo2PsKB6t8aAvMg2ujGD2rCjb4yrv5VIzAKMOZLyj7K0vSK9gwDLQ/4vq+QnKUBG5zrcOze0hX+kz2909/tmAdeCH61Ypw7gbPUJAKnmKYUiB/UgwkJvzMJSsk/SEs5SXosHDI+HsJHJp4Mp4iKD0xRMst+8f9aTjaWwh8ZvELE1ZOhhCbF3RXhxi3x2Nu8ORIz+vhEQ1NOlMc7UIo98Fk/96T36vL/fviowT4C/0AlaapZDJBmKwhmwqisMjY2n1vY29oM2p5BzY1iwP7q9BYdRFst6xwo57TNSuRwQw7IhFsf0k+ABuPEZy5xB5nPHyIRTf/pr3Hw', + }, + }, + }, + 'type': 'm.room.encrypted', + }, + ] + }, + 'device_lists': { + 'changed': [ + '@alice:example.com', + ], + 'left': [ + '@bob:example.com', + ], + }, + 'device_one_time_keys_count': {'curve25519': 10, 'signed_curve25519': 20}, + }; + + static Map archiveSyncResponse = { + 'next_batch': Random().nextDouble().toString(), + 'presence': {'events': >[]}, + 'account_data': {'events': >[]}, + 'to_device': {'events': >[]}, + 'rooms': { + 'join': {}, + 'invite': {}, + 'leave': { + '!5345234234:example.com': { + 'timeline': { + 'events': [ + { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '143273582443PhrSn:example.org', + 'room_id': '!5345234234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + ] + }, + 'state': { + 'events': [ + { + 'content': {'name': 'The room name'}, + 'type': 'm.room.name', + 'event_id': '2143273582443PhrSn:example.org', + 'room_id': '!5345234234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + ] + }, + 'account_data': { + 'events': [ + { + 'type': 'test.type.data', + 'content': {'foo': 'bar'}, + }, + ], + }, + }, + '!5345234235:example.com': { + 'timeline': {'events': >[]}, + 'state': { + 'events': [ + { + 'content': {'name': 'The room name 2'}, + 'type': 'm.room.name', + 'event_id': '2143273582443PhrSn:example.org', + 'room_id': '!5345234235:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + ] + } + }, + }, + } + }; + + static Map spaceHierarchyResponse = { + 'rooms': [ + { + 'room_id': '!gPxZhKUssFZKZcoCKY:neko.dev', + 'name': 'Gentoo Community', + 'topic': + 'Unofficial Gentoo community rooms. Please keep chatter to the chatter room and have Gentoo discussions and questions in the actual Gentoo rooms. Also listen to the mods and try to keep nasty stuff to yourself. Thank you!', + 'canonical_alias': '#gentoo-community:matrix.org', + 'num_joined_members': 71, + 'avatar_url': 'mxc://neko.dev/YShzHcuHenkxaxostDrlMEYL', + 'join_rule': 'public', + 'world_readable': true, + 'guest_can_join': true, + 'room_type': 'm.space', + 'children_state': [ + { + 'type': 'm.space.child', + 'state_key': '!UpbiadsgRndwvYKPLK:neko.dev', + 'content': { + 'via': ['neko.dev'], + 'suggested': true, + 'auto_join': false + }, + 'sender': '@deepbluev7:neko.dev', + 'origin_server_ts': 1636376075316 + }, + { + 'type': 'm.space.child', + 'state_key': '!aZUzMIEZvEwnDquxLf:neko.dev', + 'content': { + 'via': ['neko.dev', 'matrix.org', 'privacytools.io'], + 'suggested': true, + 'auto_join': false + }, + 'sender': '@deepbluev7:neko.dev', + 'origin_server_ts': 1636378043231 + }, + { + 'type': 'm.space.child', + 'state_key': '!aRzRZBvOAkHMcEjAPS:libera.chat', + 'content': { + 'via': ['libera.chat', 'kde.org', 'neko.dev'], + 'suggested': false, + 'auto_join': false + }, + 'sender': '@deepbluev7:neko.dev', + 'origin_server_ts': 1637612945217 + }, + { + 'type': 'm.space.child', + 'state_key': '!DXsRRxkqqDhDkAyXfL:matrix.org', + 'content': { + 'via': ['matrix.org', 'libera.chat', 'neko.dev'], + 'suggested': false, + 'auto_join': false + }, + 'sender': '@deepbluev7:neko.dev', + 'origin_server_ts': 1637612945334 + }, + { + 'type': 'm.space.child', + 'state_key': '!tbCRpmsMiwMBlIThOd:matrix.org', + 'content': { + 'via': ['kde.org', 'matrix.org', 'server.matrix4ulm.de'], + 'suggested': false, + 'auto_join': false + }, + 'sender': '@deepbluev7:neko.dev', + 'origin_server_ts': 1637612945491 + }, + { + 'type': 'm.space.child', + 'state_key': '!LPpmvMsEgDwuSuHSpM:matrix.org', + 'content': { + 'via': ['matrix.org', 'anontier.nl', 't2bot.io'], + 'suggested': false, + 'auto_join': false + }, + 'sender': '@deepbluev7:neko.dev', + 'origin_server_ts': 1638720840346 + }, + { + 'type': 'm.space.child', + 'state_key': '!prlZxmnmAGuCYHUNSw:neko.dev', + 'content': { + 'auto_join': false, + 'suggested': true, + 'via': ['neko.dev', 'matrix.org'] + }, + 'sender': '@mo-the-alias-manager:matrix.org', + 'origin_server_ts': 1645661824176 + }, + { + 'type': 'm.space.child', + 'state_key': '!ooHixUOgoLVUjCSMZC:matrix.org', + 'content': { + 'via': ['neko.dev'], + 'suggested': false, + 'auto_join': false + }, + 'sender': '@deepbluev7:neko.dev', + 'origin_server_ts': 1647825979776 + } + ] + }, + { + 'room_id': '!UpbiadsgRndwvYKPLK:neko.dev', + 'name': 'Gentoo Chatter', + 'topic': + 'Offtopic discussions, distractions while emerge is running and banter. Keep attacks, politics and illegal stuff out of here, but otherwise most topics can end up here.', + 'canonical_alias': '#gentoo-chatter:matrix.org', + 'num_joined_members': 109, + 'avatar_url': 'mxc://neko.dev/kGMWPCWawJbKGEtEGwCETSrM', + 'join_rule': 'public', + 'world_readable': true, + 'guest_can_join': true, + 'children_state': >[] + }, + { + 'room_id': '!aZUzMIEZvEwnDquxLf:neko.dev', + 'name': 'Gentoo', + 'topic': + "Unofficial. Talk about Gentoo stuff. Lightly moderated, i.e. try to keep topics Gentoo related and listen to the mods. Don't talk about politics, stuff like that never stays civil. (There are other rooms for that.) Checkout #gentoo-community:matrix.org and #gentoo-chatter:matrix.org. Also we love cats :3", + 'canonical_alias': '#gentoo:matrix.org', + 'num_joined_members': 1044, + 'avatar_url': 'mxc://neko.dev/dESQaOugYSkcFgLJYPNlWiNg', + 'join_rule': 'public', + 'world_readable': true, + 'guest_can_join': true, + 'children_state': >[] + }, + { + 'room_id': '!aRzRZBvOAkHMcEjAPS:libera.chat', + 'name': '#gentoo-qt', + 'topic': + 'Gentoo Qt Project | Project page: https://qt.gentoo.org/ | Bugz: https://tinyurl.com/genqtbugs | FAQ: https://bit.ly/1jz8EhK | Latest Qt Stable: 5.15.3 / Unstable 5.15.4 | LXQt Stable: 1.0.0 / Unstable: 1.1.0 | Overlay: qt (Qt 6.3.0 Masked for Testing and 9999s for All)', + 'canonical_alias': '#gentoo-qt:libera.chat', + 'num_joined_members': 72, + 'join_rule': 'public', + 'world_readable': false, + 'guest_can_join': false, + 'children_state': >[] + }, + { + 'room_id': '!DXsRRxkqqDhDkAyXfL:matrix.org', + 'name': 'Gentoo - de', + 'topic': + 'Deutschsprachiger Raum rund um die GNU/Linux Distribution Gentoo.', + 'canonical_alias': '#german-gentoo:matrix.org', + 'num_joined_members': 15, + 'avatar_url': 'mxc://matrix.org/UDJIpOEsVDGbgAliaxjqmhlf', + 'join_rule': 'public', + 'world_readable': false, + 'guest_can_join': false, + 'children_state': >[] + }, + { + 'room_id': '!tbCRpmsMiwMBlIThOd:matrix.org', + 'name': '#gentoo-kde', + 'topic': + "Gentoo KDE | Guide: http://xrl.us/kdeguide | KF5 5.54.0 stable, 5.56.0 testing | Plasma 5.14.5 stable, 5.15.3 testing | Applications 18.08.3 stable, 18.12.3 testing | Bugs: http://xrl.us/kdebugs | Statistics: https://tinyurl.com/gkdestats | Please report bugs with pastebin'd build.log", + 'canonical_alias': '#freenode_#gentoo-kde:matrix.org', + 'num_joined_members': 86, + 'join_rule': 'public', + 'world_readable': false, + 'guest_can_join': false, + 'children_state': >[] + }, + { + 'room_id': '!LPpmvMsEgDwuSuHSpM:matrix.org', + 'name': 'Gentoo-rus (Русский/Russian)', + 'topic': 'Вопросы по Gentoo', + 'canonical_alias': '#gentoo-rus:matrix.org', + 'num_joined_members': 68, + 'avatar_url': 'mxc://matrix.org/KuRHZNSQttAmQekputXpXmeQ', + 'join_rule': 'public', + 'world_readable': true, + 'guest_can_join': true, + 'children_state': >[] + }, + { + 'room_id': '!prlZxmnmAGuCYHUNSw:neko.dev', + 'name': 'Gentoo Hardening', + 'topic': + 'If you care too much about security and Gentoo, this is your place to talk about it. Bring your hard hats, it sometimes gets heated (please behave). (Unofficial)', + 'canonical_alias': '#gentoo-hardening:matrix.org', + 'num_joined_members': 47, + 'avatar_url': 'mxc://neko.dev/MMDsJENimfwQlHshSkdFlDaU', + 'join_rule': 'public', + 'world_readable': true, + 'guest_can_join': false, + 'children_state': >[] + }, + { + 'room_id': '!ooHixUOgoLVUjCSMZC:matrix.org', + 'name': 'Gentoo Brasil', + 'topic': 'Usuários brasileiros do Gentoo Linux', + 'canonical_alias': '#gentoobr:matrix.org', + 'num_joined_members': 35, + 'avatar_url': 'mxc://matrix.org/vXqGcGnyUbIjhXAalSxfDxJD', + 'join_rule': 'public', + 'world_readable': true, + 'guest_can_join': true, + 'children_state': >[] + } + ] + }; + + static final Map> api = { + 'GET': { + '/path/to/auth/error': (var req) => { + 'errcode': 'M_FORBIDDEN', + 'error': 'Blabla', + }, + '/media/v3/preview_url?url=https%3A%2F%2Fmatrix.org&ts=10': (var req) => { + 'og:image': 'mxc://example.com/ascERGshawAWawugaAcauga', + 'matrix:image:size': 102400 + }, + '/media/v3/config': (var req) => {'m.upload.size': 50000000}, + '/.well-known/matrix/client': (var req) => { + 'm.homeserver': { + 'base_url': 'https://fakeserver.notexisting', + }, + 'm.identity_server': { + 'base_url': 'https://identity.fakeserver.notexisting' + }, + 'org.example.custom.property': { + 'app_url': 'https://custom.app.fakeserver.notexisting' + } + }, + '/client/v3/user/%40alice%3Aexample.com/rooms/!localpart%3Aexample.com/tags': + (var req) => { + 'tags': { + 'm.favourite': {'order': 0.1}, + 'u.Work': {'order': 0.7}, + 'u.Customers': {}, + } + }, + '/client/v3/events?from=1234&timeout=10&room_id=%211234': (var req) => { + 'start': 's3456_9_0', + 'end': 's3457_9_0', + 'chunk': [ + { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!somewhere:over.the.rainbow', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + ] + }, + '/client/v3/thirdparty/location?alias=1234': (var req) => [ + { + 'alias': '#freenode_#matrix:matrix.org', + 'protocol': 'irc', + 'fields': {'network': 'freenode', 'channel': '#matrix'} + } + ], + '/client/v3/thirdparty/location/irc': (var req) => [ + { + 'alias': '#freenode_#matrix:matrix.org', + 'protocol': 'irc', + 'fields': {'network': 'freenode', 'channel': '#matrix'} + } + ], + '/client/v3/thirdparty/user/irc': (var req) => [ + { + 'userid': '@_gitter_jim:matrix.org', + 'protocol': 'gitter', + 'fields': {'user': 'jim'} + } + ], + '/client/v3/thirdparty/user?userid=1234': (var req) => [ + { + 'userid': '@_gitter_jim:matrix.org', + 'protocol': 'gitter', + 'fields': {'user': 'jim'} + } + ], + '/client/v3/thirdparty/protocol/irc': (var req) => { + 'user_fields': ['network', 'nickname'], + 'location_fields': ['network', 'channel'], + 'icon': 'mxc://example.org/aBcDeFgH', + 'field_types': { + 'network': { + 'regexp': '([a-z0-9]+\\.)*[a-z0-9]+', + 'placeholder': 'irc.example.org' + }, + 'nickname': {'regexp': '[^\\s#]+', 'placeholder': 'username'}, + 'channel': {'regexp': '#[^\\s]+', 'placeholder': '#foobar'} + }, + 'instances': [ + { + 'desc': 'Freenode', + 'icon': 'mxc://example.org/JkLmNoPq', + 'fields': {'network': 'freenode'}, + 'network_id': 'freenode' + } + ] + }, + '/client/v3/thirdparty/protocols': (var req) => { + 'irc': { + 'user_fields': ['network', 'nickname'], + 'location_fields': ['network', 'channel'], + 'icon': 'mxc://example.org/aBcDeFgH', + 'field_types': { + 'network': { + 'regexp': '([a-z0-9]+\\.)*[a-z0-9]+', + 'placeholder': 'irc.example.org' + }, + 'nickname': {'regexp': '[^\\s]+', 'placeholder': 'username'}, + 'channel': {'regexp': '#[^\\s]+', 'placeholder': '#foobar'} + }, + 'instances': [ + { + 'network_id': 'freenode', + 'desc': 'Freenode', + 'icon': 'mxc://example.org/JkLmNoPq', + 'fields': {'network': 'freenode.net'} + } + ] + }, + 'gitter': { + 'user_fields': ['username'], + 'location_fields': ['room'], + 'icon': 'mxc://example.org/aBcDeFgH', + 'field_types': { + 'username': {'regexp': '@[^\\s]+', 'placeholder': '@username'}, + 'room': { + 'regexp': '[^\\s]+\\/[^\\s]+', + 'placeholder': 'matrix-org/matrix-doc' + } + }, + 'instances': [ + { + 'network_id': 'gitter', + 'desc': 'Gitter', + 'icon': 'mxc://example.org/zXyWvUt', + 'fields': {} + } + ] + } + }, + '/client/v3/account/whoami': (var req) => + {'user_id': 'alice@example.com'}, + '/client/v3/capabilities': (var req) => { + 'capabilities': { + 'm.change_password': {'enabled': false}, + 'm.room_versions': { + 'default': '1', + 'available': { + '1': 'stable', + '2': 'stable', + '3': 'unstable', + 'test-version': 'unstable' + } + }, + 'com.example.custom.ratelimit': {'max_requests_per_hour': 600} + } + }, + '/client/v3/rooms/1234/context/1234?limit=10&filter=%7B%7D': (var req) => + { + 'end': 't29-57_2_0_2', + 'events_after': [ + { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + ], + 'event': { + 'content': { + 'body': 'filename.jpg', + 'info': { + 'h': 398, + 'w': 394, + 'mimetype': 'image/jpeg', + 'size': 31037 + }, + 'url': 'mxc://example.org/JWEIFJgwEIhweiWJE', + 'msgtype': 'm.image' + }, + 'type': 'm.room.message', + 'event_id': '\$f3h4d129462ha:example.com', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + 'events_before': [ + { + 'content': { + 'body': 'something-important.doc', + 'filename': 'something-important.doc', + 'info': {'mimetype': 'application/msword', 'size': 46144}, + 'msgtype': 'm.file', + 'url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + ], + 'start': 't27-54_2_0_2', + 'state': [ + { + 'content': { + 'creator': '@example:example.org', + 'room_version': '1', + 'm.federate': true, + 'predecessor': { + 'event_id': '\$something:example.org', + 'room_id': '!oldroom:example.org' + } + }, + 'type': 'm.room.create', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.org' + } + ] + }, + '/client/v3/admin/whois/%40alice%3Aexample.com': (var req) => { + 'user_id': '@peter:rabbit.rocks', + 'devices': { + 'teapot': { + 'sessions': [ + { + 'connections': [ + { + 'ip': '127.0.0.1', + 'last_seen': 1411996332123, + 'user_agent': 'curl/7.31.0-DEV' + }, + { + 'ip': '10.0.0.2', + 'last_seen': 1411996332123, + 'user_agent': + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36' + } + ] + } + ] + } + } + }, + '/client/v3/user/%40alice%3Aexample.com/account_data/test.account.data': + (var req) => {'foo': 'bar'}, + '/client/v3/user/%40alice%3Aexample.com/rooms/1234/account_data/test.account.data': + (var req) => {'foo': 'bar'}, + '/client/v3/directory/room/%23testalias%3Aexample.com': (var reqI) => { + 'room_id': '!abnjk1jdasj98:capuchins.com', + 'servers': ['capuchins.com', 'matrix.org', 'another.com'] + }, + '/client/v3/account/3pid': (var req) => { + 'threepids': [ + { + 'medium': 'email', + 'address': 'monkey@banana.island', + 'validated_at': 1535176800000, + 'added_at': 1535336848756 + } + ] + }, + '/client/v3/devices': (var req) => { + 'devices': [ + { + 'device_id': 'QBUAZIFURK', + 'display_name': 'android', + 'last_seen_ip': '1.2.3.4', + 'last_seen_ts': 1474491775024 + } + ] + }, + '/client/v3/notifications?from=1234&limit=10&only=1234': (var req) => { + 'next_token': 'abcdef', + 'notifications': [ + { + 'actions': ['notify'], + 'profile_tag': 'hcbvkzxhcvb', + 'read': true, + 'room_id': '!abcdefg:example.com', + 'ts': 1475508881945, + 'event': { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!jEsUZKDJdhlrceRyVU:example.org', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + } + ] + }, + '/client/v3/devices/QBUAZIFURK': (var req) => { + 'device_id': 'QBUAZIFURK', + 'display_name': 'android', + 'last_seen_ip': '1.2.3.4', + 'last_seen_ts': 1474491775024 + }, + '/client/v3/profile/%40alice%3Aexample.com/displayname': (var reqI) => + {'displayname': 'Alice M'}, + '/client/v3/profile/%40alice%3Aexample.com/avatar_url': (var reqI) => + {'avatar_url': 'mxc://test'}, + '/client/v3/profile/%40alice%3Aexample.com': (var reqI) => { + 'avatar_url': 'mxc://test', + 'displayname': 'Alice M', + }, + '/client/v3/voip/turnServer': (var req) => { + 'username': '1443779631:@user:example.com', + 'password': 'JlKfBy1QwLrO20385QyAtEyIv0=', + 'uris': [ + 'turn:turn.example.com:3478?transport=udp', + 'turn:10.20.30.40:3478?transport=tcp', + 'turns:10.20.30.40:443?transport=tcp' + ], + 'ttl': 86400 + }, + '/client/v3/presence/${Uri.encodeComponent('@alice:example.com')}/status': + (var req) => { + 'presence': 'unavailable', + 'last_active_ago': 420845, + 'status_msg': 'test', + 'currently_active': false + }, + '/client/v3/keys/changes?from=1234&to=1234': (var req) => { + 'changed': ['@alice:example.com', '@bob:example.org'], + 'left': ['@clara:example.com', '@doug:example.org'] + }, + '/client/v3/pushers': (var req) => { + 'pushers': [ + { + 'pushkey': 'Xp/MzCt8/9DcSNE9cuiaoT5Ac55job3TdLSSmtmYl4A=', + 'kind': 'http', + 'app_id': 'face.mcapp.appy.prod', + 'app_display_name': 'Appy McAppface', + 'device_display_name': 'Alices Phone', + 'profile_tag': 'xyz', + 'lang': 'en-US', + 'data': { + 'url': 'https://example.com/_matrix/push/v1/notify', + 'format': 'event_id_only', + } + } + ] + }, + '/client/v3/publicRooms?limit=10&since=1234&server=example.com': + (var req) => { + 'chunk': [ + { + 'canonical_alias': '#murrays:cheese.bar', + 'avatar_url': 'mxc://bleeker.street/CHEDDARandBRIE', + 'guest_can_join': false, + 'name': 'CHEESE', + 'num_joined_members': 37, + 'room_id': '!ol19s:bleecker.street', + 'topic': 'Tasty tasty cheese', + 'world_readable': true + } + ], + 'next_batch': 'p190q', + 'prev_batch': 'p1902', + 'total_room_count_estimate': 115 + }, + '/client/v3/rooms/!localpart%3Aexample.com/aliases': (var req) => { + 'aliases': [ + '#somewhere:example.com', + '#another:example.com', + '#hat_trick:example.com' + ] + }, + '/client/v3/joined_rooms': (var req) => { + 'joined_rooms': ['!foo:example.com'] + }, + '/client/v3/directory/list/room/!localpart%3Aexample.com': (var req) => + {'visibility': 'public'}, + '/client/v3/rooms/1/state/m.room.member/@alice:example.com': (var req) => + {'displayname': 'Alice'}, + '/client/v3/profile/%40getme%3Aexample.com': (var req) => { + 'avatar_url': 'mxc://test', + 'displayname': 'You got me', + }, + '/client/v3/rooms/!localpart%3Aserver.abc/state/m.room.member/@getme%3Aexample.com': + (var req) => { + 'avatar_url': 'mxc://test', + 'displayname': 'You got me', + }, + '/client/v3/rooms/!localpart%3Aserver.abc/state': (var req) => [ + { + 'content': {'join_rule': 'public'}, + 'type': 'm.room.join_rules', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.org' + }, + { + 'content': { + 'creator': '@example:example.org', + 'room_version': '1', + 'm.federate': true, + 'predecessor': { + 'event_id': '\$something:example.org', + 'room_id': '!oldroom:example.org' + } + }, + 'type': 'm.room.create', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'ban': 50, + 'events': {'m.room.name': 100, 'm.room.power_levels': 100}, + 'events_default': 0, + 'invite': 50, + 'kick': 50, + 'redact': 50, + 'state_default': 50, + 'users': {'@example:localhost': 100}, + 'users_default': 0, + 'notifications': {'room': 20} + }, + 'type': 'm.room.power_levels', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + } + ], + '/client/v3/rooms/!localpart:server.abc/state/m.room.member/@getme:example.com': + (var req) => { + 'avatar_url': 'mxc://test', + 'displayname': 'You got me', + }, + '/client/v3/rooms/!localpart:server.abc/event/1234': (var req) => { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '143273582443PhrSn:example.org', + 'room_id': '!localpart:server.abc', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + '/client/v3/rooms/!localpart%3Aserver.abc/event/1234': (var req) => { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '143273582443PhrSn:example.org', + 'room_id': '!localpart:server.abc', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + '/client/v3/rooms/!localpart%3Aserver.abc/messages?from=1234&to=1234&dir=b&limit=10&filter=%7B%22lazy_load_members%22%3Atrue%7D': + (var req) => messagesResponse, + '/client/v3/rooms/!localpart%3Aserver.abc/messages?from=&dir=b&limit=10&filter=%7B%22lazy_load_members%22%3Atrue%7D': + (var req) => messagesResponse, + '/client/v3/rooms/!1234%3Aexample.com/messages?from=1234&dir=b&limit=100&filter=%7B%22lazy_load_members%22%3Atrue%7D': + (var req) => messagesResponse, + '/client/versions': (var req) => { + 'versions': [ + 'r0.0.1', + 'r0.1.0', + 'r0.2.0', + 'r0.3.0', + 'r0.4.0', + 'r0.5.0', + 'v1.1', + 'v1.2', + ], + 'unstable_features': {'m.lazy_load_members': true}, + }, + '/client/v3/login': (var req) => { + 'flows': [ + {'type': 'm.login.password'} + ] + }, + '/client/v3/rooms/!localpart%3Aserver.abc/joined_members': (var req) => { + 'joined': { + '@bar:example.com': { + 'display_name': 'Bar', + 'avatar_url': 'mxc://riot.ovh/printErCATzZijQsSDWorRaK' + } + } + }, + '/client/v3/rooms/!localpart%3Aserver.abc/members?at=1234&membership=join¬_membership=leave': + (var req) => { + 'chunk': [ + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '§143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@alice:example.com', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.com' + } + ] + }, + '/client/v3/rooms/!696r7674:example.com/members': (var req) => { + 'chunk': [ + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '§143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@alice:example.com', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.com' + } + ] + }, + '/client/v3/rooms/!726s6s6q:example.com/members': (var req) => { + 'chunk': [ + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '§143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@alice:example.com', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.com' + } + ] + }, + '/client/v3/rooms/!localpart%3Aserver.abc/members': (var req) => { + 'chunk': [ + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '§143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.org' + } + ] + }, + '/client/v3/pushrules/global/content/nocake': (var req) => { + 'actions': ['dont_notify'], + 'pattern': 'cake*lie', + 'rule_id': 'nocake', + 'enabled': true, + 'default': false + }, + '/client/v3/pushrules/global/content/nocake/enabled': (var req) => { + 'enabled': true, + }, + '/client/v3/pushrules/global/content/nocake/actions': (var req) => { + 'actions': ['notify'] + }, + '/client/v3/pushrules': (var req) => { + 'global': { + 'content': [ + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight'} + ], + 'default': true, + 'enabled': true, + 'pattern': 'alice', + 'rule_id': '.m.rule.contains_user_name' + } + ], + 'override': [ + { + 'actions': ['dont_notify'], + 'conditions': >[], + 'default': true, + 'enabled': false, + 'rule_id': '.m.rule.master' + }, + { + 'actions': ['dont_notify'], + 'conditions': [ + { + 'key': 'content.msgtype', + 'kind': 'event_match', + 'pattern': 'm.notice' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.suppress_notices' + } + ], + 'room': >[], + 'sender': >[], + 'underride': [ + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'ring'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.call.invite' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.call' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight'} + ], + 'conditions': [ + {'kind': 'contains_display_name'} + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.contains_display_name' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + {'is': '2', 'kind': 'room_member_count'} + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.room_one_to_one' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'sound', 'value': 'default'}, + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.member' + }, + { + 'key': 'content.membership', + 'kind': 'event_match', + 'pattern': 'invite' + }, + { + 'key': 'state_key', + 'kind': 'event_match', + 'pattern': '@alice:example.com' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.invite_for_me' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.member' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.member_event' + }, + { + 'actions': [ + 'notify', + {'set_tweak': 'highlight', 'value': false} + ], + 'conditions': [ + { + 'key': 'type', + 'kind': 'event_match', + 'pattern': 'm.room.message' + } + ], + 'default': true, + 'enabled': true, + 'rule_id': '.m.rule.message' + } + ] + } + }, + '/client/v3/sync?filter=%7B%22room%22%3A%7B%22include_leave%22%3Atrue%2C%22timeline%22%3A%7B%22limit%22%3A10%7D%7D%7D&timeout=0': + (var req) => archiveSyncResponse, + '/client/v3/sync?filter=%7B%22room%22%3A%7B%22state%22%3A%7B%22lazy_load_members%22%3Atrue%7D%7D%7D': + (var req) => syncResponse, + '/client/v3/sync?filter=%7B%7D&since=1234&full_state=false&set_presence=unavailable&timeout=15': + (var req) => syncResponse, + '/client/v3/register/available?username=testuser': (var req) => + {'available': true}, + '/client/v3/user/${Uri.encodeComponent('alice@example.com')}/filter/1234': + (var req) => { + 'room': { + 'state': { + 'types': ['m.room.*'], + 'not_rooms': ['!726s6s6q:example.com'] + }, + 'timeline': { + 'limit': 10, + 'types': ['m.room.message'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + }, + 'ephemeral': { + 'types': ['m.receipt', 'm.typing'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + }, + 'account_data': { + 'types': ['m.receipt', 'm.typing'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + } + }, + 'presence': { + 'types': ['m.presence'], + 'not_senders': ['@alice:example.com'] + }, + 'event_format': 'client', + 'event_fields': ['type', 'content', 'sender'] + }, + '/client/v3/room_keys/version': (var req) => { + 'algorithm': AlgorithmTypes.megolmBackupV1Curve25519AesSha2, + 'auth_data': { + 'public_key': 'GXYaxqhNhUK28zUdxOmEsFRguz+PzBsDlTLlF0O0RkM', + 'signatures': {}, + }, + 'count': 0, + 'etag': '0', + 'version': '5', + }, + '/client/v3/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}/${Uri.encodeComponent('ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU')}?version=5': + (var req) => { + 'first_message_index': 0, + 'forwarded_count': 0, + 'is_verified': true, + 'session_data': { + 'ephemeral': 'fwRxYh+seqLykz5mQCLypJ4/59URdcFJ2s69OU1dGRc', + 'ciphertext': + '19jkQYlbgdP+VL9DH3qY/Dvpk6onJZgf+6frZFl1TinPCm9OMK9AZZLuM1haS9XLAUK1YsREgjBqfl6T+Tq8JlJ5ONZGg2Wttt24sGYc0iTMZJ8rXcNDeKMZhM96ETyjufJSeYoXLqifiVLDw9rrVBmNStF7PskYp040em+0OZ4pF85Cwsdf7l9V7MMynzh9BoXqVUCBiwT03PNYH9AEmNUxXX+6ZwCpe/saONv8MgGt5uGXMZIK29phA3D8jD6uV/WOHsB8NjHNq9FrfSEAsl+dAcS4uiYie4BKSSeQN+zGAQqu1MMW4OAdxGOuf8WpIINx7n+7cKQfxlmc/Cgg5+MmIm2H0oDwQ+Xu7aSxp1OCUzbxQRdjz6+tnbYmZBuH0Ov2RbEvC5tDb261LRqKXpub0llg5fqKHl01D0ahv4OAQgRs5oU+4mq+H2QGTwIFGFqP9tCRo0I+aICawpxYOfoLJpFW6KvEPnM2Lr3sl6Nq2fmkz6RL5F7nUtzxN8OKazLQpv8DOYzXbi7+ayEsqS0/EINetq7RfCqgjrEUgfNWYuFXWqvUT8lnxLdNu+8cyrJqh1UquFjXWTw1kWcJ0pkokVeBtK9YysCnF1UYh/Iv3rl2ZoYSSLNtuvMSYlYHggZ8xV8bz9S3X2/NwBycBiWIy5Ou/OuSX7trIKgkkmda0xjBWEM1a2acVuqu2OFbMn2zFxm2a3YwKP//OlIgMg', + 'mac': 'QzKV/fgAs4U', + }, + }, + '/client/v3/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}?version=5': + (var req) => { + 'sessions': { + 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU': { + 'first_message_index': 0, + 'forwarded_count': 0, + 'is_verified': true, + 'session_data': { + 'ephemeral': + 'fwRxYh+seqLykz5mQCLypJ4/59URdcFJ2s69OU1dGRc', + 'ciphertext': + '19jkQYlbgdP+VL9DH3qY/Dvpk6onJZgf+6frZFl1TinPCm9OMK9AZZLuM1haS9XLAUK1YsREgjBqfl6T+Tq8JlJ5ONZGg2Wttt24sGYc0iTMZJ8rXcNDeKMZhM96ETyjufJSeYoXLqifiVLDw9rrVBmNStF7PskYp040em+0OZ4pF85Cwsdf7l9V7MMynzh9BoXqVUCBiwT03PNYH9AEmNUxXX+6ZwCpe/saONv8MgGt5uGXMZIK29phA3D8jD6uV/WOHsB8NjHNq9FrfSEAsl+dAcS4uiYie4BKSSeQN+zGAQqu1MMW4OAdxGOuf8WpIINx7n+7cKQfxlmc/Cgg5+MmIm2H0oDwQ+Xu7aSxp1OCUzbxQRdjz6+tnbYmZBuH0Ov2RbEvC5tDb261LRqKXpub0llg5fqKHl01D0ahv4OAQgRs5oU+4mq+H2QGTwIFGFqP9tCRo0I+aICawpxYOfoLJpFW6KvEPnM2Lr3sl6Nq2fmkz6RL5F7nUtzxN8OKazLQpv8DOYzXbi7+ayEsqS0/EINetq7RfCqgjrEUgfNWYuFXWqvUT8lnxLdNu+8cyrJqh1UquFjXWTw1kWcJ0pkokVeBtK9YysCnF1UYh/Iv3rl2ZoYSSLNtuvMSYlYHggZ8xV8bz9S3X2/NwBycBiWIy5Ou/OuSX7trIKgkkmda0xjBWEM1a2acVuqu2OFbMn2zFxm2a3YwKP//OlIgMg', + 'mac': 'QzKV/fgAs4U', + }, + }, + }, + }, + '/client/v3/room_keys/keys?version=5': (var req) => { + 'rooms': { + '!726s6s6q:example.com': { + 'sessions': { + 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU': { + 'first_message_index': 0, + 'forwarded_count': 0, + 'is_verified': true, + 'session_data': { + 'ephemeral': + 'fwRxYh+seqLykz5mQCLypJ4/59URdcFJ2s69OU1dGRc', + 'ciphertext': + '19jkQYlbgdP+VL9DH3qY/Dvpk6onJZgf+6frZFl1TinPCm9OMK9AZZLuM1haS9XLAUK1YsREgjBqfl6T+Tq8JlJ5ONZGg2Wttt24sGYc0iTMZJ8rXcNDeKMZhM96ETyjufJSeYoXLqifiVLDw9rrVBmNStF7PskYp040em+0OZ4pF85Cwsdf7l9V7MMynzh9BoXqVUCBiwT03PNYH9AEmNUxXX+6ZwCpe/saONv8MgGt5uGXMZIK29phA3D8jD6uV/WOHsB8NjHNq9FrfSEAsl+dAcS4uiYie4BKSSeQN+zGAQqu1MMW4OAdxGOuf8WpIINx7n+7cKQfxlmc/Cgg5+MmIm2H0oDwQ+Xu7aSxp1OCUzbxQRdjz6+tnbYmZBuH0Ov2RbEvC5tDb261LRqKXpub0llg5fqKHl01D0ahv4OAQgRs5oU+4mq+H2QGTwIFGFqP9tCRo0I+aICawpxYOfoLJpFW6KvEPnM2Lr3sl6Nq2fmkz6RL5F7nUtzxN8OKazLQpv8DOYzXbi7+ayEsqS0/EINetq7RfCqgjrEUgfNWYuFXWqvUT8lnxLdNu+8cyrJqh1UquFjXWTw1kWcJ0pkokVeBtK9YysCnF1UYh/Iv3rl2ZoYSSLNtuvMSYlYHggZ8xV8bz9S3X2/NwBycBiWIy5Ou/OuSX7trIKgkkmda0xjBWEM1a2acVuqu2OFbMn2zFxm2a3YwKP//OlIgMg', + 'mac': 'QzKV/fgAs4U', + }, + }, + }, + }, + }, + }, + '/client/v1/rooms/${Uri.encodeComponent('!gPxZhKUssFZKZcoCKY:neko.dev')}/hierarchy': + (var req) => spaceHierarchyResponse, + }, + 'POST': { + '/client/v3/delete_devices': (var req) => {}, + '/client/v3/account/3pid/add': (var req) => {}, + '/client/v3/account/3pid/bind': (var req) => {}, + '/client/v3/account/3pid/delete': (var req) => + {'id_server_unbind_result': 'success'}, + '/client/v3/account/3pid/unbind': (var req) => + {'id_server_unbind_result': 'success'}, + '/client/v3/account/password': (var req) => {}, + '/client/v3/rooms/1234/report/1234': (var req) => {}, + '/client/v3/search': (var req) => { + 'search_categories': { + 'room_events': { + 'groups': { + 'room_id': { + '!qPewotXpIctQySfjSy:localhost': { + 'order': 1, + 'next_batch': 'BdgFsdfHSf-dsFD', + 'results': ['\$144429830826TWwbB:localhost'] + } + } + }, + 'highlights': ['martians', 'men'], + 'next_batch': '5FdgFsd234dfgsdfFD', + 'count': 1224, + 'results': [ + { + 'rank': 0.00424866, + 'result': { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': + 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$144429830826TWwbB:localhost', + 'room_id': '!qPewotXpIctQySfjSy:localhost', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + } + ] + } + } + }, + '/client/v3/account/deactivate': (var req) => + {'id_server_unbind_result': 'success'}, + '/client/v3/user_directory/search': (var req) => { + 'results': [ + { + 'user_id': '@foo:bar.com', + 'display_name': 'Foo', + 'avatar_url': 'mxc://bar.com/foo' + } + ], + 'limited': false + }, + '/client/v3/register/email/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/v3/register/msisdn/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/v3/account/password/email/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/v3/account/password/msisdn/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/v3/account/3pid/email/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/v3/account/3pid/msisdn/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/v3/rooms/!localpart%3Aexample.com/receipt/m.read/%241234%3Aexample.com': + (var req) => {}, + '/client/v3/rooms/!localpart%3Aexample.com/read_markers': (var req) => + {}, + '/client/v3/user/${Uri.encodeComponent('alice@example.com')}/filter': + (var req) => {'filter_id': '1234'}, + '/client/v3/publicRooms?server=example.com': (var req) => { + 'chunk': [ + { + 'canonical_alias': '#murrays:cheese.bar', + 'avatar_url': 'mxc://bleeker.street/CHEDDARandBRIE', + 'guest_can_join': false, + 'name': 'CHEESE', + 'num_joined_members': 37, + 'room_id': '!ol19s:bleecker.street', + 'topic': 'Tasty tasty cheese', + 'world_readable': true + } + ], + 'next_batch': 'p190q', + 'prev_batch': 'p1902', + 'total_room_count_estimate': 115 + }, + '/client/v3/keys/claim': (dynamic req) { + final request = decodeJson(req)["one_time_keys"]; + final keys = (request is Map) + ? request["one_time_keys"] as Map? + : null; + return { + 'failures': {}, + 'one_time_keys': { + if (keys?['@alice:example.com'] != null) + '@alice:example.com': { + 'JLAFKJWSCS': { + 'signed_curve25519:AAAAAQ': { + 'key': 'ikMXajRlkS7Xi9CROrAh3jXnbygk8mLBdSaY9/al0X0', + 'signatures': { + '@alice:example.com': { + 'ed25519:JLAFKJWSCS': + 'XdboCa0Ljoh0Y0i/IVnmMqy/+T1hJyu8BA/nRYniJMQ7QWh/pGS5AsWswdARD+MAX+r4u98Qzk0y27HUddZXDA' + } + } + } + } + }, + if (keys?['@test:fakeServer.notExisting'] != null) + '@test:fakeServer.notExisting': { + 'GHTYAJCE': { + 'signed_curve25519:AAAAAQ': { + 'key': 'qc72ve94cA28iuE0fXa98QO3uls39DHWdQlYyvvhGh0', + 'signatures': { + '@test:fakeServer.notExisting': { + 'ed25519:GHTYAJCE': + 'dFwffr5kTKefO7sjnWLMhTzw7oV31nkPIDRxFy5OQT2OP5++Ao0KRbaBZ6qfuT7lW1owKK0Xk3s7QTBvc/eNDA', + }, + }, + }, + }, + }, + } + }; + }, + '/client/v3/rooms/!localpart%3Aexample.com/invite': (var req) => + {}, + '/client/v3/rooms/!localpart%3Aexample.com/leave': (var req) => + {}, + '/client/v3/rooms/!localpart%3Aexample.com/forget': (var req) => + {}, + '/client/v3/rooms/!localpart%3Aserver.abc/kick': (var req) => + {}, + '/client/v3/rooms/!localpart%3Aexample.com/kick': (var req) => + {}, + '/client/v3/rooms/!localpart%3Aexample.com/ban': (var req) => + {}, + '/client/v3/rooms/!localpart%3Aexample.com/unban': (var req) => + {}, + '/client/v3/rooms/!localpart%3Aexample.com/join': (var req) => + {'room_id': '!localpart:example.com'}, + '/client/v3/join/!localpart%3Aexample.com?server_name=example.com&server_name=example.abc': + (var req) => {'room_id': '!localpart:example.com'}, + '/client/v3/keys/upload': (var req) => { + 'one_time_key_counts': { + 'curve25519': 10, + 'signed_curve25519': + tryCast>(decodeJson(req)) + ?.tryGetMap('one_time_keys') + ?.keys + .length ?? + 0, + } + }, + '/client/v3/keys/query': (var req) => { + 'failures': {}, + 'device_keys': { + '@alice:example.com': { + 'JLAFKJWSCS': { + 'user_id': '@alice:example.com', + 'device_id': 'JLAFKJWSCS', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:JLAFKJWSCS': + 'L+4+JCl8MD63dgo8z5Ta+9QAHXiANyOVSfgbHA5d3H8', + 'ed25519:JLAFKJWSCS': + 'rUFJftIWpFF/jqqz3bexGGYiG8UobKhzkeabqw1v0zM' + }, + 'signatures': { + '@alice:example.com': { + 'ed25519:JLAFKJWSCS': + 'go3mi5o3Ile+Ik+lCEpHmBmyJmKWfnRDCBBvfaVlKsMyha5IORuYcxwEUrAeLyAeeeHvkWDFX+No5eY1jYeKBw' + } + }, + 'unsigned': {'device_display_name': 'Alices mobile phone'} + }, + 'OTHERDEVICE': { + 'user_id': '@alice:example.com', + 'device_id': 'OTHERDEVICE', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:OTHERDEVICE': + 'wMIDhiQl5jEXQrTB03ePOSQfR8sA/KMrW0CIfFfXKEE', + 'ed25519:OTHERDEVICE': + '2Lyaj5NB7HPqKZMjZpA/pECXuQ+9wi8AGFdw33y3DuQ' + }, + 'signatures': { + '@alice:example.com': { + 'ed25519:OTHERDEVICE': + 'bwHd6ylISP13AICdDPd0HQd4V6dvvd4vno8/OwUNdm9UAprr3YjkDqVw425I74u2UQAarq9bytBqVqFyD6trAw', + } + }, + }, + }, + '@test:fakeServer.notExisting': { + 'GHTYAJCE': { + 'user_id': '@test:fakeServer.notExisting', + 'device_id': 'GHTYAJCE', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:GHTYAJCE': + '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk', + 'ed25519:GHTYAJCE': + 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo' + }, + 'signatures': { + '@test:fakeServer.notExisting': { + 'ed25519:GHTYAJCE': + 'NEQeTgv7ew1IZSLQphWd0y60EdHdcNfHgvoaMQco5XKeIYyiUZIWd7F4x/mkPDjUizv6yWMbTDCWdSg5XcgNBA', + 'ed25519:F9ypFzgbISXCzxQhhSnXMkc1vq12Luna3Nw5rqViOJY': + 'Q4/55vZjEJD7M2EC40bgZqd9Zuy/4C75UPVopJdXeioQVaKtFf6EF0nUUuql0yD+r3hinsZcock0wO6Q2xcoAQ', + }, + }, + }, + 'OTHERDEVICE': { + 'user_id': '@test:fakeServer.notExisting', + 'device_id': 'OTHERDEVICE', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:OTHERDEVICE': + 'R96BA0qE1+QAWLp7E1jyWSTJ1VXMLpEdiM2SZHlKMXM', + 'ed25519:OTHERDEVICE': + 'EQo9eYbSygIbOR+tVJziqAY1NI6Gga+JQOVIqJe4mr4' + }, + 'signatures': { + '@test:fakeServer.notExisting': { + 'ed25519:OTHERDEVICE': + '/rT6pVRypJWxGos1QcI7jHL9HwcA83nkHLHqMcRPeLSxXHh4oHWvC0/tl0Xg06ogyiGw4NuB7TpOISvJBdt7BA', + 'ed25519:F9ypFzgbISXCzxQhhSnXMkc1vq12Luna3Nw5rqViOJY': + 'qnjiLl36h/1jlLvcAgt46Igaod2T9lOSnoSVkV0KC+c7vYIjG4QBzXpH+hycfufOT/y+a/kl52dUTLQWctMKCA', + }, + }, + }, + }, + '@othertest:fakeServer.notExisting': { + 'FOXDEVICE': { + 'user_id': '@othertest:fakeServer.notExisting', + 'device_id': 'FOXDEVICE', + 'algorithms': [ + AlgorithmTypes.olmV1Curve25519AesSha2, + AlgorithmTypes.megolmV1AesSha2 + ], + 'keys': { + 'curve25519:FOXDEVICE': + 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg', + 'ed25519:FOXDEVICE': + 'R5/p04tticvdlNIxiiBIP0j9OQWv8ep6eEU6/lWKDxw', + }, + 'signatures': { + '@othertest:fakeServer.notExisting': { + 'ed25519:FOXDEVICE': + '2lJ3atmRIWgkyQNC9gvWEpxwuozsBQsg33M2IMDJqLhx/+g3Ds1vQ683dJsYIu04ORa4U0L9TqieHVpV/7qqDA', + }, + }, + }, + }, + }, + 'master_keys': { + '@test:fakeServer.notExisting': { + 'user_id': '@test:fakeServer.notExisting', + 'usage': ['master'], + 'keys': { + 'ed25519:82mAXjsmbTbrE6zyShpR869jnrANO75H8nYY0nDLoJ8': + '82mAXjsmbTbrE6zyShpR869jnrANO75H8nYY0nDLoJ8', + }, + 'signatures': {}, + }, + '@othertest:fakeServer.notExisting': { + 'user_id': '@othertest:fakeServer.notExisting', + 'usage': ['master'], + 'keys': { + 'ed25519:master': 'master', + }, + 'signatures': {}, + }, + }, + 'self_signing_keys': { + '@test:fakeServer.notExisting': { + 'user_id': '@test:fakeServer.notExisting', + 'usage': ['self_signing'], + 'keys': { + 'ed25519:F9ypFzgbISXCzxQhhSnXMkc1vq12Luna3Nw5rqViOJY': + 'F9ypFzgbISXCzxQhhSnXMkc1vq12Luna3Nw5rqViOJY', + }, + 'signatures': { + '@test:fakeServer.notExisting': { + 'ed25519:82mAXjsmbTbrE6zyShpR869jnrANO75H8nYY0nDLoJ8': + 'afkrbGvPn5Zb5zc7Lk9cz2skI3QrzI/L0st1GS+/GATxNjMzc6vKmGu7r9cMb1GJxy4RdeUpfH3L7Fs/fNL1Dw', + }, + }, + }, + '@othertest:fakeServer.notExisting': { + 'user_id': '@othertest:fakeServer.notExisting', + 'usage': ['self_signing'], + 'keys': { + 'ed25519:self_signing': 'self_signing', + }, + 'signatures': {}, + }, + }, + 'user_signing_keys': { + '@test:fakeServer.notExisting': { + 'user_id': '@test:fakeServer.notExisting', + 'usage': ['user_signing'], + 'keys': { + 'ed25519:0PiwulzJ/RU86LlzSSZ8St80HUMN3dqjKa/orIJoA0g': + '0PiwulzJ/RU86LlzSSZ8St80HUMN3dqjKa/orIJoA0g', + }, + 'signatures': { + '@test:fakeServer.notExisting': { + 'ed25519:82mAXjsmbTbrE6zyShpR869jnrANO75H8nYY0nDLoJ8': + 'pvgbZxEbllaElhpiRnb7/uOIUhrglvHCFnpoxr3/5ZrWa0EK/uaefhex9eEV4uBLrHjHg2ymwdNaM7ap9+sBBg', + }, + }, + }, + '@othertest:fakeServer.notExisting': { + 'user_id': '@othertest:fakeServer.notExisting', + 'usage': ['user_signing'], + 'keys': { + 'ed25519:user_signing': 'user_signing', + }, + 'signatures': {}, + }, + }, + }, + '/client/v3/register': (var req) => { + 'user_id': '@testuser:example.com', + 'access_token': '1234', + 'device_id': 'ABCD', + }, + '/client/v3/register?kind=user': (var req) => + {'user_id': '@testuser:example.com'}, + '/client/v3/register?kind=guest': (var req) => + {'user_id': '@testuser:example.com'}, + '/client/v3/rooms/1234/upgrade': (var req) => { + 'replacement_room': '!1234:fakeServer.notExisting', + }, + '/client/v3/user/1234/openid/request_token': (var req) => { + 'access_token': 'SomeT0kenHere', + 'token_type': 'Bearer', + 'matrix_server_name': 'example.com', + 'expires_in': 3600 + }, + '/client/v3/user/@test:fakeServer.notExisting/openid/request_token': + (var req) => { + 'access_token': 'SomeT0kenHere', + 'token_type': 'Bearer', + 'matrix_server_name': 'example.com', + 'expires_in': 3600 + }, + '/client/v3/login': (var req) => { + 'user_id': '@test:fakeServer.notExisting', + 'access_token': 'abc123', + 'device_id': 'GHTYAJCE', + 'well_known': { + 'm.homeserver': {'base_url': 'https://example.org'}, + 'm.identity_server': {'base_url': 'https://id.example.org'} + } + }, + '/media/v3/upload?filename=file.jpeg': (var req) => + {'content_uri': 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'}, + '/client/v3/logout': (var reqI) => {}, + '/client/v3/pushers/set': (var reqI) => {}, + '/client/v3/join/1234': (var reqI) => {'room_id': '1234'}, + '/client/v3/logout/all': (var reqI) => {}, + '/client/v3/createRoom': (var reqI) => { + 'room_id': '!1234:fakeServer.notExisting', + }, + '/client/v3/rooms/!localpart%3Aserver.abc/read_markers': (var reqI) => + {}, + '/client/v3/rooms/!localpart:server.abc/kick': (var reqI) => + {}, + '/client/v3/rooms/!localpart%3Aserver.abc/ban': (var reqI) => + {}, + '/client/v3/rooms/!localpart%3Aserver.abc/unban': (var reqI) => + {}, + '/client/v3/rooms/!localpart%3Aserver.abc/invite': (var reqI) => + {}, + '/client/v3/keys/device_signing/upload': (var reqI) { + return {}; + }, + '/client/v3/keys/signatures/upload': (var reqI) => + {'failures': {}}, + '/client/v3/room_keys/version': (var reqI) => {'version': '5'}, + }, + 'PUT': { + '/client/v3/user/%40test%3AfakeServer.notExisting/account_data/m.ignored_user_list': + (var req) => {}, + '/client/v3/presence/${Uri.encodeComponent('@alice:example.com')}/status': + (var req) => {}, + '/client/v3/pushrules/global/content/nocake/enabled': (var req) => + {}, + '/client/v3/pushrules/global/content/nocake/actions': (var req) => + {}, + '/client/v3/rooms/!localpart%3Aserver.abc/state/m.room.history_visibility': + (var req) => {}, + '/client/v3/rooms/!localpart%3Aserver.abc/state/m.room.join_rules': + (var req) => {}, + '/client/v3/rooms/!localpart%3Aserver.abc/state/m.room.guest_access': + (var req) => {}, + '/client/v3/rooms/!localpart%3Aserver.abc/send/m.call.invite/1234': + (var req) => {}, + '/client/v3/rooms/!localpart%3Aserver.abc/send/m.call.answer/1234': + (var req) => {}, + '/client/v3/rooms/!localpart%3Aserver.abc/send/m.call.candidates/1234': + (var req) => {}, + '/client/v3/rooms/!localpart%3Aserver.abc/send/m.call.hangup/1234': + (var req) => {}, + '/client/v3/rooms/!1234%3Aexample.com/redact/1143273582443PhrSn%3Aexample.org/1234': + (var req) => {'event_id': '1234'}, + '/client/v3/pushrules/global/room/!localpart%3Aserver.abc': (var req) => + {}, + '/client/v3/pushrules/global/override/.m.rule.master/enabled': + (var req) => {}, + '/client/v3/pushrules/global/content/nocake?before=1&after=2': + (var req) => {}, + '/client/v3/devices/QBUAZIFURK': (var req) => {}, + '/client/v3/directory/room/%23testalias%3Aexample.com': (var reqI) => + {}, + '/client/v3/rooms/!localpart%3Aserver.abc/send/m.room.message/testtxid': + (var reqI) => { + 'event_id': '\$event${FakeMatrixApi.eventCounter++}', + }, + '/client/v3/rooms/!localpart%3Aserver.abc/send/m.reaction/testtxid': + (var reqI) => { + 'event_id': '\$event${FakeMatrixApi.eventCounter++}', + }, + '/client/v3/rooms/!localpart%3Aexample.com/typing/%40alice%3Aexample.com': + (var req) => {}, + '/client/v3/rooms/!1234%3Aexample.com/send/m.room.message/1234': + (var reqI) => { + 'event_id': '\$event${FakeMatrixApi.eventCounter++}', + }, + '/client/v3/rooms/!1234%3Aexample.com/send/m.room.message/newresend': + (var reqI) => { + 'event_id': '\$event${FakeMatrixApi.eventCounter++}', + }, + '/client/v3/user/%40test%3AfakeServer.notExisting/rooms/!localpart%3Aserver.abc/tags/m.favourite': + (var req) => {}, + '/client/v3/user/%40alice%3Aexample.com/rooms/!localpart%3Aexample.com/tags/testtag': + (var req) => {}, + '/client/v3/user/%40alice%3Aexample.com/account_data/test.account.data': + (var req) => {}, + '/client/v3/user/%40test%3AfakeServer.notExisting/account_data/best%20animal': + (var req) => {}, + '/client/v3/user/%40alice%3Aexample.com/rooms/1234/account_data/test.account.data': + (var req) => {}, + '/client/v3/user/%40test%3AfakeServer.notExisting/rooms/!localpart%3Aserver.abc/account_data/com.famedly.marked_unread': + (var req) => {}, + '/client/v3/user/%40test%3AfakeServer.notExisting/account_data/m.direct': + (var req) => {}, + '/client/v3/user/%40othertest%3AfakeServer.notExisting/account_data/m.direct': + (var req) => {}, + '/client/v3/profile/%40alice%3Aexample.com/displayname': (var reqI) => + {}, + '/client/v3/profile/%40alice%3Aexample.com/avatar_url': (var reqI) => + {}, + '/client/v3/profile/%40test%3AfakeServer.notExisting/avatar_url': + (var reqI) => {}, + '/client/v3/rooms/!localpart%3Aserver.abc/state/m.room.encryption': + (var reqI) => {'event_id': 'YUwRidLecu:example.com'}, + '/client/v3/rooms/!localpart%3Aserver.abc/state/m.room.avatar': + (var reqI) => {'event_id': 'YUwRidLecu:example.com'}, + '/client/v3/rooms/!localpart%3Aserver.abc/send/m.room.message/1234': + (var reqI) => {'event_id': 'YUwRidLecu:example.com'}, + '/client/v3/rooms/!localpart%3Aserver.abc/redact/1234/1234': (var reqI) => + {'event_id': 'YUwRidLecu:example.com'}, + '/client/v3/rooms/!localpart%3Aserver.abc/state/m.room.name': + (var reqI) => { + 'event_id': '42', + }, + '/client/v3/rooms/!localpart%3Aserver.abc/state/m.room.topic': + (var reqI) => { + 'event_id': '42', + }, + '/client/v3/rooms/!localpart%3Aserver.abc/state/m.room.pinned_events': + (var reqI) => { + 'event_id': '42', + }, + '/client/v3/rooms/!localpart%3Aserver.abc/state/m.room.power_levels': + (var reqI) => { + 'event_id': '42', + }, + '/client/v3/directory/list/room/!localpart%3Aexample.com': (var req) => + {}, + '/client/v3/room_keys/version/5': (var req) => {}, + '/client/v3/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}/${Uri.encodeComponent('ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU')}?version=5': + (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + '/client/v3/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}?version=5': + (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + '/client/v3/room_keys/keys?version=5': (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + }, + 'DELETE': { + '/unknown/token': (var req) => {'errcode': 'M_UNKNOWN_TOKEN'}, + '/client/v3/devices/QBUAZIFURK': (var req) => {}, + '/client/v3/directory/room/%23testalias%3Aexample.com': (var reqI) => + {}, + '/client/v3/pushrules/global/content/nocake': (var req) => + {}, + '/client/v3/pushrules/global/override/!localpart%3Aserver.abc': + (var req) => {}, + '/client/v3/user/%40test%3AfakeServer.notExisting/rooms/!localpart%3Aserver.abc/tags/m.favourite': + (var req) => {}, + '/client/v3/user/%40alice%3Aexample.com/rooms/!localpart%3Aexample.com/tags/testtag': + (var req) => {}, + '/client/v3/room_keys/version/5': (var req) => {}, + '/client/v3/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}/${Uri.encodeComponent('ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU')}?version=5': + (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + '/client/v3/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}?version=5': + (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + '/client/v3/room_keys/keys?version=5': (var req) => { + 'etag': 'asdf', + 'count': 1, + }, + }, + }; +} diff --git a/lib/matrix_api_lite.dart b/lib/matrix_api_lite.dart new file mode 100644 index 00000000..77a4e4e0 --- /dev/null +++ b/lib/matrix_api_lite.dart @@ -0,0 +1,68 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +library matrix_api_lite; + +export 'src/generated/model.dart'; +export 'src/matrix_api.dart'; +export 'src/model/algorithm_types.dart'; +export 'src/model/auth/authentication_data.dart'; +export 'src/model/auth/authentication_identifier.dart'; +export 'src/model/auth/authentication_password.dart'; +export 'src/model/auth/authentication_phone_identifier.dart'; +export 'src/model/auth/authentication_recaptcha.dart'; +export 'src/model/auth/authentication_third_party_identifier.dart'; +export 'src/model/auth/authentication_three_pid_creds.dart'; +export 'src/model/auth/authentication_token.dart'; +export 'src/model/auth/authentication_types.dart'; +export 'src/model/auth/authentication_user_identifier.dart'; +export 'src/model/basic_event.dart'; +export 'src/model/basic_event_with_sender.dart'; +export 'src/model/basic_room_event.dart'; +export 'src/model/event_types.dart'; +export 'src/model/events/forwarded_room_key_content.dart'; +export 'src/model/events/image_pack_content.dart'; +export 'src/model/events/olm_plaintext_payload.dart'; +export 'src/model/events/room_encrypted_content.dart'; +export 'src/model/events/room_encryption_content.dart'; +export 'src/model/events/room_key_content.dart'; +export 'src/model/events/room_key_request_content.dart'; +export 'src/model/events/secret_storage_default_key_content.dart'; +export 'src/model/events/secret_storage_key_content.dart'; +export 'src/model/events/tombstone_content.dart'; +export 'src/model/matrix_connection_exception.dart'; +export 'src/model/matrix_event.dart'; +export 'src/model/matrix_exception.dart'; +export 'src/model/matrix_keys.dart'; +export 'src/model/message_types.dart'; +export 'src/model/presence.dart'; +export 'src/model/presence_content.dart'; +export 'src/model/room_creation_types.dart'; +export 'src/model/room_summary.dart'; +export 'src/model/stripped_state_event.dart'; +export 'src/model/sync_update.dart'; +export 'src/utils/filter_map_extension.dart'; +export 'src/utils/logs.dart'; +export 'src/utils/map_copy_extension.dart'; +export 'src/utils/try_get_map_extension.dart'; +export 'src/values.dart'; diff --git a/lib/matrix_api_lite/generated/api.dart b/lib/matrix_api_lite/generated/api.dart new file mode 100644 index 00000000..77390b0f --- /dev/null +++ b/lib/matrix_api_lite/generated/api.dart @@ -0,0 +1,4786 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:http/http.dart'; + +import '../model/auth/authentication_data.dart'; +import '../model/auth/authentication_identifier.dart'; +import '../model/auth/authentication_types.dart'; +import '../model/children_state.dart'; +import '../model/matrix_event.dart'; +import '../model/matrix_keys.dart'; +import '../model/sync_update.dart'; +import 'fixed_model.dart'; +import 'internal.dart'; +import 'model.dart'; + +class Api { + Client httpClient; + Uri? baseUri; + String? bearerToken; + Api({Client? httpClient, this.baseUri, this.bearerToken}) + : httpClient = httpClient ?? Client(); + Never unexpectedResponse(BaseResponse response, Uint8List body) { + throw Exception('http error response'); + } + + /// Gets discovery information about the domain. The file may include + /// additional keys, which MUST follow the Java package naming convention, + /// e.g. `com.example.myapp.property`. This ensures property names are + /// suitably namespaced for each application and reduces the risk of + /// clashes. + /// + /// Note that this endpoint is not necessarily handled by the homeserver, + /// but by another webserver, to be used for discovering the homeserver URL. + Future getWellknown() async { + final requestUri = Uri(path: '.well-known/matrix/client'); + 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 DiscoveryInformation.fromJson(json as Map); + } + + /// 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. + /// + /// Servers should be sure to rate limit this endpoint to avoid brute force + /// attacks. + /// + /// [token] The token to check validity of. + /// + /// returns `valid`: + /// True if the token is still valid, false otherwise. This should + /// additionally be false if the token is not a recognised token by + /// the server. + Future registrationTokenValidity(String token) async { + final requestUri = Uri( + path: '_matrix/client/v1/register/m.login.registration_token/validity', + queryParameters: { + 'token': token, + }); + 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 json['valid'] as bool; + } + + /// Paginates over the space tree in a depth-first manner to locate child rooms of a given space. + /// + /// Where a child room is unknown to the local server, federation is used to fill in the details. + /// The servers listed in the `via` array should be contacted to attempt to fill in missing rooms. + /// + /// Only [`m.space.child`](#mspacechild) state events of the room are considered. Invalid child + /// rooms and parent events are not covered by this endpoint. + /// + /// [roomId] The room ID of the space to get a hierarchy for. + /// + /// [suggestedOnly] Optional (default `false`) flag to indicate whether or not the server should only consider + /// suggested rooms. Suggested rooms are annotated in their [`m.space.child`](#mspacechild) event + /// contents. + /// + /// [limit] Optional limit for the maximum number of rooms to include per response. Must be an integer + /// greater than zero. + /// + /// Servers should apply a default value, and impose a maximum value to avoid resource exhaustion. + /// + /// [maxDepth] Optional limit for how far to go into the space. Must be a non-negative integer. + /// + /// When reached, no further child rooms will be returned. + /// + /// Servers should apply a default value, and impose a maximum value to avoid resource exhaustion. + /// + /// [from] A pagination token from a previous result. If specified, `max_depth` and `suggested_only` cannot + /// be changed from the first request. + Future getSpaceHierarchy(String roomId, + {bool? suggestedOnly, int? limit, int? maxDepth, String? from}) async { + final requestUri = Uri( + path: + '_matrix/client/v1/rooms/${Uri.encodeComponent(roomId)}/hierarchy', + queryParameters: { + if (suggestedOnly != null) 'suggested_only': suggestedOnly.toString(), + if (limit != null) 'limit': limit.toString(), + if (maxDepth != null) 'max_depth': maxDepth.toString(), + if (from != null) 'from': from, + }); + 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 GetSpaceHierarchyResponse.fromJson(json as Map); + } + + /// Retrieve all of the child events for a given parent event. + /// + /// Note that when paginating the `from` token should be "after" the `to` token in + /// terms of topological ordering, because it is only possible to paginate "backwards" + /// through events, starting at `from`. + /// + /// For example, passing a `from` token from page 2 of the results, and a `to` token + /// from page 1, would return the empty set. The caller can use a `from` token from + /// page 1 and a `to` token from page 2 to paginate over the same range, however. + /// + /// [roomId] The ID of the room containing the parent event. + /// + /// [eventId] The ID of the parent event whose child events are to be returned. + /// + /// [from] The pagination token to start returning results from. If not supplied, results + /// start at the most recent topological event known to the server. + /// + /// Can be a `next_batch` or `prev_batch` token from a previous call, or a returned + /// `start` token from [`/messages`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3roomsroomidmessages), + /// or a `next_batch` token from [`/sync`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3sync). + /// + /// [to] The pagination token to stop returning results at. If not supplied, results + /// continue up to `limit` or until there are no more events. + /// + /// Like `from`, this can be a previous token from a prior call to this endpoint + /// or from `/messages` or `/sync`. + /// + /// [limit] The maximum number of results to return in a single `chunk`. The server can + /// and should apply a maximum value to this parameter to avoid large responses. + /// + /// Similarly, the server should apply a default value when not supplied. + /// + /// [dir] Optional (default `b`) direction to return events from. If this is set to `f`, events + /// 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`. + Future getRelatingEvents( + String roomId, String eventId, + {String? from, String? to, int? limit, Direction? dir}) async { + final requestUri = Uri( + path: + '_matrix/client/v1/rooms/${Uri.encodeComponent(roomId)}/relations/${Uri.encodeComponent(eventId)}', + queryParameters: { + if (from != null) 'from': from, + if (to != null) 'to': to, + if (limit != null) 'limit': limit.toString(), + if (dir != null) 'dir': dir.name, + }); + 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 GetRelatingEventsResponse.fromJson(json as Map); + } + + /// Retrieve all of the child events for a given parent event which relate to the parent + /// using the given `relType`. + /// + /// Note that when paginating the `from` token should be "after" the `to` token in + /// terms of topological ordering, because it is only possible to paginate "backwards" + /// through events, starting at `from`. + /// + /// For example, passing a `from` token from page 2 of the results, and a `to` token + /// from page 1, would return the empty set. The caller can use a `from` token from + /// page 1 and a `to` token from page 2 to paginate over the same range, however. + /// + /// [roomId] The ID of the room containing the parent event. + /// + /// [eventId] The ID of the parent event whose child events are to be returned. + /// + /// [relType] The [relationship type](https://spec.matrix.org/unstable/client-server-api/#relationship-types) to search for. + /// + /// [from] The pagination token to start returning results from. If not supplied, results + /// start at the most recent topological event known to the server. + /// + /// Can be a `next_batch` or `prev_batch` token from a previous call, or a returned + /// `start` token from [`/messages`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3roomsroomidmessages), + /// or a `next_batch` token from [`/sync`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3sync). + /// + /// [to] The pagination token to stop returning results at. If not supplied, results + /// continue up to `limit` or until there are no more events. + /// + /// Like `from`, this can be a previous token from a prior call to this endpoint + /// or from `/messages` or `/sync`. + /// + /// [limit] The maximum number of results to return in a single `chunk`. The server can + /// and should apply a maximum value to this parameter to avoid large responses. + /// + /// Similarly, the server should apply a default value when not supplied. + /// + /// [dir] Optional (default `b`) direction to return events from. If this is set to `f`, events + /// 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`. + Future getRelatingEventsWithRelType( + String roomId, String eventId, String relType, + {String? from, String? to, int? limit, Direction? dir}) async { + final requestUri = Uri( + path: + '_matrix/client/v1/rooms/${Uri.encodeComponent(roomId)}/relations/${Uri.encodeComponent(eventId)}/${Uri.encodeComponent(relType)}', + queryParameters: { + if (from != null) 'from': from, + if (to != null) 'to': to, + if (limit != null) 'limit': limit.toString(), + if (dir != null) 'dir': dir.name, + }); + 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 GetRelatingEventsWithRelTypeResponse.fromJson( + json as Map); + } + + /// Retrieve all of the child events for a given parent event which relate to the parent + /// using the given `relType` and have the given `eventType`. + /// + /// Note that when paginating the `from` token should be "after" the `to` token in + /// terms of topological ordering, because it is only possible to paginate "backwards" + /// through events, starting at `from`. + /// + /// For example, passing a `from` token from page 2 of the results, and a `to` token + /// from page 1, would return the empty set. The caller can use a `from` token from + /// page 1 and a `to` token from page 2 to paginate over the same range, however. + /// + /// [roomId] The ID of the room containing the parent event. + /// + /// [eventId] The ID of the parent event whose child events are to be returned. + /// + /// [relType] The [relationship type](https://spec.matrix.org/unstable/client-server-api/#relationship-types) to search for. + /// + /// [eventType] The event type of child events to search for. + /// + /// Note that in encrypted rooms this will typically always be `m.room.encrypted` + /// regardless of the event type contained within the encrypted payload. + /// + /// [from] The pagination token to start returning results from. If not supplied, results + /// start at the most recent topological event known to the server. + /// + /// Can be a `next_batch` or `prev_batch` token from a previous call, or a returned + /// `start` token from [`/messages`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3roomsroomidmessages), + /// or a `next_batch` token from [`/sync`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3sync). + /// + /// [to] The pagination token to stop returning results at. If not supplied, results + /// continue up to `limit` or until there are no more events. + /// + /// Like `from`, this can be a previous token from a prior call to this endpoint + /// or from `/messages` or `/sync`. + /// + /// [limit] The maximum number of results to return in a single `chunk`. The server can + /// and should apply a maximum value to this parameter to avoid large responses. + /// + /// Similarly, the server should apply a default value when not supplied. + /// + /// [dir] Optional (default `b`) direction to return events from. If this is set to `f`, events + /// 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`. + Future + getRelatingEventsWithRelTypeAndEventType( + String roomId, String eventId, String relType, String eventType, + {String? from, String? to, int? limit, Direction? dir}) async { + final requestUri = Uri( + path: + '_matrix/client/v1/rooms/${Uri.encodeComponent(roomId)}/relations/${Uri.encodeComponent(eventId)}/${Uri.encodeComponent(relType)}/${Uri.encodeComponent(eventType)}', + queryParameters: { + if (from != null) 'from': from, + if (to != null) 'to': to, + if (limit != null) 'limit': limit.toString(), + if (dir != null) 'dir': dir.name, + }); + 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 GetRelatingEventsWithRelTypeAndEventTypeResponse.fromJson( + json as Map); + } + + /// Paginates over the thread roots in a room, ordered by the `latest_event` of each thread root + /// in its bundle. + /// + /// [roomId] The room ID where the thread roots are located. + /// + /// [include] Optional (default `all`) flag to denote which thread roots are of interest to the caller. + /// When `all`, all thread roots found in the room are returned. When `participated`, only + /// thread roots for threads the user has [participated in](https://spec.matrix.org/unstable/client-server-api/#server-side-aggregation-of-mthread-relationships) + /// will be returned. + /// + /// [limit] Optional limit for the maximum number of thread roots to include per response. Must be an integer + /// greater than zero. + /// + /// Servers should apply a default value, and impose a maximum value to avoid resource exhaustion. + /// + /// [from] A pagination token from a previous result. When not provided, the server starts paginating from + /// the most recent event visible to the user (as per history visibility rules; topologically). + Future getThreadRoots(String roomId, + {Include? include, int? limit, String? from}) async { + final requestUri = Uri( + path: '_matrix/client/v1/rooms/${Uri.encodeComponent(roomId)}/threads', + queryParameters: { + if (include != null) 'include': include.name, + if (limit != null) 'limit': limit.toString(), + if (from != null) 'from': from, + }); + 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 GetThreadRootsResponse.fromJson(json as Map); + } + + /// Get the ID of the event closest to the given timestamp, in the + /// direction specified by the `dir` parameter. + /// + /// If the server does not have all of the room history and does not have + /// an event suitably close to the requested timestamp, it can use the + /// corresponding [federation endpoint](https://spec.matrix.org/unstable/server-server-api/#get_matrixfederationv1timestamp_to_eventroomid) + /// to ask other servers for a suitable event. + /// + /// After calling this endpoint, clients can call + /// [`/rooms/{roomId}/context/{eventId}`](#get_matrixclientv3roomsroomidcontexteventid) + /// to obtain a pagination token to retrieve the events around the returned event. + /// + /// The event returned by this endpoint could be an event that the client + /// cannot render, and so may need to paginate in order to locate an event + /// that it can display, which may end up being outside of the client's + /// suitable range. Clients can employ different strategies to display + /// something reasonable to the user. For example, the client could try + /// paginating in one direction for a while, while looking at the + /// timestamps of the events that it is paginating through, and if it + /// exceeds a certain difference from the target timestamp, it can try + /// paginating in the opposite direction. The client could also simply + /// paginate in one direction and inform the user that the closest event + /// found in that direction is outside of the expected range. + /// + /// [roomId] The ID of the room to search + /// + /// [ts] The timestamp to search from, as given in milliseconds + /// since the Unix epoch. + /// + /// [dir] The direction in which to search. `f` for forwards, `b` for backwards. + Future getEventByTimestamp( + String roomId, int ts, Direction dir) async { + final requestUri = Uri( + path: + '_matrix/client/v1/rooms/${Uri.encodeComponent(roomId)}/timestamp_to_event', + queryParameters: { + 'ts': ts.toString(), + 'dir': dir.name, + }); + 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 GetEventByTimestampResponse.fromJson(json as Map); + } + + /// 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 + /// the user's Matrix ID in identity servers. + /// + /// Identifiers in this list may be used by the homeserver as, for example, + /// identifiers that it will accept to reset the user's account password. + /// + /// returns `threepids`: + /// + Future?> getAccount3PIDs() async { + final requestUri = Uri(path: '_matrix/client/v3/account/3pid'); + 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 ((v) => v != null + ? (v as List) + .map( + (v) => ThirdPartyIdentifier.fromJson(v as Map)) + .toList() + : null)(json['threepids']); + } + + /// Adds contact information to the user's account. + /// + /// This endpoint is deprecated in favour of the more specific `/3pid/add` + /// and `/3pid/bind` endpoints. + /// + /// **Note:** + /// Previously this endpoint supported a `bind` parameter. This parameter + /// has been removed, making this endpoint behave as though it was `false`. + /// 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. + /// + /// returns `submit_url`: + /// An optional field containing a URL where the client must + /// submit the validation token to, with identical parameters + /// to the Identity Service API's `POST + /// /validate/email/submitToken` endpoint (without the requirement + /// for an access token). The homeserver must send this token to the + /// user (if applicable), who should then be prompted to provide it + /// to the client. + /// + /// If this field is not present, the client can assume that + /// verification will happen without the client's involvement + /// provided the homeserver advertises this specification version + /// in the `/versions` response (ie: r0.5.0). + @deprecated + Future post3PIDs(ThreePidCredentials threePidCreds) async { + final requestUri = Uri(path: '_matrix/client/v3/account/3pid'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'three_pid_creds': threePidCreds.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 ((v) => + v != null ? Uri.parse(v as String) : null)(json['submit_url']); + } + + /// This API endpoint uses the [User-Interactive Authentication API](https://spec.matrix.org/unstable/client-server-api/#user-interactive-authentication-api). + /// + /// Adds contact information to the user's account. Homeservers should use 3PIDs added + /// through this endpoint for password resets instead of relying on the identity server. + /// + /// Homeservers should prevent the caller from adding a 3PID to their account if it has + /// already been added to another user's account on the homeserver. + /// + /// [auth] Additional authentication information for the + /// user-interactive authentication API. + /// + /// [clientSecret] The client secret used in the session with the homeserver. + /// + /// [sid] The session identifier given by the homeserver. + Future add3PID(String clientSecret, String sid, + {AuthenticationData? auth}) async { + final requestUri = Uri(path: '_matrix/client/v3/account/3pid/add'); + 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(), + 'client_secret': clientSecret, + 'sid': sid, + })); + 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 ignore(json); + } + + /// Binds a 3PID to the user's account through the specified identity server. + /// + /// Homeservers should not prevent this request from succeeding if another user + /// has bound the 3PID. Homeservers should simply proxy any errors received by + /// the identity server to the caller. + /// + /// Homeservers should track successful binds so they can be unbound later. + /// + /// [clientSecret] The client secret used in the session with the identity server. + /// + /// [idAccessToken] An access token previously registered with the identity server. + /// + /// [idServer] The identity server to use. + /// + /// [sid] The session identifier given by the identity server. + Future bind3PID(String clientSecret, String idAccessToken, + String idServer, String sid) async { + final requestUri = Uri(path: '_matrix/client/v3/account/3pid/bind'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'client_secret': clientSecret, + 'id_access_token': idAccessToken, + 'id_server': idServer, + 'sid': sid, + })); + 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 ignore(json); + } + + /// 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. + /// + /// [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. + /// + /// returns `id_server_unbind_result`: + /// An indicator as to whether or not the homeserver was able to unbind + /// the 3PID from the identity server. `success` indicates that the + /// identity server has unbound the identifier whereas `no-support` + /// indicates that the identity server refuses to support the request + /// or the homeserver was not able to determine an identity server to + /// unbind from. + Future delete3pidFromAccount( + String address, ThirdPartyIdentifierMedium medium, + {String? idServer}) async { + final requestUri = Uri(path: '_matrix/client/v3/account/3pid/delete'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'address': address, + if (idServer != null) 'id_server': idServer, + 'medium': medium.name, + })); + 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 IdServerUnbindResult.values + .fromString(json['id_server_unbind_result'] as String)!; + } + + /// The homeserver must check that the given email address is **not** + /// already associated with an account on this homeserver. This API should + /// be used to request validation tokens when adding an email address to an + /// account. This API's parameters and response are identical to that of + /// the [`/register/email/requestToken`](https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3registeremailrequesttoken) + /// endpoint. The homeserver should validate + /// the email itself, either by sending a validation email itself or by using + /// a service it has control over. + /// + /// [clientSecret] A unique string generated by the client, and used to identify the + /// validation attempt. It must be a string consisting of the characters + /// `[0-9a-zA-Z.=_-]`. Its length must not exceed 255 characters and it + /// must not be empty. + /// + /// + /// [email] The email address to validate. + /// + /// [nextLink] Optional. When the validation is completed, the identity server will + /// redirect the user to this URL. This option is ignored when submitting + /// 3PID validation information through a POST request. + /// + /// [sendAttempt] The server will only send an email if the `send_attempt` + /// is a number greater than the most recent one which it has seen, + /// scoped to that `email` + `client_secret` pair. This is to + /// avoid repeatedly sending the same email in the case of request + /// retries between the POSTing user and the identity server. + /// The client should increment this value if they desire a new + /// email (e.g. a reminder) to be sent. If they do not, the server + /// should respond with success but not resend the email. + /// + /// [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. + /// + /// Required if an `id_server` is supplied. + /// + /// [idServer] The hostname of the identity server to communicate with. May optionally + /// include a port. This parameter is ignored when the homeserver handles + /// 3PID verification. + /// + /// This parameter is deprecated with a plan to be removed in a future specification + /// version for `/account/password` and `/register` requests. + Future requestTokenTo3PIDEmail( + String clientSecret, String email, int sendAttempt, + {String? nextLink, String? idAccessToken, String? idServer}) async { + final requestUri = + Uri(path: '_matrix/client/v3/account/3pid/email/requestToken'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'client_secret': clientSecret, + 'email': email, + if (nextLink != null) 'next_link': nextLink, + 'send_attempt': sendAttempt, + if (idAccessToken != null) 'id_access_token': idAccessToken, + if (idServer != null) 'id_server': idServer, + })); + 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 RequestTokenResponse.fromJson(json as Map); + } + + /// The homeserver must check that the given phone number is **not** + /// already associated with an account on this homeserver. This API should + /// be used to request validation tokens when adding a phone number to an + /// account. This API's parameters and response are identical to that of + /// the [`/register/msisdn/requestToken`](https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3registermsisdnrequesttoken) + /// endpoint. The homeserver should validate + /// the phone number itself, either by sending a validation message itself or by using + /// a service it has control over. + /// + /// [clientSecret] A unique string generated by the client, and used to identify the + /// validation attempt. It must be a string consisting of the characters + /// `[0-9a-zA-Z.=_-]`. Its length must not exceed 255 characters and it + /// must not be empty. + /// + /// + /// [country] The two-letter uppercase ISO-3166-1 alpha-2 country code that the + /// number in `phone_number` should be parsed as if it were dialled from. + /// + /// [nextLink] Optional. When the validation is completed, the identity server will + /// redirect the user to this URL. This option is ignored when submitting + /// 3PID validation information through a POST request. + /// + /// [phoneNumber] The phone number to validate. + /// + /// [sendAttempt] The server will only send an SMS if the `send_attempt` is a + /// number greater than the most recent one which it has seen, + /// scoped to that `country` + `phone_number` + `client_secret` + /// triple. This is to avoid repeatedly sending the same SMS in + /// the case of request retries between the POSTing user and the + /// identity server. The client should increment this value if + /// they desire a new SMS (e.g. a reminder) to be sent. + /// + /// [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. + /// + /// Required if an `id_server` is supplied. + /// + /// [idServer] The hostname of the identity server to communicate with. May optionally + /// include a port. This parameter is ignored when the homeserver handles + /// 3PID verification. + /// + /// This parameter is deprecated with a plan to be removed in a future specification + /// version for `/account/password` and `/register` requests. + Future requestTokenTo3PIDMSISDN( + String clientSecret, String country, String phoneNumber, int sendAttempt, + {String? nextLink, String? idAccessToken, String? idServer}) async { + final requestUri = + Uri(path: '_matrix/client/v3/account/3pid/msisdn/requestToken'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'client_secret': clientSecret, + 'country': country, + if (nextLink != null) 'next_link': nextLink, + 'phone_number': phoneNumber, + 'send_attempt': sendAttempt, + if (idAccessToken != null) 'id_access_token': idAccessToken, + if (idServer != null) 'id_server': idServer, + })); + 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 RequestTokenResponse.fromJson(json as Map); + } + + /// 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. + /// + /// [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. + /// + /// returns `id_server_unbind_result`: + /// An indicator as to whether or not the identity server was able to unbind + /// the 3PID. `success` indicates that the identity server has unbound the + /// identifier whereas `no-support` indicates that the identity server + /// refuses to support the request or the homeserver was not able to determine + /// an identity server to unbind from. + Future unbind3pidFromAccount( + String address, ThirdPartyIdentifierMedium medium, + {String? idServer}) async { + final requestUri = Uri(path: '_matrix/client/v3/account/3pid/unbind'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'address': address, + if (idServer != null) 'id_server': idServer, + 'medium': medium.name, + })); + 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 IdServerUnbindResult.values + .fromString(json['id_server_unbind_result'] as String)!; + } + + /// Deactivate the user's account, removing all ability for the user to + /// login again. + /// + /// This API endpoint uses the [User-Interactive Authentication API](https://spec.matrix.org/unstable/client-server-api/#user-interactive-authentication-api). + /// + /// An access token should be submitted to this endpoint if the client has + /// an active session. + /// + /// The homeserver may change the flows available depending on whether a + /// valid access token is provided. + /// + /// 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. + /// + /// [auth] Additional authentication information for the user-interactive authentication API. + /// + /// [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 + /// homeserver does not know which `id_server` that was, + /// it must return an `id_server_unbind_result` of + /// `no-support`. + /// + /// returns `id_server_unbind_result`: + /// An indicator as to whether or not the homeserver was able to unbind + /// the user's 3PIDs from the identity server(s). `success` indicates + /// that all identifiers have been unbound from the identity server while + /// `no-support` indicates that one or more identifiers failed to unbind + /// due to the identity server refusing the request or the homeserver + /// being unable to determine an identity server to unbind from. This + /// must be `success` if the homeserver has no identifiers to unbind + /// for the user. + Future deactivateAccount( + {AuthenticationData? auth, String? idServer}) async { + final requestUri = Uri(path: '_matrix/client/v3/account/deactivate'); + 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(), + if (idServer != null) 'id_server': idServer, + })); + 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 IdServerUnbindResult.values + .fromString(json['id_server_unbind_result'] as String)!; + } + + /// Changes the password for an account on this homeserver. + /// + /// This API endpoint uses the [User-Interactive Authentication API](https://spec.matrix.org/unstable/client-server-api/#user-interactive-authentication-api) to + /// ensure the user changing the password is actually the owner of the + /// account. + /// + /// An access token should be submitted to this endpoint if the client has + /// an active session. + /// + /// The homeserver may change the flows available depending on whether a + /// valid access token is provided. The homeserver SHOULD NOT revoke the + /// access token provided in the request. Whether other access tokens for + /// the user are revoked depends on the request parameters. + /// + /// [auth] Additional authentication information for the user-interactive authentication API. + /// + /// [logoutDevices] Whether the user's other access tokens, and their associated devices, should be + /// revoked if the request succeeds. Defaults to true. + /// + /// When `false`, the server can still take advantage of the [soft logout method](https://spec.matrix.org/unstable/client-server-api/#soft-logout) + /// for the user's remaining devices. + /// + /// [newPassword] The new password for the account. + Future changePassword(String newPassword, + {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!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (auth != null) 'auth': auth.toJson(), + if (logoutDevices != null) 'logout_devices': logoutDevices, + 'new_password': newPassword, + })); + 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 ignore(json); + } + + /// The homeserver must check that the given email address **is + /// associated** with an account on this homeserver. This API should be + /// used to request validation tokens when authenticating for the + /// `/account/password` endpoint. + /// + /// This API's parameters and response are identical to that of the + /// [`/register/email/requestToken`](https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3registeremailrequesttoken) + /// endpoint, except that + /// `M_THREEPID_NOT_FOUND` may be returned if no account matching the + /// given email address could be found. The server may instead send an + /// email to the given address prompting the user to create an account. + /// `M_THREEPID_IN_USE` may not be returned. + /// + /// The homeserver should validate the email itself, either by sending a + /// validation email itself or by using a service it has control over. + /// + /// [clientSecret] A unique string generated by the client, and used to identify the + /// validation attempt. It must be a string consisting of the characters + /// `[0-9a-zA-Z.=_-]`. Its length must not exceed 255 characters and it + /// must not be empty. + /// + /// + /// [email] The email address to validate. + /// + /// [nextLink] Optional. When the validation is completed, the identity server will + /// redirect the user to this URL. This option is ignored when submitting + /// 3PID validation information through a POST request. + /// + /// [sendAttempt] The server will only send an email if the `send_attempt` + /// is a number greater than the most recent one which it has seen, + /// scoped to that `email` + `client_secret` pair. This is to + /// avoid repeatedly sending the same email in the case of request + /// retries between the POSTing user and the identity server. + /// The client should increment this value if they desire a new + /// email (e.g. a reminder) to be sent. If they do not, the server + /// should respond with success but not resend the email. + /// + /// [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. + /// + /// Required if an `id_server` is supplied. + /// + /// [idServer] The hostname of the identity server to communicate with. May optionally + /// include a port. This parameter is ignored when the homeserver handles + /// 3PID verification. + /// + /// This parameter is deprecated with a plan to be removed in a future specification + /// version for `/account/password` and `/register` requests. + Future requestTokenToResetPasswordEmail( + String clientSecret, String email, int sendAttempt, + {String? nextLink, String? idAccessToken, String? idServer}) async { + final requestUri = + Uri(path: '_matrix/client/v3/account/password/email/requestToken'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'client_secret': clientSecret, + 'email': email, + if (nextLink != null) 'next_link': nextLink, + 'send_attempt': sendAttempt, + if (idAccessToken != null) 'id_access_token': idAccessToken, + if (idServer != null) 'id_server': idServer, + })); + 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 RequestTokenResponse.fromJson(json as Map); + } + + /// The homeserver must check that the given phone number **is + /// associated** with an account on this homeserver. This API should be + /// used to request validation tokens when authenticating for the + /// `/account/password` endpoint. + /// + /// This API's parameters and response are identical to that of the + /// [`/register/msisdn/requestToken`](https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3registermsisdnrequesttoken) + /// endpoint, except that + /// `M_THREEPID_NOT_FOUND` may be returned if no account matching the + /// given phone number could be found. The server may instead send the SMS + /// to the given phone number prompting the user to create an account. + /// `M_THREEPID_IN_USE` may not be returned. + /// + /// The homeserver should validate the phone number itself, either by sending a + /// validation message itself or by using a service it has control over. + /// + /// [clientSecret] A unique string generated by the client, and used to identify the + /// validation attempt. It must be a string consisting of the characters + /// `[0-9a-zA-Z.=_-]`. Its length must not exceed 255 characters and it + /// must not be empty. + /// + /// + /// [country] The two-letter uppercase ISO-3166-1 alpha-2 country code that the + /// number in `phone_number` should be parsed as if it were dialled from. + /// + /// [nextLink] Optional. When the validation is completed, the identity server will + /// redirect the user to this URL. This option is ignored when submitting + /// 3PID validation information through a POST request. + /// + /// [phoneNumber] The phone number to validate. + /// + /// [sendAttempt] The server will only send an SMS if the `send_attempt` is a + /// number greater than the most recent one which it has seen, + /// scoped to that `country` + `phone_number` + `client_secret` + /// triple. This is to avoid repeatedly sending the same SMS in + /// the case of request retries between the POSTing user and the + /// identity server. The client should increment this value if + /// they desire a new SMS (e.g. a reminder) to be sent. + /// + /// [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. + /// + /// Required if an `id_server` is supplied. + /// + /// [idServer] The hostname of the identity server to communicate with. May optionally + /// include a port. This parameter is ignored when the homeserver handles + /// 3PID verification. + /// + /// This parameter is deprecated with a plan to be removed in a future specification + /// version for `/account/password` and `/register` requests. + Future requestTokenToResetPasswordMSISDN( + String clientSecret, String country, String phoneNumber, int sendAttempt, + {String? nextLink, String? idAccessToken, String? idServer}) async { + final requestUri = + Uri(path: '_matrix/client/v3/account/password/msisdn/requestToken'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'client_secret': clientSecret, + 'country': country, + if (nextLink != null) 'next_link': nextLink, + 'phone_number': phoneNumber, + 'send_attempt': sendAttempt, + if (idAccessToken != null) 'id_access_token': idAccessToken, + if (idServer != null) 'id_server': idServer, + })); + 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 RequestTokenResponse.fromJson(json as Map); + } + + /// Gets information about the owner of a given access token. + /// + /// Note that, as with the rest of the Client-Server API, + /// Application Services may masquerade as users within their + /// namespace by giving a `user_id` query parameter. In this + /// situation, the server should verify that the given `user_id` + /// is registered by the appservice, and return it in the response + /// body. + Future getTokenOwner() async { + final requestUri = Uri(path: '_matrix/client/v3/account/whoami'); + 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 TokenOwnerInfo.fromJson(json as Map); + } + + /// Gets information about a particular user. + /// + /// This API may be restricted to only be called by the user being looked + /// up, or by a server admin. Server-local administrator privileges are not + /// specified in this document. + /// + /// [userId] The user to look up. + Future getWhoIs(String userId) async { + final requestUri = Uri( + path: '_matrix/client/v3/admin/whois/${Uri.encodeComponent(userId)}'); + 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 WhoIsInfo.fromJson(json as Map); + } + + /// Gets information about the server's supported feature set + /// and other relevant capabilities. + /// + /// returns `capabilities`: + /// The custom capabilities the server supports, using the + /// Java package naming convention. + Future getCapabilities() async { + final requestUri = Uri(path: '_matrix/client/v3/capabilities'); + 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 Capabilities.fromJson(json['capabilities'] as Map); + } + + /// Create a new room with various configuration options. + /// + /// The server MUST apply the normal state resolution rules when creating + /// the new room, including checking power levels for each event. It MUST + /// apply the events implied by the request in the following order: + /// + /// 1. The `m.room.create` event itself. Must be the first event in the + /// room. + /// + /// 2. An `m.room.member` event for the creator to join the room. This is + /// needed so the remaining events can be sent. + /// + /// 3. A default `m.room.power_levels` event, giving the room creator + /// (and not other members) permission to send state events. Overridden + /// by the `power_level_content_override` parameter. + /// + /// 4. An `m.room.canonical_alias` event if `room_alias_name` is given. + /// + /// 5. Events set by the `preset`. Currently these are the `m.room.join_rules`, + /// `m.room.history_visibility`, and `m.room.guest_access` state events. + /// + /// 6. Events listed in `initial_state`, in the order that they are + /// listed. + /// + /// 7. Events implied by `name` and `topic` (`m.room.name` and `m.room.topic` + /// state events). + /// + /// 8. Invite events implied by `invite` and `invite_3pid` (`m.room.member` with + /// `membership: invite` and `m.room.third_party_invite`). + /// + /// The available presets do the following with respect to room state: + /// + /// | Preset | `join_rules` | `history_visibility` | `guest_access` | Other | + /// |------------------------|--------------|----------------------|----------------|-------| + /// | `private_chat` | `invite` | `shared` | `can_join` | | + /// | `trusted_private_chat` | `invite` | `shared` | `can_join` | All invitees are given the same power level as the room creator. | + /// | `public_chat` | `public` | `shared` | `forbidden` | | + /// + /// The server will create a `m.room.create` event in the room with the + /// requesting user as the creator, alongside other keys provided in the + /// `creation_content`. + /// + /// [creationContent] Extra keys, such as `m.federate`, to be added to the content + /// of the [`m.room.create`](https://spec.matrix.org/unstable/client-server-api/#mroomcreate) event. The server will overwrite the following + /// keys: `creator`, `room_version`. Future versions of the specification + /// may allow the server to overwrite other keys. + /// + /// [initialState] A list of state events to set in the new room. This allows + /// the user to override the default state events set in the new + /// room. The expected format of the state events are an object + /// with type, state_key and content keys set. + /// + /// Takes precedence over events set by `preset`, but gets + /// overridden by `name` and `topic` keys. + /// + /// [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 + /// the room. + /// + /// [isDirect] This flag makes the server set the `is_direct` flag on the + /// `m.room.member` events sent to the users in `invite` and + /// `invite_3pid`. See [Direct Messaging](https://spec.matrix.org/unstable/client-server-api/#direct-messaging) for more information. + /// + /// [name] If this is included, an `m.room.name` event will be sent + /// into the room to indicate the name of the room. See Room + /// Events for more information on `m.room.name`. + /// + /// [powerLevelContentOverride] The power level content to override in the default power level + /// event. This object is applied on top of the generated + /// [`m.room.power_levels`](https://spec.matrix.org/unstable/client-server-api/#mroompower_levels) + /// event content prior to it being sent to the room. Defaults to + /// overriding nothing. + /// + /// [preset] Convenience parameter for setting various default state events + /// 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 + /// `public_chat` and `private` visibility equates to a preset of + /// `private_chat`. + /// + /// [roomAliasName] The desired room alias **local part**. If this is included, a + /// room alias will be created and mapped to the newly created + /// room. The alias will belong on the *same* homeserver which + /// created the room. For example, if this was set to "foo" and + /// sent to the homeserver "example.com" the complete room alias + /// would be `#foo:example.com`. + /// + /// The complete room alias will become the canonical alias for + /// the room and an `m.room.canonical_alias` event will be sent + /// into the room. + /// + /// [roomVersion] The room version to set for the room. If not provided, the homeserver is + /// to use its configured default. If provided, the homeserver will return a + /// 400 error with the errcode `M_UNSUPPORTED_ROOM_VERSION` if it does not + /// support the room version. + /// + /// [topic] If this is included, an `m.room.topic` event will be sent + /// into the room to indicate the topic for the room. See Room + /// Events for more information on `m.room.topic`. + /// + /// [visibility] A `public` visibility indicates that the room will be shown + /// in the published room list. A `private` visibility will hide + /// the room from the published room list. Rooms default to + /// `private` visibility if this key is not included. NB: This + /// should not be confused with `join_rules` which also uses the + /// word `public`. + /// + /// returns `room_id`: + /// The created room's ID. + Future createRoom( + {Map? creationContent, + List? initialState, + List? invite, + List? invite3pid, + bool? isDirect, + String? name, + Map? powerLevelContentOverride, + CreateRoomPreset? preset, + String? roomAliasName, + String? roomVersion, + String? topic, + Visibility? visibility}) async { + final requestUri = Uri(path: '_matrix/client/v3/createRoom'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (creationContent != null) 'creation_content': creationContent, + if (initialState != null) + 'initial_state': initialState.map((v) => v.toJson()).toList(), + if (invite != null) 'invite': invite.map((v) => v).toList(), + if (invite3pid != null) + 'invite_3pid': invite3pid.map((v) => v.toJson()).toList(), + if (isDirect != null) 'is_direct': isDirect, + if (name != null) 'name': name, + if (powerLevelContentOverride != null) + 'power_level_content_override': powerLevelContentOverride, + if (preset != null) 'preset': preset.name, + if (roomAliasName != null) 'room_alias_name': roomAliasName, + if (roomVersion != null) 'room_version': roomVersion, + if (topic != null) 'topic': topic, + if (visibility != null) 'visibility': visibility.name, + })); + 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['room_id'] as String; + } + + /// This API endpoint uses the [User-Interactive Authentication API](https://spec.matrix.org/unstable/client-server-api/#user-interactive-authentication-api). + /// + /// Deletes the given devices, and invalidates any access token associated with them. + /// + /// [auth] Additional authentication information for the + /// user-interactive authentication API. + /// + /// [devices] The list of device IDs to delete. + Future deleteDevices(List devices, + {AuthenticationData? auth}) async { + final requestUri = Uri(path: '_matrix/client/v3/delete_devices'); + 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(), + 'devices': devices.map((v) => v).toList(), + })); + 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 ignore(json); + } + + /// Gets information about all devices for the current user. + /// + /// returns `devices`: + /// A list of all registered devices for this user. + Future?> getDevices() async { + final requestUri = Uri(path: '_matrix/client/v3/devices'); + 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 ((v) => v != null + ? (v as List) + .map((v) => Device.fromJson(v as Map)) + .toList() + : null)(json['devices']); + } + + /// This API endpoint uses the [User-Interactive Authentication API](https://spec.matrix.org/unstable/client-server-api/#user-interactive-authentication-api). + /// + /// Deletes the given device, and invalidates any access token associated with it. + /// + /// [deviceId] The device to delete. + /// + /// [auth] Additional authentication information for the + /// user-interactive authentication API. + Future deleteDevice(String deviceId, {AuthenticationData? auth}) async { + final requestUri = + Uri(path: '_matrix/client/v3/devices/${Uri.encodeComponent(deviceId)}'); + final request = Request('DELETE', 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 ignore(json); + } + + /// Gets information on a single device, by device id. + /// + /// [deviceId] The device to retrieve. + Future getDevice(String deviceId) async { + final requestUri = + Uri(path: '_matrix/client/v3/devices/${Uri.encodeComponent(deviceId)}'); + 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 Device.fromJson(json as Map); + } + + /// Updates the metadata on the given device. + /// + /// [deviceId] The device to update. + /// + /// [displayName] The new display name for this device. If not given, the + /// display name is unchanged. + Future updateDevice(String deviceId, {String? displayName}) async { + final requestUri = + Uri(path: '_matrix/client/v3/devices/${Uri.encodeComponent(deviceId)}'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (displayName != null) 'display_name': displayName, + })); + 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 ignore(json); + } + + /// Updates the visibility of a given room on the application service's room + /// directory. + /// + /// This API is similar to the room directory visibility API used by clients + /// to update the homeserver's more general room directory. + /// + /// 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. + /// + /// [networkId] The protocol (network) ID to update the room list for. This would + /// have been provided by the application service as being listed as + /// a supported protocol. + /// + /// [roomId] The room ID to add to the directory. + /// + /// [visibility] Whether the room should be visible (public) in the directory + /// or not (private). + Future> updateAppserviceRoomDirectoryVisibility( + String networkId, String roomId, Visibility visibility) async { + final requestUri = Uri( + path: + '_matrix/client/v3/directory/list/appservice/${Uri.encodeComponent(networkId)}/${Uri.encodeComponent(roomId)}'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'visibility': visibility.name, + })); + 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; + } + + /// Gets the visibility of a given room on the server's public room directory. + /// + /// [roomId] The room ID. + /// + /// returns `visibility`: + /// The visibility of the room in the directory. + Future getRoomVisibilityOnDirectory(String roomId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/directory/list/room/${Uri.encodeComponent(roomId)}'); + 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 ((v) => v != null + ? Visibility.values.fromString(v as String)! + : null)(json['visibility']); + } + + /// Sets the visibility of a given room in the server's public room + /// directory. + /// + /// Servers may choose to implement additional access control checks + /// here, for instance that room visibility can only be changed by + /// the room creator or a server administrator. + /// + /// [roomId] The room ID. + /// + /// [visibility] The new visibility setting for the room. + /// Defaults to 'public'. + Future setRoomVisibilityOnDirectory(String roomId, + {Visibility? visibility}) async { + final requestUri = Uri( + path: + '_matrix/client/v3/directory/list/room/${Uri.encodeComponent(roomId)}'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (visibility != null) 'visibility': visibility.name, + })); + 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 ignore(json); + } + + /// Remove a mapping of room alias to room ID. + /// + /// Servers may choose to implement additional access control checks here, for instance that + /// room aliases can only be deleted by their creator or a server administrator. + /// + /// **Note:** + /// Servers may choose to update the `alt_aliases` for the `m.room.canonical_alias` + /// state event in the room when an alias is removed. Servers which choose to update the + /// canonical alias event are recommended to, in addition to their other relevant permission + /// checks, delete the alias and return a successful response even if the user does not + /// have permission to update the `m.room.canonical_alias` event. + /// + /// [roomAlias] The room alias to remove. Its format is defined + /// [in the appendices](https://spec.matrix.org/unstable/appendices/#room-aliases). + /// + Future deleteRoomAlias(String roomAlias) async { + final requestUri = Uri( + path: + '_matrix/client/v3/directory/room/${Uri.encodeComponent(roomAlias)}'); + final request = Request('DELETE', 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 ignore(json); + } + + /// Requests that the server resolve a room alias to a room ID. + /// + /// The server will use the federation API to resolve the alias if the + /// domain part of the alias does not correspond to the server's own + /// domain. + /// + /// [roomAlias] The room alias. Its format is defined + /// [in the appendices](https://spec.matrix.org/unstable/appendices/#room-aliases). + /// + Future getRoomIdByAlias(String roomAlias) async { + final requestUri = Uri( + path: + '_matrix/client/v3/directory/room/${Uri.encodeComponent(roomAlias)}'); + 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 GetRoomIdByAliasResponse.fromJson(json as Map); + } + + /// + /// + /// [roomAlias] The room alias to set. Its format is defined + /// [in the appendices](https://spec.matrix.org/unstable/appendices/#room-aliases). + /// + /// + /// [roomId] The room ID to set. + Future setRoomAlias(String roomAlias, String roomId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/directory/room/${Uri.encodeComponent(roomAlias)}'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'room_id': roomId, + })); + 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 ignore(json); + } + + /// This will listen for new events and return them to the caller. This will + /// block until an event is received, or until the `timeout` is reached. + /// + /// This endpoint was deprecated in r0 of this specification. Clients + /// should instead call the [`/sync`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3sync) + /// endpoint with a `since` parameter. See + /// the [migration guide](https://matrix.org/docs/guides/migrating-from-client-server-api-v-1#deprecated-endpoints). + /// + /// [from] The token to stream from. This token is either from a previous + /// request to this API or from the initial sync API. + /// + /// [timeout] The maximum time in milliseconds to wait for an event. + @deprecated + Future getEvents({String? from, int? timeout}) async { + final requestUri = Uri(path: '_matrix/client/v3/events', queryParameters: { + if (from != null) 'from': from, + if (timeout != null) 'timeout': timeout.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 GetEventsResponse.fromJson(json as Map); + } + + /// This will listen for new events related to a particular room and return + /// them to the caller. This will block until an event is received, or until + /// the `timeout` is reached. + /// + /// This API is the same as the normal `/events` endpoint, but can be + /// called by users who have not joined the room. + /// + /// Note that the normal `/events` endpoint has been deprecated. This + /// API will also be deprecated at some point, but its replacement is not + /// yet known. + /// + /// [from] The token to stream from. This token is either from a previous + /// request to this API or from the initial sync API. + /// + /// [timeout] The maximum time in milliseconds to wait for an event. + /// + /// [roomId] The room ID for which events should be returned. + Future peekEvents( + {String? from, int? timeout, String? roomId}) async { + final requestUri = Uri(path: '_matrix/client/v3/events', queryParameters: { + if (from != null) 'from': from, + if (timeout != null) 'timeout': timeout.toString(), + if (roomId != null) 'room_id': roomId, + }); + 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 PeekEventsResponse.fromJson(json as Map); + } + + /// Get a single event based on `event_id`. You must have permission to + /// retrieve this event e.g. by being a member in the room for this event. + /// + /// This endpoint was deprecated in r0 of this specification. Clients + /// should instead call the + /// [/rooms/{roomId}/event/{eventId}](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3roomsroomideventeventid) 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 + Future getOneEvent(String eventId) async { + final requestUri = + Uri(path: '_matrix/client/v3/events/${Uri.encodeComponent(eventId)}'); + 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 MatrixEvent.fromJson(json as Map); + } + + /// *Note that this API takes either a room ID or alias, unlike* `/rooms/{roomId}/join`. + /// + /// This API starts a user participating in a particular room, if that user + /// is allowed to participate in that room. After this call, the client is + /// allowed to see all current state events in the room, and all subsequent + /// events associated with the room until the user leaves the room. + /// + /// After a user has joined a room, the room will appear as an entry in the + /// response of the [`/initialSync`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3initialsync) + /// and [`/sync`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3sync) APIs. + /// + /// [roomIdOrAlias] The room identifier or alias to join. + /// + /// [serverName] The servers to attempt to join the room through. One of the servers + /// must be participating in the room. + /// + /// [reason] Optional reason to be included as the `reason` on the subsequent + /// membership event. + /// + /// [thirdPartySigned] If a `third_party_signed` was supplied, the homeserver must verify + /// that it matches a pending `m.room.third_party_invite` event in the + /// room, and perform key validity checking if required by the event. + /// + /// returns `room_id`: + /// The joined room ID. + Future joinRoom(String roomIdOrAlias, + {List? serverName, + String? reason, + ThirdPartySigned? thirdPartySigned}) async { + final requestUri = Uri( + path: '_matrix/client/v3/join/${Uri.encodeComponent(roomIdOrAlias)}', + queryParameters: { + if (serverName != null) + 'server_name': serverName.map((v) => v).toList(), + }); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (reason != null) 'reason': reason, + if (thirdPartySigned != null) + 'third_party_signed': thirdPartySigned.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 json['room_id'] as String; + } + + /// This API returns a list of the user's current rooms. + /// + /// returns `joined_rooms`: + /// The ID of each room in which the user has `joined` membership. + Future> getJoinedRooms() async { + final requestUri = Uri(path: '_matrix/client/v3/joined_rooms'); + 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 (json['joined_rooms'] as List).map((v) => v as String).toList(); + } + + /// Gets a list of users who have updated their device identity keys since a + /// previous sync token. + /// + /// The server should include in the results any users who: + /// + /// * currently share a room with the calling user (ie, both users have + /// membership state `join`); *and* + /// * added new device identity keys or removed an existing device with + /// identity keys, between `from` and `to`. + /// + /// [from] The desired start point of the list. Should be the `next_batch` field + /// from a response to an earlier call to [`/sync`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3sync). Users who have not + /// uploaded new device identity keys since this point, nor deleted + /// existing devices with identity keys since then, will be excluded + /// from the results. + /// + /// [to] The desired end point of the list. Should be the `next_batch` + /// field from a recent call to [`/sync`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3sync) - typically the most recent + /// such call. This may be used by the server as a hint to check its + /// caches are up to date. + Future getKeysChanges(String from, String to) async { + final requestUri = + Uri(path: '_matrix/client/v3/keys/changes', queryParameters: { + 'from': from, + 'to': to, + }); + 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 GetKeysChangesResponse.fromJson(json as Map); + } + + /// Claims one-time keys for use in pre-key messages. + /// + /// [oneTimeKeys] The keys to be claimed. A map from user ID, to a map from + /// device ID to algorithm name. + /// + /// [timeout] The time (in milliseconds) to wait when downloading keys from + /// remote servers. 10 seconds is the recommended default. + Future claimKeys( + Map> oneTimeKeys, + {int? timeout}) async { + final requestUri = Uri(path: '_matrix/client/v3/keys/claim'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'one_time_keys': oneTimeKeys + .map((k, v) => MapEntry(k, v.map((k, v) => MapEntry(k, v)))), + if (timeout != null) 'timeout': timeout, + })); + 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 ClaimKeysResponse.fromJson(json as Map); + } + + /// Publishes cross-signing keys for the user. + /// + /// This API endpoint uses the [User-Interactive Authentication API](https://spec.matrix.org/unstable/client-server-api/#user-interactive-authentication-api). + /// + /// [auth] Additional authentication information for the + /// user-interactive authentication API. + /// + /// [masterKey] Optional. The user\'s master key. + /// + /// [selfSigningKey] Optional. The user\'s self-signing key. Must be signed by + /// the accompanying master key, or by the user\'s most recently + /// uploaded master key if no master key is included in the + /// request. + /// + /// [userSigningKey] Optional. The user\'s user-signing key. Must be signed by + /// the accompanying master key, or by the user\'s most recently + /// uploaded master key if no master key is included in the + /// request. + Future uploadCrossSigningKeys( + {AuthenticationData? auth, + MatrixCrossSigningKey? masterKey, + MatrixCrossSigningKey? selfSigningKey, + MatrixCrossSigningKey? userSigningKey}) async { + final requestUri = + Uri(path: '_matrix/client/v3/keys/device_signing/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 (auth != null) 'auth': auth.toJson(), + if (masterKey != null) 'master_key': masterKey.toJson(), + if (selfSigningKey != null) 'self_signing_key': selfSigningKey.toJson(), + if (userSigningKey != null) 'user_signing_key': userSigningKey.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 ignore(json); + } + + /// Returns the current devices and identity keys for the given users. + /// + /// [deviceKeys] The keys to be downloaded. A map from user ID, to a list of + /// device IDs, or to an empty list to indicate all devices for the + /// corresponding user. + /// + /// [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 { + final requestUri = Uri(path: '_matrix/client/v3/keys/query'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + '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(); + if (response.statusCode != 200) unexpectedResponse(response, responseBody); + final responseString = utf8.decode(responseBody); + final json = jsonDecode(responseString); + 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. + /// + /// [signatures] The signatures to be published. + /// + /// returns `failures`: + /// A map from user ID to key ID to an error for any signatures + /// that failed. If a signature was invalid, the `errcode` will + /// be set to `M_INVALID_SIGNATURE`. + Future>>?> + uploadCrossSigningSignatures( + Map>> signatures) 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)))))); + 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 ((v) => v != null + ? (v as Map).map((k, v) => MapEntry( + k, + (v as Map) + .map((k, v) => MapEntry(k, v as Map)))) + : null)(json['failures']); + } + + /// *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 + /// is allowed to knock on the room. Acceptance of the knock happens out of + /// band from this API, meaning that the client will have to watch for updates + /// regarding the acceptance/rejection of the knock. + /// + /// If the room history settings allow, the user will still be able to see + /// history of the room while being in the "knock" state. The user will have + /// to accept the invitation to join the room (acceptance of knock) to see + /// messages reliably. See the `/join` endpoints for more information about + /// history visibility to the user. + /// + /// The knock will appear as an entry in the response of the + /// [`/sync`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3sync) API. + /// + /// [roomIdOrAlias] The room identifier or alias to knock upon. + /// + /// [serverName] The servers to attempt to knock on the room through. One of the servers + /// must be participating in the room. + /// + /// [reason] Optional reason to be included as the `reason` on the subsequent + /// membership event. + /// + /// returns `room_id`: + /// The knocked room ID. + Future knockRoom(String roomIdOrAlias, + {List? serverName, String? reason}) async { + final requestUri = Uri( + path: '_matrix/client/v3/knock/${Uri.encodeComponent(roomIdOrAlias)}', + queryParameters: { + if (serverName != null) + 'server_name': serverName.map((v) => v).toList(), + }); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (reason != null) 'reason': reason, + })); + 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['room_id'] as String; + } + + /// Gets the homeserver's supported login types to authenticate users. Clients + /// should pick one of these and supply it as the `type` when logging in. + /// + /// returns `flows`: + /// The homeserver's supported login types + Future?> getLoginFlows() async { + final requestUri = Uri(path: '_matrix/client/v3/login'); + 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 ((v) => v != null + ? (v as List) + .map((v) => LoginFlow.fromJson(v as Map)) + .toList() + : null)(json['flows']); + } + + /// Authenticates the user, and issues an access token they can + /// use to authorize themself in subsequent requests. + /// + /// If the client does not supply a `device_id`, the server must + /// auto-generate one. + /// + /// The returned access token must be associated with the `device_id` + /// supplied by the client or generated by the server. The server may + /// 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`. + /// + /// [deviceId] ID of the client device. If this does not correspond to a + /// known client device, a new device will be created. The given + /// device ID must not be the same as a + /// [cross-signing](https://spec.matrix.org/unstable/client-server-api/#cross-signing) key ID. + /// The server will auto-generate a device_id + /// if this is not specified. + /// + /// [identifier] Identification information for a user + /// + /// [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`. + /// + /// [password] Required when `type` is `m.login.password`. The user's + /// password. + /// + /// [refreshToken] If true, the client supports refresh tokens. + /// + /// [token] Required when `type` is `m.login.token`. Part of Token-based login. + /// + /// [type] The login type being used. + /// + /// [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, + {String? address, + String? deviceId, + AuthenticationIdentifier? identifier, + String? initialDeviceDisplayName, + String? medium, + String? password, + bool? refreshToken, + String? token, + String? user}) async { + final requestUri = Uri(path: '_matrix/client/v3/login'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (address != null) 'address': address, + if (deviceId != null) 'device_id': deviceId, + if (identifier != null) 'identifier': identifier.toJson(), + if (initialDeviceDisplayName != null) + 'initial_device_display_name': initialDeviceDisplayName, + if (medium != null) 'medium': medium, + if (password != null) 'password': password, + if (refreshToken != null) 'refresh_token': refreshToken, + if (token != null) 'token': token, + 'type': type.name, + if (user != null) 'user': user, + })); + 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 LoginResponse.fromJson(json as Map); + } + + /// Invalidates an existing access token, so that it can no longer be used for + /// authorization. The device associated with the access token is also deleted. + /// [Device keys](https://spec.matrix.org/unstable/client-server-api/#device-keys) for the device are deleted alongside the device. + Future logout() async { + final requestUri = Uri(path: '_matrix/client/v3/logout'); + 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 ignore(json); + } + + /// Invalidates all access tokens for a user, so that they can no longer be used for + /// authorization. This includes the access token that made this request. All devices + /// for the user are also deleted. [Device keys](https://spec.matrix.org/unstable/client-server-api/#device-keys) for the device are + /// deleted alongside the device. + /// + /// This endpoint does not use the [User-Interactive Authentication API](https://spec.matrix.org/unstable/client-server-api/#user-interactive-authentication-api) because + /// User-Interactive Authentication is designed to protect against attacks where the + /// someone gets hold of a single access token then takes over the account. This + /// endpoint invalidates all access tokens for the user, including the token used in + /// the request, and therefore the attacker is unable to take over the account in + /// this way. + Future logoutAll() async { + final requestUri = Uri(path: '_matrix/client/v3/logout/all'); + 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 ignore(json); + } + + /// This API is used to paginate through the list of events that the + /// user has been, or would have been notified about. + /// + /// [from] Pagination token to continue from. This should be the `next_token` + /// returned from an earlier call to this endpoint. + /// + /// [limit] Limit on the number of events to return in this request. + /// + /// [only] Allows basic filtering of events returned. Supply `highlight` + /// to return only events where the notification had the highlight + /// tweak set. + Future getNotifications( + {String? from, int? limit, String? only}) async { + final requestUri = + Uri(path: '_matrix/client/v3/notifications', queryParameters: { + if (from != null) 'from': from, + if (limit != null) 'limit': limit.toString(), + if (only != null) 'only': only, + }); + 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 GetNotificationsResponse.fromJson(json as Map); + } + + /// Get the given user's presence state. + /// + /// [userId] The user whose presence state to get. + Future getPresence(String userId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/presence/${Uri.encodeComponent(userId)}/status'); + 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 GetPresenceResponse.fromJson(json as Map); + } + + /// This API sets the given user's presence state. When setting the status, + /// the activity time is updated to reflect that activity; the client does + /// not need to specify the `last_active_ago` field. You cannot set the + /// presence state of another user. + /// + /// [userId] The user whose presence state to update. + /// + /// [presence] The new presence state. + /// + /// [statusMsg] The status message to attach to this state. + Future setPresence(String userId, PresenceType presence, + {String? statusMsg}) async { + final requestUri = Uri( + path: + '_matrix/client/v3/presence/${Uri.encodeComponent(userId)}/status'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'presence': presence.name, + if (statusMsg != null) 'status_msg': statusMsg, + })); + 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 ignore(json); + } + + /// Get the combined profile information for this user. This API may be used + /// to fetch the user's own profile information or other users; either + /// locally or on remote homeservers. This API may return keys which are not + /// limited to `displayname` or `avatar_url`. + /// + /// [userId] The user whose profile information to get. + Future getUserProfile(String userId) async { + final requestUri = + Uri(path: '_matrix/client/v3/profile/${Uri.encodeComponent(userId)}'); + 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 ProfileInformation.fromJson(json as Map); + } + + /// Get the user's avatar URL. This API may be used to fetch the user's + /// own avatar URL or to query the URL of other users; either locally or + /// on remote homeservers. + /// + /// [userId] The user whose avatar URL to get. + /// + /// returns `avatar_url`: + /// The user's avatar URL if they have set one, otherwise not present. + Future getAvatarUrl(String userId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/profile/${Uri.encodeComponent(userId)}/avatar_url'); + 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 ((v) => + v != null ? Uri.parse(v as String) : null)(json['avatar_url']); + } + + /// This API sets the given user's avatar URL. You must have permission to + /// set this user's avatar URL, e.g. you need to have their `access_token`. + /// + /// [userId] The user whose avatar URL to set. + /// + /// [avatarUrl] The new avatar URL for this user. + Future setAvatarUrl(String userId, Uri? avatarUrl) async { + final requestUri = Uri( + path: + '_matrix/client/v3/profile/${Uri.encodeComponent(userId)}/avatar_url'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (avatarUrl != null) 'avatar_url': avatarUrl.toString(), + })); + 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 ignore(json); + } + + /// Get the user's display name. This API may be used to fetch the user's + /// own displayname or to query the name of other users; either locally or + /// on remote homeservers. + /// + /// [userId] The user whose display name to get. + /// + /// returns `displayname`: + /// The user's display name if they have set one, otherwise not present. + Future getDisplayName(String userId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/profile/${Uri.encodeComponent(userId)}/displayname'); + 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 ((v) => v != null ? v as String : null)(json['displayname']); + } + + /// This API sets the given user's display name. You must have permission to + /// set this user's display name, e.g. you need to have their `access_token`. + /// + /// [userId] The user whose display name to set. + /// + /// [displayname] The new display name for this user. + Future setDisplayName(String userId, String? displayname) async { + final requestUri = Uri( + path: + '_matrix/client/v3/profile/${Uri.encodeComponent(userId)}/displayname'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (displayname != null) 'displayname': displayname, + })); + 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 ignore(json); + } + + /// Lists the public rooms on the server. + /// + /// This API returns paginated responses. The rooms are ordered by the number + /// of joined members, with the largest rooms first. + /// + /// [limit] Limit the number of results returned. + /// + /// [since] A pagination token from a previous request, allowing clients to + /// get the next (or previous) batch of rooms. + /// The direction of pagination is specified solely by which token + /// is supplied, rather than via an explicit flag. + /// + /// [server] The server to fetch the public room lists from. Defaults to the + /// local server. + Future getPublicRooms( + {int? limit, String? since, String? server}) async { + final requestUri = + Uri(path: '_matrix/client/v3/publicRooms', queryParameters: { + if (limit != null) 'limit': limit.toString(), + if (since != null) 'since': since, + if (server != null) 'server': server, + }); + 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 GetPublicRoomsResponse.fromJson(json as Map); + } + + /// Lists the public rooms on the server, with optional filter. + /// + /// This API returns paginated responses. The rooms are ordered by the number + /// of joined members, with the largest rooms first. + /// + /// [server] The server to fetch the public room lists from. Defaults to the + /// local server. + /// + /// [filter] Filter to apply to the results. + /// + /// [includeAllNetworks] Whether or not to include all known networks/protocols from + /// application services on the homeserver. Defaults to false. + /// + /// [limit] Limit the number of results returned. + /// + /// [since] A pagination token from a previous request, allowing clients + /// to get the next (or previous) batch of rooms. The direction + /// 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 + /// homeserver. Can only be used if `include_all_networks` is false. + Future queryPublicRooms( + {String? server, + PublicRoomQueryFilter? filter, + bool? includeAllNetworks, + int? limit, + String? since, + String? thirdPartyInstanceId}) async { + final requestUri = + Uri(path: '_matrix/client/v3/publicRooms', queryParameters: { + if (server != null) 'server': server, + }); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (filter != null) 'filter': filter.toJson(), + if (includeAllNetworks != null) + 'include_all_networks': includeAllNetworks, + if (limit != null) 'limit': limit, + if (since != null) 'since': since, + if (thirdPartyInstanceId != null) + 'third_party_instance_id': thirdPartyInstanceId, + })); + 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 QueryPublicRoomsResponse.fromJson(json as Map); + } + + /// Gets all currently active pushers for the authenticated user. + /// + /// returns `pushers`: + /// An array containing the current pushers for the user + Future?> getPushers() async { + final requestUri = Uri(path: '_matrix/client/v3/pushers'); + 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 ((v) => v != null + ? (v as List) + .map((v) => Pusher.fromJson(v as Map)) + .toList() + : null)(json['pushers']); + } + + /// Retrieve all push rulesets for this user. Clients can "drill-down" on + /// the rulesets by suffixing a `scope` to this path e.g. + /// `/pushrules/global/`. This will return a subset of this data under the + /// specified key e.g. the `global` key. + /// + /// returns `global`: + /// The global ruleset. + Future getPushRules() async { + final requestUri = Uri(path: '_matrix/client/v3/pushrules/'); + 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 PushRuleSet.fromJson(json['global'] as Map); + } + + /// This endpoint removes the push rule defined in the path. + /// + /// [scope] `global` to specify global rules. + /// + /// [kind] The kind of rule + /// + /// + /// [ruleId] The identifier for the rule. + /// + Future deletePushRule( + String scope, PushRuleKind kind, String ruleId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}'); + final request = Request('DELETE', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + final response = await httpClient.send(request); + final responseBody = await response.stream.toBytes(); + if (response.statusCode != 200) unexpectedResponse(response, responseBody); + final responseString = utf8.decode(responseBody); + final json = jsonDecode(responseString); + return ignore(json); + } + + /// Retrieve a single specified push rule. + /// + /// [scope] `global` to specify global rules. + /// + /// [kind] The kind of rule + /// + /// + /// [ruleId] The identifier for the rule. + /// + Future getPushRule( + String scope, PushRuleKind kind, String ruleId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}'); + final request = Request('GET', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + final response = await httpClient.send(request); + final responseBody = await response.stream.toBytes(); + if (response.statusCode != 200) unexpectedResponse(response, responseBody); + final responseString = utf8.decode(responseBody); + final json = jsonDecode(responseString); + return PushRule.fromJson(json as Map); + } + + /// This endpoint allows the creation and modification of user defined push + /// rules. + /// + /// If a rule with the same `rule_id` already exists among rules of the same + /// kind, it is updated with the new parameters, otherwise a new rule is + /// created. + /// + /// If both `after` and `before` are provided, the new or updated rule must + /// be the next most important rule with respect to the rule identified by + /// `before`. + /// + /// If neither `after` nor `before` are provided and the rule is created, it + /// should be added as the most important user defined rule among rules of + /// the same kind. + /// + /// When creating push rules, they MUST be enabled by default. + /// + /// [scope] `global` to specify global rules. + /// + /// [kind] The kind of rule + /// + /// + /// [ruleId] The identifier for the rule. If the string starts with a dot ("."), + /// the request MUST be rejected as this is reserved for server-default + /// rules. Slashes ("/") and backslashes ("\\") are also not allowed. + /// + /// + /// [before] Use 'before' with a `rule_id` as its value to make the new rule the + /// next-most important rule with respect to the given user defined rule. + /// It is not possible to add a rule relative to a predefined server rule. + /// + /// [after] This makes the new rule the next-less important rule relative to the + /// given user defined rule. It is not possible to add a rule relative + /// to a predefined server rule. + /// + /// [actions] The action(s) to perform when the conditions for this rule are met. + /// + /// [conditions] The conditions that must hold true for an event in order for a + /// rule to be applied to an event. A rule with no conditions + /// always matches. Only applicable to `underride` and `override` rules. + /// + /// [pattern] Only applicable to `content` rules. The glob-style pattern to match against. + Future setPushRule( + String scope, PushRuleKind kind, String ruleId, List actions, + {String? before, + String? after, + List? conditions, + String? pattern}) async { + final requestUri = Uri( + path: + '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}', + queryParameters: { + if (before != null) 'before': before, + if (after != null) 'after': after, + }); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'actions': actions.map((v) => v).toList(), + if (conditions != null) + 'conditions': conditions.map((v) => v.toJson()).toList(), + if (pattern != null) 'pattern': pattern, + })); + 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 ignore(json); + } + + /// This endpoint get the actions for the specified push rule. + /// + /// [scope] Either `global` or `device/` to specify global + /// rules or device rules for the given `profile_tag`. + /// + /// [kind] The kind of rule + /// + /// + /// [ruleId] The identifier for the rule. + /// + /// + /// returns `actions`: + /// The action(s) to perform for this rule. + Future> getPushRuleActions( + String scope, PushRuleKind kind, String ruleId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/actions'); + final request = Request('GET', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + final response = await httpClient.send(request); + final responseBody = await response.stream.toBytes(); + if (response.statusCode != 200) unexpectedResponse(response, responseBody); + final responseString = utf8.decode(responseBody); + final json = jsonDecode(responseString); + return (json['actions'] as List).map((v) => v as Object?).toList(); + } + + /// This endpoint allows clients to change the actions of a push rule. + /// This can be used to change the actions of builtin rules. + /// + /// [scope] `global` to specify global rules. + /// + /// [kind] The kind of rule + /// + /// + /// [ruleId] The identifier for the rule. + /// + /// + /// [actions] The action(s) to perform for this rule. + Future setPushRuleActions(String scope, PushRuleKind kind, + String ruleId, List actions) async { + final requestUri = Uri( + path: + '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/actions'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'actions': actions.map((v) => v).toList(), + })); + 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 ignore(json); + } + + /// This endpoint gets whether the specified push rule is enabled. + /// + /// [scope] Either `global` or `device/` to specify global + /// rules or device rules for the given `profile_tag`. + /// + /// [kind] The kind of rule + /// + /// + /// [ruleId] The identifier for the rule. + /// + /// + /// returns `enabled`: + /// Whether the push rule is enabled or not. + Future isPushRuleEnabled( + String scope, PushRuleKind kind, String ruleId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/enabled'); + final request = Request('GET', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + final response = await httpClient.send(request); + final responseBody = await response.stream.toBytes(); + if (response.statusCode != 200) unexpectedResponse(response, responseBody); + final responseString = utf8.decode(responseBody); + final json = jsonDecode(responseString); + return json['enabled'] as bool; + } + + /// This endpoint allows clients to enable or disable the specified push rule. + /// + /// [scope] `global` to specify global rules. + /// + /// [kind] The kind of rule + /// + /// + /// [ruleId] The identifier for the rule. + /// + /// + /// [enabled] Whether the push rule is enabled or not. + Future setPushRuleEnabled( + String scope, PushRuleKind kind, String ruleId, bool enabled) async { + final requestUri = Uri( + path: + '_matrix/client/v3/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.name)}/${Uri.encodeComponent(ruleId)}/enabled'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'enabled': enabled, + })); + 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 ignore(json); + } + + /// Refresh an access token. Clients should use the returned access token + /// when making subsequent API calls, and store the returned refresh token + /// (if given) in order to refresh the new access token when necessary. + /// + /// After an access token has been refreshed, a server can choose to + /// invalidate the old access token immediately, or can choose not to, for + /// example if the access token would expire soon anyways. Clients should + /// not make any assumptions about the old access token still being valid, + /// and should use the newly provided access token instead. + /// + /// The old refresh token remains valid until the new access token or refresh token + /// is used, at which point the old refresh token is revoked. + /// + /// Note that this endpoint does not require authentication via an + /// access token. Authentication is provided via the refresh token. + /// + /// Application Service identity assertion is disabled for this endpoint. + /// + /// [refreshToken] The refresh token + Future refresh(String refreshToken) async { + final requestUri = Uri(path: '_matrix/client/v3/refresh'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'refresh_token': refreshToken, + })); + 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 RefreshResponse.fromJson(json as Map); + } + + /// This API endpoint uses the [User-Interactive Authentication API](https://spec.matrix.org/unstable/client-server-api/#user-interactive-authentication-api), except in + /// the cases where a guest account is being registered. + /// + /// Register for an account on this homeserver. + /// + /// There are two kinds of user account: + /// + /// - `user` accounts. These accounts may use the full API described in this specification. + /// + /// - `guest` accounts. These accounts may have limited permissions and may not be supported by all servers. + /// + /// If registration is successful, this endpoint will issue an access token + /// the client can use to authorize itself in subsequent requests. + /// + /// If the client does not supply a `device_id`, the server must + /// auto-generate one. + /// + /// 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 + /// `M_INVALID_USERNAME`. + /// + /// Matrix clients MUST NOT assume that localpart of the registered + /// `user_id` matches the provided `username`. + /// + /// The returned access token must be associated with the `device_id` + /// supplied by the client or generated by the server. The server may + /// 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). + /// + /// When registering a guest account, all parameters in the request body + /// with the exception of `initial_device_display_name` MUST BE ignored + /// by the server. The server MUST pick a `device_id` for the account + /// regardless of input. + /// + /// Any user ID returned by this API must conform to the grammar given in the + /// [Matrix specification](https://spec.matrix.org/unstable/appendices/#user-identifiers). + /// + /// [kind] The kind of account to register. Defaults to `user`. + /// + /// [auth] Additional authentication information for the + /// user-interactive authentication API. Note that this + /// information is *not* used to define how the registered user + /// should be authenticated, but is instead used to + /// authenticate the `register` call itself. + /// + /// [deviceId] ID of the client device. If this does not correspond to a + /// known client device, a new device will be created. The server + /// will auto-generate a device_id if this is not specified. + /// + /// [inhibitLogin] If true, an `access_token` and `device_id` should not be + /// returned from this call, therefore preventing an automatic + /// login. Defaults to false. + /// + /// [initialDeviceDisplayName] A display name to assign to the newly-created device. Ignored + /// if `device_id` corresponds to a known device. + /// + /// [password] The desired password for the account. + /// + /// [refreshToken] If true, the client supports refresh tokens. + /// + /// [username] The basis for the localpart of the desired Matrix ID. If omitted, + /// the homeserver MUST generate a Matrix ID local part. + Future register( + {AccountKind? kind, + AuthenticationData? auth, + String? deviceId, + bool? inhibitLogin, + String? initialDeviceDisplayName, + String? password, + bool? refreshToken, + String? username}) async { + final requestUri = + Uri(path: '_matrix/client/v3/register', queryParameters: { + if (kind != null) 'kind': kind.name, + }); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (auth != null) 'auth': auth.toJson(), + if (deviceId != null) 'device_id': deviceId, + if (inhibitLogin != null) 'inhibit_login': inhibitLogin, + if (initialDeviceDisplayName != null) + 'initial_device_display_name': initialDeviceDisplayName, + if (password != null) 'password': password, + if (refreshToken != null) 'refresh_token': refreshToken, + if (username != null) 'username': username, + })); + 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 RegisterResponse.fromJson(json as Map); + } + + /// Checks to see if a username is available, and valid, for the server. + /// + /// The server should check to ensure that, at the time of the request, the + /// username requested is available for use. This includes verifying that an + /// application service has not claimed the username and that the username + /// fits the server's desired requirements (for example, a server could dictate + /// that it does not permit usernames with underscores). + /// + /// Matrix clients may wish to use this API prior to attempting registration, + /// however the clients must also be aware that using this API does not normally + /// reserve the username. This can mean that the username becomes unavailable + /// between checking its availability and attempting to register it. + /// + /// [username] The username to check the availability of. + /// + /// returns `available`: + /// A flag to indicate that the username is available. This should always + /// be `true` when the server replies with 200 OK. + Future checkUsernameAvailability(String username) async { + final requestUri = + Uri(path: '_matrix/client/v3/register/available', queryParameters: { + 'username': username, + }); + 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 ((v) => v != null ? v as bool : null)(json['available']); + } + + /// The homeserver must check that the given email address is **not** + /// already associated with an account on this homeserver. The homeserver + /// should validate the email itself, either by sending a validation email + /// itself or by using a service it has control over. + /// + /// [clientSecret] A unique string generated by the client, and used to identify the + /// validation attempt. It must be a string consisting of the characters + /// `[0-9a-zA-Z.=_-]`. Its length must not exceed 255 characters and it + /// must not be empty. + /// + /// + /// [email] The email address to validate. + /// + /// [nextLink] Optional. When the validation is completed, the identity server will + /// redirect the user to this URL. This option is ignored when submitting + /// 3PID validation information through a POST request. + /// + /// [sendAttempt] The server will only send an email if the `send_attempt` + /// is a number greater than the most recent one which it has seen, + /// scoped to that `email` + `client_secret` pair. This is to + /// avoid repeatedly sending the same email in the case of request + /// retries between the POSTing user and the identity server. + /// The client should increment this value if they desire a new + /// email (e.g. a reminder) to be sent. If they do not, the server + /// should respond with success but not resend the email. + /// + /// [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. + /// + /// Required if an `id_server` is supplied. + /// + /// [idServer] The hostname of the identity server to communicate with. May optionally + /// include a port. This parameter is ignored when the homeserver handles + /// 3PID verification. + /// + /// This parameter is deprecated with a plan to be removed in a future specification + /// version for `/account/password` and `/register` requests. + Future requestTokenToRegisterEmail( + String clientSecret, String email, int sendAttempt, + {String? nextLink, String? idAccessToken, String? idServer}) async { + final requestUri = + Uri(path: '_matrix/client/v3/register/email/requestToken'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'client_secret': clientSecret, + 'email': email, + if (nextLink != null) 'next_link': nextLink, + 'send_attempt': sendAttempt, + if (idAccessToken != null) 'id_access_token': idAccessToken, + if (idServer != null) 'id_server': idServer, + })); + 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 RequestTokenResponse.fromJson(json as Map); + } + + /// The homeserver must check that the given phone number is **not** + /// already associated with an account on this homeserver. The homeserver + /// should validate the phone number itself, either by sending a validation + /// message itself or by using a service it has control over. + /// + /// [clientSecret] A unique string generated by the client, and used to identify the + /// validation attempt. It must be a string consisting of the characters + /// `[0-9a-zA-Z.=_-]`. Its length must not exceed 255 characters and it + /// must not be empty. + /// + /// + /// [country] The two-letter uppercase ISO-3166-1 alpha-2 country code that the + /// number in `phone_number` should be parsed as if it were dialled from. + /// + /// [nextLink] Optional. When the validation is completed, the identity server will + /// redirect the user to this URL. This option is ignored when submitting + /// 3PID validation information through a POST request. + /// + /// [phoneNumber] The phone number to validate. + /// + /// [sendAttempt] The server will only send an SMS if the `send_attempt` is a + /// number greater than the most recent one which it has seen, + /// scoped to that `country` + `phone_number` + `client_secret` + /// triple. This is to avoid repeatedly sending the same SMS in + /// the case of request retries between the POSTing user and the + /// identity server. The client should increment this value if + /// they desire a new SMS (e.g. a reminder) to be sent. + /// + /// [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. + /// + /// Required if an `id_server` is supplied. + /// + /// [idServer] The hostname of the identity server to communicate with. May optionally + /// include a port. This parameter is ignored when the homeserver handles + /// 3PID verification. + /// + /// This parameter is deprecated with a plan to be removed in a future specification + /// version for `/account/password` and `/register` requests. + Future requestTokenToRegisterMSISDN( + String clientSecret, String country, String phoneNumber, int sendAttempt, + {String? nextLink, String? idAccessToken, String? idServer}) async { + final requestUri = + Uri(path: '_matrix/client/v3/register/msisdn/requestToken'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'client_secret': clientSecret, + 'country': country, + if (nextLink != null) 'next_link': nextLink, + 'phone_number': phoneNumber, + 'send_attempt': sendAttempt, + if (idAccessToken != null) 'id_access_token': idAccessToken, + if (idServer != null) 'id_server': idServer, + })); + 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 RequestTokenResponse.fromJson(json as Map); + } + + /// Delete the keys from the backup. + /// + /// [version] The backup from which to delete the key + Future deleteRoomKeys(String version) async { + final requestUri = + Uri(path: '_matrix/client/v3/room_keys/keys', queryParameters: { + 'version': version, + }); + final request = Request('DELETE', 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 RoomKeysUpdateResponse.fromJson(json as Map); + } + + /// Retrieve the keys from the backup. + /// + /// [version] The backup from which to retrieve the keys. + Future getRoomKeys(String version) async { + final requestUri = + Uri(path: '_matrix/client/v3/room_keys/keys', queryParameters: { + 'version': version, + }); + 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 RoomKeys.fromJson(json as Map); + } + + /// Store several keys in the backup. + /// + /// [version] The backup in which to store the keys. Must be the current backup. + /// + /// [backupData] The backup data. + Future putRoomKeys( + String version, RoomKeys backupData) async { + final requestUri = + Uri(path: '_matrix/client/v3/room_keys/keys', queryParameters: { + 'version': version, + }); + 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())); + 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 RoomKeysUpdateResponse.fromJson(json as Map); + } + + /// Delete the keys from the backup for a given room. + /// + /// [roomId] The ID of the room that the specified key is for. + /// + /// [version] The backup from which to delete the key. + Future deleteRoomKeysByRoomId( + String roomId, String version) async { + final requestUri = Uri( + path: '_matrix/client/v3/room_keys/keys/${Uri.encodeComponent(roomId)}', + queryParameters: { + 'version': version, + }); + final request = Request('DELETE', 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 RoomKeysUpdateResponse.fromJson(json as Map); + } + + /// Retrieve the keys from the backup for a given room. + /// + /// [roomId] The ID of the room that the requested key is for. + /// + /// [version] The backup from which to retrieve the key. + Future getRoomKeysByRoomId( + String roomId, String version) async { + final requestUri = Uri( + path: '_matrix/client/v3/room_keys/keys/${Uri.encodeComponent(roomId)}', + queryParameters: { + 'version': version, + }); + 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 RoomKeyBackup.fromJson(json as Map); + } + + /// Store several keys in the backup for a given room. + /// + /// [roomId] The ID of the room that the keys are for. + /// + /// [version] The backup in which to store the keys. Must be the current backup. + /// + /// [backupData] The backup data + Future putRoomKeysByRoomId( + String roomId, String version, RoomKeyBackup backupData) async { + final requestUri = Uri( + path: '_matrix/client/v3/room_keys/keys/${Uri.encodeComponent(roomId)}', + queryParameters: { + 'version': version, + }); + 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())); + 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 RoomKeysUpdateResponse.fromJson(json as Map); + } + + /// Delete a key from the backup. + /// + /// [roomId] The ID of the room that the specified key is for. + /// + /// [sessionId] The ID of the megolm session whose key is to be deleted. + /// + /// [version] The backup from which to delete the key + Future deleteRoomKeyBySessionId( + String roomId, String sessionId, String version) async { + final requestUri = Uri( + path: + '_matrix/client/v3/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}', + queryParameters: { + 'version': version, + }); + final request = Request('DELETE', 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 RoomKeysUpdateResponse.fromJson(json as Map); + } + + /// Retrieve a key from the backup. + /// + /// [roomId] The ID of the room that the requested key is for. + /// + /// [sessionId] The ID of the megolm session whose key is requested. + /// + /// [version] The backup from which to retrieve the key. + Future getRoomKeyBySessionId( + String roomId, String sessionId, String version) async { + final requestUri = Uri( + path: + '_matrix/client/v3/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}', + queryParameters: { + 'version': version, + }); + 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 KeyBackupData.fromJson(json as Map); + } + + /// Store a key in the backup. + /// + /// [roomId] The ID of the room that the key is for. + /// + /// [sessionId] The ID of the megolm session that the key is for. + /// + /// [version] The backup in which to store the key. Must be the current backup. + /// + /// [data] The key data. + Future putRoomKeyBySessionId(String roomId, + String sessionId, String version, KeyBackupData data) async { + final requestUri = Uri( + path: + '_matrix/client/v3/room_keys/keys/${Uri.encodeComponent(roomId)}/${Uri.encodeComponent(sessionId)}', + queryParameters: { + 'version': version, + }); + 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())); + 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 RoomKeysUpdateResponse.fromJson(json as Map); + } + + /// Get information about the latest backup version. + Future getRoomKeysVersionCurrent() async { + final requestUri = Uri(path: '_matrix/client/v3/room_keys/version'); + 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 GetRoomKeysVersionCurrentResponse.fromJson( + json as Map); + } + + /// Creates a new backup. + /// + /// [algorithm] The algorithm used for storing backups. + /// + /// [authData] Algorithm-dependent data. See the documentation for the backup + /// algorithms in [Server-side key backups](https://spec.matrix.org/unstable/client-server-api/#server-side-key-backups) for more information on the + /// expected format of the data. + /// + /// returns `version`: + /// The backup version. This is an opaque string. + Future postRoomKeysVersion( + BackupAlgorithm algorithm, Map authData) async { + final requestUri = Uri(path: '_matrix/client/v3/room_keys/version'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'algorithm': algorithm.name, + 'auth_data': authData, + })); + 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['version'] as String; + } + + /// Delete an existing key backup. Both the information about the backup, + /// as well as all key data related to the backup will be deleted. + /// + /// [version] The backup version to delete, as returned in the `version` + /// parameter in the response of + /// [`POST /_matrix/client/v3/room_keys/version`](https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3room_keysversion) + /// or [`GET /_matrix/client/v3/room_keys/version/{version}`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3room_keysversionversion). + Future deleteRoomKeysVersion(String version) async { + final requestUri = Uri( + path: + '_matrix/client/v3/room_keys/version/${Uri.encodeComponent(version)}'); + final request = Request('DELETE', 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 ignore(json); + } + + /// Get information about an existing backup. + /// + /// [version] The backup version to get, as returned in the `version` parameter + /// of the response in + /// [`POST /_matrix/client/v3/room_keys/version`](https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3room_keysversion) + /// or this endpoint. + Future getRoomKeysVersion(String version) async { + final requestUri = Uri( + path: + '_matrix/client/v3/room_keys/version/${Uri.encodeComponent(version)}'); + 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 GetRoomKeysVersionResponse.fromJson(json as Map); + } + + /// Update information about an existing backup. Only `auth_data` can be modified. + /// + /// [version] The backup version to update, as returned in the `version` + /// parameter in the response of + /// [`POST /_matrix/client/v3/room_keys/version`](https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3room_keysversion) + /// or [`GET /_matrix/client/v3/room_keys/version/{version}`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3room_keysversionversion). + /// + /// [algorithm] The algorithm used for storing backups. Must be the same as + /// the algorithm currently used by the backup. + /// + /// [authData] Algorithm-dependent data. See the documentation for the backup + /// algorithms in [Server-side key backups](https://spec.matrix.org/unstable/client-server-api/#server-side-key-backups) for more information on the + /// expected format of the data. + Future> putRoomKeysVersion(String version, + BackupAlgorithm algorithm, Map authData) async { + final requestUri = Uri( + path: + '_matrix/client/v3/room_keys/version/${Uri.encodeComponent(version)}'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'algorithm': algorithm.name, + 'auth_data': authData, + })); + 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; + } + + /// Get a list of aliases maintained by the local server for the + /// given room. + /// + /// This endpoint can be called by users who are in the room (external + /// users receive an `M_FORBIDDEN` error response). If the room's + /// `m.room.history_visibility` maps to `world_readable`, any + /// user can call this endpoint. + /// + /// Servers may choose to implement additional access control checks here, + /// such as allowing server administrators to view aliases regardless of + /// membership. + /// + /// **Note:** + /// Clients are recommended not to display this list of aliases prominently + /// as they are not curated, unlike those listed in the `m.room.canonical_alias` + /// state event. + /// + /// [roomId] The room ID to find local aliases of. + /// + /// returns `aliases`: + /// The server's local aliases on the room. Can be empty. + Future> getLocalAliases(String roomId) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/aliases'); + 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 (json['aliases'] as List).map((v) => v as String).toList(); + } + + /// Ban a user in the room. If the user is currently in the room, also kick them. + /// + /// When a user is banned from a room, they may not join it or be invited to it until they are unbanned. + /// + /// The caller must have the required power level in order to perform this operation. + /// + /// [roomId] The room identifier (not alias) from which the user should be banned. + /// + /// [reason] The reason the user has been banned. This will be supplied as the `reason` on the target's updated [`m.room.member`](https://spec.matrix.org/unstable/client-server-api/#mroommember) event. + /// + /// [userId] The fully qualified user ID of the user being banned. + Future ban(String roomId, String userId, {String? reason}) async { + final requestUri = + Uri(path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/ban'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (reason != null) 'reason': reason, + 'user_id': userId, + })); + 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 ignore(json); + } + + /// This API returns a number of events that happened just before and + /// after the specified event. This allows clients to get the context + /// surrounding an event. + /// + /// *Note*: This endpoint supports lazy-loading of room member events. See + /// [Lazy-loading room members](https://spec.matrix.org/unstable/client-server-api/#lazy-loading-room-members) for more information. + /// + /// [roomId] The room to get events from. + /// + /// [eventId] The event to get context around. + /// + /// [limit] The maximum number of context events to return. The limit applies + /// to the sum of the `events_before` and `events_after` arrays. The + /// requested event ID is always returned in `event` even if `limit` is + /// 0. Defaults to 10. + /// + /// [filter] A JSON `RoomEventFilter` to filter the returned events with. The + /// filter is only applied to `events_before`, `events_after`, and + /// `state`. It is not applied to the `event` itself. The filter may + /// be applied before or/and after the `limit` parameter - whichever the + /// homeserver prefers. + /// + /// See [Filtering](https://spec.matrix.org/unstable/client-server-api/#filtering) for more information. + Future getEventContext(String roomId, String eventId, + {int? limit, String? filter}) async { + final requestUri = Uri( + path: + '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/context/${Uri.encodeComponent(eventId)}', + queryParameters: { + if (limit != null) 'limit': limit.toString(), + if (filter != null) 'filter': filter, + }); + 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 EventContext.fromJson(json as Map); + } + + /// Get a single event based on `roomId/eventId`. You must have permission to + /// retrieve this event e.g. by being a member in the room for this event. + /// + /// [roomId] The ID of the room the event is in. + /// + /// [eventId] The event ID to get. + Future getOneRoomEvent(String roomId, String eventId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/event/${Uri.encodeComponent(eventId)}'); + 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 MatrixEvent.fromJson(json as Map); + } + + /// This API stops a user remembering about a particular room. + /// + /// In general, history is a first class citizen in Matrix. After this API + /// is called, however, a user will no longer be able to retrieve history + /// for this room. If all users on a homeserver forget a room, the room is + /// eligible for deletion from that homeserver. + /// + /// If the user is currently joined to the room, they must leave the room + /// before calling this API. + /// + /// [roomId] The room identifier to forget. + Future forgetRoom(String roomId) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/forget'); + 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 ignore(json); + } + + /// *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. + /// The homeserver uses an identity server to perform the mapping from + /// 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. + /// They do not start participating in the room until they actually join the + /// room. + /// + /// Only users currently in a particular room can invite other users to + /// 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` + /// 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 + /// 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 + /// user ID, the identity server will give the user a list of pending + /// invitations, each containing: + /// + /// - The room ID to which they were invited + /// + /// - The token given to the homeserver + /// + /// - A signature of the token, signed with the identity server's private key + /// + /// - The matrix user ID who invited them to the room + /// + /// If a token is requested from the identity server, the homeserver will + /// append a `m.room.third_party_invite` event to the room. + /// + /// [roomId] The room identifier (not alias) to which to invite the user. + /// + /// [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. + /// + /// [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)). + Future inviteBy3PID(String roomId, String address, String idAccessToken, + String idServer, String medium) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/invite'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'address': address, + 'id_access_token': idAccessToken, + 'id_server': idServer, + 'medium': medium, + })); + 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 ignore(json); + } + + /// *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.* + /// + /// This API invites a user to participate in a particular room. + /// They do not start participating in the room until they actually join the + /// room. + /// + /// Only users currently in a particular room can invite other users to + /// join that room. + /// + /// If the user was invited to the room, the homeserver will append a + /// `m.room.member` event to the room. + /// + /// [roomId] The room identifier (not alias) to which to invite the user. + /// + /// [reason] Optional reason to be included as the `reason` on the subsequent + /// membership event. + /// + /// [userId] The fully qualified user ID of the invitee. + Future inviteUser(String roomId, String userId, + {String? reason}) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/invite'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (reason != null) 'reason': reason, + 'user_id': userId, + })); + 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 ignore(json); + } + + /// *Note that this API requires a room ID, not alias.* + /// `/join/{roomIdOrAlias}` *exists if you have a room alias.* + /// + /// This API starts a user participating in a particular room, if that user + /// is allowed to participate in that room. After this call, the client is + /// allowed to see all current state events in the room, and all subsequent + /// events associated with the room until the user leaves the room. + /// + /// After a user has joined a room, the room will appear as an entry in the + /// response of the [`/initialSync`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3initialsync) + /// and [`/sync`](https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3sync) APIs. + /// + /// [roomId] The room identifier (not alias) to join. + /// + /// [reason] Optional reason to be included as the `reason` on the subsequent + /// membership event. + /// + /// [thirdPartySigned] If supplied, the homeserver must verify that it matches a pending + /// `m.room.third_party_invite` event in the room, and perform + /// key validity checking if required by the event. + /// + /// returns `room_id`: + /// The joined room ID. + Future joinRoomById(String roomId, + {String? reason, ThirdPartySigned? thirdPartySigned}) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/join'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (reason != null) 'reason': reason, + if (thirdPartySigned != null) + 'third_party_signed': thirdPartySigned.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 json['room_id'] as String; + } + + /// This API returns a map of MXIDs to member info objects for members of the room. The current user must be in the room for it to work, unless it is an Application Service in which case any of the AS's users must be in the room. This API is primarily for Application Services and should be faster to respond than `/members` as it can be implemented more efficiently on the server. + /// + /// [roomId] The room to get the members of. + /// + /// returns `joined`: + /// A map from user ID to a RoomMember object. + Future?> getJoinedMembersByRoom(String roomId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/joined_members'); + 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 ((v) => v != null + ? (v as Map).map((k, v) => + MapEntry(k, RoomMember.fromJson(v as Map))) + : null)(json['joined']); + } + + /// Kick a user from the room. + /// + /// The caller must have the required power level in order to perform this operation. + /// + /// Kicking a user adjusts the target member's membership state to be `leave` with an + /// optional `reason`. Like with other membership changes, a user can directly adjust + /// the target member's state by making a request to `/rooms//state/m.room.member/`. + /// + /// [roomId] The room identifier (not alias) from which the user should be kicked. + /// + /// [reason] The reason the user has been kicked. This will be supplied as the + /// `reason` on the target's updated [`m.room.member`](https://spec.matrix.org/unstable/client-server-api/#mroommember) event. + /// + /// [userId] The fully qualified user ID of the user being kicked. + Future kick(String roomId, String userId, {String? reason}) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/kick'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (reason != null) 'reason': reason, + 'user_id': userId, + })); + 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 ignore(json); + } + + /// This API stops a user participating in a particular room. + /// + /// If the user was already in the room, they will no longer be able to see + /// new events in the room. If the room requires an invite to join, they + /// will need to be re-invited before they can re-join. + /// + /// If the user was invited to the room, but had not joined, this call + /// serves to reject the invite. + /// + /// The user will still be allowed to retrieve history from the room which + /// they were previously allowed to see. + /// + /// [roomId] The room identifier to leave. + /// + /// [reason] Optional reason to be included as the `reason` on the subsequent + /// membership event. + Future leaveRoom(String roomId, {String? reason}) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/leave'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (reason != null) 'reason': reason, + })); + 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 ignore(json); + } + + /// Get the list of members for this room. + /// + /// [roomId] The room to get the member events for. + /// + /// [at] The point in time (pagination token) to return members for in the room. + /// This token can be obtained from a `prev_batch` token returned for + /// each room by the sync API. Defaults to the current state of the room, + /// as determined by the server. + /// + /// [membership] The kind of membership to filter for. Defaults to no filtering if + /// unspecified. When specified alongside `not_membership`, the two + /// parameters create an 'or' condition: either the membership *is* + /// the same as `membership` **or** *is not* the same as `not_membership`. + /// + /// [notMembership] The kind of membership to exclude from the results. Defaults to no + /// filtering if unspecified. + /// + /// returns `chunk`: + /// + Future?> getMembersByRoom(String roomId, + {String? at, Membership? membership, Membership? notMembership}) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/members', + queryParameters: { + if (at != null) 'at': at, + if (membership != null) 'membership': membership.name, + if (notMembership != null) 'not_membership': notMembership.name, + }); + 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 ((v) => v != null + ? (v as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList() + : null)(json['chunk']); + } + + /// This API returns a list of message and state events for a room. It uses + /// pagination query parameters to paginate history in the room. + /// + /// *Note*: This endpoint supports lazy-loading of room member events. See + /// [Lazy-loading room members](https://spec.matrix.org/unstable/client-server-api/#lazy-loading-room-members) for more information. + /// + /// [roomId] The room to get events from. + /// + /// [from] The token to start returning events from. This token can be obtained + /// from a `prev_batch` or `next_batch` token returned by the `/sync` endpoint, + /// or from an `end` token returned by a previous request to this endpoint. + /// + /// This endpoint can also accept a value returned as a `start` token + /// by a previous request to this endpoint, though servers are not + /// required to support this. Clients should not rely on the behaviour. + /// + /// If it is not provided, the homeserver shall return a list of messages + /// from the first or last (per the value of the `dir` parameter) visible + /// event in the room history for the requesting user. + /// + /// [to] The token to stop returning events at. This token can be obtained from + /// a `prev_batch` or `next_batch` token returned by the `/sync` endpoint, + /// or from an `end` token returned by a previous request to this endpoint. + /// + /// [dir] The direction to return events from. If this is set to `f`, events + /// 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`. + /// + /// [limit] The maximum number of events to return. Default: 10. + /// + /// [filter] A JSON RoomEventFilter to filter returned events with. + Future getRoomEvents(String roomId, Direction dir, + {String? from, String? to, int? limit, String? filter}) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/messages', + queryParameters: { + if (from != null) 'from': from, + if (to != null) 'to': to, + 'dir': dir.name, + if (limit != null) 'limit': limit.toString(), + if (filter != null) 'filter': filter, + }); + 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 GetRoomEventsResponse.fromJson(json as Map); + } + + /// Sets the position of the read marker for a given room, and optionally + /// the read receipt's location. + /// + /// [roomId] The room ID to set the read marker in for the user. + /// + /// [mFullyRead] The event ID the read marker should be located at. The + /// event MUST belong to the room. + /// + /// [mRead] The event ID to set the read receipt location at. This is + /// equivalent to calling `/receipt/m.read/$elsewhere:example.org` + /// and is provided here to save that extra call. + /// + /// [mReadPrivate] The event ID to set the *private* read receipt location at. This + /// equivalent to calling `/receipt/m.read.private/$elsewhere:example.org` + /// and is provided here to save that extra call. + Future setReadMarker(String roomId, + {String? mFullyRead, String? mRead, String? mReadPrivate}) async { + final requestUri = Uri( + path: + '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/read_markers'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (mFullyRead != null) 'm.fully_read': mFullyRead, + if (mRead != null) 'm.read': mRead, + if (mReadPrivate != null) 'm.read.private': mReadPrivate, + })); + 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 ignore(json); + } + + /// This API updates the marker for the given receipt type to the event ID + /// specified. + /// + /// [roomId] The room in which to send the event. + /// + /// [receiptType] The type of receipt to send. This can also be `m.fully_read` as an + /// alternative to [`/read_markers`](https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3roomsroomidread_markers). + /// + /// Note that `m.fully_read` does not appear under `m.receipt`: this endpoint + /// effectively calls `/read_markers` internally when presented with a receipt + /// type of `m.fully_read`. + /// + /// [eventId] The event ID to acknowledge up to. + /// + /// [threadId] The root thread event's ID (or `main`) for which + /// thread this receipt is intended to be under. If + /// not specified, the read receipt is *unthreaded* + /// (default). + Future postReceipt( + String roomId, ReceiptType receiptType, String eventId, + {String? threadId}) async { + final requestUri = Uri( + path: + '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/receipt/${Uri.encodeComponent(receiptType.name)}/${Uri.encodeComponent(eventId)}'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (threadId != null) 'thread_id': threadId, + })); + 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 ignore(json); + } + + /// Strips all information out of an event which isn't critical to the + /// integrity of the server-side representation of the room. + /// + /// This cannot be undone. + /// + /// Any user with a power level greater than or equal to the `m.room.redaction` + /// event power level may send redaction events in the room. If the user's power + /// level greater is also greater than or equal to the `redact` power level + /// of the room, the user may redact events sent by other users. + /// + /// Server administrators may redact events sent by users on their server. + /// + /// [roomId] The room from which to redact the event. + /// + /// [eventId] The ID of the event to redact + /// + /// [txnId] The [transaction ID](https://spec.matrix.org/unstable/client-server-api/#transaction-identifiers) for this event. Clients should generate a + /// unique ID; it will be used by the server to ensure idempotency of requests. + /// + /// [reason] The reason for the event being redacted. + /// + /// returns `event_id`: + /// A unique identifier for the event. + Future redactEvent(String roomId, String eventId, String txnId, + {String? reason}) async { + final requestUri = Uri( + path: + '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/redact/${Uri.encodeComponent(eventId)}/${Uri.encodeComponent(txnId)}'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (reason != null) 'reason': reason, + })); + 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 ((v) => v != null ? v as String : null)(json['event_id']); + } + + /// Reports an event as inappropriate to the server, which may then notify + /// the appropriate people. + /// + /// [roomId] The room in which the event being reported is located. + /// + /// [eventId] The event to report. + /// + /// [reason] The reason the content is being reported. May be blank. + /// + /// [score] The score to rate this content as where -100 is most offensive + /// and 0 is inoffensive. + Future reportContent(String roomId, String eventId, + {String? reason, int? score}) async { + final requestUri = Uri( + path: + '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/report/${Uri.encodeComponent(eventId)}'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (reason != null) 'reason': reason, + if (score != null) 'score': score, + })); + 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 ignore(json); + } + + /// This endpoint is used to send a message event to a room. Message events + /// allow access to historical events and pagination, making them suited + /// for "once-off" activity in a room. + /// + /// The body of the request should be the content object of the event; the + /// fields in this object will vary depending on the type of event. See + /// [Room Events](https://spec.matrix.org/unstable/client-server-api/#room-events) for the m. event specification. + /// + /// [roomId] The room to send the event to. + /// + /// [eventType] The type of event to send. + /// + /// [txnId] The [transaction ID](https://spec.matrix.org/unstable/client-server-api/#transaction-identifiers) for this event. Clients should generate an + /// ID unique across requests with the same access token; it will be + /// used by the server to ensure idempotency of requests. + /// + /// [body] + /// + /// returns `event_id`: + /// A unique identifier for the event. + Future sendMessage(String roomId, String eventType, String txnId, + Map body) async { + final requestUri = Uri( + path: + '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/send/${Uri.encodeComponent(eventType)}/${Uri.encodeComponent(txnId)}'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + 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); + final responseString = utf8.decode(responseBody); + final json = jsonDecode(responseString); + return json['event_id'] as String; + } + + /// Get the state events for the current state of a room. + /// + /// [roomId] The room to look up the state for. + Future> getRoomState(String roomId) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/state'); + 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 (json as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList(); + } + + /// Looks up the contents of a state event in a room. If the user is + /// joined to the room then the state is taken from the current + /// state of the room. If the user has left the room then the state is + /// taken from the state of the room when they left. + /// + /// [roomId] The room to look up the state in. + /// + /// [eventType] The type of state to look up. + /// + /// [stateKey] The key of the state to look up. Defaults to an empty string. When + /// an empty string, the trailing slash on this endpoint is optional. + Future> getRoomStateWithKey( + String roomId, String eventType, String stateKey) async { + final requestUri = Uri( + path: + '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/state/${Uri.encodeComponent(eventType)}/${Uri.encodeComponent(stateKey)}'); + 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 json as Map; + } + + /// State events can be sent using this endpoint. These events will be + /// overwritten if ``, `` and `` all + /// match. + /// + /// Requests to this endpoint **cannot use transaction IDs** + /// like other `PUT` paths because they cannot be differentiated from the + /// `state_key`. Furthermore, `POST` is unsupported on state paths. + /// + /// The body of the request should be the content object of the event; the + /// fields in this object will vary depending on the type of event. See + /// [Room Events](https://spec.matrix.org/unstable/client-server-api/#room-events) for the `m.` event specification. + /// + /// If the event type being sent is `m.room.canonical_alias` servers + /// SHOULD ensure that any new aliases being listed in the event are valid + /// per their grammar/syntax and that they point to the room ID where the + /// state event is to be sent. Servers do not validate aliases which are + /// being removed or are already present in the state event. + /// + /// + /// [roomId] The room to set the state in + /// + /// [eventType] The type of event to send. + /// + /// [stateKey] The state_key for the state to send. Defaults to the empty string. When + /// an empty string, the trailing slash on this endpoint is optional. + /// + /// [body] + /// + /// returns `event_id`: + /// A unique identifier for the event. + Future setRoomStateWithKey(String roomId, String eventType, + String stateKey, Map body) async { + final requestUri = Uri( + path: + '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/state/${Uri.encodeComponent(eventType)}/${Uri.encodeComponent(stateKey)}'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + 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); + final responseString = utf8.decode(responseBody); + final json = jsonDecode(responseString); + return json['event_id'] as String; + } + + /// This tells the server that the user is typing for the next N + /// milliseconds where N is the value specified in the `timeout` key. + /// Alternatively, if `typing` is `false`, it tells the server that the + /// user has stopped typing. + /// + /// [userId] The user who has started to type. + /// + /// [roomId] The room in which the user is typing. + /// + /// [timeout] The length of time in milliseconds to mark this user as typing. + /// + /// [typing] Whether the user is typing or not. If `false`, the `timeout` + /// key can be omitted. + Future setTyping(String userId, String roomId, bool typing, + {int? timeout}) async { + final requestUri = Uri( + path: + '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/typing/${Uri.encodeComponent(userId)}'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (timeout != null) 'timeout': timeout, + 'typing': typing, + })); + 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 ignore(json); + } + + /// Unban a user from the room. This allows them to be invited to the room, + /// and join if they would otherwise be allowed to join according to its join rules. + /// + /// The caller must have the required power level in order to perform this operation. + /// + /// [roomId] The room identifier (not alias) from which the user should be unbanned. + /// + /// [reason] Optional reason to be included as the `reason` on the subsequent + /// membership event. + /// + /// [userId] The fully qualified user ID of the user being unbanned. + Future unban(String roomId, String userId, {String? reason}) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/unban'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (reason != null) 'reason': reason, + 'user_id': userId, + })); + 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 ignore(json); + } + + /// Upgrades the given room to a particular room version. + /// + /// [roomId] The ID of the room to upgrade. + /// + /// [newVersion] The new version for the room. + /// + /// returns `replacement_room`: + /// The ID of the new room. + Future upgradeRoom(String roomId, String newVersion) async { + final requestUri = Uri( + path: '_matrix/client/v3/rooms/${Uri.encodeComponent(roomId)}/upgrade'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'new_version': newVersion, + })); + 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['replacement_room'] as String; + } + + /// Performs a full text search across different categories. + /// + /// [nextBatch] The point to return events from. If given, this should be a + /// `next_batch` result from a previous call to this endpoint. + /// + /// [searchCategories] Describes which categories to search in and their criteria. + Future search(Categories searchCategories, + {String? nextBatch}) async { + final requestUri = Uri(path: '_matrix/client/v3/search', queryParameters: { + if (nextBatch != null) 'next_batch': nextBatch, + }); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'search_categories': searchCategories.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 SearchResults.fromJson(json as Map); + } + + /// This endpoint is used to send send-to-device events to a set of + /// client devices. + /// + /// [eventType] The type of event to send. + /// + /// [txnId] The [transaction ID](https://spec.matrix.org/unstable/client-server-api/#transaction-identifiers) for this event. Clients should generate an + /// ID unique across requests with the same access token; it will be + /// used by the server to ensure idempotency of requests. + /// + /// [messages] The messages to send. A map from user ID, to a map from + /// device ID to message body. The device ID may also be `*`, + /// meaning all known devices for the user. + Future sendToDevice(String eventType, String txnId, + Map>> messages) async { + final requestUri = Uri( + path: + '_matrix/client/v3/sendToDevice/${Uri.encodeComponent(eventType)}/${Uri.encodeComponent(txnId)}'); + final request = Request('PUT', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'messages': + messages.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); + final responseString = utf8.decode(responseBody); + final json = jsonDecode(responseString); + return ignore(json); + } + + /// Synchronise the client's state with the latest state on the server. + /// Clients use this API when they first log in to get an initial snapshot + /// of the state on the server, and then continue to call this API to get + /// incremental deltas to the state, and to receive new messages. + /// + /// *Note*: This endpoint supports lazy-loading. See [Filtering](https://spec.matrix.org/unstable/client-server-api/#filtering) + /// for more information. Lazy-loading members is only supported on a `StateFilter` + /// for this endpoint. When lazy-loading is enabled, servers MUST include the + /// syncing user's own membership event when they join a room, or when the + /// full state of rooms is requested, to aid discovering the user's avatar & + /// displayname. + /// + /// Further, like other members, the user's own membership event is eligible + /// for being considered redundant by the server. When a sync is `limited`, + /// the server MUST return membership events for events in the gap + /// (between `since` and the start of the returned timeline), regardless + /// as to whether or not they are redundant. This ensures that joins/leaves + /// and profile changes which occur during the gap are not lost. + /// + /// Note that the default behaviour of `state` is to include all membership + /// events, alongside other state, when lazy-loading is not enabled. + /// + /// [filter] The ID of a filter created using the filter API or a filter JSON + /// object encoded as a string. The server will detect whether it is + /// an ID or a JSON object by whether the first character is a `"{"` + /// open brace. Passing the JSON inline is best suited to one off + /// requests. Creating a filter using the filter API is recommended for + /// clients that reuse the same filter multiple times, for example in + /// long poll requests. + /// + /// See [Filtering](https://spec.matrix.org/unstable/client-server-api/#filtering) for more information. + /// + /// [since] A point in time to continue a sync from. This should be the + /// `next_batch` token returned by an earlier call to this endpoint. + /// + /// [fullState] Controls whether to include the full state for all rooms the user + /// is a member of. + /// + /// If this is set to `true`, then all state events will be returned, + /// even if `since` is non-empty. The timeline will still be limited + /// by the `since` parameter. In this case, the `timeout` parameter + /// will be ignored and the query will return immediately, possibly with + /// an empty timeline. + /// + /// If `false`, and `since` is non-empty, only state which has + /// changed since the point indicated by `since` will be returned. + /// + /// By default, this is `false`. + /// + /// [setPresence] Controls whether the client is automatically marked as online by + /// polling this API. If this parameter is omitted then the client is + /// automatically marked as online when it uses this API. Otherwise if + /// the parameter is set to "offline" then the client is not marked as + /// being online when it uses this API. When set to "unavailable", the + /// client is marked as being idle. + /// + /// [timeout] The maximum time to wait, in milliseconds, before returning this + /// request. If no events (or other data) become available before this + /// time elapses, the server will return a response with empty fields. + /// + /// By default, this is `0`, so the server will return immediately + /// even if the response is empty. + Future sync( + {String? filter, + String? since, + bool? fullState, + PresenceType? setPresence, + int? timeout}) async { + final requestUri = Uri(path: '_matrix/client/v3/sync', queryParameters: { + if (filter != null) 'filter': filter, + if (since != null) 'since': since, + if (fullState != null) 'full_state': fullState.toString(), + if (setPresence != null) 'set_presence': setPresence.name, + if (timeout != null) 'timeout': timeout.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 SyncUpdate.fromJson(json as Map); + } + + /// Retrieve an array of third party network locations from a Matrix room + /// alias. + /// + /// [alias] The Matrix room alias to look up. + Future> queryLocationByAlias(String alias) async { + final requestUri = + Uri(path: '_matrix/client/v3/thirdparty/location', queryParameters: { + 'alias': alias, + }); + 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 (json as List) + .map((v) => Location.fromJson(v as Map)) + .toList(); + } + + /// 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 + /// 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. + /// + /// [searchFields] One or more custom fields to help identify the third party + /// location. + Future> queryLocationByProtocol(String protocol, + {String? searchFields}) async { + final requestUri = Uri( + path: + '_matrix/client/v3/thirdparty/location/${Uri.encodeComponent(protocol)}', + queryParameters: { + if (searchFields != null) 'searchFields': searchFields, + }); + 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 (json as List) + .map((v) => Location.fromJson(v as Map)) + .toList(); + } + + /// Fetches the metadata from the homeserver about a particular third party protocol. + /// + /// [protocol] The name of the protocol. + Future getProtocolMetadata(String protocol) async { + final requestUri = Uri( + path: + '_matrix/client/v3/thirdparty/protocol/${Uri.encodeComponent(protocol)}'); + 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 Protocol.fromJson(json as Map); + } + + /// Fetches the overall metadata about protocols supported by the + /// homeserver. Includes both the available protocols and all fields + /// required for queries against each protocol. + Future> getProtocols() async { + final requestUri = Uri(path: '_matrix/client/v3/thirdparty/protocols'); + 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 (json as Map).map( + (k, v) => MapEntry(k, Protocol.fromJson(v as Map))); + } + + /// 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 { + final requestUri = + Uri(path: '_matrix/client/v3/thirdparty/user', queryParameters: { + 'userid': userid, + }); + 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 (json as List) + .map((v) => ThirdPartyUser.fromJson(v as Map)) + .toList(); + } + + /// 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 { + final requestUri = Uri( + path: + '_matrix/client/v3/thirdparty/user/${Uri.encodeComponent(protocol)}', + queryParameters: { + if (fields != null) 'fields...': fields, + }); + 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 (json as List) + .map((v) => ThirdPartyUser.fromJson(v as Map)) + .toList(); + } + + /// Get some account data for the client. This config is only visible to the user + /// that set the account data. + /// + /// [userId] The ID of the user to get account data for. The access token must be + /// authorized to make requests for this user ID. + /// + /// [type] The event type of the account data to get. Custom types should be + /// namespaced to avoid clashes. + Future> getAccountData( + String userId, String type) async { + final requestUri = Uri( + path: + '_matrix/client/v3/user/${Uri.encodeComponent(userId)}/account_data/${Uri.encodeComponent(type)}'); + 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 json as Map; + } + + /// Set some account data for the client. This config is only visible to the user + /// that set the account data. The config will be available to clients through the + /// top-level `account_data` field in the homeserver response to + /// [/sync](#get_matrixclientv3sync). + /// + /// [userId] The ID of the user to set account data for. The access token must be + /// authorized to make requests for this user ID. + /// + /// [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. + Future setAccountData( + String userId, String type, Map content) 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)); + 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 ignore(json); + } + + /// Uploads a new filter definition to the homeserver. + /// Returns a filter ID that may be used in future requests to + /// restrict which events are returned to the client. + /// + /// [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. + /// + /// 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 { + 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())); + 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['filter_id'] as String; + } + + /// + /// + /// [userId] The user ID to download a filter for. + /// + /// [filterId] The filter ID to download. + Future getFilter(String userId, String filterId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/user/${Uri.encodeComponent(userId)}/filter/${Uri.encodeComponent(filterId)}'); + 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 Filter.fromJson(json as Map); + } + + /// Gets an OpenID token object that the requester may supply to another + /// service to verify their identity in Matrix. The generated token is only + /// valid for exchanging for user information from the federation API for + /// OpenID. + /// + /// The access token generated is only valid for the OpenID API. It cannot + /// be used to request another OpenID access token or call `/sync`, for + /// example. + /// + /// [userId] The user to request an OpenID token for. Should be the user who + /// is authenticated for the request. + /// + /// [body] An empty object. Reserved for future expansion. + Future requestOpenIdToken( + String userId, Map body) async { + final requestUri = Uri( + path: + '_matrix/client/v3/user/${Uri.encodeComponent(userId)}/openid/request_token'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + 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); + final responseString = utf8.decode(responseBody); + final json = jsonDecode(responseString); + return OpenIdCredentials.fromJson(json as Map); + } + + /// Get some account data for the client on a given room. This config is only + /// visible to the user that set the account data. + /// + /// [userId] The ID of the user to get account data for. The access token must be + /// authorized to make requests for this user ID. + /// + /// [roomId] The ID of the room to get account data for. + /// + /// [type] The event type of the account data to get. Custom types should be + /// namespaced to avoid clashes. + Future> getAccountDataPerRoom( + String userId, String roomId, String type) async { + final requestUri = Uri( + path: + '_matrix/client/v3/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/account_data/${Uri.encodeComponent(type)}'); + 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 json as Map; + } + + /// Set some account data for the client on a given room. This config is only + /// visible to the user that set the account data. The config will be delivered to + /// clients in the per-room entries via [/sync](#get_matrixclientv3sync). + /// + /// [userId] The ID of the user to set account data for. The access token must be + /// authorized to make requests for this user ID. + /// + /// [roomId] The ID of the room to set account data on. + /// + /// [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. + Future setAccountDataPerRoom(String userId, String roomId, String type, + Map content) 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)); + 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 ignore(json); + } + + /// List the tags set by a user on a room. + /// + /// [userId] The id of the user to get tags for. The access token must be + /// authorized to make requests for this user ID. + /// + /// [roomId] The ID of the room to get tags for. + /// + /// returns `tags`: + /// + Future?> getRoomTags(String userId, String roomId) async { + final requestUri = Uri( + path: + '_matrix/client/v3/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/tags'); + 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 ((v) => v != null + ? (v as Map) + .map((k, v) => MapEntry(k, Tag.fromJson(v as Map))) + : null)(json['tags']); + } + + /// Remove a tag from the room. + /// + /// [userId] The id of the user to remove a tag for. The access token must be + /// authorized to make requests for this user ID. + /// + /// [roomId] The ID of the room to remove a tag from. + /// + /// [tag] The tag to remove. + Future deleteRoomTag(String userId, String roomId, String tag) async { + final requestUri = Uri( + path: + '_matrix/client/v3/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/tags/${Uri.encodeComponent(tag)}'); + final request = Request('DELETE', 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 ignore(json); + } + + /// Add a tag to the room. + /// + /// [userId] The id of the user to add a tag for. The access token must be + /// authorized to make requests for this user ID. + /// + /// [roomId] The ID of the room to add a tag to. + /// + /// [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 { + 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, + })); + 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 ignore(json); + } + + /// Performs a search for users. The homeserver may + /// determine which subset of users are searched, however the homeserver + /// MUST at a minimum consider the users the requesting user shares a + /// room with and those who reside in public rooms (known to the homeserver). + /// The search MUST consider local users to the homeserver, and SHOULD + /// query remote users as part of the search. + /// + /// The search is performed case-insensitively on user IDs and display + /// names preferably using a collation determined based upon the + /// `Accept-Language` header provided in the request, if present. + /// + /// [limit] The maximum number of results to return. Defaults to 10. + /// + /// [searchTerm] The term to search for + Future searchUserDirectory(String searchTerm, + {int? limit}) async { + final requestUri = Uri(path: '_matrix/client/v3/user_directory/search'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + if (limit != null) 'limit': limit, + 'search_term': searchTerm, + })); + 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 SearchUserDirectoryResponse.fromJson(json as Map); + } + + /// This API provides credentials for the client to use when initiating + /// calls. + Future getTurnServer() async { + final requestUri = Uri(path: '_matrix/client/v3/voip/turnServer'); + 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 TurnServerCredentials.fromJson(json as Map); + } + + /// Gets the versions of the specification supported by the server. + /// + /// Values will take the form `vX.Y` or `rX.Y.Z` in historical cases. See + /// [the Specification Versioning](../#specification-versions) for more + /// information. + /// + /// The server may additionally advertise experimental features it supports + /// through `unstable_features`. These features should be namespaced and + /// may optionally include version information within their name if desired. + /// Features listed here are not for optionally toggling parts of the Matrix + /// specification and should only be used to advertise support for a feature + /// 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. + Future getVersions() async { + final requestUri = Uri(path: '_matrix/client/versions'); + 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 GetVersionsResponse.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. + /// + /// **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. + Future getConfig() async { + final requestUri = Uri(path: '_matrix/media/v3/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 ServerConfig.fromJson(json as Map); + } + + /// + /// + /// [serverName] The server name from the `mxc://` URI (the authoritory 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. + /// + Future getContent(String serverName, String mediaId, + {bool? allowRemote}) async { + final requestUri = Uri( + path: + '_matrix/media/v3/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}', + queryParameters: { + if (allowRemote != null) 'allow_remote': allowRemote.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 replace the target file name with the one + /// provided by the caller. + /// + /// [serverName] The server name from the `mxc://` URI (the authoritory 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. + /// + Future getContentOverrideName( + String serverName, String mediaId, String fileName, + {bool? allowRemote}) 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(), + }); + 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. + /// + /// **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. + Future getUrlPreview(Uri url, {int? ts}) async { + final requestUri = + Uri(path: '_matrix/media/v3/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 GetUrlPreviewResponse.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. + /// + /// [serverName] The server name from the `mxc://` URI (the authoritory 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. + /// + /// [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. + Future getContentThumbnail( + String serverName, String mediaId, int width, int height, + {Method? method, bool? allowRemote}) async { + final requestUri = Uri( + path: + '_matrix/media/v3/thumbnail/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}', + queryParameters: { + 'width': width.toString(), + 'height': height.toString(), + if (method != null) 'method': method.name, + if (allowRemote != null) 'allow_remote': allowRemote.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); + } + + /// + /// + /// [filename] The name of the file being uploaded + /// + /// [content] The content to be uploaded. + /// + /// [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, + {String? filename, String? contentType}) async { + final requestUri = Uri(path: '_matrix/media/v3/upload', queryParameters: { + if (filename != null) 'filename': filename, + }); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['authorization'] = 'Bearer ${bearerToken!}'; + if (contentType != null) request.headers['content-type'] = contentType; + request.bodyBytes = content; + 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); + } +} diff --git a/lib/matrix_api_lite/generated/fixed_model.dart b/lib/matrix_api_lite/generated/fixed_model.dart new file mode 100644 index 00000000..22c27070 --- /dev/null +++ b/lib/matrix_api_lite/generated/fixed_model.dart @@ -0,0 +1,7 @@ +import 'dart:typed_data'; + +class FileResponse { + FileResponse({this.contentType, required this.data}); + String? contentType; + Uint8List data; +} diff --git a/lib/matrix_api_lite/generated/internal.dart b/lib/matrix_api_lite/generated/internal.dart new file mode 100644 index 00000000..48d80d0c --- /dev/null +++ b/lib/matrix_api_lite/generated/internal.dart @@ -0,0 +1,6 @@ +import 'fixed_model.dart'; + +void ignore(Object? input) {} +FileResponse ignoreFile(dynamic input) { + throw UnimplementedError(); +} diff --git a/lib/matrix_api_lite/generated/model.dart b/lib/matrix_api_lite/generated/model.dart new file mode 100644 index 00000000..3ab82f75 --- /dev/null +++ b/lib/matrix_api_lite/generated/model.dart @@ -0,0 +1,4091 @@ +import 'package:enhanced_enum/enhanced_enum.dart'; + +import '../model/auth/authentication_data.dart'; +import '../model/auth/authentication_identifier.dart'; +import '../model/auth/authentication_types.dart'; +import '../model/children_state.dart'; +import '../model/matrix_event.dart'; +import '../model/matrix_keys.dart'; +import '../model/sync_update.dart'; +import 'internal.dart'; + +part 'model.g.dart'; + +class _NameSource { + final String source; + const _NameSource(this.source); +} + +/// +@_NameSource('spec') +class HomeserverInformation { + HomeserverInformation({ + required this.baseUrl, + }); + + HomeserverInformation.fromJson(Map json) + : baseUrl = Uri.parse(json['base_url'] as String); + Map toJson() => { + 'base_url': baseUrl.toString(), + }; + + /// The base URL for the homeserver for client-server connections. + Uri baseUrl; +} + +/// +@_NameSource('spec') +class IdentityServerInformation { + IdentityServerInformation({ + required this.baseUrl, + }); + + IdentityServerInformation.fromJson(Map json) + : baseUrl = Uri.parse(json['base_url'] as String); + Map toJson() => { + 'base_url': baseUrl.toString(), + }; + + /// The base URL for the identity server for client-server connections. + Uri baseUrl; +} + +/// Used by clients to determine the homeserver, identity server, and other +/// optional components they should be interacting with. +@_NameSource('spec') +class DiscoveryInformation { + DiscoveryInformation({ + required this.mHomeserver, + this.mIdentityServer, + this.additionalProperties = const {}, + }); + + DiscoveryInformation.fromJson(Map json) + : mHomeserver = HomeserverInformation.fromJson( + json['m.homeserver'] as Map), + mIdentityServer = ((v) => v != null + ? IdentityServerInformation.fromJson(v as Map) + : null)(json['m.identity_server']), + additionalProperties = Map.fromEntries(json.entries + .where( + (e) => !['m.homeserver', 'm.identity_server'].contains(e.key)) + .map((e) => MapEntry(e.key, e.value as Map))); + Map toJson() { + final mIdentityServer = this.mIdentityServer; + return { + ...additionalProperties, + 'm.homeserver': mHomeserver.toJson(), + if (mIdentityServer != null) + 'm.identity_server': mIdentityServer.toJson(), + }; + } + + /// Used by clients to discover homeserver information. + HomeserverInformation mHomeserver; + + /// Used by clients to discover identity server information. + IdentityServerInformation? mIdentityServer; + + Map> additionalProperties; +} + +/// +@_NameSource('spec') +class PublicRoomsChunk { + PublicRoomsChunk({ + this.avatarUrl, + this.canonicalAlias, + required this.guestCanJoin, + this.joinRule, + this.name, + required this.numJoinedMembers, + required this.roomId, + this.roomType, + this.topic, + required this.worldReadable, + }); + + PublicRoomsChunk.fromJson(Map json) + : avatarUrl = ((v) => + v != null ? Uri.parse(v as String) : null)(json['avatar_url']), + canonicalAlias = + ((v) => v != null ? v as String : null)(json['canonical_alias']), + guestCanJoin = json['guest_can_join'] as bool, + joinRule = ((v) => v != null ? v as String : null)(json['join_rule']), + name = ((v) => v != null ? v as String : null)(json['name']), + numJoinedMembers = json['num_joined_members'] as int, + roomId = json['room_id'] as String, + roomType = ((v) => v != null ? v as String : null)(json['room_type']), + topic = ((v) => v != null ? v as String : null)(json['topic']), + worldReadable = json['world_readable'] as bool; + Map toJson() { + final avatarUrl = this.avatarUrl; + final canonicalAlias = this.canonicalAlias; + final joinRule = this.joinRule; + final name = this.name; + final roomType = this.roomType; + final topic = this.topic; + return { + if (avatarUrl != null) 'avatar_url': avatarUrl.toString(), + if (canonicalAlias != null) 'canonical_alias': canonicalAlias, + 'guest_can_join': guestCanJoin, + if (joinRule != null) 'join_rule': joinRule, + if (name != null) 'name': name, + 'num_joined_members': numJoinedMembers, + 'room_id': roomId, + if (roomType != null) 'room_type': roomType, + if (topic != null) 'topic': topic, + 'world_readable': worldReadable, + }; + } + + /// The URL for the room's avatar, if one is set. + Uri? avatarUrl; + + /// The canonical alias of the room, if any. + String? canonicalAlias; + + /// Whether guest users may join the room and participate in it. + /// If they can, they will be subject to ordinary power level + /// rules like any other user. + bool guestCanJoin; + + /// The room's join rule. When not present, the room is assumed to + /// be `public`. + String? joinRule; + + /// The name of the room, if any. + String? name; + + /// The number of members joined to the room. + int numJoinedMembers; + + /// The ID of the room. + String roomId; + + /// The `type` of room (from [`m.room.create`](https://spec.matrix.org/unstable/client-server-api/#mroomcreate)), if any. + String? roomType; + + /// The topic of the room, if any. + String? topic; + + /// Whether the room may be viewed by guest users without joining. + bool worldReadable; +} + +/// +@_NameSource('spec') +class ChildRoomsChunk { + ChildRoomsChunk({ + required this.childrenState, + this.roomType, + }); + + ChildRoomsChunk.fromJson(Map json) + : childrenState = (json['children_state'] as List) + .map((v) => ChildrenState.fromJson(v as Map)) + .toList(), + roomType = ((v) => v != null ? v as String : null)(json['room_type']); + Map toJson() { + final roomType = this.roomType; + return { + 'children_state': childrenState.map((v) => v.toJson()).toList(), + if (roomType != null) 'room_type': roomType, + }; + } + + /// The [`m.space.child`](#mspacechild) events of the space-room, represented + /// as [Stripped State Events](#stripped-state) with an added `origin_server_ts` key. + /// + /// If the room is not a space-room, this should be empty. + List childrenState; + + /// The `type` of room (from [`m.room.create`](https://spec.matrix.org/unstable/client-server-api/#mroomcreate)), if any. + String? roomType; +} + +/// +@_NameSource('rule override generated') +class SpaceRoomsChunk implements PublicRoomsChunk, ChildRoomsChunk { + SpaceRoomsChunk({ + this.avatarUrl, + this.canonicalAlias, + required this.guestCanJoin, + this.joinRule, + this.name, + required this.numJoinedMembers, + required this.roomId, + this.roomType, + this.topic, + required this.worldReadable, + required this.childrenState, + }); + + SpaceRoomsChunk.fromJson(Map json) + : avatarUrl = ((v) => + v != null ? Uri.parse(v as String) : null)(json['avatar_url']), + canonicalAlias = + ((v) => v != null ? v as String : null)(json['canonical_alias']), + guestCanJoin = json['guest_can_join'] as bool, + joinRule = ((v) => v != null ? v as String : null)(json['join_rule']), + name = ((v) => v != null ? v as String : null)(json['name']), + numJoinedMembers = json['num_joined_members'] as int, + roomId = json['room_id'] as String, + roomType = ((v) => v != null ? v as String : null)(json['room_type']), + topic = ((v) => v != null ? v as String : null)(json['topic']), + worldReadable = json['world_readable'] as bool, + childrenState = (json['children_state'] as List) + .map((v) => ChildrenState.fromJson(v as Map)) + .toList(); + Map toJson() { + final avatarUrl = this.avatarUrl; + final canonicalAlias = this.canonicalAlias; + final joinRule = this.joinRule; + final name = this.name; + final roomType = this.roomType; + final topic = this.topic; + return { + if (avatarUrl != null) 'avatar_url': avatarUrl.toString(), + if (canonicalAlias != null) 'canonical_alias': canonicalAlias, + 'guest_can_join': guestCanJoin, + if (joinRule != null) 'join_rule': joinRule, + if (name != null) 'name': name, + 'num_joined_members': numJoinedMembers, + 'room_id': roomId, + if (roomType != null) 'room_type': roomType, + if (topic != null) 'topic': topic, + 'world_readable': worldReadable, + 'children_state': childrenState.map((v) => v.toJson()).toList(), + }; + } + + /// The URL for the room's avatar, if one is set. + Uri? avatarUrl; + + /// The canonical alias of the room, if any. + String? canonicalAlias; + + /// Whether guest users may join the room and participate in it. + /// If they can, they will be subject to ordinary power level + /// rules like any other user. + bool guestCanJoin; + + /// The room's join rule. When not present, the room is assumed to + /// be `public`. + String? joinRule; + + /// The name of the room, if any. + String? name; + + /// The number of members joined to the room. + int numJoinedMembers; + + /// The ID of the room. + String roomId; + + /// The `type` of room (from [`m.room.create`](https://spec.matrix.org/unstable/client-server-api/#mroomcreate)), if any. + String? roomType; + + /// The topic of the room, if any. + String? topic; + + /// Whether the room may be viewed by guest users without joining. + bool worldReadable; + + /// The [`m.space.child`](#mspacechild) events of the space-room, represented + /// as [Stripped State Events](#stripped-state) with an added `origin_server_ts` key. + /// + /// If the room is not a space-room, this should be empty. + List childrenState; +} + +/// +@_NameSource('generated') +class GetSpaceHierarchyResponse { + GetSpaceHierarchyResponse({ + this.nextBatch, + required this.rooms, + }); + + GetSpaceHierarchyResponse.fromJson(Map json) + : nextBatch = ((v) => v != null ? v as String : null)(json['next_batch']), + rooms = (json['rooms'] as List) + .map((v) => SpaceRoomsChunk.fromJson(v as Map)) + .toList(); + Map toJson() { + final nextBatch = this.nextBatch; + return { + if (nextBatch != null) 'next_batch': nextBatch, + 'rooms': rooms.map((v) => v.toJson()).toList(), + }; + } + + /// A token to supply to `from` to keep paginating the responses. Not present when there are + /// no further results. + String? nextBatch; + + /// The rooms for the current page, with the current filters. + List rooms; +} + +/// +@_NameSource('rule override generated') +@EnhancedEnum() +enum Direction { + @EnhancedEnumValue(name: 'b') + b, + @EnhancedEnumValue(name: 'f') + f +} + +/// +@_NameSource('generated') +class GetRelatingEventsResponse { + GetRelatingEventsResponse({ + required this.chunk, + this.nextBatch, + this.prevBatch, + }); + + GetRelatingEventsResponse.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. + 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']); + 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` 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; +} + +/// +@_NameSource('generated') +@EnhancedEnum() +enum Include { + @EnhancedEnumValue(name: 'all') + all, + @EnhancedEnumValue(name: 'participated') + participated +} + +/// +@_NameSource('generated') +class GetThreadRootsResponse { + GetThreadRootsResponse({ + required this.chunk, + this.nextBatch, + }); + + GetThreadRootsResponse.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']); + Map toJson() { + final nextBatch = this.nextBatch; + return { + 'chunk': chunk.map((v) => v.toJson()).toList(), + if (nextBatch != null) 'next_batch': nextBatch, + }; + } + + /// 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). + /// + /// 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 + /// aggregation locally on the thread. + List chunk; + + /// A token to supply to `from` to keep paginating the responses. Not present when there are + /// no further results. + String? nextBatch; +} + +/// +@_NameSource('generated') +class GetEventByTimestampResponse { + GetEventByTimestampResponse({ + required this.eventId, + required this.originServerTs, + }); + + GetEventByTimestampResponse.fromJson(Map json) + : eventId = json['event_id'] as String, + originServerTs = json['origin_server_ts'] as int; + Map toJson() => { + 'event_id': eventId, + 'origin_server_ts': originServerTs, + }; + + /// The ID of the event found + String eventId; + + /// The event's timestamp, in milliseconds since the Unix epoch. + /// This makes it easy to do a quick comparison to see if the + /// `event_id` fetched is too far out of range to be useful for your + /// use case. + int originServerTs; +} + +/// +@_NameSource('rule override generated') +@EnhancedEnum() +enum ThirdPartyIdentifierMedium { + @EnhancedEnumValue(name: 'email') + email, + @EnhancedEnumValue(name: 'msisdn') + msisdn +} + +/// +@_NameSource('spec') +class ThirdPartyIdentifier { + ThirdPartyIdentifier({ + required this.addedAt, + required this.address, + required this.medium, + required this.validatedAt, + }); + + ThirdPartyIdentifier.fromJson(Map json) + : addedAt = json['added_at'] as int, + address = json['address'] as String, + medium = ThirdPartyIdentifierMedium.values + .fromString(json['medium'] as String)!, + validatedAt = json['validated_at'] as int; + Map toJson() => { + 'added_at': addedAt, + 'address': address, + 'medium': medium.name, + 'validated_at': validatedAt, + }; + + /// The timestamp, in milliseconds, when the homeserver associated the third party identifier with the user. + int addedAt; + + /// The third party identifier address. + String address; + + /// The medium of the third party identifier. + ThirdPartyIdentifierMedium medium; + + /// The timestamp, in milliseconds, when the identifier was + /// validated by the identity server. + int validatedAt; +} + +/// +@_NameSource('spec') +class ThreePidCredentials { + ThreePidCredentials({ + required this.clientSecret, + required this.idAccessToken, + required this.idServer, + required this.sid, + }); + + ThreePidCredentials.fromJson(Map json) + : clientSecret = json['client_secret'] as String, + idAccessToken = json['id_access_token'] as String, + idServer = json['id_server'] as String, + sid = json['sid'] as String; + Map toJson() => { + 'client_secret': clientSecret, + 'id_access_token': idAccessToken, + 'id_server': idServer, + 'sid': sid, + }; + + /// The client secret used in the session with the identity server. + String clientSecret; + + /// 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. + String idAccessToken; + + /// The identity server to use. + String idServer; + + /// The session identifier given by the identity server. + String sid; +} + +/// +@_NameSource('generated') +@EnhancedEnum() +enum IdServerUnbindResult { + @EnhancedEnumValue(name: 'no-support') + noSupport, + @EnhancedEnumValue(name: 'success') + success +} + +/// +@_NameSource('spec') +class RequestTokenResponse { + RequestTokenResponse({ + required this.sid, + this.submitUrl, + }); + + RequestTokenResponse.fromJson(Map json) + : sid = json['sid'] as String, + submitUrl = ((v) => + v != null ? Uri.parse(v as String) : null)(json['submit_url']); + Map toJson() { + final submitUrl = this.submitUrl; + return { + 'sid': sid, + if (submitUrl != null) 'submit_url': submitUrl.toString(), + }; + } + + /// The session ID. Session IDs are opaque strings that must consist entirely + /// of the characters `[0-9a-zA-Z.=_-]`. Their length must not exceed 255 + /// characters and they must not be empty. + String sid; + + /// An optional field containing a URL where the client must submit the + /// validation token to, with identical parameters to the Identity Service + /// API's `POST /validate/email/submitToken` endpoint (without the requirement + /// for an access token). The homeserver must send this token to the user (if + /// applicable), who should then be prompted to provide it to the client. + /// + /// If this field is not present, the client can assume that verification + /// will happen without the client's involvement provided the homeserver + /// advertises this specification version in the `/versions` response + /// (ie: r0.5.0). + Uri? submitUrl; +} + +/// +@_NameSource('rule override generated') +class TokenOwnerInfo { + TokenOwnerInfo({ + this.deviceId, + this.isGuest, + required this.userId, + }); + + TokenOwnerInfo.fromJson(Map json) + : deviceId = ((v) => v != null ? v as String : null)(json['device_id']), + isGuest = ((v) => v != null ? v as bool : null)(json['is_guest']), + userId = json['user_id'] as String; + Map toJson() { + final deviceId = this.deviceId; + final isGuest = this.isGuest; + return { + if (deviceId != null) 'device_id': deviceId, + if (isGuest != null) 'is_guest': isGuest, + 'user_id': userId, + }; + } + + /// Device ID associated with the access token. If no device + /// is associated with the access token (such as in the case + /// of application services) then this field can be omitted. + /// Otherwise this is required. + String? deviceId; + + /// When `true`, the user is a [Guest User](#guest-access). When + /// not present or `false`, the user is presumed to be a non-guest + /// user. + bool? isGuest; + + /// The user ID that owns the access token. + String userId; +} + +/// +@_NameSource('spec') +class ConnectionInfo { + ConnectionInfo({ + this.ip, + this.lastSeen, + this.userAgent, + }); + + ConnectionInfo.fromJson(Map json) + : ip = ((v) => v != null ? v as String : null)(json['ip']), + lastSeen = ((v) => v != null ? v as int : null)(json['last_seen']), + userAgent = ((v) => v != null ? v as String : null)(json['user_agent']); + Map toJson() { + final ip = this.ip; + final lastSeen = this.lastSeen; + final userAgent = this.userAgent; + return { + if (ip != null) 'ip': ip, + if (lastSeen != null) 'last_seen': lastSeen, + if (userAgent != null) 'user_agent': userAgent, + }; + } + + /// Most recently seen IP address of the session. + String? ip; + + /// Unix timestamp that the session was last active. + int? lastSeen; + + /// User agent string last seen in the session. + String? userAgent; +} + +/// +@_NameSource('spec') +class SessionInfo { + SessionInfo({ + this.connections, + }); + + SessionInfo.fromJson(Map json) + : connections = ((v) => v != null + ? (v as List) + .map((v) => ConnectionInfo.fromJson(v as Map)) + .toList() + : null)(json['connections']); + Map toJson() { + final connections = this.connections; + return { + if (connections != null) + 'connections': connections.map((v) => v.toJson()).toList(), + }; + } + + /// Information particular connections in the session. + List? connections; +} + +/// +@_NameSource('spec') +class DeviceInfo { + DeviceInfo({ + this.sessions, + }); + + DeviceInfo.fromJson(Map json) + : sessions = ((v) => v != null + ? (v as List) + .map((v) => SessionInfo.fromJson(v as Map)) + .toList() + : null)(json['sessions']); + Map toJson() { + final sessions = this.sessions; + return { + if (sessions != null) + 'sessions': sessions.map((v) => v.toJson()).toList(), + }; + } + + /// A user's sessions (i.e. what they did with an access token from one login). + List? sessions; +} + +/// +@_NameSource('rule override generated') +class WhoIsInfo { + WhoIsInfo({ + this.devices, + this.userId, + }); + + WhoIsInfo.fromJson(Map json) + : devices = ((v) => v != null + ? (v as Map).map((k, v) => + MapEntry(k, DeviceInfo.fromJson(v as Map))) + : null)(json['devices']), + userId = ((v) => v != null ? v as String : null)(json['user_id']); + Map toJson() { + final devices = this.devices; + final userId = this.userId; + return { + if (devices != null) + 'devices': devices.map((k, v) => MapEntry(k, v.toJson())), + if (userId != null) 'user_id': userId, + }; + } + + /// Each key is an identifier for one of the user's devices. + Map? devices; + + /// The Matrix user ID of the user. + String? userId; +} + +/// +@_NameSource('spec') +class ChangePasswordCapability { + ChangePasswordCapability({ + required this.enabled, + }); + + ChangePasswordCapability.fromJson(Map json) + : enabled = json['enabled'] as bool; + Map toJson() => { + 'enabled': enabled, + }; + + /// True if the user can change their password, false otherwise. + bool enabled; +} + +/// The stability of the room version. +@_NameSource('rule override generated') +@EnhancedEnum() +enum RoomVersionAvailable { + @EnhancedEnumValue(name: 'stable') + stable, + @EnhancedEnumValue(name: 'unstable') + unstable +} + +/// +@_NameSource('spec') +class RoomVersionsCapability { + RoomVersionsCapability({ + required this.available, + required this.default$, + }); + + RoomVersionsCapability.fromJson(Map json) + : available = (json['available'] as Map).map((k, v) => + MapEntry(k, RoomVersionAvailable.values.fromString(v as String)!)), + default$ = json['default'] as String; + Map toJson() => { + 'available': available.map((k, v) => MapEntry(k, v.name)), + 'default': default$, + }; + + /// A detailed description of the room versions the server supports. + Map available; + + /// The default room version the server is using for new rooms. + String default$; +} + +/// +@_NameSource('spec') +class Capabilities { + Capabilities({ + this.mChangePassword, + this.mRoomVersions, + this.additionalProperties = const {}, + }); + + Capabilities.fromJson(Map json) + : mChangePassword = ((v) => v != null + ? ChangePasswordCapability.fromJson(v as Map) + : null)(json['m.change_password']), + mRoomVersions = ((v) => v != null + ? RoomVersionsCapability.fromJson(v as Map) + : null)(json['m.room_versions']), + additionalProperties = Map.fromEntries(json.entries + .where((e) => + !['m.change_password', 'm.room_versions'].contains(e.key)) + .map((e) => MapEntry(e.key, e.value as Map))); + Map toJson() { + final mChangePassword = this.mChangePassword; + final mRoomVersions = this.mRoomVersions; + return { + ...additionalProperties, + if (mChangePassword != null) + 'm.change_password': mChangePassword.toJson(), + if (mRoomVersions != null) 'm.room_versions': mRoomVersions.toJson(), + }; + } + + /// Capability to indicate if the user can change their password. + ChangePasswordCapability? mChangePassword; + + /// The room versions the server supports. + RoomVersionsCapability? mRoomVersions; + + Map> additionalProperties; +} + +/// +@_NameSource('spec') +class StateEvent { + StateEvent({ + required this.content, + this.stateKey, + required this.type, + }); + + StateEvent.fromJson(Map json) + : content = json['content'] as Map, + stateKey = ((v) => v != null ? v as String : null)(json['state_key']), + type = json['type'] as String; + Map toJson() { + final stateKey = this.stateKey; + return { + 'content': content, + if (stateKey != null) 'state_key': stateKey, + 'type': type, + }; + } + + /// The content of the event. + Map content; + + /// The state_key of the state event. Defaults to an empty string. + String? stateKey; + + /// The type of event to send. + String type; +} + +/// +@_NameSource('spec') +class Invite3pid { + Invite3pid({ + required this.address, + required this.idAccessToken, + required this.idServer, + required this.medium, + }); + + Invite3pid.fromJson(Map json) + : address = json['address'] as String, + idAccessToken = json['id_access_token'] as String, + idServer = json['id_server'] as String, + medium = json['medium'] as String; + Map toJson() => { + 'address': address, + 'id_access_token': idAccessToken, + 'id_server': idServer, + 'medium': medium, + }; + + /// The invitee's third party identifier. + String address; + + /// 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. + String idAccessToken; + + /// 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` + /// (see [the list of recognised values](https://spec.matrix.org/unstable/appendices/#3pid-types)). + String medium; +} + +/// +@_NameSource('rule override generated') +@EnhancedEnum() +enum CreateRoomPreset { + @EnhancedEnumValue(name: 'private_chat') + privateChat, + @EnhancedEnumValue(name: 'public_chat') + publicChat, + @EnhancedEnumValue(name: 'trusted_private_chat') + trustedPrivateChat +} + +/// +@_NameSource('generated') +@EnhancedEnum() +enum Visibility { + @EnhancedEnumValue(name: 'private') + private, + @EnhancedEnumValue(name: 'public') + public +} + +/// A client device +@_NameSource('spec') +class Device { + Device({ + required this.deviceId, + this.displayName, + this.lastSeenIp, + this.lastSeenTs, + }); + + Device.fromJson(Map json) + : deviceId = json['device_id'] as String, + displayName = + ((v) => v != null ? v as String : null)(json['display_name']), + lastSeenIp = + ((v) => v != null ? v as String : null)(json['last_seen_ip']), + lastSeenTs = ((v) => v != null ? v as int : null)(json['last_seen_ts']); + Map toJson() { + final displayName = this.displayName; + final lastSeenIp = this.lastSeenIp; + final lastSeenTs = this.lastSeenTs; + return { + 'device_id': deviceId, + if (displayName != null) 'display_name': displayName, + if (lastSeenIp != null) 'last_seen_ip': lastSeenIp, + if (lastSeenTs != null) 'last_seen_ts': lastSeenTs, + }; + } + + /// Identifier of this device. + String deviceId; + + /// Display name set by the user for this device. Absent if no name has been + /// set. + String? displayName; + + /// The IP address where this device was last seen. (May be a few minutes out + /// of date, for efficiency reasons). + String? lastSeenIp; + + /// The timestamp (in milliseconds since the unix epoch) when this devices + /// was last seen. (May be a few minutes out of date, for efficiency + /// reasons). + int? lastSeenTs; +} + +/// +@_NameSource('generated') +class GetRoomIdByAliasResponse { + GetRoomIdByAliasResponse({ + this.roomId, + this.servers, + }); + + GetRoomIdByAliasResponse.fromJson(Map json) + : roomId = ((v) => v != null ? v as String : null)(json['room_id']), + servers = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['servers']); + Map toJson() { + final roomId = this.roomId; + final servers = this.servers; + return { + if (roomId != null) 'room_id': roomId, + if (servers != null) 'servers': servers.map((v) => v).toList(), + }; + } + + /// The room ID for this room alias. + String? roomId; + + /// A list of servers that are aware of this room alias. + List? servers; +} + +/// +@_NameSource('generated') +class GetEventsResponse { + GetEventsResponse({ + this.chunk, + this.end, + this.start, + }); + + GetEventsResponse.fromJson(Map json) + : chunk = ((v) => v != null + ? (v as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList() + : null)(json['chunk']), + end = ((v) => v != null ? v as String : null)(json['end']), + start = ((v) => v != null ? v as String : null)(json['start']); + Map toJson() { + final chunk = this.chunk; + final end = this.end; + final start = this.start; + return { + if (chunk != null) 'chunk': chunk.map((v) => v.toJson()).toList(), + if (end != null) 'end': end, + if (start != null) 'start': start, + }; + } + + /// An array of events. + List? chunk; + + /// A token which correlates to the end of `chunk`. This + /// token should be used in the next request to `/events`. + String? end; + + /// A token which correlates to the start of `chunk`. This + /// is usually the same token supplied to `from=`. + String? start; +} + +/// +@_NameSource('generated') +class PeekEventsResponse { + PeekEventsResponse({ + this.chunk, + this.end, + this.start, + }); + + PeekEventsResponse.fromJson(Map json) + : chunk = ((v) => v != null + ? (v as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList() + : null)(json['chunk']), + end = ((v) => v != null ? v as String : null)(json['end']), + start = ((v) => v != null ? v as String : null)(json['start']); + Map toJson() { + final chunk = this.chunk; + final end = this.end; + final start = this.start; + return { + if (chunk != null) 'chunk': chunk.map((v) => v.toJson()).toList(), + if (end != null) 'end': end, + if (start != null) 'start': start, + }; + } + + /// An array of events. + List? chunk; + + /// A token which correlates to the last value in `chunk`. This + /// token should be used in the next request to `/events`. + String? end; + + /// A token which correlates to the first value in `chunk`. This + /// is usually the same token supplied to `from=`. + String? start; +} + +/// 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. +@_NameSource('spec') +class ThirdPartySigned { + ThirdPartySigned({ + required this.mxid, + required this.sender, + required this.signatures, + required this.token, + }); + + ThirdPartySigned.fromJson(Map json) + : mxid = json['mxid'] as String, + sender = json['sender'] as String, + signatures = (json['signatures'] as Map).map((k, v) => + MapEntry( + k, + (v as Map) + .map((k, v) => MapEntry(k, v as String)))), + token = json['token'] as String; + Map toJson() => { + 'mxid': mxid, + 'sender': sender, + 'signatures': signatures + .map((k, v) => MapEntry(k, v.map((k, v) => MapEntry(k, v)))), + 'token': token, + }; + + /// The Matrix ID of the invitee. + String mxid; + + /// The Matrix ID of the user who issued the invite. + String sender; + + /// A signatures object containing a signature of the entire signed object. + Map> signatures; + + /// The state key of the m.third_party_invite event. + String token; +} + +/// +@_NameSource('generated') +class GetKeysChangesResponse { + GetKeysChangesResponse({ + this.changed, + this.left, + }); + + GetKeysChangesResponse.fromJson(Map json) + : changed = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['changed']), + left = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['left']); + Map toJson() { + final changed = this.changed; + final left = this.left; + return { + if (changed != null) 'changed': changed.map((v) => v).toList(), + if (left != null) 'left': left.map((v) => v).toList(), + }; + } + + /// The Matrix User IDs of all users who updated their device + /// identity keys. + List? changed; + + /// The Matrix User IDs of all users who may have left all + /// the end-to-end encrypted rooms they previously shared + /// with the user. + List? left; +} + +/// +@_NameSource('generated') +class ClaimKeysResponse { + ClaimKeysResponse({ + this.failures, + required this.oneTimeKeys, + }); + + ClaimKeysResponse.fromJson(Map json) + : failures = ((v) => v != null + ? (v as Map) + .map((k, v) => MapEntry(k, v as Map)) + : null)(json['failures']), + oneTimeKeys = (json['one_time_keys'] as Map).map( + (k, v) => MapEntry( + k, + (v as Map) + .map((k, v) => MapEntry(k, v as Map)))); + Map toJson() { + final failures = this.failures; + return { + if (failures != null) 'failures': failures.map((k, v) => MapEntry(k, v)), + 'one_time_keys': oneTimeKeys + .map((k, v) => MapEntry(k, v.map((k, v) => MapEntry(k, v)))), + }; + } + + /// If any remote homeservers could not be reached, they are + /// recorded here. The names of the properties are the names of + /// the unreachable servers. + /// + /// If the homeserver could be reached, but the user or device + /// was unknown, no failure is recorded. Instead, the corresponding + /// user or device is missing from the `one_time_keys` result. + Map>? failures; + + /// One-time keys for the queried devices. A map from user ID, to a + /// map from devices to a map from `:` to the key object. + /// + /// See the [key algorithms](https://spec.matrix.org/unstable/client-server-api/#key-algorithms) section for information + /// on the Key Object format. + /// + /// If necessary, the claimed key might be a fallback key. Fallback + /// keys are re-used by the server until replaced by the device. + Map>> oneTimeKeys; +} + +/// +@_NameSource('generated') +class QueryKeysResponse { + QueryKeysResponse({ + this.deviceKeys, + this.failures, + this.masterKeys, + this.selfSigningKeys, + this.userSigningKeys, + }); + + QueryKeysResponse.fromJson(Map json) + : deviceKeys = ((v) => v != null + ? (v as Map).map((k, v) => MapEntry( + k, + (v as Map).map((k, v) => MapEntry( + k, MatrixDeviceKeys.fromJson(v as Map))))) + : null)(json['device_keys']), + failures = ((v) => v != null + ? (v as Map) + .map((k, v) => MapEntry(k, v as Map)) + : null)(json['failures']), + masterKeys = ((v) => v != null + ? (v as Map).map((k, v) => MapEntry( + k, MatrixCrossSigningKey.fromJson(v as Map))) + : null)(json['master_keys']), + selfSigningKeys = ((v) => v != null + ? (v as Map).map((k, v) => MapEntry( + k, MatrixCrossSigningKey.fromJson(v as Map))) + : null)(json['self_signing_keys']), + userSigningKeys = ((v) => v != null + ? (v as Map).map((k, v) => MapEntry( + k, MatrixCrossSigningKey.fromJson(v as Map))) + : null)(json['user_signing_keys']); + Map toJson() { + final deviceKeys = this.deviceKeys; + final failures = this.failures; + final masterKeys = this.masterKeys; + final selfSigningKeys = this.selfSigningKeys; + final userSigningKeys = this.userSigningKeys; + return { + if (deviceKeys != null) + 'device_keys': deviceKeys.map( + (k, v) => MapEntry(k, v.map((k, v) => MapEntry(k, v.toJson())))), + if (failures != null) 'failures': failures.map((k, v) => MapEntry(k, v)), + if (masterKeys != null) + 'master_keys': masterKeys.map((k, v) => MapEntry(k, v.toJson())), + if (selfSigningKeys != null) + 'self_signing_keys': + selfSigningKeys.map((k, v) => MapEntry(k, v.toJson())), + if (userSigningKeys != null) + 'user_signing_keys': + userSigningKeys.map((k, v) => MapEntry(k, v.toJson())), + }; + } + + /// Information on the queried devices. A map from user ID, to a + /// map from device ID to device information. For each device, + /// the information returned will be the same as uploaded via + /// `/keys/upload`, with the addition of an `unsigned` + /// property. + Map>? deviceKeys; + + /// If any remote homeservers could not be reached, they are + /// recorded here. The names of the properties are the names of + /// the unreachable servers. + /// + /// If the homeserver could be reached, but the user or device + /// was unknown, no failure is recorded. Instead, the corresponding + /// user or device is missing from the `device_keys` result. + Map>? failures; + + /// Information on the master cross-signing keys of the queried users. + /// A map from user ID, to master key information. For each key, the + /// information returned will be the same as uploaded via + /// `/keys/device_signing/upload`, along with the signatures + /// uploaded via `/keys/signatures/upload` that the requesting user + /// is allowed to see. + Map? masterKeys; + + /// Information on the self-signing keys of the queried users. A map + /// from user ID, to self-signing key information. For each key, the + /// information returned will be the same as uploaded via + /// `/keys/device_signing/upload`. + Map? selfSigningKeys; + + /// Information on the user-signing key of the user making the + /// request, if they queried their own device information. A map + /// from user ID, to user-signing key information. The + /// information returned will be the same as uploaded via + /// `/keys/device_signing/upload`. + Map? userSigningKeys; +} + +/// +@_NameSource('spec') +class LoginFlow { + LoginFlow({ + this.type, + }); + + LoginFlow.fromJson(Map json) + : type = ((v) => v != null ? v as String : null)(json['type']); + Map toJson() { + final type = this.type; + return { + if (type != null) 'type': type, + }; + } + + /// 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 +} + +/// +@_NameSource('generated') +class LoginResponse { + LoginResponse({ + required this.accessToken, + required this.deviceId, + this.expiresInMs, + this.homeServer, + this.refreshToken, + required this.userId, + this.wellKnown, + }); + + LoginResponse.fromJson(Map json) + : accessToken = json['access_token'] as String, + deviceId = json['device_id'] as String, + expiresInMs = + ((v) => v != null ? v as int : null)(json['expires_in_ms']), + homeServer = + ((v) => v != null ? v as String : null)(json['home_server']), + refreshToken = + ((v) => v != null ? v as String : null)(json['refresh_token']), + userId = json['user_id'] as String, + wellKnown = ((v) => v != null + ? DiscoveryInformation.fromJson(v as Map) + : null)(json['well_known']); + Map toJson() { + final expiresInMs = this.expiresInMs; + final homeServer = this.homeServer; + final refreshToken = this.refreshToken; + final wellKnown = this.wellKnown; + return { + 'access_token': accessToken, + 'device_id': deviceId, + if (expiresInMs != null) 'expires_in_ms': expiresInMs, + if (homeServer != null) 'home_server': homeServer, + if (refreshToken != null) 'refresh_token': refreshToken, + 'user_id': userId, + if (wellKnown != null) 'well_known': wellKnown.toJson(), + }; + } + + /// An access token for the account. + /// This access token can then be used to authorize other requests. + String accessToken; + + /// ID of the logged-in device. Will be the same as the + /// corresponding parameter in the request, if one was specified. + String deviceId; + + /// The lifetime of the access token, in milliseconds. Once + /// the access token has expired a new access token can be + /// obtained by using the provided refresh token. If no + /// refresh token is provided, the client will need to re-log in + /// to obtain a new access token. If not given, the client can + /// assume that the access token will not expire. + int? expiresInMs; + + /// The server_name of the homeserver on which the account has + /// been registered. + /// + /// **Deprecated**. Clients should extract the server_name from + /// `user_id` (by splitting at the first colon) if they require + /// it. Note also that `homeserver` is not spelt this way. + String? homeServer; + + /// A refresh token for the account. This token can be used to + /// obtain a new access token when it expires by calling the + /// `/refresh` endpoint. + String? refreshToken; + + /// The fully-qualified Matrix ID for the account. + String userId; + + /// Optional client configuration provided by the server. If present, + /// clients SHOULD use the provided object to reconfigure themselves, + /// optionally validating the URLs within. This object takes the same + /// form as the one returned from .well-known autodiscovery. + DiscoveryInformation? wellKnown; +} + +/// +@_NameSource('spec') +class Notification { + Notification({ + required this.actions, + required this.event, + this.profileTag, + required this.read, + required this.roomId, + required this.ts, + }); + + Notification.fromJson(Map json) + : actions = (json['actions'] as List).map((v) => v as Object?).toList(), + event = MatrixEvent.fromJson(json['event'] as Map), + profileTag = + ((v) => v != null ? v as String : null)(json['profile_tag']), + read = json['read'] as bool, + roomId = json['room_id'] as String, + ts = json['ts'] as int; + Map toJson() { + final profileTag = this.profileTag; + return { + 'actions': actions.map((v) => v).toList(), + 'event': event.toJson(), + if (profileTag != null) 'profile_tag': profileTag, + 'read': read, + 'room_id': roomId, + 'ts': ts, + }; + } + + /// The action(s) to perform when the conditions for this rule are met. + /// See [Push Rules: API](https://spec.matrix.org/unstable/client-server-api/#push-rules-api). + List actions; + + /// The Event object for the event that triggered the notification. + MatrixEvent event; + + /// The profile tag of the rule that matched this event. + String? profileTag; + + /// Indicates whether the user has sent a read receipt indicating + /// that they have read this message. + bool read; + + /// The ID of the room in which the event was posted. + String roomId; + + /// The unix timestamp at which the event notification was sent, + /// in milliseconds. + int ts; +} + +/// +@_NameSource('generated') +class GetNotificationsResponse { + GetNotificationsResponse({ + this.nextToken, + required this.notifications, + }); + + GetNotificationsResponse.fromJson(Map json) + : nextToken = ((v) => v != null ? v as String : null)(json['next_token']), + notifications = (json['notifications'] as List) + .map((v) => Notification.fromJson(v as Map)) + .toList(); + Map toJson() { + final nextToken = this.nextToken; + return { + if (nextToken != null) 'next_token': nextToken, + 'notifications': notifications.map((v) => v.toJson()).toList(), + }; + } + + /// The token to supply in the `from` param of the next + /// `/notifications` request in order to request more + /// events. If this is absent, there are no more results. + String? nextToken; + + /// The list of events that triggered notifications. + List notifications; +} + +/// +@_NameSource('rule override generated') +@EnhancedEnum() +enum PresenceType { + @EnhancedEnumValue(name: 'offline') + offline, + @EnhancedEnumValue(name: 'online') + online, + @EnhancedEnumValue(name: 'unavailable') + unavailable +} + +/// +@_NameSource('generated') +class GetPresenceResponse { + GetPresenceResponse({ + this.currentlyActive, + this.lastActiveAgo, + required this.presence, + this.statusMsg, + }); + + GetPresenceResponse.fromJson(Map json) + : currentlyActive = + ((v) => v != null ? v as bool : null)(json['currently_active']), + lastActiveAgo = + ((v) => v != null ? v as int : null)(json['last_active_ago']), + presence = PresenceType.values.fromString(json['presence'] as String)!, + statusMsg = ((v) => v != null ? v as String : null)(json['status_msg']); + Map toJson() { + final currentlyActive = this.currentlyActive; + final lastActiveAgo = this.lastActiveAgo; + final statusMsg = this.statusMsg; + return { + if (currentlyActive != null) 'currently_active': currentlyActive, + if (lastActiveAgo != null) 'last_active_ago': lastActiveAgo, + 'presence': presence.name, + if (statusMsg != null) 'status_msg': statusMsg, + }; + } + + /// Whether the user is currently active + bool? currentlyActive; + + /// The length of time in milliseconds since an action was performed + /// by this user. + int? lastActiveAgo; + + /// This user's presence. + PresenceType presence; + + /// The state message for this user if one was set. + String? statusMsg; +} + +/// +@_NameSource('rule override generated') +class ProfileInformation { + ProfileInformation({ + this.avatarUrl, + this.displayname, + }); + + ProfileInformation.fromJson(Map json) + : avatarUrl = ((v) => + v != null ? Uri.parse(v as String) : null)(json['avatar_url']), + displayname = + ((v) => v != null ? v as String : null)(json['displayname']); + Map toJson() { + final avatarUrl = this.avatarUrl; + final displayname = this.displayname; + return { + if (avatarUrl != null) 'avatar_url': avatarUrl.toString(), + if (displayname != null) 'displayname': displayname, + }; + } + + /// The user's avatar URL if they have set one, otherwise not present. + Uri? avatarUrl; + + /// The user's display name if they have set one, otherwise not present. + String? displayname; +} + +/// A list of the rooms on the server. +@_NameSource('generated') +class GetPublicRoomsResponse { + GetPublicRoomsResponse({ + required this.chunk, + this.nextBatch, + this.prevBatch, + this.totalRoomCountEstimate, + }); + + GetPublicRoomsResponse.fromJson(Map json) + : chunk = (json['chunk'] as List) + .map((v) => PublicRoomsChunk.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']), + totalRoomCountEstimate = ((v) => + v != null ? v as int : null)(json['total_room_count_estimate']); + Map toJson() { + final nextBatch = this.nextBatch; + final prevBatch = this.prevBatch; + final totalRoomCountEstimate = this.totalRoomCountEstimate; + return { + 'chunk': chunk.map((v) => v.toJson()).toList(), + if (nextBatch != null) 'next_batch': nextBatch, + if (prevBatch != null) 'prev_batch': prevBatch, + if (totalRoomCountEstimate != null) + 'total_room_count_estimate': totalRoomCountEstimate, + }; + } + + /// A paginated chunk of public rooms. + List chunk; + + /// A pagination token for the response. The absence of this token + /// means there are no more results to fetch and the client should + /// stop paginating. + String? nextBatch; + + /// A pagination token that allows fetching previous results. The + /// absence of this token means there are no results before this + /// batch, i.e. this is the first batch. + String? prevBatch; + + /// An estimate on the total number of public rooms, if the + /// server has an estimate. + int? totalRoomCountEstimate; +} + +/// +@_NameSource('rule override spec') +class PublicRoomQueryFilter { + PublicRoomQueryFilter({ + this.genericSearchTerm, + this.roomTypes, + }); + + PublicRoomQueryFilter.fromJson(Map json) + : 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() + : null)(json['room_types']); + Map toJson() { + final genericSearchTerm = this.genericSearchTerm; + final roomTypes = this.roomTypes; + return { + if (genericSearchTerm != null) 'generic_search_term': genericSearchTerm, + if (roomTypes != null) 'room_types': roomTypes.map((v) => v).toList(), + }; + } + + /// An optional string to search for in the room metadata, e.g. name, + /// topic, canonical alias, etc. + String? genericSearchTerm; + + /// An optional list of [room types](https://spec.matrix.org/unstable/client-server-api/#types) to search + /// 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; +} + +/// A list of the rooms on the server. +@_NameSource('generated') +class QueryPublicRoomsResponse { + QueryPublicRoomsResponse({ + required this.chunk, + this.nextBatch, + this.prevBatch, + this.totalRoomCountEstimate, + }); + + QueryPublicRoomsResponse.fromJson(Map json) + : chunk = (json['chunk'] as List) + .map((v) => PublicRoomsChunk.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']), + totalRoomCountEstimate = ((v) => + v != null ? v as int : null)(json['total_room_count_estimate']); + Map toJson() { + final nextBatch = this.nextBatch; + final prevBatch = this.prevBatch; + final totalRoomCountEstimate = this.totalRoomCountEstimate; + return { + 'chunk': chunk.map((v) => v.toJson()).toList(), + if (nextBatch != null) 'next_batch': nextBatch, + if (prevBatch != null) 'prev_batch': prevBatch, + if (totalRoomCountEstimate != null) + 'total_room_count_estimate': totalRoomCountEstimate, + }; + } + + /// A paginated chunk of public rooms. + List chunk; + + /// A pagination token for the response. The absence of this token + /// means there are no more results to fetch and the client should + /// stop paginating. + String? nextBatch; + + /// A pagination token that allows fetching previous results. The + /// absence of this token means there are no results before this + /// batch, i.e. this is the first batch. + String? prevBatch; + + /// An estimate on the total number of public rooms, if the + /// server has an estimate. + int? totalRoomCountEstimate; +} + +/// +@_NameSource('spec') +class PusherData { + PusherData({ + this.format, + this.url, + this.additionalProperties = const {}, + }); + + PusherData.fromJson(Map json) + : format = ((v) => v != null ? v as String : null)(json['format']), + url = ((v) => v != null ? Uri.parse(v as String) : null)(json['url']), + additionalProperties = Map.fromEntries(json.entries + .where((e) => !['format', 'url'].contains(e.key)) + .map((e) => MapEntry(e.key, e.value as Object?))); + Map toJson() { + final format = this.format; + final url = this.url; + return { + ...additionalProperties, + if (format != null) 'format': format, + if (url != null) 'url': url.toString(), + }; + } + + /// The format to use when sending notifications to the Push + /// Gateway. + String? format; + + /// Required if `kind` is `http`. The URL to use to send + /// notifications to. + Uri? url; + + Map additionalProperties; +} + +/// +@_NameSource('spec') +class PusherId { + PusherId({ + required this.appId, + required this.pushkey, + }); + + PusherId.fromJson(Map json) + : appId = json['app_id'] as String, + pushkey = json['pushkey'] as String; + Map toJson() => { + 'app_id': appId, + 'pushkey': pushkey, + }; + + /// This is a reverse-DNS style identifier for the application. + /// Max length, 64 chars. + String appId; + + /// This is a unique identifier for this pusher. See `/set` for + /// more detail. + /// Max length, 512 bytes. + String pushkey; +} + +/// +@_NameSource('spec') +class Pusher implements PusherId { + Pusher({ + required this.appId, + required this.pushkey, + required this.appDisplayName, + required this.data, + required this.deviceDisplayName, + required this.kind, + required this.lang, + this.profileTag, + }); + + Pusher.fromJson(Map json) + : appId = json['app_id'] as String, + pushkey = json['pushkey'] as String, + appDisplayName = json['app_display_name'] as String, + data = PusherData.fromJson(json['data'] as Map), + deviceDisplayName = json['device_display_name'] as String, + kind = json['kind'] as String, + lang = json['lang'] as String, + profileTag = + ((v) => v != null ? v as String : null)(json['profile_tag']); + Map toJson() { + final profileTag = this.profileTag; + return { + 'app_id': appId, + 'pushkey': pushkey, + 'app_display_name': appDisplayName, + 'data': data.toJson(), + 'device_display_name': deviceDisplayName, + 'kind': kind, + 'lang': lang, + if (profileTag != null) 'profile_tag': profileTag, + }; + } + + /// This is a reverse-DNS style identifier for the application. + /// Max length, 64 chars. + String appId; + + /// This is a unique identifier for this pusher. See `/set` for + /// more detail. + /// Max length, 512 bytes. + String pushkey; + + /// A string that will allow the user to identify what application + /// owns this pusher. + String appDisplayName; + + /// A dictionary of information for the pusher implementation + /// itself. + PusherData data; + + /// A string that will allow the user to identify what device owns + /// this pusher. + String deviceDisplayName; + + /// The kind of pusher. `"http"` is a pusher that + /// sends HTTP pokes. + String kind; + + /// The preferred language for receiving notifications (e.g. 'en' + /// or 'en-US') + String lang; + + /// This string determines which set of device specific rules this + /// pusher executes. + String? profileTag; +} + +/// +@_NameSource('spec') +class PushCondition { + PushCondition({ + this.is$, + this.key, + required this.kind, + this.pattern, + }); + + 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']); + Map toJson() { + final is$ = this.is$; + final key = this.key; + final pattern = this.pattern; + return { + if (is$ != null) 'is': is$, + if (key != null) 'key': key, + 'kind': kind, + if (pattern != null) 'pattern': pattern, + }; + } + + /// Required for `room_member_count` conditions. A decimal integer + /// optionally prefixed by one of, ==, <, >, >= or <=. A prefix of < matches + /// rooms where the member count is strictly less than the given number and + /// 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 `sender_notification_permission` conditions. The field in + /// the power level event the user needs a minimum power level for. Fields + /// must be specified under the `notifications` property in the power level + /// event's `content`. + String? key; + + /// The kind of condition to apply. See [conditions](https://spec.matrix.org/unstable/client-server-api/#conditions) 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. + String? pattern; +} + +/// +@_NameSource('spec') +class PushRule { + PushRule({ + required this.actions, + this.conditions, + required this.default$, + required this.enabled, + this.pattern, + required this.ruleId, + }); + + PushRule.fromJson(Map json) + : actions = (json['actions'] as List).map((v) => v as Object?).toList(), + conditions = ((v) => v != null + ? (v as List) + .map((v) => PushCondition.fromJson(v as Map)) + .toList() + : null)(json['conditions']), + default$ = json['default'] as bool, + enabled = json['enabled'] as bool, + pattern = ((v) => v != null ? v as String : null)(json['pattern']), + ruleId = json['rule_id'] as String; + Map toJson() { + final conditions = this.conditions; + final pattern = this.pattern; + return { + 'actions': actions.map((v) => v).toList(), + if (conditions != null) + 'conditions': conditions.map((v) => v.toJson()).toList(), + 'default': default$, + 'enabled': enabled, + if (pattern != null) 'pattern': pattern, + 'rule_id': ruleId, + }; + } + + /// The actions to perform when this rule is matched. + List actions; + + /// The conditions that must hold true for an event in order for a rule to be + /// applied to an event. A rule with no conditions always matches. Only + /// applicable to `underride` and `override` rules. + List? conditions; + + /// Whether this is a default rule, or has been set explicitly. + bool default$; + + /// Whether the push rule is enabled or not. + bool enabled; + + /// The glob-style pattern to match against. Only applicable to `content` + /// rules. + String? pattern; + + /// The ID of this rule. + String ruleId; +} + +/// +@_NameSource('rule override generated') +class PushRuleSet { + PushRuleSet({ + this.content, + this.override, + this.room, + this.sender, + this.underride, + }); + + PushRuleSet.fromJson(Map json) + : content = ((v) => v != null + ? (v as List) + .map((v) => PushRule.fromJson(v as Map)) + .toList() + : null)(json['content']), + override = ((v) => v != null + ? (v as List) + .map((v) => PushRule.fromJson(v as Map)) + .toList() + : null)(json['override']), + room = ((v) => v != null + ? (v as List) + .map((v) => PushRule.fromJson(v as Map)) + .toList() + : null)(json['room']), + sender = ((v) => v != null + ? (v as List) + .map((v) => PushRule.fromJson(v as Map)) + .toList() + : null)(json['sender']), + underride = ((v) => v != null + ? (v as List) + .map((v) => PushRule.fromJson(v as Map)) + .toList() + : null)(json['underride']); + Map toJson() { + final content = this.content; + final override = this.override; + final room = this.room; + final sender = this.sender; + final underride = this.underride; + return { + if (content != null) 'content': content.map((v) => v.toJson()).toList(), + if (override != null) + 'override': override.map((v) => v.toJson()).toList(), + if (room != null) 'room': room.map((v) => v.toJson()).toList(), + if (sender != null) 'sender': sender.map((v) => v.toJson()).toList(), + if (underride != null) + 'underride': underride.map((v) => v.toJson()).toList(), + }; + } + + /// + List? content; + + /// + List? override; + + /// + List? room; + + /// + List? sender; + + /// + List? underride; +} + +/// +@_NameSource('rule override generated') +@EnhancedEnum() +enum PushRuleKind { + @EnhancedEnumValue(name: 'content') + content, + @EnhancedEnumValue(name: 'override') + override, + @EnhancedEnumValue(name: 'room') + room, + @EnhancedEnumValue(name: 'sender') + sender, + @EnhancedEnumValue(name: 'underride') + underride +} + +/// +@_NameSource('generated') +class RefreshResponse { + RefreshResponse({ + required this.accessToken, + this.expiresInMs, + this.refreshToken, + }); + + RefreshResponse.fromJson(Map json) + : accessToken = json['access_token'] as String, + expiresInMs = + ((v) => v != null ? v as int : null)(json['expires_in_ms']), + refreshToken = + ((v) => v != null ? v as String : null)(json['refresh_token']); + Map toJson() { + final expiresInMs = this.expiresInMs; + final refreshToken = this.refreshToken; + return { + 'access_token': accessToken, + if (expiresInMs != null) 'expires_in_ms': expiresInMs, + if (refreshToken != null) 'refresh_token': refreshToken, + }; + } + + /// The new access token to use. + String accessToken; + + /// The lifetime of the access token, in milliseconds. If not + /// given, the client can assume that the access token will not + /// expire. + int? expiresInMs; + + /// The new refresh token to use when the access token needs to + /// be refreshed again. If not given, the old refresh token can + /// be re-used. + String? refreshToken; +} + +/// +@_NameSource('rule override generated') +@EnhancedEnum() +enum AccountKind { + @EnhancedEnumValue(name: 'guest') + guest, + @EnhancedEnumValue(name: 'user') + user +} + +/// +@_NameSource('generated') +class RegisterResponse { + RegisterResponse({ + this.accessToken, + this.deviceId, + this.expiresInMs, + this.homeServer, + this.refreshToken, + required this.userId, + }); + + RegisterResponse.fromJson(Map json) + : accessToken = + ((v) => v != null ? v as String : null)(json['access_token']), + deviceId = ((v) => v != null ? v as String : null)(json['device_id']), + expiresInMs = + ((v) => v != null ? v as int : null)(json['expires_in_ms']), + homeServer = + ((v) => v != null ? v as String : null)(json['home_server']), + refreshToken = + ((v) => v != null ? v as String : null)(json['refresh_token']), + userId = json['user_id'] as String; + Map toJson() { + final accessToken = this.accessToken; + final deviceId = this.deviceId; + final expiresInMs = this.expiresInMs; + final homeServer = this.homeServer; + final refreshToken = this.refreshToken; + return { + if (accessToken != null) 'access_token': accessToken, + if (deviceId != null) 'device_id': deviceId, + if (expiresInMs != null) 'expires_in_ms': expiresInMs, + if (homeServer != null) 'home_server': homeServer, + if (refreshToken != null) 'refresh_token': refreshToken, + 'user_id': userId, + }; + } + + /// An access token for the account. + /// This access token can then be used to authorize other requests. + /// Required if the `inhibit_login` option is false. + String? accessToken; + + /// ID of the registered device. Will be the same as the + /// corresponding parameter in the request, if one was specified. + /// Required if the `inhibit_login` option is false. + String? deviceId; + + /// The lifetime of the access token, in milliseconds. Once + /// the access token has expired a new access token can be + /// obtained by using the provided refresh token. If no + /// refresh token is provided, the client will need to re-log in + /// to obtain a new access token. If not given, the client can + /// assume that the access token will not expire. + /// + /// Omitted if the `inhibit_login` option is true. + int? expiresInMs; + + /// The server_name of the homeserver on which the account has + /// been registered. + /// + /// **Deprecated**. Clients should extract the server_name from + /// `user_id` (by splitting at the first colon) if they require + /// it. Note also that `homeserver` is not spelt this way. + String? homeServer; + + /// A refresh token for the account. This token can be used to + /// obtain a new access token when it expires by calling the + /// `/refresh` endpoint. + /// + /// Omitted if the `inhibit_login` option is true. + String? refreshToken; + + /// The fully-qualified Matrix user ID (MXID) that has been registered. + /// + /// Any user ID returned by this API must conform to the grammar given in the + /// [Matrix specification](https://spec.matrix.org/unstable/appendices/#user-identifiers). + String userId; +} + +/// +@_NameSource('spec') +class RoomKeysUpdateResponse { + RoomKeysUpdateResponse({ + required this.count, + required this.etag, + }); + + RoomKeysUpdateResponse.fromJson(Map json) + : count = json['count'] as int, + etag = json['etag'] as String; + Map toJson() => { + 'count': count, + 'etag': etag, + }; + + /// The number of keys stored in the backup + int count; + + /// The new etag value representing stored keys in the backup. + /// See `GET /room_keys/version/{version}` for more details. + String etag; +} + +/// The key data +@_NameSource('spec') +class KeyBackupData { + KeyBackupData({ + required this.firstMessageIndex, + required this.forwardedCount, + required this.isVerified, + required this.sessionData, + }); + + KeyBackupData.fromJson(Map json) + : firstMessageIndex = json['first_message_index'] as int, + forwardedCount = json['forwarded_count'] as int, + isVerified = json['is_verified'] as bool, + sessionData = json['session_data'] as Map; + Map toJson() => { + 'first_message_index': firstMessageIndex, + 'forwarded_count': forwardedCount, + 'is_verified': isVerified, + 'session_data': sessionData, + }; + + /// The index of the first message in the session that the key can decrypt. + int firstMessageIndex; + + /// The number of times this key has been forwarded via key-sharing between devices. + int forwardedCount; + + /// Whether the device backing up the key verified the device that the key + /// is from. + bool isVerified; + + /// Algorithm-dependent data. See the documentation for the backup + /// algorithms in [Server-side key backups](https://spec.matrix.org/unstable/client-server-api/#server-side-key-backups) for more information on the + /// expected format of the data. + Map sessionData; +} + +/// The backed up keys for a room. +@_NameSource('spec') +class RoomKeyBackup { + RoomKeyBackup({ + required this.sessions, + }); + + RoomKeyBackup.fromJson(Map json) + : sessions = (json['sessions'] as Map).map((k, v) => + MapEntry(k, KeyBackupData.fromJson(v as Map))); + Map toJson() => { + 'sessions': sessions.map((k, v) => MapEntry(k, v.toJson())), + }; + + /// A map of session IDs to key data. + Map sessions; +} + +/// +@_NameSource('rule override generated') +class RoomKeys { + RoomKeys({ + required this.rooms, + }); + + RoomKeys.fromJson(Map json) + : rooms = (json['rooms'] as Map).map((k, v) => + MapEntry(k, RoomKeyBackup.fromJson(v as Map))); + Map toJson() => { + 'rooms': rooms.map((k, v) => MapEntry(k, v.toJson())), + }; + + /// A map of room IDs to room key backup data. + Map rooms; +} + +/// +@_NameSource('rule override generated') +@EnhancedEnum() +enum BackupAlgorithm { + @EnhancedEnumValue(name: 'm.megolm_backup.v1.curve25519-aes-sha2') + mMegolmBackupV1Curve25519AesSha2 +} + +/// +@_NameSource('generated') +class GetRoomKeysVersionCurrentResponse { + GetRoomKeysVersionCurrentResponse({ + required this.algorithm, + required this.authData, + required this.count, + required this.etag, + required this.version, + }); + + GetRoomKeysVersionCurrentResponse.fromJson(Map json) + : algorithm = + BackupAlgorithm.values.fromString(json['algorithm'] as String)!, + authData = json['auth_data'] as Map, + count = json['count'] as int, + etag = json['etag'] as String, + version = json['version'] as String; + Map toJson() => { + 'algorithm': algorithm.name, + 'auth_data': authData, + 'count': count, + 'etag': etag, + 'version': version, + }; + + /// The algorithm used for storing backups. + BackupAlgorithm algorithm; + + /// Algorithm-dependent data. See the documentation for the backup + /// algorithms in [Server-side key backups](https://spec.matrix.org/unstable/client-server-api/#server-side-key-backups) for more information on the + /// expected format of the data. + Map authData; + + /// The number of keys stored in the backup. + int count; + + /// An opaque string representing stored keys in the backup. + /// Clients can compare it with the `etag` value they received + /// in the request of their last key storage request. If not + /// equal, another client has modified the backup. + String etag; + + /// The backup version. + String version; +} + +/// +@_NameSource('generated') +class GetRoomKeysVersionResponse { + GetRoomKeysVersionResponse({ + required this.algorithm, + required this.authData, + required this.count, + required this.etag, + required this.version, + }); + + GetRoomKeysVersionResponse.fromJson(Map json) + : algorithm = + BackupAlgorithm.values.fromString(json['algorithm'] as String)!, + authData = json['auth_data'] as Map, + count = json['count'] as int, + etag = json['etag'] as String, + version = json['version'] as String; + Map toJson() => { + 'algorithm': algorithm.name, + 'auth_data': authData, + 'count': count, + 'etag': etag, + 'version': version, + }; + + /// The algorithm used for storing backups. + BackupAlgorithm algorithm; + + /// Algorithm-dependent data. See the documentation for the backup + /// algorithms in [Server-side key backups](https://spec.matrix.org/unstable/client-server-api/#server-side-key-backups) for more information on the + /// expected format of the data. + Map authData; + + /// The number of keys stored in the backup. + int count; + + /// An opaque string representing stored keys in the backup. + /// Clients can compare it with the `etag` value they received + /// in the request of their last key storage request. If not + /// equal, another client has modified the backup. + String etag; + + /// The backup version. + String version; +} + +/// The events and state surrounding the requested event. +@_NameSource('rule override generated') +class EventContext { + EventContext({ + this.end, + this.event, + this.eventsAfter, + this.eventsBefore, + this.start, + this.state, + }); + + EventContext.fromJson(Map json) + : end = ((v) => v != null ? v as String : null)(json['end']), + event = ((v) => v != null + ? MatrixEvent.fromJson(v as Map) + : null)(json['event']), + eventsAfter = ((v) => v != null + ? (v as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList() + : null)(json['events_after']), + eventsBefore = ((v) => v != null + ? (v as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList() + : null)(json['events_before']), + start = ((v) => v != null ? v as String : null)(json['start']), + state = ((v) => v != null + ? (v as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList() + : null)(json['state']); + Map toJson() { + final end = this.end; + final event = this.event; + final eventsAfter = this.eventsAfter; + final eventsBefore = this.eventsBefore; + final start = this.start; + final state = this.state; + return { + if (end != null) 'end': end, + if (event != null) 'event': event.toJson(), + if (eventsAfter != null) + 'events_after': eventsAfter.map((v) => v.toJson()).toList(), + if (eventsBefore != null) + 'events_before': eventsBefore.map((v) => v.toJson()).toList(), + if (start != null) 'start': start, + if (state != null) 'state': state.map((v) => v.toJson()).toList(), + }; + } + + /// A token that can be used to paginate forwards with. + String? end; + + /// Details of the requested event. + MatrixEvent? event; + + /// A list of room events that happened just after the + /// requested event, in chronological order. + List? eventsAfter; + + /// A list of room events that happened just before the + /// requested event, in reverse-chronological order. + List? eventsBefore; + + /// A token that can be used to paginate backwards with. + String? start; + + /// The state of the room at the last event returned. + List? state; +} + +/// +@_NameSource('spec') +class RoomMember { + RoomMember({ + this.avatarUrl, + this.displayName, + }); + + RoomMember.fromJson(Map json) + : avatarUrl = ((v) => + v != null ? Uri.parse(v as String) : null)(json['avatar_url']), + displayName = + ((v) => v != null ? v as String : null)(json['display_name']); + Map toJson() { + final avatarUrl = this.avatarUrl; + final displayName = this.displayName; + return { + if (avatarUrl != null) 'avatar_url': avatarUrl.toString(), + if (displayName != null) 'display_name': displayName, + }; + } + + /// The mxc avatar url of the user this object is representing. + Uri? avatarUrl; + + /// The display name of the user this object is representing. + String? displayName; +} + +/// +@_NameSource('(generated, rule override generated)') +@EnhancedEnum() +enum Membership { + @EnhancedEnumValue(name: 'ban') + ban, + @EnhancedEnumValue(name: 'invite') + invite, + @EnhancedEnumValue(name: 'join') + join, + @EnhancedEnumValue(name: 'knock') + knock, + @EnhancedEnumValue(name: 'leave') + leave +} + +/// A list of messages with a new token to request more. +@_NameSource('generated') +class GetRoomEventsResponse { + GetRoomEventsResponse({ + required this.chunk, + this.end, + required this.start, + this.state, + }); + + GetRoomEventsResponse.fromJson(Map json) + : chunk = (json['chunk'] as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList(), + end = ((v) => v != null ? v as String : null)(json['end']), + start = json['start'] as String, + state = ((v) => v != null + ? (v as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList() + : null)(json['state']); + Map toJson() { + final end = this.end; + final state = this.state; + return { + 'chunk': chunk.map((v) => v.toJson()).toList(), + if (end != null) 'end': end, + 'start': start, + if (state != null) 'state': state.map((v) => v.toJson()).toList(), + }; + } + + /// A list of room events. The order depends on the `dir` parameter. + /// For `dir=b` events will be in reverse-chronological order, + /// for `dir=f` in chronological order. (The exact definition of `chronological` + /// is dependent on the server implementation.) + /// + /// Note that an empty `chunk` does not *necessarily* imply that no more events + /// are available. Clients should continue to paginate until no `end` property + /// is returned. + List chunk; + + /// A token corresponding to the end of `chunk`. This token can be passed + /// back to this endpoint to request further events. + /// + /// If no further events are available (either because we have + /// reached the start of the timeline, or because the user does + /// not have permission to see any more events), this property + /// is omitted from the response. + String? end; + + /// A token corresponding to the start of `chunk`. This will be the same as + /// the value given in `from`. + String start; + + /// A list of state events relevant to showing the `chunk`. For example, if + /// `lazy_load_members` is enabled in the filter then this may contain + /// the membership events for the senders of events in the `chunk`. + /// + /// Unless `include_redundant_members` is `true`, the server + /// may remove membership events which would have already been + /// sent to the client in prior calls to this endpoint, assuming + /// the membership of those members has not changed. + List? state; +} + +/// +@_NameSource('generated') +@EnhancedEnum() +enum ReceiptType { + @EnhancedEnumValue(name: 'm.fully_read') + mFullyRead, + @EnhancedEnumValue(name: 'm.read') + mRead, + @EnhancedEnumValue(name: 'm.read.private') + mReadPrivate +} + +/// +@_NameSource('spec') +class IncludeEventContext { + IncludeEventContext({ + this.afterLimit, + this.beforeLimit, + this.includeProfile, + }); + + IncludeEventContext.fromJson(Map json) + : afterLimit = ((v) => v != null ? v as int : null)(json['after_limit']), + beforeLimit = + ((v) => v != null ? v as int : null)(json['before_limit']), + includeProfile = + ((v) => v != null ? v as bool : null)(json['include_profile']); + Map toJson() { + final afterLimit = this.afterLimit; + final beforeLimit = this.beforeLimit; + final includeProfile = this.includeProfile; + return { + if (afterLimit != null) 'after_limit': afterLimit, + if (beforeLimit != null) 'before_limit': beforeLimit, + if (includeProfile != null) 'include_profile': includeProfile, + }; + } + + /// How many events after the result are + /// returned. By default, this is `5`. + int? afterLimit; + + /// How many events before the result are + /// returned. By default, this is `5`. + int? beforeLimit; + + /// Requests that the server returns the + /// historic profile information for the users + /// that sent the events that were returned. + /// By default, this is `false`. + bool? includeProfile; +} + +/// +@_NameSource('spec') +class EventFilter { + EventFilter({ + this.limit, + this.notSenders, + this.notTypes, + this.senders, + this.types, + }); + + EventFilter.fromJson(Map json) + : limit = ((v) => v != null ? v as int : null)(json['limit']), + notSenders = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['not_senders']), + notTypes = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['not_types']), + senders = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['senders']), + types = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['types']); + Map toJson() { + final limit = this.limit; + final notSenders = this.notSenders; + final notTypes = this.notTypes; + final senders = this.senders; + final types = this.types; + return { + if (limit != null) 'limit': limit, + if (notSenders != null) 'not_senders': notSenders.map((v) => v).toList(), + if (notTypes != null) 'not_types': notTypes.map((v) => v).toList(), + if (senders != null) 'senders': senders.map((v) => v).toList(), + if (types != null) 'types': types.map((v) => v).toList(), + }; + } + + /// The maximum number of events to return. + 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. + List? notSenders; + + /// A list of event types to exclude. If this list is absent then no event types are excluded. A matching type will be excluded even if it is listed in the `'types'` filter. A '*' can be used as a wildcard to match any sequence of characters. + List? notTypes; + + /// A list of senders IDs to include. If this list is absent then all senders are included. + List? senders; + + /// A list of event types to include. If this list is absent then all event types are included. A `'*'` can be used as a wildcard to match any sequence of characters. + List? types; +} + +/// +@_NameSource('spec') +class RoomEventFilter { + RoomEventFilter({ + this.containsUrl, + this.includeRedundantMembers, + this.lazyLoadMembers, + this.notRooms, + this.rooms, + this.unreadThreadNotifications, + }); + + RoomEventFilter.fromJson(Map json) + : containsUrl = + ((v) => v != null ? v as bool : null)(json['contains_url']), + includeRedundantMembers = ((v) => + v != null ? v as bool : null)(json['include_redundant_members']), + lazyLoadMembers = + ((v) => v != null ? v as bool : null)(json['lazy_load_members']), + notRooms = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['not_rooms']), + rooms = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['rooms']), + unreadThreadNotifications = ((v) => + v != null ? v as bool : null)(json['unread_thread_notifications']); + Map toJson() { + final containsUrl = this.containsUrl; + final includeRedundantMembers = this.includeRedundantMembers; + final lazyLoadMembers = this.lazyLoadMembers; + final notRooms = this.notRooms; + final rooms = this.rooms; + final unreadThreadNotifications = this.unreadThreadNotifications; + return { + if (containsUrl != null) 'contains_url': containsUrl, + if (includeRedundantMembers != null) + 'include_redundant_members': includeRedundantMembers, + if (lazyLoadMembers != null) 'lazy_load_members': lazyLoadMembers, + if (notRooms != null) 'not_rooms': notRooms.map((v) => v).toList(), + if (rooms != null) 'rooms': rooms.map((v) => v).toList(), + if (unreadThreadNotifications != null) + 'unread_thread_notifications': unreadThreadNotifications, + }; + } + + /// If `true`, includes only events with a `url` key in their content. If `false`, excludes those events. If omitted, `url` key is not considered for filtering. + bool? containsUrl; + + /// If `true`, sends all membership events for all events, even if they have already + /// been sent to the client. Does not + /// apply unless `lazy_load_members` is `true`. See + /// [Lazy-loading room members](https://spec.matrix.org/unstable/client-server-api/#lazy-loading-room-members) + /// for more information. Defaults to `false`. + bool? includeRedundantMembers; + + /// If `true`, enables lazy-loading of membership events. See + /// [Lazy-loading room members](https://spec.matrix.org/unstable/client-server-api/#lazy-loading-room-members) + /// for more information. Defaults to `false`. + bool? lazyLoadMembers; + + /// A list of room IDs to exclude. If this list is absent then no rooms are excluded. A matching room will be excluded even if it is listed in the `'rooms'` filter. + List? notRooms; + + /// A list of room IDs to include. If this list is absent then all rooms are included. + List? rooms; + + /// If `true`, enables per-[thread](https://spec.matrix.org/unstable/client-server-api/#threading) notification + /// counts. Only applies to the `/sync` endpoint. Defaults to `false`. + bool? unreadThreadNotifications; +} + +/// +@_NameSource('rule override generated') +class SearchFilter implements EventFilter, RoomEventFilter { + SearchFilter({ + this.limit, + this.notSenders, + this.notTypes, + this.senders, + this.types, + this.containsUrl, + this.includeRedundantMembers, + this.lazyLoadMembers, + this.notRooms, + this.rooms, + this.unreadThreadNotifications, + }); + + SearchFilter.fromJson(Map json) + : limit = ((v) => v != null ? v as int : null)(json['limit']), + notSenders = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['not_senders']), + notTypes = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['not_types']), + senders = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['senders']), + types = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['types']), + containsUrl = + ((v) => v != null ? v as bool : null)(json['contains_url']), + includeRedundantMembers = ((v) => + v != null ? v as bool : null)(json['include_redundant_members']), + lazyLoadMembers = + ((v) => v != null ? v as bool : null)(json['lazy_load_members']), + notRooms = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['not_rooms']), + rooms = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['rooms']), + unreadThreadNotifications = ((v) => + v != null ? v as bool : null)(json['unread_thread_notifications']); + Map toJson() { + final limit = this.limit; + final notSenders = this.notSenders; + final notTypes = this.notTypes; + final senders = this.senders; + final types = this.types; + final containsUrl = this.containsUrl; + final includeRedundantMembers = this.includeRedundantMembers; + final lazyLoadMembers = this.lazyLoadMembers; + final notRooms = this.notRooms; + final rooms = this.rooms; + final unreadThreadNotifications = this.unreadThreadNotifications; + return { + if (limit != null) 'limit': limit, + if (notSenders != null) 'not_senders': notSenders.map((v) => v).toList(), + if (notTypes != null) 'not_types': notTypes.map((v) => v).toList(), + if (senders != null) 'senders': senders.map((v) => v).toList(), + if (types != null) 'types': types.map((v) => v).toList(), + if (containsUrl != null) 'contains_url': containsUrl, + if (includeRedundantMembers != null) + 'include_redundant_members': includeRedundantMembers, + if (lazyLoadMembers != null) 'lazy_load_members': lazyLoadMembers, + if (notRooms != null) 'not_rooms': notRooms.map((v) => v).toList(), + if (rooms != null) 'rooms': rooms.map((v) => v).toList(), + if (unreadThreadNotifications != null) + 'unread_thread_notifications': unreadThreadNotifications, + }; + } + + /// The maximum number of events to return. + 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. + List? notSenders; + + /// A list of event types to exclude. If this list is absent then no event types are excluded. A matching type will be excluded even if it is listed in the `'types'` filter. A '*' can be used as a wildcard to match any sequence of characters. + List? notTypes; + + /// A list of senders IDs to include. If this list is absent then all senders are included. + List? senders; + + /// A list of event types to include. If this list is absent then all event types are included. A `'*'` can be used as a wildcard to match any sequence of characters. + List? types; + + /// If `true`, includes only events with a `url` key in their content. If `false`, excludes those events. If omitted, `url` key is not considered for filtering. + bool? containsUrl; + + /// If `true`, sends all membership events for all events, even if they have already + /// been sent to the client. Does not + /// apply unless `lazy_load_members` is `true`. See + /// [Lazy-loading room members](https://spec.matrix.org/unstable/client-server-api/#lazy-loading-room-members) + /// for more information. Defaults to `false`. + bool? includeRedundantMembers; + + /// If `true`, enables lazy-loading of membership events. See + /// [Lazy-loading room members](https://spec.matrix.org/unstable/client-server-api/#lazy-loading-room-members) + /// for more information. Defaults to `false`. + bool? lazyLoadMembers; + + /// A list of room IDs to exclude. If this list is absent then no rooms are excluded. A matching room will be excluded even if it is listed in the `'rooms'` filter. + List? notRooms; + + /// A list of room IDs to include. If this list is absent then all rooms are included. + List? rooms; + + /// If `true`, enables per-[thread](https://spec.matrix.org/unstable/client-server-api/#threading) notification + /// counts. Only applies to the `/sync` endpoint. Defaults to `false`. + bool? unreadThreadNotifications; +} + +/// +@_NameSource('rule override generated') +@EnhancedEnum() +enum GroupKey { + @EnhancedEnumValue(name: 'room_id') + roomId, + @EnhancedEnumValue(name: 'sender') + sender +} + +/// Configuration for group. +@_NameSource('spec') +class Group { + Group({ + this.key, + }); + + Group.fromJson(Map json) + : key = ((v) => v != null + ? GroupKey.values.fromString(v as String)! + : null)(json['key']); + Map toJson() { + final key = this.key; + return { + if (key != null) 'key': key.name, + }; + } + + /// Key that defines the group. + GroupKey? key; +} + +/// +@_NameSource('spec') +class Groupings { + Groupings({ + this.groupBy, + }); + + Groupings.fromJson(Map json) + : groupBy = ((v) => v != null + ? (v as List) + .map((v) => Group.fromJson(v as Map)) + .toList() + : null)(json['group_by']); + Map toJson() { + final groupBy = this.groupBy; + return { + if (groupBy != null) 'group_by': groupBy.map((v) => v.toJson()).toList(), + }; + } + + /// List of groups to request. + List? groupBy; +} + +/// +@_NameSource('rule override generated') +@EnhancedEnum() +enum KeyKind { + @EnhancedEnumValue(name: 'content.body') + contentBody, + @EnhancedEnumValue(name: 'content.name') + contentName, + @EnhancedEnumValue(name: 'content.topic') + contentTopic +} + +/// +@_NameSource('rule override generated') +@EnhancedEnum() +enum SearchOrder { + @EnhancedEnumValue(name: 'rank') + rank, + @EnhancedEnumValue(name: 'recent') + recent +} + +/// +@_NameSource('spec') +class RoomEventsCriteria { + RoomEventsCriteria({ + this.eventContext, + this.filter, + this.groupings, + this.includeState, + this.keys, + this.orderBy, + required this.searchTerm, + }); + + RoomEventsCriteria.fromJson(Map json) + : eventContext = ((v) => v != null + ? IncludeEventContext.fromJson(v as Map) + : null)(json['event_context']), + filter = ((v) => v != null + ? SearchFilter.fromJson(v as Map) + : null)(json['filter']), + groupings = ((v) => v != null + ? Groupings.fromJson(v as Map) + : null)(json['groupings']), + includeState = + ((v) => v != null ? v as bool : null)(json['include_state']), + keys = ((v) => v != null + ? (v as List) + .map((v) => KeyKind.values.fromString(v as String)!) + .toList() + : null)(json['keys']), + orderBy = ((v) => v != null + ? SearchOrder.values.fromString(v as String)! + : null)(json['order_by']), + searchTerm = json['search_term'] as String; + Map toJson() { + final eventContext = this.eventContext; + final filter = this.filter; + final groupings = this.groupings; + final includeState = this.includeState; + final keys = this.keys; + final orderBy = this.orderBy; + return { + if (eventContext != null) 'event_context': eventContext.toJson(), + if (filter != null) 'filter': filter.toJson(), + if (groupings != null) 'groupings': groupings.toJson(), + if (includeState != null) 'include_state': includeState, + if (keys != null) 'keys': keys.map((v) => v.name).toList(), + if (orderBy != null) 'order_by': orderBy.name, + 'search_term': searchTerm, + }; + } + + /// Configures whether any context for the events + /// returned are included in the response. + IncludeEventContext? eventContext; + + /// This takes a [filter](https://spec.matrix.org/unstable/client-server-api/#filtering). + SearchFilter? filter; + + /// Requests that the server partitions the result set + /// based on the provided list of keys. + Groupings? groupings; + + /// Requests the server return the current state for + /// each room returned. + bool? includeState; + + /// The keys to search. Defaults to all. + List? keys; + + /// The order in which to search for results. + /// By default, this is `"rank"`. + SearchOrder? orderBy; + + /// The string to search events for + String searchTerm; +} + +/// +@_NameSource('spec') +class Categories { + Categories({ + this.roomEvents, + }); + + Categories.fromJson(Map json) + : roomEvents = ((v) => v != null + ? RoomEventsCriteria.fromJson(v as Map) + : null)(json['room_events']); + Map toJson() { + final roomEvents = this.roomEvents; + return { + if (roomEvents != null) 'room_events': roomEvents.toJson(), + }; + } + + /// Mapping of category name to search criteria. + RoomEventsCriteria? roomEvents; +} + +/// The results for a particular group value. +@_NameSource('spec') +class GroupValue { + GroupValue({ + this.nextBatch, + this.order, + this.results, + }); + + GroupValue.fromJson(Map json) + : nextBatch = ((v) => v != null ? v as String : null)(json['next_batch']), + order = ((v) => v != null ? v as int : null)(json['order']), + results = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['results']); + Map toJson() { + final nextBatch = this.nextBatch; + final order = this.order; + final results = this.results; + return { + if (nextBatch != null) 'next_batch': nextBatch, + if (order != null) 'order': order, + if (results != null) 'results': results.map((v) => v).toList(), + }; + } + + /// Token that can be used to get the next batch + /// of results in the group, by passing as the + /// `next_batch` parameter to the next call. If + /// this field is absent, there are no more + /// results in this group. + String? nextBatch; + + /// Key that can be used to order different + /// groups. + int? order; + + /// Which results are in this group. + List? results; +} + +/// +@_NameSource('spec') +class UserProfile { + UserProfile({ + this.avatarUrl, + this.displayname, + }); + + UserProfile.fromJson(Map json) + : avatarUrl = ((v) => + v != null ? Uri.parse(v as String) : null)(json['avatar_url']), + displayname = + ((v) => v != null ? v as String : null)(json['displayname']); + Map toJson() { + final avatarUrl = this.avatarUrl; + final displayname = this.displayname; + return { + if (avatarUrl != null) 'avatar_url': avatarUrl.toString(), + if (displayname != null) 'displayname': displayname, + }; + } + + /// + Uri? avatarUrl; + + /// + String? displayname; +} + +/// +@_NameSource('rule override spec') +class SearchResultsEventContext { + SearchResultsEventContext({ + this.end, + this.eventsAfter, + this.eventsBefore, + this.profileInfo, + this.start, + }); + + SearchResultsEventContext.fromJson(Map json) + : end = ((v) => v != null ? v as String : null)(json['end']), + eventsAfter = ((v) => v != null + ? (v as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList() + : null)(json['events_after']), + eventsBefore = ((v) => v != null + ? (v as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList() + : null)(json['events_before']), + profileInfo = ((v) => v != null + ? (v as Map).map((k, v) => + MapEntry(k, UserProfile.fromJson(v as Map))) + : null)(json['profile_info']), + start = ((v) => v != null ? v as String : null)(json['start']); + Map toJson() { + final end = this.end; + final eventsAfter = this.eventsAfter; + final eventsBefore = this.eventsBefore; + final profileInfo = this.profileInfo; + final start = this.start; + return { + if (end != null) 'end': end, + if (eventsAfter != null) + 'events_after': eventsAfter.map((v) => v.toJson()).toList(), + if (eventsBefore != null) + 'events_before': eventsBefore.map((v) => v.toJson()).toList(), + if (profileInfo != null) + 'profile_info': profileInfo.map((k, v) => MapEntry(k, v.toJson())), + if (start != null) 'start': start, + }; + } + + /// Pagination token for the end of the chunk + String? end; + + /// Events just after the result. + List? eventsAfter; + + /// Events just before the result. + List? eventsBefore; + + /// The historic profile information of the + /// users that sent the events returned. + /// + /// The `string` key is the user ID for which + /// the profile belongs to. + Map? profileInfo; + + /// Pagination token for the start of the chunk + String? start; +} + +/// The result object. +@_NameSource('spec') +class Result { + Result({ + this.context, + this.rank, + this.result, + }); + + Result.fromJson(Map json) + : context = ((v) => v != null + ? SearchResultsEventContext.fromJson(v as Map) + : null)(json['context']), + rank = ((v) => v != null ? (v as num).toDouble() : null)(json['rank']), + result = ((v) => v != null + ? MatrixEvent.fromJson(v as Map) + : null)(json['result']); + Map toJson() { + final context = this.context; + final rank = this.rank; + final result = this.result; + return { + if (context != null) 'context': context.toJson(), + if (rank != null) 'rank': rank, + if (result != null) 'result': result.toJson(), + }; + } + + /// Context for result, if requested. + SearchResultsEventContext? context; + + /// A number that describes how closely this result matches the search. Higher is closer. + double? rank; + + /// The event that matched. + MatrixEvent? result; +} + +/// +@_NameSource('spec') +class ResultRoomEvents { + ResultRoomEvents({ + this.count, + this.groups, + this.highlights, + this.nextBatch, + this.results, + this.state, + }); + + ResultRoomEvents.fromJson(Map json) + : count = ((v) => v != null ? v as int : null)(json['count']), + groups = ((v) => v != null + ? (v as Map).map((k, v) => MapEntry( + k, + (v as Map).map((k, v) => MapEntry( + k, GroupValue.fromJson(v as Map))))) + : null)(json['groups']), + highlights = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['highlights']), + nextBatch = ((v) => v != null ? v as String : null)(json['next_batch']), + results = ((v) => v != null + ? (v as List) + .map((v) => Result.fromJson(v as Map)) + .toList() + : null)(json['results']), + state = ((v) => v != null + ? (v as Map).map((k, v) => MapEntry( + k, + (v as List) + .map((v) => MatrixEvent.fromJson(v as Map)) + .toList())) + : null)(json['state']); + Map toJson() { + final count = this.count; + final groups = this.groups; + final highlights = this.highlights; + final nextBatch = this.nextBatch; + final results = this.results; + final state = this.state; + return { + if (count != null) 'count': count, + if (groups != null) + 'groups': groups.map( + (k, v) => MapEntry(k, v.map((k, v) => MapEntry(k, v.toJson())))), + if (highlights != null) 'highlights': highlights.map((v) => v).toList(), + if (nextBatch != null) 'next_batch': nextBatch, + if (results != null) 'results': results.map((v) => v.toJson()).toList(), + if (state != null) + 'state': + state.map((k, v) => MapEntry(k, v.map((v) => v.toJson()).toList())), + }; + } + + /// An approximate count of the total number of results found. + int? count; + + /// Any groups that were requested. + /// + /// The outer `string` key is the group key requested (eg: `room_id` + /// or `sender`). The inner `string` key is the grouped value (eg: + /// a room's ID or a user's ID). + Map>? groups; + + /// List of words which should be highlighted, useful for stemming which may change the query terms. + List? highlights; + + /// Token that can be used to get the next batch of + /// results, by passing as the `next_batch` parameter to + /// the next call. If this field is absent, there are no + /// more results. + String? nextBatch; + + /// List of results in the requested order. + List? results; + + /// The current state for every room in the results. + /// 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 + /// Event` array belongs to. + Map>? state; +} + +/// +@_NameSource('spec') +class ResultCategories { + ResultCategories({ + this.roomEvents, + }); + + ResultCategories.fromJson(Map json) + : roomEvents = ((v) => v != null + ? ResultRoomEvents.fromJson(v as Map) + : null)(json['room_events']); + Map toJson() { + final roomEvents = this.roomEvents; + return { + if (roomEvents != null) 'room_events': roomEvents.toJson(), + }; + } + + /// Mapping of category name to search criteria. + ResultRoomEvents? roomEvents; +} + +/// +@_NameSource('rule override spec') +class SearchResults { + SearchResults({ + required this.searchCategories, + }); + + SearchResults.fromJson(Map json) + : searchCategories = ResultCategories.fromJson( + json['search_categories'] as Map); + Map toJson() => { + 'search_categories': searchCategories.toJson(), + }; + + /// Describes which categories to search in and their criteria. + ResultCategories searchCategories; +} + +/// +@_NameSource('spec') +class Location { + Location({ + required this.alias, + required this.fields, + required this.protocol, + }); + + Location.fromJson(Map json) + : alias = json['alias'] as String, + fields = json['fields'] as Map, + protocol = json['protocol'] as String; + Map toJson() => { + 'alias': alias, + 'fields': fields, + 'protocol': protocol, + }; + + /// An alias for a matrix room. + String alias; + + /// Information used to identify this third party location. + Map fields; + + /// The protocol ID that the third party location is a part of. + String protocol; +} + +/// Definition of valid values for a field. +@_NameSource('spec') +class FieldType { + FieldType({ + required this.placeholder, + required this.regexp, + }); + + FieldType.fromJson(Map json) + : placeholder = json['placeholder'] as String, + regexp = json['regexp'] as String; + Map toJson() => { + 'placeholder': placeholder, + 'regexp': regexp, + }; + + /// An placeholder serving as a valid example of the field value. + String placeholder; + + /// A regular expression for validation of a field's value. This may be relatively + /// coarse to verify the value as the application service providing this protocol + /// may apply additional validation or filtering. + String regexp; +} + +/// +@_NameSource('spec') +class ProtocolInstance { + ProtocolInstance({ + required this.desc, + required this.fields, + this.icon, + required this.networkId, + }); + + ProtocolInstance.fromJson(Map json) + : desc = json['desc'] as String, + fields = json['fields'] as Map, + icon = ((v) => v != null ? v as String : null)(json['icon']), + networkId = json['network_id'] as String; + Map toJson() { + final icon = this.icon; + return { + 'desc': desc, + 'fields': fields, + if (icon != null) 'icon': icon, + 'network_id': networkId, + }; + } + + /// A human-readable description for the protocol, such as the name. + String desc; + + /// Preset values for `fields` the client may use to search by. + Map fields; + + /// An optional content URI representing the protocol. Overrides the one provided + /// at the higher level Protocol object. + String? icon; + + /// A unique identifier across all instances. + String networkId; +} + +/// +@_NameSource('spec') +class Protocol { + Protocol({ + required this.fieldTypes, + required this.icon, + required this.instances, + required this.locationFields, + required this.userFields, + }); + + Protocol.fromJson(Map json) + : fieldTypes = (json['field_types'] as Map).map((k, v) => + MapEntry(k, FieldType.fromJson(v as Map))), + icon = json['icon'] as String, + instances = (json['instances'] as List) + .map((v) => ProtocolInstance.fromJson(v as Map)) + .toList(), + locationFields = + (json['location_fields'] as List).map((v) => v as String).toList(), + userFields = + (json['user_fields'] as List).map((v) => v as String).toList(); + Map toJson() => { + 'field_types': fieldTypes.map((k, v) => MapEntry(k, v.toJson())), + 'icon': icon, + 'instances': instances.map((v) => v.toJson()).toList(), + 'location_fields': locationFields.map((v) => v).toList(), + 'user_fields': userFields.map((v) => v).toList(), + }; + + /// The type definitions for the fields defined in the `user_fields` and + /// `location_fields`. Each entry in those arrays MUST have an entry here. The + /// `string` key for this object is field name itself. + /// + /// May be an empty object if no fields are defined. + Map fieldTypes; + + /// A content URI representing an icon for the third party protocol. + String icon; + + /// A list of objects representing independent instances of configuration. + /// For example, multiple networks on IRC if multiple are provided by the + /// same application service. + List instances; + + /// 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 + /// 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. + List userFields; +} + +/// +@_NameSource('rule override spec') +class ThirdPartyUser { + ThirdPartyUser({ + required this.fields, + required this.protocol, + required this.userid, + }); + + ThirdPartyUser.fromJson(Map json) + : fields = json['fields'] as Map, + protocol = json['protocol'] as String, + userid = json['userid'] as String; + Map toJson() => { + 'fields': fields, + 'protocol': protocol, + 'userid': userid, + }; + + /// Information used to identify this third party location. + Map fields; + + /// The protocol ID that the third party location is a part of. + String protocol; + + /// A Matrix User ID represting a third party user. + String userid; +} + +/// +@_NameSource('generated') +@EnhancedEnum() +enum EventFormat { + @EnhancedEnumValue(name: 'client') + client, + @EnhancedEnumValue(name: 'federation') + federation +} + +/// +@_NameSource('rule override generated') +class StateFilter implements EventFilter, RoomEventFilter { + StateFilter({ + this.limit, + this.notSenders, + this.notTypes, + this.senders, + this.types, + this.containsUrl, + this.includeRedundantMembers, + this.lazyLoadMembers, + this.notRooms, + this.rooms, + this.unreadThreadNotifications, + }); + + StateFilter.fromJson(Map json) + : limit = ((v) => v != null ? v as int : null)(json['limit']), + notSenders = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['not_senders']), + notTypes = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['not_types']), + senders = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['senders']), + types = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['types']), + containsUrl = + ((v) => v != null ? v as bool : null)(json['contains_url']), + includeRedundantMembers = ((v) => + v != null ? v as bool : null)(json['include_redundant_members']), + lazyLoadMembers = + ((v) => v != null ? v as bool : null)(json['lazy_load_members']), + notRooms = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['not_rooms']), + rooms = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['rooms']), + unreadThreadNotifications = ((v) => + v != null ? v as bool : null)(json['unread_thread_notifications']); + Map toJson() { + final limit = this.limit; + final notSenders = this.notSenders; + final notTypes = this.notTypes; + final senders = this.senders; + final types = this.types; + final containsUrl = this.containsUrl; + final includeRedundantMembers = this.includeRedundantMembers; + final lazyLoadMembers = this.lazyLoadMembers; + final notRooms = this.notRooms; + final rooms = this.rooms; + final unreadThreadNotifications = this.unreadThreadNotifications; + return { + if (limit != null) 'limit': limit, + if (notSenders != null) 'not_senders': notSenders.map((v) => v).toList(), + if (notTypes != null) 'not_types': notTypes.map((v) => v).toList(), + if (senders != null) 'senders': senders.map((v) => v).toList(), + if (types != null) 'types': types.map((v) => v).toList(), + if (containsUrl != null) 'contains_url': containsUrl, + if (includeRedundantMembers != null) + 'include_redundant_members': includeRedundantMembers, + if (lazyLoadMembers != null) 'lazy_load_members': lazyLoadMembers, + if (notRooms != null) 'not_rooms': notRooms.map((v) => v).toList(), + if (rooms != null) 'rooms': rooms.map((v) => v).toList(), + if (unreadThreadNotifications != null) + 'unread_thread_notifications': unreadThreadNotifications, + }; + } + + /// The maximum number of events to return. + 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. + List? notSenders; + + /// A list of event types to exclude. If this list is absent then no event types are excluded. A matching type will be excluded even if it is listed in the `'types'` filter. A '*' can be used as a wildcard to match any sequence of characters. + List? notTypes; + + /// A list of senders IDs to include. If this list is absent then all senders are included. + List? senders; + + /// A list of event types to include. If this list is absent then all event types are included. A `'*'` can be used as a wildcard to match any sequence of characters. + List? types; + + /// If `true`, includes only events with a `url` key in their content. If `false`, excludes those events. If omitted, `url` key is not considered for filtering. + bool? containsUrl; + + /// If `true`, sends all membership events for all events, even if they have already + /// been sent to the client. Does not + /// apply unless `lazy_load_members` is `true`. See + /// [Lazy-loading room members](https://spec.matrix.org/unstable/client-server-api/#lazy-loading-room-members) + /// for more information. Defaults to `false`. + bool? includeRedundantMembers; + + /// If `true`, enables lazy-loading of membership events. See + /// [Lazy-loading room members](https://spec.matrix.org/unstable/client-server-api/#lazy-loading-room-members) + /// for more information. Defaults to `false`. + bool? lazyLoadMembers; + + /// A list of room IDs to exclude. If this list is absent then no rooms are excluded. A matching room will be excluded even if it is listed in the `'rooms'` filter. + List? notRooms; + + /// A list of room IDs to include. If this list is absent then all rooms are included. + List? rooms; + + /// If `true`, enables per-[thread](https://spec.matrix.org/unstable/client-server-api/#threading) notification + /// counts. Only applies to the `/sync` endpoint. Defaults to `false`. + bool? unreadThreadNotifications; +} + +/// +@_NameSource('spec') +class RoomFilter { + RoomFilter({ + this.accountData, + this.ephemeral, + this.includeLeave, + this.notRooms, + this.rooms, + this.state, + this.timeline, + }); + + RoomFilter.fromJson(Map json) + : accountData = ((v) => v != null + ? StateFilter.fromJson(v as Map) + : null)(json['account_data']), + ephemeral = ((v) => v != null + ? StateFilter.fromJson(v as Map) + : null)(json['ephemeral']), + includeLeave = + ((v) => v != null ? v as bool : null)(json['include_leave']), + notRooms = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['not_rooms']), + rooms = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['rooms']), + state = ((v) => v != null + ? StateFilter.fromJson(v as Map) + : null)(json['state']), + timeline = ((v) => v != null + ? StateFilter.fromJson(v as Map) + : null)(json['timeline']); + Map toJson() { + final accountData = this.accountData; + final ephemeral = this.ephemeral; + final includeLeave = this.includeLeave; + final notRooms = this.notRooms; + final rooms = this.rooms; + final state = this.state; + final timeline = this.timeline; + return { + if (accountData != null) 'account_data': accountData.toJson(), + if (ephemeral != null) 'ephemeral': ephemeral.toJson(), + if (includeLeave != null) 'include_leave': includeLeave, + if (notRooms != null) 'not_rooms': notRooms.map((v) => v).toList(), + if (rooms != null) 'rooms': rooms.map((v) => v).toList(), + if (state != null) 'state': state.toJson(), + if (timeline != null) 'timeline': timeline.toJson(), + }; + } + + /// The per user account data to include for rooms. + StateFilter? accountData; + + /// The ephemeral events to include for rooms. These are the events that appear in the `ephemeral` property in the `/sync` response. + StateFilter? ephemeral; + + /// Include rooms that the user has left in the sync, default false + bool? includeLeave; + + /// A list of room IDs to exclude. If this list is absent then no rooms are excluded. A matching room will be excluded even if it is listed in the `'rooms'` filter. This filter is applied before the filters in `ephemeral`, `state`, `timeline` or `account_data` + List? notRooms; + + /// A list of room IDs to include. If this list is absent then all rooms are included. This filter is applied before the filters in `ephemeral`, `state`, `timeline` or `account_data` + List? rooms; + + /// The state events to include for rooms. + StateFilter? state; + + /// The message and state update events to include for rooms. + StateFilter? timeline; +} + +/// +@_NameSource('spec') +class Filter { + Filter({ + this.accountData, + this.eventFields, + this.eventFormat, + this.presence, + this.room, + }); + + Filter.fromJson(Map json) + : accountData = ((v) => v != null + ? EventFilter.fromJson(v as Map) + : null)(json['account_data']), + eventFields = ((v) => v != null + ? (v as List).map((v) => v as String).toList() + : null)(json['event_fields']), + eventFormat = ((v) => v != null + ? EventFormat.values.fromString(v as String)! + : null)(json['event_format']), + presence = ((v) => v != null + ? EventFilter.fromJson(v as Map) + : null)(json['presence']), + room = ((v) => v != null + ? RoomFilter.fromJson(v as Map) + : null)(json['room']); + Map toJson() { + final accountData = this.accountData; + final eventFields = this.eventFields; + final eventFormat = this.eventFormat; + final presence = this.presence; + final room = this.room; + return { + if (accountData != null) 'account_data': accountData.toJson(), + if (eventFields != null) + 'event_fields': eventFields.map((v) => v).toList(), + if (eventFormat != null) 'event_format': eventFormat.name, + if (presence != null) 'presence': presence.toJson(), + if (room != null) 'room': room.toJson(), + }; + } + + /// 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? 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'. + EventFormat? eventFormat; + + /// The presence updates to include. + EventFilter? presence; + + /// Filters to be applied to room data. + RoomFilter? room; +} + +/// +@_NameSource('spec') +class OpenIdCredentials { + OpenIdCredentials({ + required this.accessToken, + required this.expiresIn, + required this.matrixServerName, + required this.tokenType, + }); + + OpenIdCredentials.fromJson(Map json) + : accessToken = json['access_token'] as String, + expiresIn = json['expires_in'] as int, + matrixServerName = json['matrix_server_name'] as String, + tokenType = json['token_type'] as String; + Map toJson() => { + 'access_token': accessToken, + 'expires_in': expiresIn, + 'matrix_server_name': matrixServerName, + 'token_type': tokenType, + }; + + /// An access token the consumer may use to verify the identity of + /// the person who generated the token. This is given to the federation + /// API `GET /openid/userinfo` to verify the user's identity. + String accessToken; + + /// The number of seconds before this token expires and a new one must + /// be generated. + int expiresIn; + + /// The homeserver domain the consumer should use when attempting to + /// verify the user's identity. + String matrixServerName; + + /// The string `Bearer`. + String tokenType; +} + +/// +@_NameSource('spec') +class Tag { + Tag({ + this.order, + this.additionalProperties = const {}, + }); + + Tag.fromJson(Map json) + : order = + ((v) => v != null ? (v as num).toDouble() : null)(json['order']), + additionalProperties = Map.fromEntries(json.entries + .where((e) => !['order'].contains(e.key)) + .map((e) => MapEntry(e.key, e.value as Object?))); + Map toJson() { + final order = this.order; + return { + ...additionalProperties, + if (order != null) 'order': order, + }; + } + + /// A number in a range `[0,1]` describing a relative + /// position of the room under the given tag. + double? order; + + Map additionalProperties; +} + +/// +@_NameSource('rule override spec') +class Profile { + Profile({ + this.avatarUrl, + this.displayName, + required this.userId, + }); + + Profile.fromJson(Map json) + : avatarUrl = ((v) => + v != null ? Uri.parse(v as String) : null)(json['avatar_url']), + displayName = + ((v) => v != null ? v as String : null)(json['display_name']), + userId = json['user_id'] as String; + Map toJson() { + final avatarUrl = this.avatarUrl; + final displayName = this.displayName; + return { + if (avatarUrl != null) 'avatar_url': avatarUrl.toString(), + if (displayName != null) 'display_name': displayName, + 'user_id': userId, + }; + } + + /// The avatar url, as an MXC, if one exists. + Uri? avatarUrl; + + /// The display name of the user, if one exists. + String? displayName; + + /// The user's matrix user ID. + String userId; +} + +/// +@_NameSource('generated') +class SearchUserDirectoryResponse { + SearchUserDirectoryResponse({ + required this.limited, + required this.results, + }); + + SearchUserDirectoryResponse.fromJson(Map json) + : limited = json['limited'] as bool, + results = (json['results'] as List) + .map((v) => Profile.fromJson(v as Map)) + .toList(); + Map toJson() => { + 'limited': limited, + 'results': results.map((v) => v.toJson()).toList(), + }; + + /// Indicates if the result list has been truncated by the limit. + bool limited; + + /// Ordered by rank and then whether or not profile info is available. + List results; +} + +/// +@_NameSource('rule override generated') +class TurnServerCredentials { + TurnServerCredentials({ + required this.password, + required this.ttl, + required this.uris, + required this.username, + }); + + TurnServerCredentials.fromJson(Map json) + : password = json['password'] as String, + ttl = json['ttl'] as int, + uris = (json['uris'] as List).map((v) => v as String).toList(), + username = json['username'] as String; + Map toJson() => { + 'password': password, + 'ttl': ttl, + 'uris': uris.map((v) => v).toList(), + 'username': username, + }; + + /// The password to use. + String password; + + /// The time-to-live in seconds + int ttl; + + /// A list of TURN URIs + List uris; + + /// The username to use. + String username; +} + +/// +@_NameSource('generated') +class GetVersionsResponse { + GetVersionsResponse({ + this.unstableFeatures, + required this.versions, + }); + + GetVersionsResponse.fromJson(Map json) + : unstableFeatures = ((v) => v != null + ? (v as Map).map((k, v) => MapEntry(k, v as bool)) + : null)(json['unstable_features']), + versions = (json['versions'] as List).map((v) => v as String).toList(); + Map toJson() { + final unstableFeatures = this.unstableFeatures; + return { + if (unstableFeatures != null) + 'unstable_features': unstableFeatures.map((k, v) => MapEntry(k, v)), + 'versions': versions.map((v) => v).toList(), + }; + } + + /// Experimental features the server supports. Features not listed here, + /// or the lack of this property all together, indicate that a feature is + /// not supported. + Map? unstableFeatures; + + /// The supported versions. + List versions; +} + +/// +@_NameSource('rule override generated') +class ServerConfig { + ServerConfig({ + this.mUploadSize, + }); + + ServerConfig.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('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 +} diff --git a/lib/matrix_api_lite/generated/model.g.dart b/lib/matrix_api_lite/generated/model.g.dart new file mode 100644 index 00000000..08265a66 --- /dev/null +++ b/lib/matrix_api_lite/generated/model.g.dart @@ -0,0 +1,857 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'model.dart'; + +// ************************************************************************** +// EnhancedEnumGenerator +// ************************************************************************** + +extension DirectionFromStringExtension on Iterable { + Direction? fromString(String val) { + final override = { + 'b': Direction.b, + 'f': Direction.f, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension DirectionEnhancedEnum on Direction { + @override +// ignore: override_on_non_overriding_member + String get name => { + Direction.b: 'b', + Direction.f: 'f', + }[this]!; + bool get isB => this == Direction.b; + bool get isF => this == Direction.f; + T when({ + required T Function() b, + required T Function() f, + }) => + { + Direction.b: b, + Direction.f: f, + }[this]!(); + T maybeWhen({ + T? Function()? b, + T? Function()? f, + required T Function() orElse, + }) => + { + Direction.b: b, + Direction.f: f, + }[this] + ?.call() ?? + orElse(); +} + +extension IncludeFromStringExtension on Iterable { + Include? fromString(String val) { + final override = { + 'all': Include.all, + 'participated': Include.participated, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension IncludeEnhancedEnum on Include { + @override +// ignore: override_on_non_overriding_member + String get name => { + Include.all: 'all', + Include.participated: 'participated', + }[this]!; + bool get isAll => this == Include.all; + bool get isParticipated => this == Include.participated; + T when({ + required T Function() all, + required T Function() participated, + }) => + { + Include.all: all, + Include.participated: participated, + }[this]!(); + T maybeWhen({ + T? Function()? all, + T? Function()? participated, + required T Function() orElse, + }) => + { + Include.all: all, + Include.participated: participated, + }[this] + ?.call() ?? + orElse(); +} + +extension ThirdPartyIdentifierMediumFromStringExtension + on Iterable { + ThirdPartyIdentifierMedium? fromString(String val) { + final override = { + 'email': ThirdPartyIdentifierMedium.email, + 'msisdn': ThirdPartyIdentifierMedium.msisdn, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension ThirdPartyIdentifierMediumEnhancedEnum on ThirdPartyIdentifierMedium { + @override +// ignore: override_on_non_overriding_member + String get name => { + ThirdPartyIdentifierMedium.email: 'email', + ThirdPartyIdentifierMedium.msisdn: 'msisdn', + }[this]!; + bool get isEmail => this == ThirdPartyIdentifierMedium.email; + bool get isMsisdn => this == ThirdPartyIdentifierMedium.msisdn; + T when({ + required T Function() email, + required T Function() msisdn, + }) => + { + ThirdPartyIdentifierMedium.email: email, + ThirdPartyIdentifierMedium.msisdn: msisdn, + }[this]!(); + T maybeWhen({ + T? Function()? email, + T? Function()? msisdn, + required T Function() orElse, + }) => + { + ThirdPartyIdentifierMedium.email: email, + ThirdPartyIdentifierMedium.msisdn: msisdn, + }[this] + ?.call() ?? + orElse(); +} + +extension IdServerUnbindResultFromStringExtension + on Iterable { + IdServerUnbindResult? fromString(String val) { + final override = { + 'no-support': IdServerUnbindResult.noSupport, + 'success': IdServerUnbindResult.success, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension IdServerUnbindResultEnhancedEnum on IdServerUnbindResult { + @override +// ignore: override_on_non_overriding_member + String get name => { + IdServerUnbindResult.noSupport: 'no-support', + IdServerUnbindResult.success: 'success', + }[this]!; + bool get isNoSupport => this == IdServerUnbindResult.noSupport; + bool get isSuccess => this == IdServerUnbindResult.success; + T when({ + required T Function() noSupport, + required T Function() success, + }) => + { + IdServerUnbindResult.noSupport: noSupport, + IdServerUnbindResult.success: success, + }[this]!(); + T maybeWhen({ + T? Function()? noSupport, + T? Function()? success, + required T Function() orElse, + }) => + { + IdServerUnbindResult.noSupport: noSupport, + IdServerUnbindResult.success: success, + }[this] + ?.call() ?? + orElse(); +} + +extension RoomVersionAvailableFromStringExtension + on Iterable { + RoomVersionAvailable? fromString(String val) { + final override = { + 'stable': RoomVersionAvailable.stable, + 'unstable': RoomVersionAvailable.unstable, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension RoomVersionAvailableEnhancedEnum on RoomVersionAvailable { + @override +// ignore: override_on_non_overriding_member + String get name => { + RoomVersionAvailable.stable: 'stable', + RoomVersionAvailable.unstable: 'unstable', + }[this]!; + bool get isStable => this == RoomVersionAvailable.stable; + bool get isUnstable => this == RoomVersionAvailable.unstable; + T when({ + required T Function() stable, + required T Function() unstable, + }) => + { + RoomVersionAvailable.stable: stable, + RoomVersionAvailable.unstable: unstable, + }[this]!(); + T maybeWhen({ + T? Function()? stable, + T? Function()? unstable, + required T Function() orElse, + }) => + { + RoomVersionAvailable.stable: stable, + RoomVersionAvailable.unstable: unstable, + }[this] + ?.call() ?? + orElse(); +} + +extension CreateRoomPresetFromStringExtension on Iterable { + CreateRoomPreset? fromString(String val) { + final override = { + 'private_chat': CreateRoomPreset.privateChat, + 'public_chat': CreateRoomPreset.publicChat, + 'trusted_private_chat': CreateRoomPreset.trustedPrivateChat, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension CreateRoomPresetEnhancedEnum on CreateRoomPreset { + @override +// ignore: override_on_non_overriding_member + String get name => { + CreateRoomPreset.privateChat: 'private_chat', + CreateRoomPreset.publicChat: 'public_chat', + CreateRoomPreset.trustedPrivateChat: 'trusted_private_chat', + }[this]!; + bool get isPrivateChat => this == CreateRoomPreset.privateChat; + bool get isPublicChat => this == CreateRoomPreset.publicChat; + bool get isTrustedPrivateChat => this == CreateRoomPreset.trustedPrivateChat; + T when({ + required T Function() privateChat, + required T Function() publicChat, + required T Function() trustedPrivateChat, + }) => + { + CreateRoomPreset.privateChat: privateChat, + CreateRoomPreset.publicChat: publicChat, + CreateRoomPreset.trustedPrivateChat: trustedPrivateChat, + }[this]!(); + T maybeWhen({ + T? Function()? privateChat, + T? Function()? publicChat, + T? Function()? trustedPrivateChat, + required T Function() orElse, + }) => + { + CreateRoomPreset.privateChat: privateChat, + CreateRoomPreset.publicChat: publicChat, + CreateRoomPreset.trustedPrivateChat: trustedPrivateChat, + }[this] + ?.call() ?? + orElse(); +} + +extension VisibilityFromStringExtension on Iterable { + Visibility? fromString(String val) { + final override = { + 'private': Visibility.private, + 'public': Visibility.public, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension VisibilityEnhancedEnum on Visibility { + @override +// ignore: override_on_non_overriding_member + String get name => { + Visibility.private: 'private', + Visibility.public: 'public', + }[this]!; + bool get isPrivate => this == Visibility.private; + bool get isPublic => this == Visibility.public; + T when({ + required T Function() private, + required T Function() public, + }) => + { + Visibility.private: private, + Visibility.public: public, + }[this]!(); + T maybeWhen({ + T? Function()? private, + T? Function()? public, + required T Function() orElse, + }) => + { + Visibility.private: private, + Visibility.public: public, + }[this] + ?.call() ?? + orElse(); +} + +extension LoginTypeFromStringExtension on Iterable { + LoginType? fromString(String val) { + final override = { + 'm.login.password': LoginType.mLoginPassword, + 'm.login.token': LoginType.mLoginToken, + }[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', + }[this]!; + bool get isMLoginPassword => this == LoginType.mLoginPassword; + bool get isMLoginToken => this == LoginType.mLoginToken; + T when({ + required T Function() mLoginPassword, + required T Function() mLoginToken, + }) => + { + LoginType.mLoginPassword: mLoginPassword, + LoginType.mLoginToken: mLoginToken, + }[this]!(); + T maybeWhen({ + T? Function()? mLoginPassword, + T? Function()? mLoginToken, + required T Function() orElse, + }) => + { + LoginType.mLoginPassword: mLoginPassword, + LoginType.mLoginToken: mLoginToken, + }[this] + ?.call() ?? + orElse(); +} + +extension PresenceTypeFromStringExtension on Iterable { + PresenceType? fromString(String val) { + final override = { + 'offline': PresenceType.offline, + 'online': PresenceType.online, + 'unavailable': PresenceType.unavailable, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension PresenceTypeEnhancedEnum on PresenceType { + @override +// ignore: override_on_non_overriding_member + String get name => { + PresenceType.offline: 'offline', + PresenceType.online: 'online', + PresenceType.unavailable: 'unavailable', + }[this]!; + bool get isOffline => this == PresenceType.offline; + bool get isOnline => this == PresenceType.online; + bool get isUnavailable => this == PresenceType.unavailable; + T when({ + required T Function() offline, + required T Function() online, + required T Function() unavailable, + }) => + { + PresenceType.offline: offline, + PresenceType.online: online, + PresenceType.unavailable: unavailable, + }[this]!(); + T maybeWhen({ + T? Function()? offline, + T? Function()? online, + T? Function()? unavailable, + required T Function() orElse, + }) => + { + PresenceType.offline: offline, + PresenceType.online: online, + PresenceType.unavailable: unavailable, + }[this] + ?.call() ?? + orElse(); +} + +extension PushRuleKindFromStringExtension on Iterable { + PushRuleKind? fromString(String val) { + final override = { + 'content': PushRuleKind.content, + 'override': PushRuleKind.override, + 'room': PushRuleKind.room, + 'sender': PushRuleKind.sender, + 'underride': PushRuleKind.underride, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension PushRuleKindEnhancedEnum on PushRuleKind { + @override +// ignore: override_on_non_overriding_member + String get name => { + PushRuleKind.content: 'content', + PushRuleKind.override: 'override', + PushRuleKind.room: 'room', + PushRuleKind.sender: 'sender', + PushRuleKind.underride: 'underride', + }[this]!; + bool get isContent => this == PushRuleKind.content; + bool get isOverride => this == PushRuleKind.override; + bool get isRoom => this == PushRuleKind.room; + bool get isSender => this == PushRuleKind.sender; + bool get isUnderride => this == PushRuleKind.underride; + T when({ + required T Function() content, + required T Function() override, + required T Function() room, + required T Function() sender, + required T Function() underride, + }) => + { + PushRuleKind.content: content, + PushRuleKind.override: override, + PushRuleKind.room: room, + PushRuleKind.sender: sender, + PushRuleKind.underride: underride, + }[this]!(); + T maybeWhen({ + T? Function()? content, + T? Function()? override, + T? Function()? room, + T? Function()? sender, + T? Function()? underride, + required T Function() orElse, + }) => + { + PushRuleKind.content: content, + PushRuleKind.override: override, + PushRuleKind.room: room, + PushRuleKind.sender: sender, + PushRuleKind.underride: underride, + }[this] + ?.call() ?? + orElse(); +} + +extension AccountKindFromStringExtension on Iterable { + AccountKind? fromString(String val) { + final override = { + 'guest': AccountKind.guest, + 'user': AccountKind.user, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension AccountKindEnhancedEnum on AccountKind { + @override +// ignore: override_on_non_overriding_member + String get name => { + AccountKind.guest: 'guest', + AccountKind.user: 'user', + }[this]!; + bool get isGuest => this == AccountKind.guest; + bool get isUser => this == AccountKind.user; + T when({ + required T Function() guest, + required T Function() user, + }) => + { + AccountKind.guest: guest, + AccountKind.user: user, + }[this]!(); + T maybeWhen({ + T? Function()? guest, + T? Function()? user, + required T Function() orElse, + }) => + { + AccountKind.guest: guest, + AccountKind.user: user, + }[this] + ?.call() ?? + orElse(); +} + +extension BackupAlgorithmFromStringExtension on Iterable { + BackupAlgorithm? fromString(String val) { + final override = { + 'm.megolm_backup.v1.curve25519-aes-sha2': + BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension BackupAlgorithmEnhancedEnum on BackupAlgorithm { + @override +// ignore: override_on_non_overriding_member + String get name => { + BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2: + 'm.megolm_backup.v1.curve25519-aes-sha2', + }[this]!; + bool get isMMegolmBackupV1Curve25519AesSha2 => + this == BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2; + T when({ + required T Function() mMegolmBackupV1Curve25519AesSha2, + }) => + { + BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2: + mMegolmBackupV1Curve25519AesSha2, + }[this]!(); + T maybeWhen({ + T? Function()? mMegolmBackupV1Curve25519AesSha2, + required T Function() orElse, + }) => + { + BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2: + mMegolmBackupV1Curve25519AesSha2, + }[this] + ?.call() ?? + orElse(); +} + +extension MembershipFromStringExtension on Iterable { + Membership? fromString(String val) { + final override = { + 'ban': Membership.ban, + 'invite': Membership.invite, + 'join': Membership.join, + 'knock': Membership.knock, + 'leave': Membership.leave, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension MembershipEnhancedEnum on Membership { + @override +// ignore: override_on_non_overriding_member + String get name => { + Membership.ban: 'ban', + Membership.invite: 'invite', + Membership.join: 'join', + Membership.knock: 'knock', + Membership.leave: 'leave', + }[this]!; + bool get isBan => this == Membership.ban; + bool get isInvite => this == Membership.invite; + bool get isJoin => this == Membership.join; + bool get isKnock => this == Membership.knock; + bool get isLeave => this == Membership.leave; + T when({ + required T Function() ban, + required T Function() invite, + required T Function() join, + required T Function() knock, + required T Function() leave, + }) => + { + Membership.ban: ban, + Membership.invite: invite, + Membership.join: join, + Membership.knock: knock, + Membership.leave: leave, + }[this]!(); + T maybeWhen({ + T? Function()? ban, + T? Function()? invite, + T? Function()? join, + T? Function()? knock, + T? Function()? leave, + required T Function() orElse, + }) => + { + Membership.ban: ban, + Membership.invite: invite, + Membership.join: join, + Membership.knock: knock, + Membership.leave: leave, + }[this] + ?.call() ?? + orElse(); +} + +extension ReceiptTypeFromStringExtension on Iterable { + ReceiptType? fromString(String val) { + final override = { + 'm.fully_read': ReceiptType.mFullyRead, + 'm.read': ReceiptType.mRead, + 'm.read.private': ReceiptType.mReadPrivate, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension ReceiptTypeEnhancedEnum on ReceiptType { + @override +// ignore: override_on_non_overriding_member + String get name => { + ReceiptType.mFullyRead: 'm.fully_read', + ReceiptType.mRead: 'm.read', + ReceiptType.mReadPrivate: 'm.read.private', + }[this]!; + bool get isMFullyRead => this == ReceiptType.mFullyRead; + bool get isMRead => this == ReceiptType.mRead; + bool get isMReadPrivate => this == ReceiptType.mReadPrivate; + T when({ + required T Function() mFullyRead, + required T Function() mRead, + required T Function() mReadPrivate, + }) => + { + ReceiptType.mFullyRead: mFullyRead, + ReceiptType.mRead: mRead, + ReceiptType.mReadPrivate: mReadPrivate, + }[this]!(); + T maybeWhen({ + T? Function()? mFullyRead, + T? Function()? mRead, + T? Function()? mReadPrivate, + required T Function() orElse, + }) => + { + ReceiptType.mFullyRead: mFullyRead, + ReceiptType.mRead: mRead, + ReceiptType.mReadPrivate: mReadPrivate, + }[this] + ?.call() ?? + orElse(); +} + +extension GroupKeyFromStringExtension on Iterable { + GroupKey? fromString(String val) { + final override = { + 'room_id': GroupKey.roomId, + 'sender': GroupKey.sender, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension GroupKeyEnhancedEnum on GroupKey { + @override +// ignore: override_on_non_overriding_member + String get name => { + GroupKey.roomId: 'room_id', + GroupKey.sender: 'sender', + }[this]!; + bool get isRoomId => this == GroupKey.roomId; + bool get isSender => this == GroupKey.sender; + T when({ + required T Function() roomId, + required T Function() sender, + }) => + { + GroupKey.roomId: roomId, + GroupKey.sender: sender, + }[this]!(); + T maybeWhen({ + T? Function()? roomId, + T? Function()? sender, + required T Function() orElse, + }) => + { + GroupKey.roomId: roomId, + GroupKey.sender: sender, + }[this] + ?.call() ?? + orElse(); +} + +extension KeyKindFromStringExtension on Iterable { + KeyKind? fromString(String val) { + final override = { + 'content.body': KeyKind.contentBody, + 'content.name': KeyKind.contentName, + 'content.topic': KeyKind.contentTopic, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension KeyKindEnhancedEnum on KeyKind { + @override +// ignore: override_on_non_overriding_member + String get name => { + KeyKind.contentBody: 'content.body', + KeyKind.contentName: 'content.name', + KeyKind.contentTopic: 'content.topic', + }[this]!; + bool get isContentBody => this == KeyKind.contentBody; + bool get isContentName => this == KeyKind.contentName; + bool get isContentTopic => this == KeyKind.contentTopic; + T when({ + required T Function() contentBody, + required T Function() contentName, + required T Function() contentTopic, + }) => + { + KeyKind.contentBody: contentBody, + KeyKind.contentName: contentName, + KeyKind.contentTopic: contentTopic, + }[this]!(); + T maybeWhen({ + T? Function()? contentBody, + T? Function()? contentName, + T? Function()? contentTopic, + required T Function() orElse, + }) => + { + KeyKind.contentBody: contentBody, + KeyKind.contentName: contentName, + KeyKind.contentTopic: contentTopic, + }[this] + ?.call() ?? + orElse(); +} + +extension SearchOrderFromStringExtension on Iterable { + SearchOrder? fromString(String val) { + final override = { + 'rank': SearchOrder.rank, + 'recent': SearchOrder.recent, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension SearchOrderEnhancedEnum on SearchOrder { + @override +// ignore: override_on_non_overriding_member + String get name => { + SearchOrder.rank: 'rank', + SearchOrder.recent: 'recent', + }[this]!; + bool get isRank => this == SearchOrder.rank; + bool get isRecent => this == SearchOrder.recent; + T when({ + required T Function() rank, + required T Function() recent, + }) => + { + SearchOrder.rank: rank, + SearchOrder.recent: recent, + }[this]!(); + T maybeWhen({ + T? Function()? rank, + T? Function()? recent, + required T Function() orElse, + }) => + { + SearchOrder.rank: rank, + SearchOrder.recent: recent, + }[this] + ?.call() ?? + orElse(); +} + +extension EventFormatFromStringExtension on Iterable { + EventFormat? fromString(String val) { + final override = { + 'client': EventFormat.client, + 'federation': EventFormat.federation, + }[val]; +// ignore: unnecessary_this + return this.contains(override) ? override : null; + } +} + +extension EventFormatEnhancedEnum on EventFormat { + @override +// ignore: override_on_non_overriding_member + String get name => { + EventFormat.client: 'client', + EventFormat.federation: 'federation', + }[this]!; + bool get isClient => this == EventFormat.client; + bool get isFederation => this == EventFormat.federation; + T when({ + required T Function() client, + required T Function() federation, + }) => + { + EventFormat.client: client, + EventFormat.federation: federation, + }[this]!(); + T maybeWhen({ + T? Function()? client, + T? Function()? federation, + required T Function() orElse, + }) => + { + EventFormat.client: client, + EventFormat.federation: federation, + }[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(); +} diff --git a/lib/matrix_api_lite/matrix_api.dart b/lib/matrix_api_lite/matrix_api.dart new file mode 100644 index 00000000..605b4932 --- /dev/null +++ b/lib/matrix_api_lite/matrix_api.dart @@ -0,0 +1,240 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:http/http.dart' as http; + +import '../matrix_api_lite.dart'; +import 'generated/api.dart'; + +enum RequestType { GET, POST, PUT, DELETE } + +class MatrixApi extends Api { + /// The homeserver this client is communicating with. + Uri? get homeserver => baseUri; + + set homeserver(Uri? uri) => baseUri = uri; + + /// This is the access token for the matrix client. When it is undefined, then + /// the user needs to sign in first. + String? get accessToken => bearerToken; + + set accessToken(String? token) => bearerToken = token; + + @override + Never unexpectedResponse(http.BaseResponse response, Uint8List body) { + if (response.statusCode >= 400 && response.statusCode < 500) { + final resp = json.decode(utf8.decode(body)); + if (resp is Map) { + throw MatrixException.fromJson(resp); + } + } + super.unexpectedResponse(response, body); + } + + MatrixApi({ + Uri? homeserver, + String? accessToken, + http.Client? httpClient, + }) : super( + httpClient: httpClient, + baseUri: homeserver, + bearerToken: accessToken); + + /// Used for all Matrix json requests using the [c2s API](https://matrix.org/docs/spec/client_server/r0.6.0.html). + /// + /// Throws: FormatException, MatrixException + /// + /// You must first set [this.homeserver] and for some endpoints also + /// [this.accessToken] before you can use this! For example to send a + /// message to a Matrix room with the id '!fjd823j:example.com' you call: + /// ``` + /// final resp = await request( + /// RequestType.PUT, + /// '/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId', + /// data: { + /// 'msgtype': 'm.text', + /// 'body': 'hello' + /// } + /// ); + /// ``` + /// + Future> request( + RequestType type, + String action, { + dynamic data = '', + String contentType = 'application/json', + Map? query, + }) async { + if (homeserver == null) { + throw ('No homeserver specified.'); + } + dynamic json; + (data is! String) ? json = jsonEncode(data) : json = data; + if (data is List || action.startsWith('/media/v3/upload')) json = data; + + final url = homeserver! + .resolveUri(Uri(path: '_matrix$action', queryParameters: query)); + + final headers = {}; + if (type == RequestType.PUT || type == RequestType.POST) { + headers['Content-Type'] = contentType; + } + if (accessToken != null) { + headers['Authorization'] = 'Bearer $accessToken'; + } + + late http.Response resp; + Map? jsonResp = {}; + try { + switch (type) { + case RequestType.GET: + resp = await httpClient.get(url, headers: headers); + break; + case RequestType.POST: + resp = await httpClient.post(url, body: json, headers: headers); + break; + case RequestType.PUT: + resp = await httpClient.put(url, body: json, headers: headers); + break; + case RequestType.DELETE: + resp = await httpClient.delete(url, headers: headers); + break; + } + var respBody = resp.body; + try { + respBody = utf8.decode(resp.bodyBytes); + } catch (_) { + // No-OP + } + if (resp.statusCode >= 500 && resp.statusCode < 600) { + throw Exception(respBody); + } + var jsonString = String.fromCharCodes(respBody.runes); + if (jsonString.startsWith('[') && jsonString.endsWith(']')) { + jsonString = '{"chunk":$jsonString}'; + } + jsonResp = jsonDecode(jsonString) + as Map?; // May throw FormatException + } catch (e, s) { + throw MatrixConnectionException(e, s); + } + if (resp.statusCode >= 400 && resp.statusCode < 500) { + throw MatrixException(resp); + } + + 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. + /// + /// See [deletePusher] to issue requests with `kind: null`. + /// + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-pushers-set + Future postPusher(Pusher pusher, {bool? append}) async { + final data = pusher.toJson(); + if (append != null) { + data['append'] = append; + } + await request( + RequestType.POST, + '/client/v3/pushers/set', + data: data, + ); + return; + } + + /// Variant of postPusher operation that deletes pushers by setting `kind: null`. + /// + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-pushers-set + Future deletePusher(PusherId pusher) async { + final data = PusherData.fromJson(pusher.toJson()).toJson(); + data['kind'] = null; + await request( + RequestType.POST, + '/client/v3/pushers/set', + data: data, + ); + return; + } + + /// This API provides credentials for the client to use when initiating + /// calls. + @override + Future getTurnServer() async { + final json = await request(RequestType.GET, '/client/v3/voip/turnServer'); + + // fix invalid responses from synapse + // https://github.com/matrix-org/synapse/pull/10922 + final ttl = json['ttl']; + if (ttl is double) { + json['ttl'] = ttl.toInt(); + } + + return TurnServerCredentials.fromJson(json); + } + + @Deprecated('Use [deleteRoomKeyBySessionId] instead') + Future deleteRoomKeysBySessionId( + String roomId, String sessionId, String version) async { + return deleteRoomKeyBySessionId(roomId, sessionId, version); + } + + @Deprecated('Use [deleteRoomKeyBySessionId] instead') + Future putRoomKeysBySessionId(String roomId, + String sessionId, String version, KeyBackupData data) async { + return putRoomKeyBySessionId(roomId, sessionId, version, data); + } + + @Deprecated('Use [getRoomKeyBySessionId] instead') + Future getRoomKeysBySessionId( + String roomId, String sessionId, String version) async { + return getRoomKeyBySessionId(roomId, sessionId, version); + } +} diff --git a/lib/matrix_api_lite/model/algorithm_types.dart b/lib/matrix_api_lite/model/algorithm_types.dart new file mode 100644 index 00000000..e4f8548b --- /dev/null +++ b/lib/matrix_api_lite/model/algorithm_types.dart @@ -0,0 +1,32 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +abstract class AlgorithmTypes { + static const String olmV1Curve25519AesSha2 = 'm.olm.v1.curve25519-aes-sha2'; + static const String megolmV1AesSha2 = 'm.megolm.v1.aes-sha2'; + static const String secretStorageV1AesHmcSha2 = + 'm.secret_storage.v1.aes-hmac-sha2'; + static const String megolmBackupV1Curve25519AesSha2 = + 'm.megolm_backup.v1.curve25519-aes-sha2'; + static const String pbkdf2 = 'm.pbkdf2'; +} diff --git a/lib/matrix_api_lite/model/auth/authentication_data.dart b/lib/matrix_api_lite/model/auth/authentication_data.dart new file mode 100644 index 00000000..27eae960 --- /dev/null +++ b/lib/matrix_api_lite/model/auth/authentication_data.dart @@ -0,0 +1,42 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +class AuthenticationData { + // Should be non-nullable according to the spec but this leads to this problem + // https://github.com/matrix-org/matrix-doc/issues/3370 + String? type; + String? session; + + AuthenticationData({this.type, this.session}); + + AuthenticationData.fromJson(Map json) + : type = json['type'] as String?, + session = json['session'] as String?; + + Map toJson() { + final data = {}; + if (type != null) data['type'] = type; + if (session != null) data['session'] = session; + return data; + } +} diff --git a/lib/matrix_api_lite/model/auth/authentication_identifier.dart b/lib/matrix_api_lite/model/auth/authentication_identifier.dart new file mode 100644 index 00000000..06ead12d --- /dev/null +++ b/lib/matrix_api_lite/model/auth/authentication_identifier.dart @@ -0,0 +1,55 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'authentication_phone_identifier.dart'; +import 'authentication_third_party_identifier.dart'; +import 'authentication_types.dart'; +import 'authentication_user_identifier.dart'; + +class AuthenticationIdentifier { + String type; + + AuthenticationIdentifier({required this.type}); + + AuthenticationIdentifier.fromJson(Map json) + : type = json['type'] as String; + + factory AuthenticationIdentifier.subFromJson(Map json) { + switch (json['type']) { + case AuthenticationIdentifierTypes.userId: + return AuthenticationUserIdentifier.fromJson(json); + case AuthenticationIdentifierTypes.phone: + return AuthenticationPhoneIdentifier.fromJson(json); + case AuthenticationIdentifierTypes.thirdParty: + return AuthenticationThirdPartyIdentifier.fromJson(json); + default: + return AuthenticationIdentifier.fromJson(json); + } + } + + Map toJson() { + final data = {}; + data['type'] = type; + return data; + } +} diff --git a/lib/matrix_api_lite/model/auth/authentication_password.dart b/lib/matrix_api_lite/model/auth/authentication_password.dart new file mode 100644 index 00000000..a9033445 --- /dev/null +++ b/lib/matrix_api_lite/model/auth/authentication_password.dart @@ -0,0 +1,56 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'authentication_data.dart'; +import 'authentication_identifier.dart'; +import 'authentication_types.dart'; +import 'authentication_user_identifier.dart'; + +class AuthenticationPassword extends AuthenticationData { + String password; + + /// You may want to cast this as [AuthenticationUserIdentifier] or other + /// Identifier classes extending AuthenticationIdentifier. + AuthenticationIdentifier identifier; + + AuthenticationPassword( + {String? session, required this.password, required this.identifier}) + : super( + type: AuthenticationTypes.password, + session: session, + ); + + AuthenticationPassword.fromJson(Map json) + : password = json['password'] as String, + identifier = AuthenticationIdentifier.subFromJson( + json['identifier'] as Map), + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['password'] = password; + data['identifier'] = identifier.toJson(); + return data; + } +} diff --git a/lib/matrix_api_lite/model/auth/authentication_phone_identifier.dart b/lib/matrix_api_lite/model/auth/authentication_phone_identifier.dart new file mode 100644 index 00000000..e12b510e --- /dev/null +++ b/lib/matrix_api_lite/model/auth/authentication_phone_identifier.dart @@ -0,0 +1,46 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'authentication_identifier.dart'; +import 'authentication_types.dart'; + +class AuthenticationPhoneIdentifier extends AuthenticationIdentifier { + String country; + String phone; + + AuthenticationPhoneIdentifier({required this.country, required this.phone}) + : super(type: AuthenticationIdentifierTypes.phone); + + AuthenticationPhoneIdentifier.fromJson(Map json) + : country = json['country'] as String, + phone = json['phone'] as String, + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['country'] = country; + data['phone'] = phone; + return data; + } +} diff --git a/lib/matrix_api_lite/model/auth/authentication_recaptcha.dart b/lib/matrix_api_lite/model/auth/authentication_recaptcha.dart new file mode 100644 index 00000000..33009b31 --- /dev/null +++ b/lib/matrix_api_lite/model/auth/authentication_recaptcha.dart @@ -0,0 +1,46 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'authentication_data.dart'; +import 'authentication_types.dart'; + +class AuthenticationRecaptcha extends AuthenticationData { + String response; + + AuthenticationRecaptcha({required String session, required this.response}) + : super( + type: AuthenticationTypes.recaptcha, + session: session, + ); + + AuthenticationRecaptcha.fromJson(Map json) + : response = json['response'] as String, + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['response'] = response; + return data; + } +} diff --git a/lib/matrix_api_lite/model/auth/authentication_third_party_identifier.dart b/lib/matrix_api_lite/model/auth/authentication_third_party_identifier.dart new file mode 100644 index 00000000..55bc8c40 --- /dev/null +++ b/lib/matrix_api_lite/model/auth/authentication_third_party_identifier.dart @@ -0,0 +1,47 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'authentication_identifier.dart'; +import 'authentication_types.dart'; + +class AuthenticationThirdPartyIdentifier extends AuthenticationIdentifier { + String medium; + String address; + + AuthenticationThirdPartyIdentifier( + {required this.medium, required this.address}) + : super(type: AuthenticationIdentifierTypes.thirdParty); + + AuthenticationThirdPartyIdentifier.fromJson(Map json) + : medium = json['medium'] as String, + address = json['address'] as String, + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['medium'] = medium; + data['address'] = address; + return data; + } +} diff --git a/lib/matrix_api_lite/model/auth/authentication_three_pid_creds.dart b/lib/matrix_api_lite/model/auth/authentication_three_pid_creds.dart new file mode 100644 index 00000000..89e5784a --- /dev/null +++ b/lib/matrix_api_lite/model/auth/authentication_three_pid_creds.dart @@ -0,0 +1,82 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'authentication_data.dart'; + +/// For email based identity: +/// https://matrix.org/docs/spec/client_server/r0.6.1#email-based-identity-homeserver +/// Or phone number based identity: +/// https://matrix.org/docs/spec/client_server/r0.6.1#phone-number-msisdn-based-identity-homeserver +class AuthenticationThreePidCreds extends AuthenticationData { + late ThreepidCreds threepidCreds; + + AuthenticationThreePidCreds( + {String? session, required String type, required this.threepidCreds}) + : super( + type: type, + session: session, + ); + + AuthenticationThreePidCreds.fromJson(Map json) + : super.fromJson(json) { + final creds = json['threepid_creds']; + if (creds is Map) { + threepidCreds = ThreepidCreds.fromJson(creds); + } + } + + @override + Map toJson() { + final data = super.toJson(); + data['threepid_creds'] = threepidCreds.toJson(); + return data; + } +} + +class ThreepidCreds { + String sid; + String clientSecret; + String? idServer; + String? idAccessToken; + + ThreepidCreds( + {required this.sid, + required this.clientSecret, + this.idServer, + this.idAccessToken}); + + ThreepidCreds.fromJson(Map json) + : sid = json['sid'] as String, + clientSecret = json['client_secret'] as String, + idServer = json['id_server'] as String?, + idAccessToken = json['id_access_token'] as String?; + + Map toJson() { + final data = {}; + data['sid'] = sid; + data['client_secret'] = clientSecret; + if (idServer != null) data['id_server'] = idServer; + if (idAccessToken != null) data['id_access_token'] = idAccessToken; + return data; + } +} diff --git a/lib/matrix_api_lite/model/auth/authentication_token.dart b/lib/matrix_api_lite/model/auth/authentication_token.dart new file mode 100644 index 00000000..fc375f5d --- /dev/null +++ b/lib/matrix_api_lite/model/auth/authentication_token.dart @@ -0,0 +1,51 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'authentication_data.dart'; +import 'authentication_types.dart'; + +class AuthenticationToken extends AuthenticationData { + String token; + + /// removed in the unstable version of the spec + String? txnId; + + AuthenticationToken({String? session, required this.token, this.txnId}) + : super( + type: AuthenticationTypes.token, + session: session, + ); + + AuthenticationToken.fromJson(Map json) + : token = json['token'] as String, + txnId = json['txn_id'] as String?, + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['token'] = token; + data['txn_id'] = txnId; + return data; + } +} diff --git a/lib/matrix_api_lite/model/auth/authentication_types.dart b/lib/matrix_api_lite/model/auth/authentication_types.dart new file mode 100644 index 00000000..ea181e40 --- /dev/null +++ b/lib/matrix_api_lite/model/auth/authentication_types.dart @@ -0,0 +1,39 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +abstract class AuthenticationTypes { + static const String password = 'm.login.password'; + static const String recaptcha = 'm.login.recaptcha'; + static const String token = 'm.login.token'; + static const String oauth2 = 'm.login.oauth2'; + static const String sso = 'm.login.sso'; + static const String emailIdentity = 'm.login.email.identity'; + static const String msisdn = 'm.login.msisdn'; + static const String dummy = 'm.login.dummy'; +} + +abstract class AuthenticationIdentifierTypes { + static const String userId = 'm.id.user'; + static const String thirdParty = 'm.id.thirdparty'; + static const String phone = 'm.id.phone'; +} diff --git a/lib/matrix_api_lite/model/auth/authentication_user_identifier.dart b/lib/matrix_api_lite/model/auth/authentication_user_identifier.dart new file mode 100644 index 00000000..232cafe6 --- /dev/null +++ b/lib/matrix_api_lite/model/auth/authentication_user_identifier.dart @@ -0,0 +1,43 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'authentication_identifier.dart'; +import 'authentication_types.dart'; + +class AuthenticationUserIdentifier extends AuthenticationIdentifier { + String user; + + AuthenticationUserIdentifier({required this.user}) + : super(type: AuthenticationIdentifierTypes.userId); + + AuthenticationUserIdentifier.fromJson(Map json) + : user = json['user'] as String, + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['user'] = user; + return data; + } +} diff --git a/lib/matrix_api_lite/model/basic_event.dart b/lib/matrix_api_lite/model/basic_event.dart new file mode 100644 index 00000000..9700fc02 --- /dev/null +++ b/lib/matrix_api_lite/model/basic_event.dart @@ -0,0 +1,45 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import '../utils/map_copy_extension.dart'; + +class BasicEvent { + String type; + Map content; + + BasicEvent({ + required this.type, + required this.content, + }); + + BasicEvent.fromJson(Map json) + : type = json['type'] as String, + content = (json['content'] as Map).copy(); + + Map toJson() { + final data = {}; + data['type'] = type; + data['content'] = content; + return data; + } +} diff --git a/lib/matrix_api_lite/model/basic_event_with_sender.dart b/lib/matrix_api_lite/model/basic_event_with_sender.dart new file mode 100644 index 00000000..7da96bf8 --- /dev/null +++ b/lib/matrix_api_lite/model/basic_event_with_sender.dart @@ -0,0 +1,45 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'basic_event.dart'; + +class BasicEventWithSender extends BasicEvent { + String senderId; + + BasicEventWithSender( + {required String type, + required Map content, + required this.senderId}) + : super(type: type, content: content); + + BasicEventWithSender.fromJson(Map json) + : senderId = json['sender'] as String, + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['sender'] = senderId; + return data; + } +} diff --git a/lib/matrix_api_lite/model/basic_room_event.dart b/lib/matrix_api_lite/model/basic_room_event.dart new file mode 100644 index 00000000..97f242ea --- /dev/null +++ b/lib/matrix_api_lite/model/basic_room_event.dart @@ -0,0 +1,48 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'basic_event.dart'; + +class BasicRoomEvent extends BasicEvent { + String? roomId; + + BasicRoomEvent({ + this.roomId, + required Map content, + required String type, + }) : super( + content: content, + type: type, + ); + + BasicRoomEvent.fromJson(Map json) + : roomId = json['room_id'] as String?, + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + if (roomId != null) data['room_id'] = roomId; + return data; + } +} diff --git a/lib/matrix_api_lite/model/children_state.dart b/lib/matrix_api_lite/model/children_state.dart new file mode 100644 index 00000000..f5ae40de --- /dev/null +++ b/lib/matrix_api_lite/model/children_state.dart @@ -0,0 +1,52 @@ +/* MIT License +* +* Copyright (C) 2022 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'stripped_state_event.dart'; + +class ChildrenState extends StrippedStateEvent { + DateTime originServerTs; + + ChildrenState({ + required String type, + required Map content, + required String senderId, + required String stateKey, + required this.originServerTs, + }) : super( + type: type, + content: content, + senderId: senderId, + stateKey: stateKey); + + ChildrenState.fromJson(Map json) + : originServerTs = DateTime.fromMillisecondsSinceEpoch( + json['origin_server_ts'] as int), + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['origin_server_ts'] = originServerTs.millisecondsSinceEpoch; + return data; + } +} diff --git a/lib/matrix_api_lite/model/event_types.dart b/lib/matrix_api_lite/model/event_types.dart new file mode 100644 index 00000000..700a1882 --- /dev/null +++ b/lib/matrix_api_lite/model/event_types.dart @@ -0,0 +1,97 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +abstract class EventTypes { + // Room timeline and state event types + static const String Message = 'm.room.message'; + static const String Sticker = 'm.sticker'; + static const String Reaction = 'm.reaction'; + static const String Redaction = 'm.room.redaction'; + static const String RoomAliases = 'm.room.aliases'; + static const String RoomCanonicalAlias = 'm.room.canonical_alias'; + static const String RoomCreate = 'm.room.create'; + static const String RoomJoinRules = 'm.room.join_rules'; + static const String RoomMember = 'm.room.member'; + static const String RoomPowerLevels = 'm.room.power_levels'; + static const String RoomName = 'm.room.name'; + static const String RoomPinnedEvents = 'm.room.pinned_events'; + static const String RoomTopic = 'm.room.topic'; + static const String RoomAvatar = 'm.room.avatar'; + static const String RoomTombstone = 'm.room.tombstone'; + static const String GuestAccess = 'm.room.guest_access'; + static const String HistoryVisibility = 'm.room.history_visibility'; + static const String Encryption = 'm.room.encryption'; + static const String Encrypted = 'm.room.encrypted'; + static const String CallInvite = 'm.call.invite'; + static const String CallAnswer = 'm.call.answer'; + static const String CallCandidates = 'm.call.candidates'; + static const String CallHangup = 'm.call.hangup'; + static const String CallSelectAnswer = 'm.call.select_answer'; + static const String CallReject = 'm.call.reject'; + static const String CallNegotiate = 'm.call.negotiate'; + static const String CallSDPStreamMetadataChanged = + 'm.call.sdp_stream_metadata_changed'; + static const String CallSDPStreamMetadataChangedPrefix = + 'org.matrix.call.sdp_stream_metadata_changed'; + static const String CallReplaces = 'm.call.replaces'; + static const String CallAssertedIdentity = 'm.call.asserted_identity'; + static const String CallAssertedIdentityPrefix = + 'org.matrix.call.asserted_identity'; + static const String GroupCallPrefix = 'org.matrix.msc3401.call'; + static const String GroupCallMemberPrefix = 'org.matrix.msc3401.call.member'; + static const String Unknown = 'm.unknown'; + + // To device event types + static const String RoomKey = 'm.room_key'; + static const String ForwardedRoomKey = 'm.forwarded_room_key'; + static const String RoomKeyRequest = 'm.room_key_request'; + static const String KeyVerificationRequest = 'm.key.verification.request'; + static const String KeyVerificationStart = 'm.key.verification.start'; + static const String KeyVerificationReady = 'm.key.verification.ready'; + static const String KeyVerificationDone = 'm.key.verification.done'; + static const String KeyVerificationCancel = 'm.key.verification.cancel'; + static const String KeyVerificationAccept = 'm.key.verification.accept'; + static const String SecretRequest = 'm.secret.request'; + static const String SecretSend = 'm.secret.send'; + static const String Dummy = 'm.dummy'; + + // QR + static const String QRShow = 'm.qr_code.show.v1'; + static const String QRScan = 'm.qr_code.scan.v1'; + static const String Reciprocate = 'm.reciprocate.v1'; + static const String Sas = 'm.sas.v1'; + + // Account data event types + static const String CrossSigningSelfSigning = 'm.cross_signing.self_signing'; + static const String CrossSigningUserSigning = 'm.cross_signing.user_signing'; + static const String CrossSigningMasterKey = 'm.cross_signing.master'; + static const String MegolmBackup = 'm.megolm_backup.v1'; + static const String SecretStorageDefaultKey = 'm.secret_storage.default_key'; + static const String PushRules = 'm.push_rules'; + + static String secretStorageKey(String keyId) => 'm.secret_storage.key.$keyId'; + + // Spaces + static const String spaceParent = 'm.space.parent'; + static const String spaceChild = 'm.space.child'; +} diff --git a/lib/matrix_api_lite/model/events/forwarded_room_key_content.dart b/lib/matrix_api_lite/model/events/forwarded_room_key_content.dart new file mode 100644 index 00000000..8fbf4311 --- /dev/null +++ b/lib/matrix_api_lite/model/events/forwarded_room_key_content.dart @@ -0,0 +1,56 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import '../../utils/try_get_map_extension.dart'; +import '../basic_event.dart'; +import 'room_key_content.dart'; + +extension ForwardedRoomKeyContentBasicEventExtension on BasicEvent { + ForwardedRoomKeyContent get parsedForwardedRoomKeyContent => + ForwardedRoomKeyContent.fromJson(content); +} + +class ForwardedRoomKeyContent extends RoomKeyContent { + String senderKey; + String senderClaimedEd25519Key; + List forwardingCurve25519KeyChain; + + ForwardedRoomKeyContent.fromJson(Map json) + : senderKey = json.tryGet('sender_key', TryGet.required) ?? '', + senderClaimedEd25519Key = + json.tryGet('sender_claimed_ed25519_key', TryGet.required) ?? '', + forwardingCurve25519KeyChain = json.tryGetList( + 'forwarding_curve25519_key_chain', TryGet.required) ?? + [], + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['sender_key'] = senderKey; + data['sender_claimed_ed25519_key'] = senderClaimedEd25519Key; + data['forwarding_curve25519_key_chain'] = forwardingCurve25519KeyChain; + + return data; + } +} diff --git a/lib/matrix_api_lite/model/events/image_pack_content.dart b/lib/matrix_api_lite/model/events/image_pack_content.dart new file mode 100644 index 00000000..ff465df2 --- /dev/null +++ b/lib/matrix_api_lite/model/events/image_pack_content.dart @@ -0,0 +1,174 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import '../../utils/filter_map_extension.dart'; +import '../../utils/try_get_map_extension.dart'; +import '../basic_event.dart'; + +extension ImagePackContentBasicEventExtension on BasicEvent { + ImagePackContent get parsedImagePackContent => + ImagePackContent.fromJson(content); +} + +enum ImagePackUsage { + sticker, + emoticon, +} + +List? imagePackUsageFromJson(List? json) => json + ?.map((v) => { + 'sticker': ImagePackUsage.sticker, + 'emoticon': ImagePackUsage.emoticon, + }[v]) + .whereType() + .toList(); + +List imagePackUsageToJson( + List? usage, List? prevUsage) { + final knownUsages = {'sticker', 'emoticon'}; + final usagesStr = usage + ?.map((v) => { + ImagePackUsage.sticker: 'sticker', + ImagePackUsage.emoticon: 'emoticon', + }[v]) + .whereType() + .toList() ?? + []; + // first we add all the unknown usages and the previous known usages which are new again + final newUsages = prevUsage + ?.where((v) => !knownUsages.contains(v) || usagesStr.contains(v)) + .toList() ?? + []; + // now we need to add the new usages that we didn't add yet + newUsages.addAll(usagesStr.where((v) => !newUsages.contains(v))); + return newUsages; +} + +class ImagePackContent { + // we want to preserve potential custom keys in this object + final Map _json; + + Map images; + ImagePackPackContent pack; + + ImagePackContent({required this.images, required this.pack}) : _json = {}; + + ImagePackContent.fromJson(Map json) + : _json = Map.fromEntries(json.entries.where( + (e) => !['images', 'pack', 'emoticons', 'short'].contains(e.key))), + pack = ImagePackPackContent.fromJson( + json.tryGetMap('pack') ?? {}), + images = json.tryGetMap('images')?.catchMap((k, v) => + MapEntry( + k, + ImagePackImageContent.fromJson( + v as Map))) ?? + // the "emoticons" key needs a small migration on the key, ":string:" --> "string" + json.tryGetMap('emoticons')?.catchMap((k, v) => + MapEntry( + k.startsWith(':') && k.endsWith(':') + ? k.substring(1, k.length - 1) + : k, + ImagePackImageContent.fromJson( + v as Map))) ?? + // the "short" key was still just a map from shortcode to mxc uri + json.tryGetMap('short')?.catchMap((k, v) => + MapEntry( + k.startsWith(':') && k.endsWith(':') + ? k.substring(1, k.length - 1) + : k, + ImagePackImageContent(url: Uri.parse(v)))) ?? + {}; + + Map toJson() => { + ..._json, + 'images': images.map((k, v) => MapEntry(k, v.toJson())), + 'pack': pack.toJson(), + }; +} + +class ImagePackImageContent { + // we want to preserve potential custom keys in this object + final Map _json; + + Uri url; + String? body; + Map? info; + List? usage; + + ImagePackImageContent({required this.url, this.body, this.info, this.usage}) + : _json = {}; + + ImagePackImageContent.fromJson(Map json) + : _json = Map.fromEntries(json.entries + .where((e) => !['url', 'body', 'info'].contains(e.key))), + url = Uri.parse(json['url'] as String), + body = json.tryGet('body'), + info = json.tryGetMap('info'), + usage = imagePackUsageFromJson(json.tryGetList('usage')); + + Map toJson() { + return { + ...Map.from(_json)..remove('usage'), + 'url': url.toString(), + if (body != null) 'body': body, + if (info != null) 'info': info, + if (usage != null) + 'usage': imagePackUsageToJson(usage, _json.tryGetList('usage')), + }; + } +} + +class ImagePackPackContent { + // we want to preserve potential custom keys in this object + final Map _json; + + String? displayName; + Uri? avatarUrl; + List? usage; + String? attribution; + + ImagePackPackContent( + {this.displayName, this.avatarUrl, this.usage, this.attribution}) + : _json = {}; + + ImagePackPackContent.fromJson(Map json) + : _json = Map.fromEntries(json.entries.where((e) => + !['display_name', 'avatar_url', 'attribution'].contains(e.key))), + displayName = json.tryGet('display_name'), + // we default to an invalid uri + avatarUrl = Uri.tryParse(json.tryGet('avatar_url') ?? '.::'), + usage = imagePackUsageFromJson(json.tryGetList('usage')), + attribution = json.tryGet('attribution'); + + Map toJson() { + return { + ...Map.from(_json)..remove('usage'), + if (displayName != null) 'display_name': displayName, + if (avatarUrl != null) 'avatar_url': avatarUrl.toString(), + if (usage != null) + 'usage': imagePackUsageToJson(usage, _json.tryGetList('usage')), + if (attribution != null) 'attribution': attribution, + }; + } +} diff --git a/lib/matrix_api_lite/model/events/olm_plaintext_payload.dart b/lib/matrix_api_lite/model/events/olm_plaintext_payload.dart new file mode 100644 index 00000000..11ec26c7 --- /dev/null +++ b/lib/matrix_api_lite/model/events/olm_plaintext_payload.dart @@ -0,0 +1,63 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import '../../utils/try_get_map_extension.dart'; + +class OlmPlaintextPayload { + String? type; + Map? content; + String? sender; + String? recipient; + Map? recipientKeys; + Map? keys; + + OlmPlaintextPayload({ + this.type, + this.content, + this.sender, + this.recipient, + this.recipientKeys, + this.keys, + }) : super(); + + factory OlmPlaintextPayload.fromJson(Map json) => + OlmPlaintextPayload( + sender: json.tryGet('sender', TryGet.required), + type: json.tryGet('type', TryGet.required), + content: json.tryGetMap('content', TryGet.required), + recipient: json.tryGet('recipient', TryGet.required), + recipientKeys: json.tryGetMap('recipient_keys', TryGet.required), + keys: json.tryGetMap('keys', TryGet.required), + ); + + Map toJson() { + final data = {}; + if (type != null) data['type'] = type; + if (sender != null) data['sender'] = sender; + if (content != null) data['content'] = content; + if (recipient != null) data['recipient'] = recipient; + if (recipientKeys != null) data['recipient_keys'] = recipientKeys; + if (keys != null) data['keys'] = keys; + return data; + } +} diff --git a/lib/matrix_api_lite/model/events/room_encrypted_content.dart b/lib/matrix_api_lite/model/events/room_encrypted_content.dart new file mode 100644 index 00000000..c92ed261 --- /dev/null +++ b/lib/matrix_api_lite/model/events/room_encrypted_content.dart @@ -0,0 +1,93 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'package:matrix_api_lite/src/utils/logs.dart'; +import '../../utils/filter_map_extension.dart'; +import '../../utils/try_get_map_extension.dart'; +import '../basic_event.dart'; + +extension RoomEncryptedContentBasicEventExtension on BasicEvent { + RoomEncryptedContent get parsedRoomEncryptedContent => + RoomEncryptedContent.fromJson(content); +} + +class RoomEncryptedContent { + String algorithm; + String senderKey; + String? deviceId; + String? sessionId; + String? ciphertextMegolm; + Map? ciphertextOlm; + + RoomEncryptedContent.fromJson(Map json) + : algorithm = json.tryGet('algorithm', TryGet.required) ?? '', + senderKey = json.tryGet('sender_key', TryGet.required) ?? '', + deviceId = json.tryGet('device_id'), + sessionId = json.tryGet('session_id'), + ciphertextMegolm = json.tryGet('ciphertext', TryGet.silent), + // filter out invalid/incomplete CiphertextInfos + ciphertextOlm = json + .tryGet>('ciphertext', TryGet.silent) + ?.catchMap((k, v) => MapEntry( + k, CiphertextInfo.fromJson(v as Map))); + + Map toJson() { + final data = {}; + data['algorithm'] = algorithm; + data['sender_key'] = senderKey; + if (deviceId != null) { + data['device_id'] = deviceId; + } + if (sessionId != null) { + data['session_id'] = sessionId; + } + if (ciphertextMegolm != null) { + data['ciphertext'] = ciphertextMegolm; + } + if (ciphertextOlm != null) { + data['ciphertext'] = + ciphertextOlm!.map((k, v) => MapEntry(k, v.toJson())); + if (ciphertextMegolm != null) { + Logs().wtf( + 'ciphertextOlm and ciphertextMegolm are both set, which should never happen!'); + } + } + return data; + } +} + +class CiphertextInfo { + String body; + int type; + + CiphertextInfo.fromJson(Map json) + : body = json['body'] as String, + type = json['type'] as int; + + Map toJson() { + final data = {}; + data['body'] = body; + data['type'] = type; + return data; + } +} diff --git a/lib/matrix_api_lite/model/events/room_encryption_content.dart b/lib/matrix_api_lite/model/events/room_encryption_content.dart new file mode 100644 index 00000000..15ed3763 --- /dev/null +++ b/lib/matrix_api_lite/model/events/room_encryption_content.dart @@ -0,0 +1,53 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import '../../utils/try_get_map_extension.dart'; +import '../basic_event.dart'; + +extension RoomEncryptionContentBasicEventExtension on BasicEvent { + RoomEncryptionContent get parsedRoomEncryptionContent => + RoomEncryptionContent.fromJson(content); +} + +class RoomEncryptionContent { + String algorithm; + int? rotationPeriodMs; + int? rotationPeriodMsgs; + + RoomEncryptionContent.fromJson(Map json) + : algorithm = json.tryGet('algorithm', TryGet.required) ?? '', + rotationPeriodMs = json.tryGet('rotation_period_ms'), + rotationPeriodMsgs = json.tryGet('rotation_period_msgs'); + + Map toJson() { + final data = {}; + data['algorithm'] = algorithm; + if (rotationPeriodMs != null) { + data['rotation_period_ms'] = rotationPeriodMs; + } + if (rotationPeriodMsgs != null) { + data['rotation_period_msgs'] = rotationPeriodMsgs; + } + return data; + } +} diff --git a/lib/matrix_api_lite/model/events/room_key_content.dart b/lib/matrix_api_lite/model/events/room_key_content.dart new file mode 100644 index 00000000..a710523b --- /dev/null +++ b/lib/matrix_api_lite/model/events/room_key_content.dart @@ -0,0 +1,57 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import '../../utils/try_get_map_extension.dart'; +import '../basic_event.dart'; + +extension RoomKeyContentBasicEventExtension on BasicEvent { + RoomKeyContent get parsedRoomKeyContent => RoomKeyContent.fromJson(content); +} + +class RoomKeyContent { + String algorithm; + String roomId; + String sessionId; + String sessionKey; + + RoomKeyContent( + {required this.algorithm, + required this.roomId, + required this.sessionId, + required this.sessionKey}); + + RoomKeyContent.fromJson(Map json) + : algorithm = json.tryGet('algorithm', TryGet.required) ?? '', + roomId = json.tryGet('room_id', TryGet.required) ?? '', + sessionId = json.tryGet('session_id', TryGet.required) ?? '', + sessionKey = json.tryGet('session_key', TryGet.required) ?? ''; + + Map toJson() { + final data = {}; + data['algorithm'] = algorithm; + data['room_id'] = roomId; + data['session_id'] = sessionId; + data['session_key'] = sessionKey; + return data; + } +} diff --git a/lib/matrix_api_lite/model/events/room_key_request_content.dart b/lib/matrix_api_lite/model/events/room_key_request_content.dart new file mode 100644 index 00000000..ec6162d4 --- /dev/null +++ b/lib/matrix_api_lite/model/events/room_key_request_content.dart @@ -0,0 +1,83 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import '../../utils/try_get_map_extension.dart'; +import '../basic_event.dart'; + +extension RoomKeyRequestContentBasicEventExtension on BasicEvent { + RoomKeyRequestContent get parsedRoomKeyRequestContent => + RoomKeyRequestContent.fromJson(content); +} + +class RoomKeyRequestContent { + RequestedKeyInfo? body; + String action; + String requestingDeviceId; + String requestId; + + RoomKeyRequestContent.fromJson(Map json) + : body = ((Map? x) => x != null + ? RequestedKeyInfo.fromJson(x) + : null)(json.tryGet('body')), + action = json.tryGet('action', TryGet.required) ?? '', + requestingDeviceId = + json.tryGet('requesting_device_id', TryGet.required) ?? '', + requestId = json.tryGet('request_id', TryGet.required) ?? ''; + + Map toJson() { + final data = {}; + if (body != null) data['body'] = body!.toJson(); + data['action'] = action; + data['requesting_device_id'] = requestingDeviceId; + data['request_id'] = requestId; + return data; + } +} + +class RequestedKeyInfo { + String algorithm; + String roomId; + String sessionId; + String senderKey; + + RequestedKeyInfo( + {required this.algorithm, + required this.roomId, + required this.sessionId, + required this.senderKey}); + + RequestedKeyInfo.fromJson(Map json) + : algorithm = json.tryGet('algorithm', TryGet.required) ?? '', + roomId = json.tryGet('room_id', TryGet.required) ?? '', + sessionId = json.tryGet('session_id', TryGet.required) ?? '', + senderKey = json.tryGet('sender_key', TryGet.required) ?? ''; + + Map toJson() { + final data = {}; + data['algorithm'] = algorithm; + data['room_id'] = roomId; + data['session_id'] = sessionId; + data['sender_key'] = senderKey; + return data; + } +} diff --git a/lib/matrix_api_lite/model/events/secret_storage_default_key_content.dart b/lib/matrix_api_lite/model/events/secret_storage_default_key_content.dart new file mode 100644 index 00000000..7623bcdd --- /dev/null +++ b/lib/matrix_api_lite/model/events/secret_storage_default_key_content.dart @@ -0,0 +1,46 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import '../../utils/try_get_map_extension.dart'; +import '../basic_event.dart'; + +extension SecretStorageDefaultKeyContentBasicEventExtension on BasicEvent { + SecretStorageDefaultKeyContent get parsedSecretStorageDefaultKeyContent => + SecretStorageDefaultKeyContent.fromJson(content); +} + +class SecretStorageDefaultKeyContent { + //TODO: Required by spec, we should require it here and make sure to catch it everywhere + String? key; + + SecretStorageDefaultKeyContent({required this.key}); + + SecretStorageDefaultKeyContent.fromJson(Map json) + : key = json.tryGet('key', TryGet.required); + + Map toJson() { + final data = {}; + if (key != null) data['key'] = key; + return data; + } +} diff --git a/lib/matrix_api_lite/model/events/secret_storage_key_content.dart b/lib/matrix_api_lite/model/events/secret_storage_key_content.dart new file mode 100644 index 00000000..42019615 --- /dev/null +++ b/lib/matrix_api_lite/model/events/secret_storage_key_content.dart @@ -0,0 +1,86 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import '../../utils/try_get_map_extension.dart'; +import '../basic_event.dart'; + +extension SecretStorageKeyContentBasicEventExtension on BasicEvent { + SecretStorageKeyContent get parsedSecretStorageKeyContent => + SecretStorageKeyContent.fromJson(content); +} + +class SecretStorageKeyContent { + PassphraseInfo? passphrase; + String? iv; + String? mac; + String? algorithm; + + SecretStorageKeyContent(); + + SecretStorageKeyContent.fromJson(Map json) + : passphrase = ((Map? x) => x != null + ? PassphraseInfo.fromJson(x) + : null)(json.tryGet('passphrase')), + iv = json.tryGet('iv'), + mac = json.tryGet('mac'), + algorithm = json.tryGet('algorithm'); + + Map toJson() { + final data = {}; + if (passphrase != null) data['passphrase'] = passphrase!.toJson(); + if (iv != null) data['iv'] = iv; + if (mac != null) data['mac'] = mac; + if (algorithm != null) data['algorithm'] = algorithm; + return data; + } +} + +class PassphraseInfo { + //TODO: algorithm, salt, iterations are required by spec, + //TODO: we should require it here and make sure to catch it everywhere + String? algorithm; + String? salt; + int? iterations; + int? bits; + + PassphraseInfo( + {required this.algorithm, + required this.salt, + required this.iterations, + this.bits}); + + PassphraseInfo.fromJson(Map json) + : algorithm = json.tryGet('algorithm', TryGet.required), + salt = json.tryGet('salt', TryGet.required), + iterations = json.tryGet('iterations', TryGet.required), + bits = json.tryGet('bits'); + + Map toJson() { + final data = {}; + data['algorithm'] = algorithm; + data['salt'] = salt; + data['iterations'] = iterations; + if (bits != null) data['bits'] = bits; + return data; + } +} diff --git a/lib/matrix_api_lite/model/events/tombstone_content.dart b/lib/matrix_api_lite/model/events/tombstone_content.dart new file mode 100644 index 00000000..b3dccbec --- /dev/null +++ b/lib/matrix_api_lite/model/events/tombstone_content.dart @@ -0,0 +1,47 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import '../../utils/try_get_map_extension.dart'; +import '../basic_event.dart'; + +extension TombstoneContentBasicEventExtension on BasicEvent { + TombstoneContent get parsedTombstoneContent => + TombstoneContent.fromJson(content); +} + +class TombstoneContent { + String body; + String replacementRoom; + + TombstoneContent.fromJson(Map json) + : body = json.tryGet('body', TryGet.required) ?? '', + replacementRoom = + json.tryGet('replacement_room', TryGet.required) ?? ''; + + Map toJson() { + final data = {}; + data['body'] = body; + data['replacement_room'] = replacementRoom; + return data; + } +} diff --git a/lib/matrix_api_lite/model/matrix_connection_exception.dart b/lib/matrix_api_lite/model/matrix_connection_exception.dart new file mode 100644 index 00000000..97194f64 --- /dev/null +++ b/lib/matrix_api_lite/model/matrix_connection_exception.dart @@ -0,0 +1,32 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +class MatrixConnectionException implements Exception { + final dynamic original; + final StackTrace stackTrace; + + MatrixConnectionException(this.original, this.stackTrace); + + @override + String toString() => original.toString(); +} diff --git a/lib/matrix_api_lite/model/matrix_event.dart b/lib/matrix_api_lite/model/matrix_event.dart new file mode 100644 index 00000000..6d3c6cc9 --- /dev/null +++ b/lib/matrix_api_lite/model/matrix_event.dart @@ -0,0 +1,84 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import '../utils/map_copy_extension.dart'; +import 'stripped_state_event.dart'; + +class MatrixEvent extends StrippedStateEvent { + String eventId; + String? roomId; + DateTime originServerTs; + Map? unsigned; + Map? prevContent; + String? redacts; + + MatrixEvent({ + required String type, + required Map content, + required String senderId, + String? stateKey, + required this.eventId, + this.roomId, + required this.originServerTs, + this.unsigned, + this.prevContent, + this.redacts, + }) : super( + type: type, + content: content, + senderId: senderId, + stateKey: stateKey); + + MatrixEvent.fromJson(Map json) + : eventId = json['event_id'] as String, + roomId = json['room_id'] as String?, + originServerTs = DateTime.fromMillisecondsSinceEpoch( + json['origin_server_ts'] as int), + unsigned = (json['unsigned'] as Map?)?.copy(), + prevContent = (json['prev_content'] as Map?)?.copy(), + redacts = json['redacts'] as String?, + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['event_id'] = eventId; + data['origin_server_ts'] = originServerTs.millisecondsSinceEpoch; + if (unsigned != null) { + data['unsigned'] = unsigned; + } + if (prevContent != null) { + data['prev_content'] = prevContent; + } + if (roomId != null) { + data['room_id'] = roomId; + } + if (data['state_key'] == null) { + data.remove('state_key'); + } + if (redacts != null) { + data['redacts'] = redacts; + } + return data; + } +} diff --git a/lib/matrix_api_lite/model/matrix_exception.dart b/lib/matrix_api_lite/model/matrix_exception.dart new file mode 100644 index 00000000..1c10b55c --- /dev/null +++ b/lib/matrix_api_lite/model/matrix_exception.dart @@ -0,0 +1,137 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +import 'package:matrix_api_lite/matrix_api_lite.dart'; + +enum MatrixError { + M_UNKNOWN, + M_UNKNOWN_TOKEN, + M_NOT_FOUND, + M_FORBIDDEN, + M_LIMIT_EXCEEDED, + M_USER_IN_USE, + M_THREEPID_IN_USE, + M_THREEPID_DENIED, + M_THREEPID_NOT_FOUND, + M_THREEPID_AUTH_FAILED, + M_TOO_LARGE, + M_MISSING_PARAM, + M_UNSUPPORTED_ROOM_VERSION, + M_UNRECOGNIZED, + M_BAD_JSON, + M_NOT_JSON, + M_UNAUTHORIZED, + M_USER_DEACTIVATED, + M_INVALID_USERNAME, + M_ROOM_IN_USE, + M_INVALID_ROOM_STATE, + M_SERVER_NOT_TRUSTED, + M_INCOMPATIBLE_ROOM_VERSION, + M_BAD_STATE, + M_GUEST_ACCESS_FORBIDDEN, + M_CAPTCHA_NEEDED, + M_CAPTCHA_INVALID, + M_INVALID_PARAM, + M_EXCLUSIVE, + M_RESOURCE_LIMIT_EXCEEDED, + M_CANNOT_LEAVE_SERVER_NOTICE_ROOM, +} + +/// Represents a special response from the Homeserver for errors. +class MatrixException implements Exception { + final Map raw; + + /// The unique identifier for this error. + String get errcode => + raw.tryGet('errcode') ?? + (requireAdditionalAuthentication ? 'M_FORBIDDEN' : 'M_UNKNOWN'); + + /// A human readable error description. + String get errorMessage => + raw.tryGet('error') ?? + (requireAdditionalAuthentication + ? 'Require additional authentication' + : 'Unknown error'); + + /// The frozen request which triggered this Error + http.Response? response; + + MatrixException(http.Response this.response) + : raw = json.decode(response.body) as Map; + + MatrixException.fromJson(Map content) : raw = content; + + @override + String toString() => '$errcode: $errorMessage'; + + /// Returns the errcode as an [MatrixError]. + MatrixError get error => MatrixError.values.firstWhere( + (e) => e.name == errcode, + orElse: () => MatrixError.M_UNKNOWN, + ); + + int? get retryAfterMs => raw.tryGet('retry_after_ms'); + + /// This is a session identifier that the client must pass back to the homeserver, if one is provided, + /// in subsequent attempts to authenticate in the same API call. + String? get session => raw.tryGet('session'); + + /// Returns true if the server requires additional authentication. + bool get requireAdditionalAuthentication => response != null + ? response!.statusCode == 401 + : authenticationFlows != null; + + /// For each endpoint, a server offers one or more 'flows' that the client can use + /// to authenticate itself. Each flow comprises a series of stages. If this request + /// doesn't need additional authentication, then this is null. + List? get authenticationFlows => raw + .tryGet>('flows') + ?.whereType>() + .map((flow) => flow['stages']) + .whereType>() + .map((stages) => + AuthenticationFlow(List.from(stages.whereType()))) + .toList(); + + /// This section contains any information that the client will need to know in order to use a given type + /// of authentication. For each authentication type presented, that type may be present as a key in this + /// dictionary. For example, the public part of an OAuth client ID could be given here. + Map? get authenticationParams => + raw.tryGetMap('params'); + + /// Returns the list of already completed authentication flows from previous requests. + List get completedAuthenticationFlows => + raw.tryGetList('completed') ?? []; +} + +/// For each endpoint, a server offers one or more 'flows' that the client can use +/// to authenticate itself. Each flow comprises a series of stages +class AuthenticationFlow { + final List stages; + + const AuthenticationFlow(this.stages); +} diff --git a/lib/matrix_api_lite/model/matrix_keys.dart b/lib/matrix_api_lite/model/matrix_keys.dart new file mode 100644 index 00000000..d6a8fde8 --- /dev/null +++ b/lib/matrix_api_lite/model/matrix_keys.dart @@ -0,0 +1,141 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'package:matrix_api_lite/matrix_api_lite.dart'; + +abstract class MatrixSignableKey { + String userId; + + String? get identifier; + + Map keys; + Map>? signatures; + Map? unsigned; + + MatrixSignableKey(this.userId, this.keys, this.signatures, {this.unsigned}); + + // This object is used for signing so we need the raw json too + Map? _json; + + MatrixSignableKey.fromJson(Map json) + : _json = json, + userId = json['user_id'] as String, + keys = Map.from(json['keys'] as Map), + // we need to manually copy to ensure that our map is Map> + signatures = (() { + final orig = json.tryGetMap('signatures'); + final res = >{}; + for (final entry + in (orig?.entries ?? >[])) { + final deviceSigs = entry.value; + if (deviceSigs is Map) { + for (final nestedEntry in deviceSigs.entries) { + final nestedValue = nestedEntry.value; + if (nestedValue is String) { + (res[entry.key] ??= {})[nestedEntry.key] = + nestedValue; + } + } + } + } + return res; + }()), + unsigned = json.tryGetMap('unsigned')?.copy(); + + Map toJson() { + final data = _json ?? {}; + data['user_id'] = userId; + data['keys'] = keys; + + if (signatures != null) { + data['signatures'] = signatures; + } + if (unsigned != null) { + data['unsigned'] = unsigned; + } + return data; + } +} + +class MatrixCrossSigningKey extends MatrixSignableKey { + List usage; + + String? get publicKey => identifier; + + MatrixCrossSigningKey( + String userId, + this.usage, + Map keys, + Map> signatures, { + Map? unsigned, + }) : super(userId, keys, signatures, unsigned: unsigned); + + @override + String? get identifier => keys.values.first; + + @override + MatrixCrossSigningKey.fromJson(Map json) + : usage = json.tryGetList('usage') ?? [], + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['usage'] = usage; + return data; + } +} + +class MatrixDeviceKeys extends MatrixSignableKey { + String deviceId; + List algorithms; + + String? get deviceDisplayName => + unsigned?.tryGet('device_display_name'); + + MatrixDeviceKeys( + String userId, + this.deviceId, + this.algorithms, + Map keys, + Map> signatures, { + Map? unsigned, + }) : super(userId, keys, signatures, unsigned: unsigned); + + @override + String? get identifier => deviceId; + + @override + MatrixDeviceKeys.fromJson(Map json) + : algorithms = json.tryGetList('algorithms') ?? [], + deviceId = json['device_id'] as String, + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['device_id'] = deviceId; + data['algorithms'] = algorithms; + return data; + } +} diff --git a/lib/matrix_api_lite/model/message_types.dart b/lib/matrix_api_lite/model/message_types.dart new file mode 100644 index 00000000..d6745350 --- /dev/null +++ b/lib/matrix_api_lite/model/message_types.dart @@ -0,0 +1,36 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +abstract class MessageTypes { + static const String Text = 'm.text'; + static const String Emote = 'm.emote'; + static const String Notice = 'm.notice'; + static const String Image = 'm.image'; + static const String Video = 'm.video'; + static const String Audio = 'm.audio'; + static const String File = 'm.file'; + static const String Location = 'm.location'; + static const String Sticker = 'm.sticker'; + static const String BadEncrypted = 'm.bad.encrypted'; + static const String None = 'm.none'; +} diff --git a/lib/matrix_api_lite/model/presence.dart b/lib/matrix_api_lite/model/presence.dart new file mode 100644 index 00000000..90e42cc5 --- /dev/null +++ b/lib/matrix_api_lite/model/presence.dart @@ -0,0 +1,34 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'basic_event_with_sender.dart'; +import 'presence_content.dart'; + +class Presence extends BasicEventWithSender { + PresenceContent presence; + + Presence.fromJson(Map json) + : presence = + PresenceContent.fromJson(json['content'] as Map), + super.fromJson(json); +} diff --git a/lib/matrix_api_lite/model/presence_content.dart b/lib/matrix_api_lite/model/presence_content.dart new file mode 100644 index 00000000..25665960 --- /dev/null +++ b/lib/matrix_api_lite/model/presence_content.dart @@ -0,0 +1,53 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'package:matrix_api_lite/matrix_api_lite.dart'; + +class PresenceContent { + PresenceType presence; + int? lastActiveAgo; + String? statusMsg; + bool? currentlyActive; + + PresenceContent.fromJson(Map json) + : presence = PresenceType.values.firstWhere( + (p) => p.toString().split('.').last == json['presence']), + lastActiveAgo = json.tryGet('last_active_ago'), + statusMsg = json.tryGet('status_msg'), + currentlyActive = json.tryGet('currently_active'); + + Map toJson() { + final data = {}; + data['presence'] = presence.toString().split('.').last; + if (lastActiveAgo != null) { + data['last_active_ago'] = lastActiveAgo; + } + if (statusMsg != null) { + data['status_msg'] = statusMsg; + } + if (currentlyActive != null) { + data['currently_active'] = currentlyActive; + } + return data; + } +} diff --git a/lib/matrix_api_lite/model/room_creation_types.dart b/lib/matrix_api_lite/model/room_creation_types.dart new file mode 100644 index 00000000..703f0ce1 --- /dev/null +++ b/lib/matrix_api_lite/model/room_creation_types.dart @@ -0,0 +1,3 @@ +abstract class RoomCreationTypes { + static const String mSpace = 'm.space'; +} diff --git a/lib/matrix_api_lite/model/room_keys_keys.dart b/lib/matrix_api_lite/model/room_keys_keys.dart new file mode 100644 index 00000000..6a3762ef --- /dev/null +++ b/lib/matrix_api_lite/model/room_keys_keys.dart @@ -0,0 +1,85 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'package:matrix_api_lite/matrix_api_lite.dart'; + +class RoomKeysSingleKey { + int firstMessageIndex; + int forwardedCount; + bool isVerified; + Map sessionData; + + RoomKeysSingleKey( + {required this.firstMessageIndex, + required this.forwardedCount, + required this.isVerified, + required this.sessionData}); + + RoomKeysSingleKey.fromJson(Map json) + : firstMessageIndex = json['first_message_index'] as int, + forwardedCount = json['forwarded_count'] as int, + isVerified = json['is_verified'] as bool, + sessionData = json['session_data'] as Map; + + Map toJson() { + final data = {}; + data['first_message_index'] = firstMessageIndex; + data['forwarded_count'] = forwardedCount; + data['is_verified'] = isVerified; + data['session_data'] = sessionData; + return data; + } +} + +class RoomKeysRoom { + Map sessions; + + RoomKeysRoom({required this.sessions}); + + RoomKeysRoom.fromJson(Map json) + : sessions = (json['sessions'] as Map).map((k, v) => + MapEntry(k, RoomKeysSingleKey.fromJson(v as Map))); + + Map toJson() { + final data = {}; + data['sessions'] = sessions.map((k, v) => MapEntry(k, v.toJson())); + return data; + } +} + +class RoomKeysUpdateResponse { + String etag; + int count; + + RoomKeysUpdateResponse.fromJson(Map json) + : etag = json.tryGet('etag') ?? + '', // synapse replies an int but docs say string? + count = json.tryGet('count') ?? 0; + + Map toJson() { + final data = {}; + data['etag'] = etag; + data['count'] = count; + return data; + } +} diff --git a/lib/matrix_api_lite/model/room_summary.dart b/lib/matrix_api_lite/model/room_summary.dart new file mode 100644 index 00000000..d0300b89 --- /dev/null +++ b/lib/matrix_api_lite/model/room_summary.dart @@ -0,0 +1,49 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +class RoomSummary { + List? mHeroes; + int? mJoinedMemberCount; + int? mInvitedMemberCount; + + RoomSummary.fromJson(Map json) + : mHeroes = json['m.heroes'] != null + ? List.from(json['m.heroes'] as List) + : null, + mJoinedMemberCount = json['m.joined_member_count'] as int?, + mInvitedMemberCount = json['m.invited_member_count'] as int?; + + Map toJson() { + final data = {}; + if (mHeroes != null) { + data['m.heroes'] = mHeroes; + } + if (mJoinedMemberCount != null) { + data['m.joined_member_count'] = mJoinedMemberCount; + } + if (mInvitedMemberCount != null) { + data['m.invited_member_count'] = mInvitedMemberCount; + } + return data; + } +} diff --git a/lib/matrix_api_lite/model/room_types.dart b/lib/matrix_api_lite/model/room_types.dart new file mode 100644 index 00000000..ba551ae9 --- /dev/null +++ b/lib/matrix_api_lite/model/room_types.dart @@ -0,0 +1,29 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +/// Type of a room which is presented in a `m.room.creation` state content in +/// the `type` key. +abstract class RoomCreationTypes { + static const String mSpace = 'm.space'; + static const String mDirectMessage = 'm.dm'; +} diff --git a/lib/matrix_api_lite/model/stripped_state_event.dart b/lib/matrix_api_lite/model/stripped_state_event.dart new file mode 100644 index 00000000..dd78184d --- /dev/null +++ b/lib/matrix_api_lite/model/stripped_state_event.dart @@ -0,0 +1,46 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'package:matrix_api_lite/matrix_api_lite.dart'; + +class StrippedStateEvent extends BasicEventWithSender { + String? stateKey; + + StrippedStateEvent( + {required String type, + required Map content, + required String senderId, + this.stateKey}) + : super(type: type, content: content, senderId: senderId); + + StrippedStateEvent.fromJson(Map json) + : stateKey = json.tryGet('state_key'), + super.fromJson(json); + + @override + Map toJson() { + final data = super.toJson(); + data['state_key'] = stateKey; + return data; + } +} diff --git a/lib/matrix_api_lite/model/sync_update.dart b/lib/matrix_api_lite/model/sync_update.dart new file mode 100644 index 00000000..806c9c52 --- /dev/null +++ b/lib/matrix_api_lite/model/sync_update.dart @@ -0,0 +1,360 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'package:matrix_api_lite/matrix_api_lite.dart'; + +class SyncUpdate { + String nextBatch; + RoomsUpdate? rooms; + List? presence; + List? accountData; + List? toDevice; + DeviceListsUpdate? deviceLists; + Map? deviceOneTimeKeysCount; + List? deviceUnusedFallbackKeyTypes; + + SyncUpdate({ + required this.nextBatch, + this.rooms, + this.presence, + this.accountData, + this.toDevice, + this.deviceLists, + this.deviceOneTimeKeysCount, + this.deviceUnusedFallbackKeyTypes, + }); + + SyncUpdate.fromJson(Map json) + : nextBatch = json.tryGet('next_batch') ?? '', + rooms = (() { + final temp = json.tryGetMap('rooms'); + return temp != null ? RoomsUpdate.fromJson(temp) : null; + }()), + presence = json + .tryGetMap>('presence')?['events'] + ?.map((i) => Presence.fromJson(i as Map)) + .toList(), + accountData = json + .tryGetMap>('account_data')?['events'] + ?.map((i) => BasicEvent.fromJson(i as Map)) + .toList(), + toDevice = json + .tryGetMap>('to_device')?['events'] + ?.map( + (i) => BasicEventWithSender.fromJson(i as Map)) + .toList(), + deviceLists = (() { + final temp = json.tryGetMap('device_lists'); + return temp != null ? DeviceListsUpdate.fromJson(temp) : null; + }()), + deviceOneTimeKeysCount = + json.tryGetMap('device_one_time_keys_count'), + deviceUnusedFallbackKeyTypes = + json.tryGetList('device_unused_fallback_key_types') ?? + json.tryGetList( + 'org.matrix.msc2732.device_unused_fallback_key_types'); + + Map toJson() { + final data = {}; + data['next_batch'] = nextBatch; + if (rooms != null) { + data['rooms'] = rooms!.toJson(); + } + if (presence != null) { + data['presence'] = { + 'events': presence!.map((i) => i.toJson()).toList(), + }; + } + if (accountData != null) { + data['account_data'] = { + 'events': accountData!.map((i) => i.toJson()).toList(), + }; + } + if (toDevice != null) { + data['to_device'] = { + 'events': toDevice!.map((i) => i.toJson()).toList(), + }; + } + if (deviceLists != null) { + data['device_lists'] = deviceLists!.toJson(); + } + if (deviceOneTimeKeysCount != null) { + data['device_one_time_keys_count'] = deviceOneTimeKeysCount; + } + if (deviceUnusedFallbackKeyTypes != null) { + data['device_unused_fallback_key_types'] = deviceUnusedFallbackKeyTypes; + data['org.matrix.msc2732.device_unused_fallback_key_types'] = + deviceUnusedFallbackKeyTypes; + } + return data; + } +} + +class RoomsUpdate { + Map? join; + Map? invite; + Map? leave; + + RoomsUpdate({ + this.join, + this.invite, + this.leave, + }); + + RoomsUpdate.fromJson(Map json) { + join = json.tryGetMap('join')?.catchMap((k, v) => + MapEntry(k, JoinedRoomUpdate.fromJson(v as Map))); + invite = json.tryGetMap('invite')?.catchMap((k, v) => + MapEntry(k, InvitedRoomUpdate.fromJson(v as Map))); + leave = json.tryGetMap('leave')?.catchMap((k, v) => + MapEntry(k, LeftRoomUpdate.fromJson(v as Map))); + } + + Map toJson() { + final data = {}; + if (join != null) { + data['join'] = join!.map((k, v) => MapEntry(k, v.toJson())); + } + if (invite != null) { + data['invite'] = invite!.map((k, v) => MapEntry(k, v.toJson())); + } + if (leave != null) { + data['leave'] = leave!.map((k, v) => MapEntry(k, v.toJson())); + } + return data; + } +} + +abstract class SyncRoomUpdate {} + +class JoinedRoomUpdate extends SyncRoomUpdate { + RoomSummary? summary; + List? state; + TimelineUpdate? timeline; + List? ephemeral; + List? accountData; + UnreadNotificationCounts? unreadNotifications; + + JoinedRoomUpdate({ + this.summary, + this.state, + this.timeline, + this.ephemeral, + this.accountData, + this.unreadNotifications, + }); + + JoinedRoomUpdate.fromJson(Map json) + : summary = json.tryGetFromJson('summary', RoomSummary.fromJson), + state = json + .tryGetMap>('state')?['events'] + ?.map((i) => MatrixEvent.fromJson(i as Map)) + .toList(), + timeline = json.tryGetFromJson('timeline', TimelineUpdate.fromJson), + ephemeral = json + .tryGetMap>('ephemeral')?['events'] + ?.map((i) => BasicRoomEvent.fromJson(i as Map)) + .toList(), + accountData = json + .tryGetMap>('account_data')?['events'] + ?.map((i) => BasicRoomEvent.fromJson(i as Map)) + .toList(), + unreadNotifications = json.tryGetFromJson( + 'unread_notifications', UnreadNotificationCounts.fromJson); + + Map toJson() { + final data = {}; + if (summary != null) { + data['summary'] = summary!.toJson(); + } + if (state != null) { + data['state'] = { + 'events': state!.map((i) => i.toJson()).toList(), + }; + } + if (timeline != null) { + data['timeline'] = timeline!.toJson(); + } + if (ephemeral != null) { + data['ephemeral'] = { + 'events': ephemeral!.map((i) => i.toJson()).toList(), + }; + } + if (accountData != null) { + data['account_data'] = { + 'events': accountData!.map((i) => i.toJson()).toList(), + }; + } + if (unreadNotifications != null) { + data['unread_notifications'] = unreadNotifications!.toJson(); + } + return data; + } +} + +class InvitedRoomUpdate extends SyncRoomUpdate { + List? inviteState; + + InvitedRoomUpdate({this.inviteState}); + + InvitedRoomUpdate.fromJson(Map json) + : inviteState = json + .tryGetMap>('invite_state')?['events'] + ?.map((i) => StrippedStateEvent.fromJson(i as Map)) + .toList(); + + Map toJson() { + final data = {}; + if (inviteState != null) { + data['invite_state'] = { + 'events': inviteState!.map((i) => i.toJson()).toList(), + }; + } + return data; + } +} + +class LeftRoomUpdate extends SyncRoomUpdate { + List? state; + TimelineUpdate? timeline; + List? accountData; + + LeftRoomUpdate({ + this.state, + this.timeline, + this.accountData, + }); + + LeftRoomUpdate.fromJson(Map json) + : state = json + .tryGetMap>('state')?['events'] + ?.map((i) => MatrixEvent.fromJson(i as Map)) + .toList(), + timeline = json.tryGetFromJson('timeline', TimelineUpdate.fromJson), + accountData = json + .tryGetMap>('account_data')?['events'] + ?.map((i) => BasicRoomEvent.fromJson(i as Map)) + .toList(); + + Map toJson() { + final data = {}; + if (state != null) { + data['state'] = { + 'events': state!.map((i) => i.toJson()).toList(), + }; + } + if (timeline != null) { + data['timeline'] = timeline!.toJson(); + } + if (accountData != null) { + data['account_data'] = { + 'events': accountData!.map((i) => i.toJson()).toList(), + }; + } + return data; + } +} + +class TimelineUpdate { + List? events; + bool? limited; + String? prevBatch; + + TimelineUpdate({ + this.events, + this.limited, + this.prevBatch, + }); + + TimelineUpdate.fromJson(Map json) + : events = json + .tryGetList>('events') + ?.map((v) => MatrixEvent.fromJson(v)) + .toList(), + limited = json.tryGet('limited'), + prevBatch = json.tryGet('prev_batch'); + + Map toJson() { + final data = {}; + if (events != null) { + data['events'] = events!.map((i) => i.toJson()).toList(); + } + if (limited != null) { + data['limited'] = limited; + } + if (prevBatch != null) { + data['prev_batch'] = prevBatch; + } + return data; + } +} + +class UnreadNotificationCounts { + int? highlightCount; + int? notificationCount; + + UnreadNotificationCounts({ + this.notificationCount, + this.highlightCount, + }); + + UnreadNotificationCounts.fromJson(Map json) + : highlightCount = json.tryGet('highlight_count'), + notificationCount = json.tryGet('notification_count'); + + Map toJson() { + final data = {}; + if (highlightCount != null) { + data['highlight_count'] = highlightCount; + } + if (notificationCount != null) { + data['notification_count'] = notificationCount; + } + return data; + } +} + +class DeviceListsUpdate { + List? changed; + List? left; + + DeviceListsUpdate({ + this.changed, + this.left, + }); + + DeviceListsUpdate.fromJson(Map json) + : changed = json.tryGetList('changed') ?? [], + left = json.tryGetList('left') ?? []; + + Map toJson() { + final data = {}; + if (changed != null) { + data['changed'] = changed; + } + if (left != null) { + data['left'] = left; + } + return data; + } +} diff --git a/lib/matrix_api_lite/utils/filter_map_extension.dart b/lib/matrix_api_lite/utils/filter_map_extension.dart new file mode 100644 index 00000000..11621e76 --- /dev/null +++ b/lib/matrix_api_lite/utils/filter_map_extension.dart @@ -0,0 +1,14 @@ +extension FilterMap on Map { + Map filterMap(MapEntry? Function(K, V) f) => + Map.fromEntries( + entries.map((e) => f(e.key, e.value)).whereType>()); + + Map catchMap(MapEntry Function(K, V) f) => + filterMap((k, v) { + try { + return f(k, v); + } catch (_) { + return null; + } + }); +} diff --git a/lib/matrix_api_lite/utils/logs.dart b/lib/matrix_api_lite/utils/logs.dart new file mode 100644 index 00000000..628e6d55 --- /dev/null +++ b/lib/matrix_api_lite/utils/logs.dart @@ -0,0 +1,134 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'print_logs_native.dart' if (dart.library.html) 'print_logs_web.dart'; + +enum Level { + wtf, + error, + warning, + info, + debug, + verbose, +} + +class Logs { + static final Logs _singleton = Logs._internal(); + + /// Override this function if you want to convert a stacktrace for some reason + /// for example to apply a source map in the browser. + static StackTrace? Function(StackTrace?) stackTraceConverter = (s) => s; + + factory Logs() { + return _singleton; + } + + Level level = Level.info; + bool nativeColors = true; + + final List outputEvents = []; + + Logs._internal(); + + void addLogEvent(LogEvent logEvent) { + outputEvents.add(logEvent); + if (logEvent.level.index <= level.index) { + logEvent.printOut(); + } + } + + void wtf(String title, [Object? exception, StackTrace? stackTrace]) => + addLogEvent( + LogEvent( + title, + exception: exception, + stackTrace: stackTraceConverter(stackTrace), + level: Level.wtf, + ), + ); + + void e(String title, [Object? exception, StackTrace? stackTrace]) => + addLogEvent( + LogEvent( + title, + exception: exception, + stackTrace: stackTraceConverter(stackTrace), + level: Level.error, + ), + ); + + void w(String title, [Object? exception, StackTrace? stackTrace]) => + addLogEvent( + LogEvent( + title, + exception: exception, + stackTrace: stackTraceConverter(stackTrace), + level: Level.warning, + ), + ); + + void i(String title, [Object? exception, StackTrace? stackTrace]) => + addLogEvent( + LogEvent( + title, + exception: exception, + stackTrace: stackTraceConverter(stackTrace), + level: Level.info, + ), + ); + + void d(String title, [Object? exception, StackTrace? stackTrace]) => + addLogEvent( + LogEvent( + title, + exception: exception, + stackTrace: stackTraceConverter(stackTrace), + level: Level.debug, + ), + ); + + void v(String title, [Object? exception, StackTrace? stackTrace]) => + addLogEvent( + LogEvent( + title, + exception: exception, + stackTrace: stackTraceConverter(stackTrace), + level: Level.verbose, + ), + ); +} + +// ignore: avoid_print +class LogEvent { + final String title; + final Object? exception; + final StackTrace? stackTrace; + final Level level; + + LogEvent( + this.title, { + this.exception, + this.stackTrace, + this.level = Level.debug, + }); +} diff --git a/lib/matrix_api_lite/utils/map_copy_extension.dart b/lib/matrix_api_lite/utils/map_copy_extension.dart new file mode 100644 index 00000000..6ece67d3 --- /dev/null +++ b/lib/matrix_api_lite/utils/map_copy_extension.dart @@ -0,0 +1,43 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +extension MapCopyExtension on Map { + dynamic _copyValue(dynamic value) { + if (value is Map) { + return value.copy(); + } + if (value is List) { + return value.map(_copyValue).toList(); + } + return value; + } + + /// Deep-copies a given json map + Map copy() { + final copy = Map.from(this); + for (final entry in copy.entries) { + copy[entry.key] = _copyValue(entry.value); + } + return copy; + } +} diff --git a/lib/matrix_api_lite/utils/print_logs_native.dart b/lib/matrix_api_lite/utils/print_logs_native.dart new file mode 100644 index 00000000..7b8f5314 --- /dev/null +++ b/lib/matrix_api_lite/utils/print_logs_native.dart @@ -0,0 +1,36 @@ +import 'package:matrix_api_lite/matrix_api_lite.dart'; + +extension PrintLogs on LogEvent { + void printOut() { + var logsStr = title; + if (exception != null) { + logsStr += ' - ${exception.toString()}'; + } + if (stackTrace != null) { + logsStr += '\n${stackTrace.toString()}'; + } + if (Logs().nativeColors) { + switch (level) { + case Level.wtf: + logsStr = '\x1B[31m!!!CRITICAL!!! $logsStr\x1B[0m'; + break; + case Level.error: + logsStr = '\x1B[31m$logsStr\x1B[0m'; + break; + case Level.warning: + logsStr = '\x1B[33m$logsStr\x1B[0m'; + break; + case Level.info: + logsStr = '\x1B[32m$logsStr\x1B[0m'; + break; + case Level.debug: + logsStr = '\x1B[34m$logsStr\x1B[0m'; + break; + case Level.verbose: + break; + } + } + // ignore: avoid_print + print('[Matrix] $logsStr'); + } +} diff --git a/lib/matrix_api_lite/utils/print_logs_web.dart b/lib/matrix_api_lite/utils/print_logs_web.dart new file mode 100644 index 00000000..f13f0311 --- /dev/null +++ b/lib/matrix_api_lite/utils/print_logs_web.dart @@ -0,0 +1,35 @@ +import 'dart:html'; + +import 'package:matrix_api_lite/matrix_api_lite.dart'; + +extension PrintLogs on LogEvent { + void printOut() { + var logsStr = '[Matrix] $title'; + if (exception != null) { + logsStr += ' - ${exception.toString()}'; + } + if (stackTrace != null) { + logsStr += '\n${stackTrace.toString()}'; + } + switch (level) { + case Level.wtf: + window.console.error('!!!CRITICAL!!! $logsStr'); + break; + case Level.error: + window.console.error(logsStr); + break; + case Level.warning: + window.console.warn(logsStr); + break; + case Level.info: + window.console.info(logsStr); + break; + case Level.debug: + window.console.debug(logsStr); + break; + case Level.verbose: + window.console.log(logsStr); + break; + } + } +} diff --git a/lib/matrix_api_lite/utils/try_get_map_extension.dart b/lib/matrix_api_lite/utils/try_get_map_extension.dart new file mode 100644 index 00000000..5a9cf7a8 --- /dev/null +++ b/lib/matrix_api_lite/utils/try_get_map_extension.dart @@ -0,0 +1,121 @@ +/* MIT License +* +* Copyright (C) 2019, 2020, 2021 Famedly GmbH +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import 'dart:core'; + +import 'logs.dart'; + +abstract class TryGet { + void call(String key, Type expected, Type actual); + + static const TryGet required = _RequiredLog(); + static const TryGet optional = _OptionalLog(); + + /// This is helpful if you have a field that can mean multiple things on purpose. + static const TryGet silent = _SilentLog(); +} + +class _RequiredLog implements TryGet { + const _RequiredLog(); + @override + void call(String key, Type expected, Type actual) => Logs().w( + 'Expected required "$expected" in event content for the Key "$key" but got "$actual" at ${StackTrace.current.firstLine}'); +} + +class _OptionalLog implements TryGet { + const _OptionalLog(); + @override + void call(String key, Type expected, Type actual) { + if (actual != Null) { + Logs().w( + 'Expected optional "$expected" in event content for the Key "$key" but got "$actual" at ${StackTrace.current.firstLine}'); + } + } +} + +class _SilentLog implements TryGet { + const _SilentLog(); + @override + void call(String key, Type expected, Type actual) {} +} + +extension TryGetMapExtension on Map { + T? tryGet(String key, [TryGet log = TryGet.optional]) { + final Object? value = this[key]; + if (value is! T) { + log(key, T, value.runtimeType); + return null; + } + return value; + } + + List? tryGetList(String key, [TryGet log = TryGet.optional]) { + final Object? value = this[key]; + if (value is! List) { + log(key, T, value.runtimeType); + return null; + } + try { + // copy entries to ensure type check failures here and not an access + return value.cast().toList(); + } catch (_) { + Logs().v( + 'Unable to create "List<$T>" in event content for the key "$key" at ${StackTrace.current.firstLine}'); + return null; + } + } + + Map? tryGetMap(String key, [TryGet log = TryGet.optional]) { + final Object? value = this[key]; + if (value is! Map) { + log(key, {}.runtimeType, value.runtimeType); + return null; + } + try { + // copy map to ensure type check failures here and not an access + return Map.from(value.cast()); + } catch (_) { + Logs().v( + 'Unable to create "Map<$A,$B>" in event content for the key "$key" at ${StackTrace.current.firstLine}'); + return null; + } + } + + A? tryGetFromJson(String key, A Function(Map) fromJson, + [TryGet log = TryGet.optional]) { + final value = tryGetMap(key, log); + + return value != null ? fromJson(value) : null; + } +} + +extension on StackTrace { + String get firstLine { + final lines = toString().split('\n'); + return lines.length >= 3 + ? lines[2].replaceFirst('#2 ', '') + : lines.isNotEmpty + ? lines.first + : '(unknown position)'; + } +} diff --git a/lib/matrix_api_lite/values.dart b/lib/matrix_api_lite/values.dart new file mode 100644 index 00000000..aa4f66ac --- /dev/null +++ b/lib/matrix_api_lite/values.dart @@ -0,0 +1,21 @@ +// OpenAPI only supports real enums (modeled as enum in generated/model.dart). + +// In this file, possible values are defined manually, +// for cases where other values are allowed too. + +class PushRuleAction { + static final notify = 'notify'; + static final dontNotify = 'dont_notify'; + static final coalesce = 'coalesce'; + static final setTweak = 'set_tweak'; +} + +class TagType { + static final favourite = 'm.favourite'; + static final lowPriority = 'm.lowpriority'; + static final serverNotice = 'm.server_notice'; + + static bool isValid(String tag) => tag.startsWith('m.') + ? [favourite, lowPriority, serverNotice].contains(tag) + : true; +}