about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.idea/.idea.LibMatrix/.idea/vcs.xml1
m---------ArcaneLibs0
-rw-r--r--CONTRIBUTING.MD30
-rw-r--r--LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs2
-rw-r--r--LibMatrix.EventTypes/Common/Msc2545EmoteRoomsAccountDataEventContent.cs31
-rw-r--r--LibMatrix.EventTypes/EventContent.cs2
-rw-r--r--LibMatrix.EventTypes/LibMatrix.EventTypes.csproj8
-rw-r--r--LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs28
-rw-r--r--LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs81
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs5
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs7
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPolicyServerEventContent.cs11
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomRedactionEventContent.cs18
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs7
-rw-r--r--LibMatrix.EventTypes/deps.json0
-rw-r--r--LibMatrix.Federation/AuthenticatedFederationClient.cs24
-rw-r--r--LibMatrix.Federation/Extensions/Ed25519Extensions.cs10
-rw-r--r--LibMatrix.Federation/Extensions/ObjectExtensions.cs31
-rw-r--r--LibMatrix.Federation/Extensions/SignedObjectExtensions.cs37
-rw-r--r--LibMatrix.Federation/Extensions/XMatrixAuthorizationSchemeExtensions.cs20
-rw-r--r--LibMatrix.Federation/LibMatrix.Federation.csproj20
-rw-r--r--LibMatrix.Federation/Utilities/UnpaddedBase64.cs17
-rw-r--r--LibMatrix.Federation/XMatrixAuthorizationScheme.cs70
-rw-r--r--LibMatrix.Federation/deps.json22
-rw-r--r--LibMatrix.sln458
-rw-r--r--LibMatrix/Abstractions/VersionedKeyId.cs24
-rw-r--r--LibMatrix/Abstractions/VersionedPublicKey.cs16
-rw-r--r--LibMatrix/Extensions/CanonicalJsonSerializer.cs9
-rw-r--r--LibMatrix/Extensions/EnumerableExtensions.cs12
-rw-r--r--LibMatrix/Extensions/JsonElementExtensions.cs6
-rw-r--r--LibMatrix/Extensions/MatrixHttpClient.Single.cs258
-rw-r--r--LibMatrix/Helpers/MessageBuilder.cs32
-rw-r--r--LibMatrix/Helpers/MessageFormatter.cs7
-rw-r--r--LibMatrix/Helpers/RoomBuilder.cs279
-rw-r--r--LibMatrix/Helpers/RoomUpgradeBuilder.cs232
-rw-r--r--LibMatrix/Helpers/SyncHelper.cs27
-rw-r--r--LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs44
-rw-r--r--LibMatrix/Helpers/SyncStateResolver.cs2
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs124
-rw-r--r--LibMatrix/Homeservers/FederationClient.cs14
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalRoomQueryFilter.cs109
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs22
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/EventReportListResult.cs6
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/RoomListResult.cs52
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminRoomStateResult.cs2
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs170
-rw-r--r--LibMatrix/Homeservers/RemoteHomeServer.cs54
-rw-r--r--LibMatrix/LibMatrix.csproj11
-rw-r--r--LibMatrix/LibMatrixNetworkException.cs33
-rw-r--r--LibMatrix/Responses/CreateRoomRequest.cs21
-rw-r--r--LibMatrix/Responses/EventIdResponse.cs (renamed from LibMatrix/EventIdResponse.cs)2
-rw-r--r--LibMatrix/Responses/Federation/ServerKeysResponse.cs55
-rw-r--r--LibMatrix/Responses/Federation/ServerVersionResponse.cs16
-rw-r--r--LibMatrix/Responses/Federation/SignedObject.cs68
-rw-r--r--LibMatrix/Responses/LoginResponse.cs5
-rw-r--r--LibMatrix/Responses/MessagesResponse.cs (renamed from LibMatrix/MessagesResponse.cs)6
-rw-r--r--LibMatrix/Responses/SyncResponse.cs14
-rw-r--r--LibMatrix/Responses/UserIdAndReason.cs (renamed from LibMatrix/UserIdAndReason.cs)2
-rw-r--r--LibMatrix/Responses/WhoAmIResponse.cs (renamed from LibMatrix/WhoAmIResponse.cs)2
-rw-r--r--LibMatrix/RoomTypes/GenericRoom.cs166
-rw-r--r--LibMatrix/RoomTypes/PolicyRoom.cs10
-rw-r--r--LibMatrix/RoomTypes/SpaceRoom.cs1
-rw-r--r--LibMatrix/Services/HomeserverProviderService.cs3
-rw-r--r--LibMatrix/Services/HomeserverResolverService.cs47
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs20
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs2
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs5
-rw-r--r--LibMatrix/StateEvent.cs57
-rw-r--r--LibMatrix/StructuredData/MxcUri.cs (renamed from LibMatrix/MxcUri.cs)2
-rw-r--r--LibMatrix/StructuredData/UserId.cs27
-rw-r--r--LibMatrix/deps.json12
-rw-r--r--README.MD29
-rw-r--r--Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs18
-rw-r--r--Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs18
-rw-r--r--Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs2
-rw-r--r--Tests/LibMatrix.Tests/LibMatrix.Tests.csproj16
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs25
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs17
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs13
-rw-r--r--Utilities/LibMatrix.DebugDataValidationApi/LibMatrix.DebugDataValidationApi.csproj6
-rw-r--r--Utilities/LibMatrix.DevTestBot/Bot/DevTestBot.cs2
-rw-r--r--Utilities/LibMatrix.DevTestBot/Bot/PingTestBot.cs2
-rw-r--r--Utilities/LibMatrix.DevTestBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs2
-rw-r--r--Utilities/LibMatrix.DevTestBot/LibMatrix.DevTestBot.csproj72
-rw-r--r--Utilities/LibMatrix.E2eeTestKit/LibMatrix.E2eeTestKit.csproj46
-rw-r--r--Utilities/LibMatrix.FederationTest/.gitignore1
-rw-r--r--Utilities/LibMatrix.FederationTest/Controllers/RemoteServerPingController.cs79
-rw-r--r--Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs42
-rw-r--r--Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs19
-rw-r--r--Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs19
-rw-r--r--Utilities/LibMatrix.FederationTest/Controllers/TestController.cs52
-rw-r--r--Utilities/LibMatrix.FederationTest/LibMatrix.FederationTest.csproj18
-rw-r--r--Utilities/LibMatrix.FederationTest/Program.cs40
-rw-r--r--Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs54
-rw-r--r--Utilities/LibMatrix.FederationTest/Services/FederationTestConfiguration.cs10
-rw-r--r--Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs28
-rw-r--r--Utilities/LibMatrix.FederationTest/appsettings.Development.json13
-rw-r--r--Utilities/LibMatrix.FederationTest/appsettings.json9
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/LegacyController.cs2
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomAccountDataController.cs6
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomMembersController.cs4
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs5
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs22
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs1
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs1
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Extensions/EventExtensions.cs6
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/LibMatrix.HomeserverEmulator.csproj2020
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs4
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Services/RoomStore.cs36
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Services/UserStore.cs9
-rw-r--r--Utilities/LibMatrix.JsonSerializerContextGenerator/LibMatrix.JsonSerializerContextGenerator.csproj2
-rw-r--r--Utilities/LibMatrix.TestDataGenerator/Bot/DataFetcher.cs4
-rw-r--r--Utilities/LibMatrix.TestDataGenerator/LibMatrix.TestDataGenerator.csproj4
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs3
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/Interfaces/RoomInviteContext.cs2
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/LibMatrix.Utilities.Bot.csproj9
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs15
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/deps.json142
-rw-r--r--flake.lock116
-rw-r--r--flake.nix104
-rwxr-xr-xscripts/fetchdeps.sh5
-rwxr-xr-xscripts/publish.sh20
123 files changed, 4446 insertions, 1777 deletions
diff --git a/.gitignore b/.gitignore

index 509a921..c6629e3 100644 --- a/.gitignore +++ b/.gitignore
@@ -12,3 +12,4 @@ appsettings.Local*.json appservice.yaml appservice.json Tests/LibMatrix.Tests/appsettings.json +result diff --git a/.idea/.idea.LibMatrix/.idea/vcs.xml b/.idea/.idea.LibMatrix/.idea/vcs.xml
index 35eb1dd..63ea78b 100644 --- a/.idea/.idea.LibMatrix/.idea/vcs.xml +++ b/.idea/.idea.LibMatrix/.idea/vcs.xml
@@ -2,5 +2,6 @@ <project version="4"> <component name="VcsDirectoryMappings"> <mapping directory="" vcs="Git" /> + <mapping directory="$PROJECT_DIR$/ArcaneLibs" vcs="Git" /> </component> </project> \ No newline at end of file diff --git a/ArcaneLibs b/ArcaneLibs -Subproject a2c73b7b89ca8910a0f9a9856bb06a6d5f49ce0 +Subproject 412a14c2ad2fed85066155d1060c35cbe23d6c9 diff --git a/CONTRIBUTING.MD b/CONTRIBUTING.MD new file mode 100644
index 0000000..8c305b7 --- /dev/null +++ b/CONTRIBUTING.MD
@@ -0,0 +1,30 @@ +# Contributing + +Any contribution is welcome, even if it's just documentation or recommended git practices! We're not too strict on code style, but we do have a few guidelines: +- Use spaces, not tabs +- Use 4 spaces for indentation +- Use the C# naming convention for variables, methods, etc. +- Wrap lines at 160 characters, though this value can be changed if it's too lean or strict +- Use the `#region` and `#endregion` directives to group code if you're adding utility functions + +```sh +# Prepare patch set +mkdir patches +git format-patch --output-directory "./patches" @{u}.. + +# Send patches +... +``` +You can send the patches to [@emma:rory.gay](https://matrix.to/#/@emma:rory.gay) or in the [Rory&::LibMatrix room](https://matrix.to/#/#libmatrix:rory.gay). + +### Developer utility commands + +Error reporting upon file save (may not work): +```sh +inotifywait -rmqe CLOSE_WRITE --include '.*\.cs$' . | while read l; do clear; dotnet build --property WarningLevel=0; done +``` + +Hot rebuild on file save: +```sh +dotnet watch run --no-hot-reload --property WarningLevel=0 +``` diff --git a/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs b/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs
index a31cbbb..a1ebd79 100644 --- a/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs +++ b/LibMatrix.EventTypes/Common/MjolnirShortcodeEventContent.cs
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization; namespace LibMatrix.EventTypes.Common; [MatrixEvent(EventName = EventId)] -public class MjolnirShortcodeEventContent : TimelineEventContent { +public class MjolnirShortcodeEventContent : EventContent { public const string EventId = "org.matrix.mjolnir.shortcode"; [JsonPropertyName("shortcode")] diff --git a/LibMatrix.EventTypes/Common/Msc2545EmoteRoomsAccountDataEventContent.cs b/LibMatrix.EventTypes/Common/Msc2545EmoteRoomsAccountDataEventContent.cs new file mode 100644
index 0000000..4fd5c29 --- /dev/null +++ b/LibMatrix.EventTypes/Common/Msc2545EmoteRoomsAccountDataEventContent.cs
@@ -0,0 +1,31 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace LibMatrix.EventTypes.Spec; + +[MatrixEvent(EventName = EventId)] +public class Msc2545EmoteRoomsAccountDataEventContent : EventContent { + public const string EventId = "im.ponies.emote_rooms"; + + [JsonPropertyName("rooms")] + public Dictionary<string, Dictionary<string, EnabledEmotePackEntry>> Rooms { get; set; } = new(); + + // Dummy type to provide easy access to the by-spec empty content + public class EnabledEmotePackEntry { + [JsonExtensionData] + public Dictionary<string, object>? AdditionalData { get; set; } = []; + + public T? GetAdditionalData<T>(string key) where T : class { + if (AdditionalData == null || !AdditionalData.TryGetValue(key, out var value)) + return null; + + if (value is T tValue) + return tValue; + if (value is JsonElement jsonElement) + return jsonElement.Deserialize<T>(); + + throw new InvalidCastException($"Value for key '{key}' ({value.GetType()}) cannot be cast to type '{typeof(T)}'. Cannot continue."); + } + } +} \ No newline at end of file diff --git a/LibMatrix.EventTypes/EventContent.cs b/LibMatrix.EventTypes/EventContent.cs
index 07f56e2..d612e44 100644 --- a/LibMatrix.EventTypes/EventContent.cs +++ b/LibMatrix.EventTypes/EventContent.cs
@@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; using System.Text.Json.Nodes; @@ -5,6 +6,7 @@ using System.Text.Json.Serialization; namespace LibMatrix.EventTypes; +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "These get instantiated via reflection")] public abstract class EventContent { [JsonExtensionData] public Dictionary<string, object>? AdditionalData { get; set; } = []; diff --git a/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj b/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj
index 0924aba..31dfe16 100644 --- a/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj +++ b/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj
@@ -1,14 +1,16 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> - <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250419-174711" Condition="'$(Configuration)' == 'Release'" /> - <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(Configuration)' == 'Debug'"/> + <!-- <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250806-011111" Condition="'$(Configuration)' == 'Release'" />--> + <!-- <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(Configuration)' == 'Debug'"/>--> + <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(ContinuousIntegrationBuild)'!='true'"/> + <PackageReference Include="ArcaneLibs" Version="*-*" Condition="'$(ContinuousIntegrationBuild)'=='true'"/> </ItemGroup> </Project> diff --git a/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs
index 9602bf3..d1cf8be 100644 --- a/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs +++ b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs
@@ -34,9 +34,29 @@ public class RoomMessageEventContent : TimelineEventContent { [JsonPropertyName("info")] public FileInfoStruct? FileInfo { get; set; } - + [JsonIgnore] - public string BodyWithoutReplyFallback => Body.Split('\n').SkipWhile(x => x.StartsWith(">")).SkipWhile(x=>x.Trim().Length == 0).Aggregate((x, y) => $"{x}\n{y}"); + public string BodyWithoutReplyFallback { + get { + var parts = Body + .Split('\n') + .SkipWhile(x => x.StartsWith(">")) + .SkipWhile(x => x.Trim().Length == 0) + .ToList(); + return parts.Count > 0 ? parts.Aggregate((x, y) => $"{x}\n{y}") : Body; + } + } + + [JsonPropertyName("m.mentions")] + public MentionsStruct? Mentions { get; set; } + + public class MentionsStruct { + [JsonPropertyName("user_ids")] + public List<string>? Users { get; set; } + + [JsonPropertyName("room")] + public bool? Room { get; set; } + } public class FileInfoStruct { [JsonPropertyName("mimetype")] @@ -47,10 +67,10 @@ public class RoomMessageEventContent : TimelineEventContent { [JsonPropertyName("thumbnail_url")] public string? ThumbnailUrl { get; set; } - + [JsonPropertyName("w")] public int? Width { get; set; } - + [JsonPropertyName("h")] public int? Height { get; set; } } diff --git a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
index 6f8c194..36c94ae 100644 --- a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
@@ -1,5 +1,6 @@ using System.Diagnostics; using System.Security.Cryptography; +using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using ArcaneLibs.Attributes; @@ -35,7 +36,6 @@ public class RoomPolicyRuleEventContent : PolicyRuleEventContent { [DebuggerDisplay("""{GetType().Name.Replace("PolicyRuleEventContent", ""),nq} policy matching {Entity}, Reason: {Reason}""")] public abstract class PolicyRuleEventContent : EventContent { // public PolicyRuleEventContent() => Console.WriteLine($"init policy {GetType().Name}"); - private string? _reason; /// <summary> /// Entity this ban applies to, can use * and ? as globs. @@ -52,17 +52,7 @@ public abstract class PolicyRuleEventContent : EventContent { /// </summary> [JsonPropertyName("reason")] [FriendlyName(Name = "Reason")] - public virtual string? Reason { - get => - // Console.WriteLine($"Read policy reason: {_reason}"); - _reason; - set => - // Console.WriteLine($"Set policy reason: {value}"); - // if(init) - // Console.WriteLine(string.Join('\n', Environment.StackTrace.Split('\n')[..5])); - // init = true; - _reason = value; - } + public string? Reason { get; set; } /// <summary> /// Suggested action to take @@ -93,29 +83,55 @@ public abstract class PolicyRuleEventContent : EventContent { } } + [JsonPropertyName("org.matrix.msc4205.hashes")] + [TableHide] + public PolicyHash? Hashes { get; set; } + public string GetDraupnir2StateKey() => Convert.ToBase64String(SHA256.HashData($"{Entity}{Recommendation}".AsBytes().ToArray())); + public Regex? GetEntityRegex() => Entity is null ? null : new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", "."), RegexOptions.Compiled); + public bool IsGlobRule() => !string.IsNullOrWhiteSpace(Entity) && (Entity.Contains('*') || Entity.Contains('?')); + public bool IsHashedRule() => string.IsNullOrWhiteSpace(Entity) && Hashes is not null; - public Regex? GetEntityRegex() => Entity is null ? null : new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", ".")); + public bool EntityMatches(string entity) { + if (string.IsNullOrWhiteSpace(entity)) return false; - public bool IsGlobRule() => - Entity != null - && (Entity.Contains('*') || Entity.Contains('?')); + if (!string.IsNullOrWhiteSpace(Entity)) { + // Check if entity is equal regardless of glob check + var match = Entity == entity || (IsGlobRule() && GetEntityRegex()!.IsMatch(entity)); + if (match) return match; + } - public bool EntityMatches(string entity) => - Entity != null - && ( - Entity == entity - || ( - IsGlobRule() - ? GetEntityRegex()!.IsMatch(entity) - : entity == Entity - ) - ); + if (Hashes is not null) { + if (!string.IsNullOrWhiteSpace(Hashes.Sha256)) { + var hash = SHA256.HashData(entity.AsBytes().ToArray()); + var match = Convert.ToBase64String(hash) == Hashes.Sha256; + if (match) return match; + } + } + + return false; + } public string? GetNormalizedRecommendation() { if (Recommendation is "m.ban" or "org.matrix.mjolnir.ban") return PolicyRecommendationTypes.Ban; + if (Recommendation is "m.takedown" or "org.matrix.msc4204.takedown") + return "m.takedown"; + + return Recommendation; + } + + public string? GetSpecRecommendation() { + if (Recommendation is "m.ban" or "org.matrix.mjolnir.ban") + return PolicyRecommendationTypes.Ban; + + if (Recommendation is "m.mute" or "support.feline.policy.recommendation_mute") + return PolicyRecommendationTypes.Mute; + + if (Recommendation is "m.takedown" or "org.matrix.msc4204.takedown") + return PolicyRecommendationTypes.Takedown; + return Recommendation; } } @@ -130,6 +146,19 @@ public static class PolicyRecommendationTypes { /// Mute this user /// </summary> public static string Mute = "support.feline.policy.recommendation_mute"; //stable prefix: m.mute, msc pending + + /// <summary> + /// Take down the user with all means available + /// </summary> + public static string Takedown = "org.matrix.msc4204.takedown"; //stable prefix: m.takedown, msc pending +} + +public class PolicyHash { + [JsonPropertyName("sha256")] + public string? Sha256 { get; set; } + + [JsonExtensionData] + public Dictionary<string, object>? AdditionalProperties { get; set; } } // public class PolicySchemaDefinition { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
index 37b831a..8519889 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
@@ -12,9 +12,14 @@ public class RoomCreateEventContent : EventContent { [JsonPropertyName("room_version")] public string? RoomVersion { get; set; } + // missing in room version 11+ [JsonPropertyName("creator")] public string? Creator { get; set; } + // v12+ + [JsonPropertyName("additional_creators")] + public List<string>? AdditionalCreators { get; set; } + [JsonPropertyName("m.federate")] public bool? Federate { get; set; } diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
index 16cfcb0..3b3ba34 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
@@ -8,4 +8,11 @@ public class RoomHistoryVisibilityEventContent : EventContent { [JsonPropertyName("history_visibility")] public required string HistoryVisibility { get; set; } + + public static class HistoryVisibilityTypes { + public const string WorldReadable = "world_readable"; + public const string Invited = "invited"; + public const string Shared = "shared"; + public const string Joined = "joined"; + } } \ No newline at end of file diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPolicyServerEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPolicyServerEventContent.cs new file mode 100644
index 0000000..80e254f --- /dev/null +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPolicyServerEventContent.cs
@@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; + +[MatrixEvent(EventName = EventId)] +public class RoomPolicyServerEventContent : EventContent { + public const string EventId = "org.matrix.msc4284.policy"; + + [JsonPropertyName("via")] + public string? Via { get; set; } +} \ No newline at end of file diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomRedactionEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomRedactionEventContent.cs new file mode 100644
index 0000000..055f22d --- /dev/null +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomRedactionEventContent.cs
@@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; + +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; + +[MatrixEvent(EventName = EventId)] +public class RoomRedactionEventContent : EventContent { + public const string EventId = "m.room.redaction"; + + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// <summary> + /// Required in room version 11 + /// </summary> + [JsonPropertyName("redacts")] + public string? Redacts { get; set; } +} \ No newline at end of file diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
index c492250..c7ad491 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
@@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using System.Text.RegularExpressions; namespace LibMatrix.EventTypes.Spec.State.RoomInfo; @@ -14,4 +15,10 @@ public class RoomServerAclEventContent : EventContent { [JsonPropertyName("allow_ip_literals")] public bool AllowIpLiterals { get; set; } // = false; + + [JsonIgnore] + public List<Regex>? AllowRegexes => Allow?.ConvertAll(pattern => new Regex(pattern.Replace(".", "\\.").Replace("*", ".*").Replace("?", "."), RegexOptions.Compiled)) ?? []; + + [JsonIgnore] + public List<Regex>? DenyRegexes => Deny?.ConvertAll(pattern => new Regex(pattern.Replace(".", "\\.").Replace("*", ".*").Replace("?", "."), RegexOptions.Compiled)) ?? []; } \ No newline at end of file diff --git a/LibMatrix.EventTypes/deps.json b/LibMatrix.EventTypes/deps.json new file mode 100644
index 0000000..e69de29 --- /dev/null +++ b/LibMatrix.EventTypes/deps.json
diff --git a/LibMatrix.Federation/AuthenticatedFederationClient.cs b/LibMatrix.Federation/AuthenticatedFederationClient.cs new file mode 100644
index 0000000..ee4bb25 --- /dev/null +++ b/LibMatrix.Federation/AuthenticatedFederationClient.cs
@@ -0,0 +1,24 @@ +using LibMatrix.Abstractions; +using LibMatrix.Federation.Extensions; +using LibMatrix.Homeservers; + +namespace LibMatrix.Federation; + +public class AuthenticatedFederationClient(string federationEndpoint, AuthenticatedFederationClient.AuthenticatedFederationConfiguration config, string? proxy = null) : FederationClient(federationEndpoint, proxy) { + + public class AuthenticatedFederationConfiguration { + public required VersionedHomeserverPrivateKey PrivateKey { get; set; } + public required string OriginServerName { get; set; } + } + + // public async Task<UserDeviceListResponse> GetUserDevicesAsync(string userId) { + // var response = await HttpClient.SendAsync(new XMatrixAuthorizationScheme.XMatrixRequestSignature() { + // OriginServerName = config.OriginServerName, + // DestinationServerName = userId.Split(':', 2)[1], + // Method = "GET", + // Uri = $"/_matrix/federation/v1/user/devices/{userId}", + // }.ToSignedHttpRequestMessage(config.PrivateKey)); + // return response; + // } + +} \ No newline at end of file diff --git a/LibMatrix.Federation/Extensions/Ed25519Extensions.cs b/LibMatrix.Federation/Extensions/Ed25519Extensions.cs new file mode 100644
index 0000000..e5a9e5d --- /dev/null +++ b/LibMatrix.Federation/Extensions/Ed25519Extensions.cs
@@ -0,0 +1,10 @@ +using LibMatrix.Abstractions; +using LibMatrix.FederationTest.Utilities; +using Org.BouncyCastle.Crypto.Parameters; + +namespace LibMatrix.Federation.Extensions; + +public static class Ed25519Extensions { + public static string ToUnpaddedBase64(this Ed25519PublicKeyParameters key) => UnpaddedBase64.Encode(key.GetEncoded()); + public static Ed25519PrivateKeyParameters GetPrivateEd25519Key(this VersionedHomeserverPrivateKey key) => new(UnpaddedBase64.Decode(key.PrivateKey), 0); +} \ No newline at end of file diff --git a/LibMatrix.Federation/Extensions/ObjectExtensions.cs b/LibMatrix.Federation/Extensions/ObjectExtensions.cs new file mode 100644
index 0000000..d20385d --- /dev/null +++ b/LibMatrix.Federation/Extensions/ObjectExtensions.cs
@@ -0,0 +1,31 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using LibMatrix.Abstractions; +using LibMatrix.Extensions; +using LibMatrix.FederationTest.Utilities; +using LibMatrix.Responses.Federation; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math.EC.Rfc8032; + +namespace LibMatrix.Federation.Extensions; +public static class ObjectExtensions { + public static SignedObject<T> Sign<T>(this T content, string serverName, string keyName, Ed25519PrivateKeyParameters key) { + SignedObject<T> signedObject = new() { + Signatures = [], + Content = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(content)) ?? new JsonObject(), + }; + + var contentBytes = CanonicalJsonSerializer.SerializeToUtf8Bytes(signedObject.Content); + var signature = new byte[Ed25519.SignatureSize]; + key.Sign(Ed25519.Algorithm.Ed25519, null, contentBytes, 0, contentBytes.Length, signature, 0); + + if (!signedObject.Signatures.ContainsKey(serverName)) + signedObject.Signatures[serverName] = new Dictionary<string, string>(); + + signedObject.Signatures[serverName][keyName] = UnpaddedBase64.Encode(signature); + return signedObject; + } + + public static SignedObject<T> Sign<T>(this T content, VersionedHomeserverPrivateKey privateKey) + => Sign(content, privateKey.ServerName, privateKey.KeyId, privateKey.GetPrivateEd25519Key()); +} \ No newline at end of file diff --git a/LibMatrix.Federation/Extensions/SignedObjectExtensions.cs b/LibMatrix.Federation/Extensions/SignedObjectExtensions.cs new file mode 100644
index 0000000..eb1376e --- /dev/null +++ b/LibMatrix.Federation/Extensions/SignedObjectExtensions.cs
@@ -0,0 +1,37 @@ +using LibMatrix.Extensions; +using LibMatrix.FederationTest.Utilities; +using LibMatrix.Responses.Federation; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math.EC.Rfc8032; + +namespace LibMatrix.Federation.Extensions; +public static class SignedObjectExtensions { + public static SignedObject<T> Sign<T>(this SignedObject<T> content, string serverName, string keyName, Ed25519PrivateKeyParameters key) { + var signResult = content.Content.Sign(serverName, keyName, key); + var signedObject = new SignedObject<T> { + Signatures = content.Signatures, + Content = signResult.Content + }; + + if (!signedObject.Signatures.ContainsKey(serverName)) + signedObject.Signatures[serverName] = new Dictionary<string, string>(); + + signedObject.Signatures[serverName][keyName] = signResult.Signatures[serverName][keyName]; + return signedObject; + } + + public static bool ValidateSignature<T>(this SignedObject<T> content, string serverName, string keyName, Ed25519PublicKeyParameters key) { + if (!content.Signatures.TryGetValue(serverName, out var serverSignatures)) + return false; + + if (!serverSignatures.TryGetValue(keyName, out var signatureBase64)) + return false; + + var signature = UnpaddedBase64.Decode(signatureBase64); + if (signature.Length != Ed25519.SignatureSize) + return false; + + var contentBytes = CanonicalJsonSerializer.SerializeToUtf8Bytes(content.Content); + return Ed25519.Verify(signature, 0, key.GetEncoded(), 0, contentBytes, 0, contentBytes.Length); + } +} \ No newline at end of file diff --git a/LibMatrix.Federation/Extensions/XMatrixAuthorizationSchemeExtensions.cs b/LibMatrix.Federation/Extensions/XMatrixAuthorizationSchemeExtensions.cs new file mode 100644
index 0000000..792264a --- /dev/null +++ b/LibMatrix.Federation/Extensions/XMatrixAuthorizationSchemeExtensions.cs
@@ -0,0 +1,20 @@ +using System.Net.Http.Json; +using LibMatrix.Abstractions; + +namespace LibMatrix.Federation.Extensions; + +public static class XMatrixAuthorizationSchemeExtensions { + public static HttpRequestMessage ToSignedHttpRequestMessage(this XMatrixAuthorizationScheme.XMatrixRequestSignature requestSignature, VersionedHomeserverPrivateKey privateKey) { + var signature = requestSignature.Sign(privateKey); + var requestMessage = new HttpRequestMessage { + Method = new HttpMethod(requestSignature.Method), + RequestUri = new Uri(requestSignature.Uri, UriKind.Relative) + }; + + if (requestSignature.Content != null) { + requestMessage.Content = JsonContent.Create(requestSignature.Content); + } + + return requestMessage; + } +} \ No newline at end of file diff --git a/LibMatrix.Federation/LibMatrix.Federation.csproj b/LibMatrix.Federation/LibMatrix.Federation.csproj new file mode 100644
index 0000000..851bf25 --- /dev/null +++ b/LibMatrix.Federation/LibMatrix.Federation.csproj
@@ -0,0 +1,20 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <LangVersion>preview</LangVersion> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\LibMatrix\LibMatrix.csproj"/> + <PackageReference Include="LibMatrix" Version="*-*" Condition="'$(ContinuousIntegrationBuild)'=='true'"/> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2"/> + <PackageReference Include="Microsoft.Extensions.Primitives" Version="10.0.0-rc.2.25502.107"/> + </ItemGroup> + +</Project> diff --git a/LibMatrix.Federation/Utilities/UnpaddedBase64.cs b/LibMatrix.Federation/Utilities/UnpaddedBase64.cs new file mode 100644
index 0000000..06f84b2 --- /dev/null +++ b/LibMatrix.Federation/Utilities/UnpaddedBase64.cs
@@ -0,0 +1,17 @@ +namespace LibMatrix.FederationTest.Utilities; + +public static class UnpaddedBase64 { + public static string Encode(byte[] data) { + return Convert.ToBase64String(data).TrimEnd('='); + } + + public static byte[] Decode(string base64) { + string paddedBase64 = base64; + switch (paddedBase64.Length % 4) { + case 2: paddedBase64 += "=="; break; + case 3: paddedBase64 += "="; break; + } + + return Convert.FromBase64String(paddedBase64); + } +} \ No newline at end of file diff --git a/LibMatrix.Federation/XMatrixAuthorizationScheme.cs b/LibMatrix.Federation/XMatrixAuthorizationScheme.cs new file mode 100644
index 0000000..45899b8 --- /dev/null +++ b/LibMatrix.Federation/XMatrixAuthorizationScheme.cs
@@ -0,0 +1,70 @@ +using System.Net.Http.Headers; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using ArcaneLibs.Extensions; +using Microsoft.Extensions.Primitives; + +namespace LibMatrix.Federation; + +public class XMatrixAuthorizationScheme { + public class XMatrixAuthorizationHeader { + public const string Scheme = "X-Matrix"; + + [JsonPropertyName("origin")] + public required string Origin { get; set; } + + [JsonPropertyName("destination")] + public required string Destination { get; set; } + + [JsonPropertyName("key")] + public required string Key { get; set; } + + [JsonPropertyName("sig")] + public required string Signature { get; set; } + + public static XMatrixAuthorizationHeader FromHeaderValue(AuthenticationHeaderValue header) { + if (header.Scheme != Scheme) + throw new LibMatrixException() { + Error = $"Expected authentication scheme of {Scheme}, got {header.Scheme}", + ErrorCode = MatrixException.ErrorCodes.M_UNAUTHORIZED + }; + + if (string.IsNullOrWhiteSpace(header.Parameter)) + throw new LibMatrixException() { + Error = $"Expected authentication header to have a value.", + ErrorCode = MatrixException.ErrorCodes.M_UNAUTHORIZED + }; + + var headerValues = new StringValues(header.Parameter); + foreach (var value in headerValues) { + Console.WriteLine(headerValues.ToJson()); + } + + return new() { + Destination = "", + Key = "", + Origin = "", + Signature = "" + }; + } + + public string ToHeaderValue() => $"{Scheme} origin=\"{Origin}\", destination=\"{Destination}\", key=\"{Key}\", sig=\"{Signature}\""; + } + + public class XMatrixRequestSignature { + [JsonPropertyName("method")] + public required string Method { get; set; } + + [JsonPropertyName("uri")] + public required string Uri { get; set; } + + [JsonPropertyName("origin")] + public required string OriginServerName { get; set; } + + [JsonPropertyName("destination")] + public required string DestinationServerName { get; set; } + + [JsonPropertyName("content"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public JsonObject? Content { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix.Federation/deps.json b/LibMatrix.Federation/deps.json new file mode 100644
index 0000000..cccc7bb --- /dev/null +++ b/LibMatrix.Federation/deps.json
@@ -0,0 +1,22 @@ +[ + { + "pname": "BouncyCastle.Cryptography", + "version": "2.6.2", + "hash": "sha256-Yjk2+x/RcVeccGOQOQcRKCiYzyx1mlFnhS5auCII+Ms=" + }, + { + "pname": "Microsoft.Extensions.DependencyInjection.Abstractions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-1nh8z2nglCizQkl0iWwJ/au4BAuuBu0xghKHGBeTM1I=" + }, + { + "pname": "Microsoft.Extensions.Logging.Abstractions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-krml7WL+lF7oiYOvQ8NHQp7BVpHJrLIHhyxUgkHO+WE=" + }, + { + "pname": "Microsoft.Extensions.Primitives", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-jvjZK/c8TGYIUA4zw7yR9uAFJmw90YE7TD3+DaxX9Ls=" + } +] diff --git a/LibMatrix.sln b/LibMatrix.sln
index 5feeeee..d6c1c0f 100644 --- a/LibMatrix.sln +++ b/LibMatrix.sln
@@ -1,148 +1,310 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArcaneLibs", "ArcaneLibs", "{32C0F9AC-AF7D-4476-A269-99ACA000EF9F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Blazor.Components", "ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj", "{93D00F03-02FF-4C2A-8917-6863D6E633D9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Legacy", "ArcaneLibs\ArcaneLibs.Legacy\ArcaneLibs.Legacy.csproj", "{14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Logging", "ArcaneLibs\ArcaneLibs.Logging\ArcaneLibs.Logging.csproj", "{51F770AF-C0C6-4247-A358-82DF323F473D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.StringNormalisation", "ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj", "{8A8E8B67-9351-471E-A502-AF7FAE596CB4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Tests", "ArcaneLibs\ArcaneLibs.Tests\ArcaneLibs.Tests.csproj", "{5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "ArcaneLibs\ArcaneLibs.Timings\ArcaneLibs.Timings.csproj", "{567DABFB-611E-4779-9F39-1D4A5B8F0247}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{28AFDFEF-7597-4450-B999-87C22C24DCD8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs", "ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj", "{F80D5395-28E3-4C34-9662-A890A215DDA2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{90A38896-993A-49F1-903B-8C989D8C9B3A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix", "LibMatrix\LibMatrix.csproj", "{6AE504F8-734B-456B-8BEA-DE37639CB3A7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{AAAA5609-D8C1-401E-BB5C-724EFE3955FB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Tests", "Tests\LibMatrix.Tests\LibMatrix.Tests.csproj", "{7E15D295-B938-409C-98F6-A22ABC7F3005}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{FBC6F613-4E0B-4A90-8854-31887A796EEF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DebugDataValidationApi", "Utilities\LibMatrix.DebugDataValidationApi\LibMatrix.DebugDataValidationApi.csproj", "{D632374A-70A1-4954-8F2D-C0B511A24426}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{91E41EE3-687B-4CFD-94C4-028D09BACFAB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.E2eeTestKit", "Utilities\LibMatrix.E2eeTestKit\LibMatrix.E2eeTestKit.csproj", "{4D411F2D-A97D-4485-A318-ED98B96B6CF6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "Utilities\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{7F36D200-FE91-4761-B681-BEE091FB953F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.JsonSerializerContextGenerator", "Utilities\LibMatrix.JsonSerializerContextGenerator\LibMatrix.JsonSerializerContextGenerator.csproj", "{69CCB780-50CC-4E75-B794-932E44ACA80F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.TestDataGenerator", "Utilities\LibMatrix.TestDataGenerator\LibMatrix.TestDataGenerator.csproj", "{C087F8AC-B438-4980-9A79-1E16D0CFB67A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Utilities.Bot", "Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj", "{E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|Any CPU.Build.0 = Release|Any CPU - {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|Any CPU.Build.0 = Release|Any CPU - {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|Any CPU.Build.0 = Release|Any CPU - {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|Any CPU.Build.0 = Release|Any CPU - {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|Any CPU.Build.0 = Release|Any CPU - {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|Any CPU.Build.0 = Debug|Any CPU - {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|Any CPU.ActiveCfg = Release|Any CPU - {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|Any CPU.Build.0 = Release|Any CPU - {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|Any CPU.Build.0 = Release|Any CPU - {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|Any CPU.Build.0 = Release|Any CPU - {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|Any CPU.Build.0 = Release|Any CPU - {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|Any CPU.Build.0 = Release|Any CPU - {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|Any CPU.Build.0 = Release|Any CPU - {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|Any CPU.Build.0 = Release|Any CPU - {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|Any CPU.Build.0 = Release|Any CPU - {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|Any CPU.Build.0 = Release|Any CPU - {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|Any CPU.Build.0 = Release|Any CPU - {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|Any CPU.Build.0 = Release|Any CPU - {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|Any CPU.Build.0 = Release|Any CPU - {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {93D00F03-02FF-4C2A-8917-6863D6E633D9} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {51F770AF-C0C6-4247-A358-82DF323F473D} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {8A8E8B67-9351-471E-A502-AF7FAE596CB4} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {567DABFB-611E-4779-9F39-1D4A5B8F0247} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {28AFDFEF-7597-4450-B999-87C22C24DCD8} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {F80D5395-28E3-4C34-9662-A890A215DDA2} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} - {7E15D295-B938-409C-98F6-A22ABC7F3005} = {AAAA5609-D8C1-401E-BB5C-724EFE3955FB} - {D632374A-70A1-4954-8F2D-C0B511A24426} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {91E41EE3-687B-4CFD-94C4-028D09BACFAB} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {4D411F2D-A97D-4485-A318-ED98B96B6CF6} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {7F36D200-FE91-4761-B681-BEE091FB953F} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {69CCB780-50CC-4E75-B794-932E44ACA80F} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {C087F8AC-B438-4980-9A79-1E16D0CFB67A} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArcaneLibs", "ArcaneLibs", "{32C0F9AC-AF7D-4476-A269-99ACA000EF9F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Blazor.Components", "ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj", "{93D00F03-02FF-4C2A-8917-6863D6E633D9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Legacy", "ArcaneLibs\ArcaneLibs.Legacy\ArcaneLibs.Legacy.csproj", "{14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Logging", "ArcaneLibs\ArcaneLibs.Logging\ArcaneLibs.Logging.csproj", "{51F770AF-C0C6-4247-A358-82DF323F473D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.StringNormalisation", "ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj", "{8A8E8B67-9351-471E-A502-AF7FAE596CB4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Tests", "ArcaneLibs\ArcaneLibs.Tests\ArcaneLibs.Tests.csproj", "{5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "ArcaneLibs\ArcaneLibs.Timings\ArcaneLibs.Timings.csproj", "{567DABFB-611E-4779-9F39-1D4A5B8F0247}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{28AFDFEF-7597-4450-B999-87C22C24DCD8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs", "ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj", "{F80D5395-28E3-4C34-9662-A890A215DDA2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{90A38896-993A-49F1-903B-8C989D8C9B3A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix", "LibMatrix\LibMatrix.csproj", "{6AE504F8-734B-456B-8BEA-DE37639CB3A7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{AAAA5609-D8C1-401E-BB5C-724EFE3955FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Tests", "Tests\LibMatrix.Tests\LibMatrix.Tests.csproj", "{7E15D295-B938-409C-98F6-A22ABC7F3005}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{FBC6F613-4E0B-4A90-8854-31887A796EEF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DebugDataValidationApi", "Utilities\LibMatrix.DebugDataValidationApi\LibMatrix.DebugDataValidationApi.csproj", "{D632374A-70A1-4954-8F2D-C0B511A24426}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{91E41EE3-687B-4CFD-94C4-028D09BACFAB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.E2eeTestKit", "Utilities\LibMatrix.E2eeTestKit\LibMatrix.E2eeTestKit.csproj", "{4D411F2D-A97D-4485-A318-ED98B96B6CF6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "Utilities\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{7F36D200-FE91-4761-B681-BEE091FB953F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.JsonSerializerContextGenerator", "Utilities\LibMatrix.JsonSerializerContextGenerator\LibMatrix.JsonSerializerContextGenerator.csproj", "{69CCB780-50CC-4E75-B794-932E44ACA80F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.TestDataGenerator", "Utilities\LibMatrix.TestDataGenerator\LibMatrix.TestDataGenerator.csproj", "{C087F8AC-B438-4980-9A79-1E16D0CFB67A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Utilities.Bot", "Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj", "{E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Federation", "LibMatrix.Federation\LibMatrix.Federation.csproj", "{0E6CF267-14FD-43D7-81CB-EE020FD1D106}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|x64.ActiveCfg = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|x64.Build.0 = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|x86.ActiveCfg = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|x86.Build.0 = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|Any CPU.Build.0 = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|x64.ActiveCfg = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|x64.Build.0 = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|x86.ActiveCfg = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|x86.Build.0 = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|x64.ActiveCfg = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|x64.Build.0 = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|x86.ActiveCfg = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|x86.Build.0 = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|Any CPU.Build.0 = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|x64.ActiveCfg = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|x64.Build.0 = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|x86.ActiveCfg = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|x86.Build.0 = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|x64.ActiveCfg = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|x64.Build.0 = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|x86.ActiveCfg = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|x86.Build.0 = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|Any CPU.Build.0 = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|x64.ActiveCfg = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|x64.Build.0 = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|x86.ActiveCfg = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|x86.Build.0 = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|x64.ActiveCfg = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|x64.Build.0 = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|x86.ActiveCfg = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|x86.Build.0 = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|Any CPU.Build.0 = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|x64.ActiveCfg = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|x64.Build.0 = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|x86.ActiveCfg = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|x86.Build.0 = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|x64.ActiveCfg = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|x64.Build.0 = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|x86.ActiveCfg = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|x86.Build.0 = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|Any CPU.Build.0 = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|x64.ActiveCfg = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|x64.Build.0 = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|x86.ActiveCfg = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|x86.Build.0 = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|Any CPU.Build.0 = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|x64.ActiveCfg = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|x64.Build.0 = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|x86.ActiveCfg = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|x86.Build.0 = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|Any CPU.ActiveCfg = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|Any CPU.Build.0 = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|x64.ActiveCfg = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|x64.Build.0 = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|x86.ActiveCfg = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|x86.Build.0 = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|x64.ActiveCfg = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|x64.Build.0 = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|x86.ActiveCfg = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|x86.Build.0 = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|Any CPU.Build.0 = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|x64.ActiveCfg = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|x64.Build.0 = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|x86.ActiveCfg = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|x86.Build.0 = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|x64.ActiveCfg = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|x64.Build.0 = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|x86.ActiveCfg = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|x86.Build.0 = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|Any CPU.Build.0 = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|x64.ActiveCfg = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|x64.Build.0 = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|x86.ActiveCfg = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|x86.Build.0 = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|x64.ActiveCfg = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|x64.Build.0 = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|x86.ActiveCfg = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|x86.Build.0 = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|Any CPU.Build.0 = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|x64.ActiveCfg = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|x64.Build.0 = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|x86.ActiveCfg = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|x86.Build.0 = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|x64.ActiveCfg = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|x64.Build.0 = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|x86.Build.0 = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|Any CPU.Build.0 = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|x64.ActiveCfg = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|x64.Build.0 = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|x86.ActiveCfg = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|x86.Build.0 = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|x64.ActiveCfg = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|x64.Build.0 = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|x86.ActiveCfg = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|x86.Build.0 = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|Any CPU.Build.0 = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|x64.ActiveCfg = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|x64.Build.0 = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|x86.ActiveCfg = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|x86.Build.0 = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|x64.ActiveCfg = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|x64.Build.0 = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|x86.ActiveCfg = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|x86.Build.0 = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|Any CPU.Build.0 = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|x64.ActiveCfg = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|x64.Build.0 = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|x86.ActiveCfg = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|x86.Build.0 = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|x64.ActiveCfg = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|x64.Build.0 = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|x86.ActiveCfg = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|x86.Build.0 = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|Any CPU.Build.0 = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|x64.ActiveCfg = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|x64.Build.0 = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|x86.ActiveCfg = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|x86.Build.0 = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|x64.ActiveCfg = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|x64.Build.0 = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|x86.Build.0 = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|Any CPU.Build.0 = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|x64.ActiveCfg = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|x64.Build.0 = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|x86.ActiveCfg = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|x86.Build.0 = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|x64.ActiveCfg = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|x64.Build.0 = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|x86.ActiveCfg = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|x86.Build.0 = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|Any CPU.Build.0 = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|x64.ActiveCfg = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|x64.Build.0 = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|x86.ActiveCfg = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|x86.Build.0 = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|x64.ActiveCfg = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|x64.Build.0 = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|x86.ActiveCfg = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|x86.Build.0 = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|Any CPU.Build.0 = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|x64.ActiveCfg = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|x64.Build.0 = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|x86.ActiveCfg = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|x86.Build.0 = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|x64.ActiveCfg = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|x64.Build.0 = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|x86.ActiveCfg = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|x86.Build.0 = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|Any CPU.Build.0 = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|x64.ActiveCfg = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|x64.Build.0 = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|x86.ActiveCfg = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|x86.Build.0 = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|x64.ActiveCfg = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|x64.Build.0 = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|x86.ActiveCfg = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|x86.Build.0 = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|Any CPU.Build.0 = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|x64.ActiveCfg = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|x64.Build.0 = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|x86.ActiveCfg = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|x86.Build.0 = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|x64.ActiveCfg = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|x64.Build.0 = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|x86.ActiveCfg = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Debug|x86.Build.0 = Debug|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|Any CPU.Build.0 = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|x64.ActiveCfg = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|x64.Build.0 = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|x86.ActiveCfg = Release|Any CPU + {0E6CF267-14FD-43D7-81CB-EE020FD1D106}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {93D00F03-02FF-4C2A-8917-6863D6E633D9} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {51F770AF-C0C6-4247-A358-82DF323F473D} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {8A8E8B67-9351-471E-A502-AF7FAE596CB4} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {567DABFB-611E-4779-9F39-1D4A5B8F0247} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {28AFDFEF-7597-4450-B999-87C22C24DCD8} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {F80D5395-28E3-4C34-9662-A890A215DDA2} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {7E15D295-B938-409C-98F6-A22ABC7F3005} = {AAAA5609-D8C1-401E-BB5C-724EFE3955FB} + {D632374A-70A1-4954-8F2D-C0B511A24426} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {91E41EE3-687B-4CFD-94C4-028D09BACFAB} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {4D411F2D-A97D-4485-A318-ED98B96B6CF6} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {7F36D200-FE91-4761-B681-BEE091FB953F} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {69CCB780-50CC-4E75-B794-932E44ACA80F} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {C087F8AC-B438-4980-9A79-1E16D0CFB67A} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + EndGlobalSection +EndGlobal diff --git a/LibMatrix/Abstractions/VersionedKeyId.cs b/LibMatrix/Abstractions/VersionedKeyId.cs new file mode 100644
index 0000000..0a6d651 --- /dev/null +++ b/LibMatrix/Abstractions/VersionedKeyId.cs
@@ -0,0 +1,24 @@ +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace LibMatrix.Abstractions; + +[DebuggerDisplay("{Algorithm}:{KeyId}")] +[SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")] +public class VersionedKeyId { + public required string Algorithm { get; set; } + public required string KeyId { get; set; } + + public static implicit operator VersionedKeyId(string key) { + var parts = key.Split(':', 2); + if (parts.Length != 2) throw new ArgumentException("Invalid key format. Expected 'algorithm:keyId'.", nameof(key)); + return new VersionedKeyId { Algorithm = parts[0], KeyId = parts[1] }; + } + + public static implicit operator string(VersionedKeyId key) => $"{key.Algorithm}:{key.KeyId}"; + public static implicit operator (string, string)(VersionedKeyId key) => (key.Algorithm, key.KeyId); + public static implicit operator VersionedKeyId((string algorithm, string keyId) key) => (key.algorithm, key.keyId); + + public override bool Equals(object? obj) => obj is VersionedKeyId other && Algorithm == other.Algorithm && KeyId == other.KeyId; + public override int GetHashCode() => HashCode.Combine(Algorithm, KeyId); +} \ No newline at end of file diff --git a/LibMatrix/Abstractions/VersionedPublicKey.cs b/LibMatrix/Abstractions/VersionedPublicKey.cs new file mode 100644
index 0000000..ad6747d --- /dev/null +++ b/LibMatrix/Abstractions/VersionedPublicKey.cs
@@ -0,0 +1,16 @@ +namespace LibMatrix.Abstractions; + +public class VersionedPublicKey { + public required VersionedKeyId KeyId { get; set; } + public required string PublicKey { get; set; } +} + +public class VersionedPrivateKey : VersionedPublicKey { + public required string PrivateKey { get; set; } +} +public class VersionedHomeserverPublicKey : VersionedPublicKey { + public required string ServerName { get; set; } +} +public class VersionedHomeserverPrivateKey : VersionedPrivateKey { + public required string ServerName { get; set; } +} \ No newline at end of file diff --git a/LibMatrix/Extensions/CanonicalJsonSerializer.cs b/LibMatrix/Extensions/CanonicalJsonSerializer.cs
index 55a4b1a..ae535aa 100644 --- a/LibMatrix/Extensions/CanonicalJsonSerializer.cs +++ b/LibMatrix/Extensions/CanonicalJsonSerializer.cs
@@ -1,6 +1,7 @@ using System.Collections.Frozen; using System.Reflection; using System.Text.Json; +using System.Text.Json.Nodes; using System.Text.Json.Serialization.Metadata; using ArcaneLibs.Extensions; @@ -57,6 +58,14 @@ public static class CanonicalJsonSerializer { // public static String Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => JsonSerializer.Serialize(value, jsonTypeInfo, _options); // public static String Serialize(Object value, JsonTypeInfo jsonTypeInfo) + public static byte[] SerializeToUtf8Bytes<T>(T value, JsonSerializerOptions? options = null) { + var newOptions = MergeOptions(null); + return JsonSerializer.SerializeToNode(value, options) // We want to allow passing custom converters for eg. double/float -> string here... + .SortProperties()! + .CanonicalizeNumbers()! + .ToJsonString(newOptions).AsBytes().ToArray(); + } + #endregion // ReSharper disable once UnusedType.Local diff --git a/LibMatrix/Extensions/EnumerableExtensions.cs b/LibMatrix/Extensions/EnumerableExtensions.cs
index 4dcf26e..88e79f0 100644 --- a/LibMatrix/Extensions/EnumerableExtensions.cs +++ b/LibMatrix/Extensions/EnumerableExtensions.cs
@@ -4,7 +4,7 @@ using System.Collections.Immutable; namespace LibMatrix.Extensions; public static class EnumerableExtensions { - public static void MergeStateEventLists(this IList<StateEvent> oldState, IList<StateEvent> newState) { + public static void MergeStateEventLists(this IList<MatrixEvent> oldState, IList<MatrixEvent> newState) { // foreach (var stateEvent in newState) { // var old = oldState.FirstOrDefault(x => x.Type == stateEvent.Type && x.StateKey == stateEvent.StateKey); // if (old is null) { @@ -27,7 +27,7 @@ public static class EnumerableExtensions { } } - int FindIndex(StateEvent needle) { + int FindIndex(MatrixEvent needle) { for (int i = 0; i < oldState.Count; i++) { var old = oldState[i]; if (old.Type == needle.Type && old.StateKey == needle.StateKey) @@ -38,7 +38,7 @@ public static class EnumerableExtensions { } } - public static void MergeStateEventLists(this IList<StateEventResponse> oldState, IList<StateEventResponse> newState) { + public static void MergeStateEventLists(this IList<MatrixEventResponse> oldState, IList<MatrixEventResponse> newState) { foreach (var e in newState) { switch (FindIndex(e)) { case -1: @@ -50,7 +50,7 @@ public static class EnumerableExtensions { } } - int FindIndex(StateEventResponse needle) { + int FindIndex(MatrixEventResponse needle) { for (int i = 0; i < oldState.Count; i++) { var old = oldState[i]; if (old.Type == needle.Type && old.StateKey == needle.StateKey) @@ -61,7 +61,7 @@ public static class EnumerableExtensions { } } - public static void MergeStateEventLists(this List<StateEventResponse> oldState, List<StateEventResponse> newState) { + public static void MergeStateEventLists(this List<MatrixEventResponse> oldState, List<MatrixEventResponse> newState) { foreach (var e in newState) { switch (FindIndex(e)) { case -1: @@ -73,7 +73,7 @@ public static class EnumerableExtensions { } } - int FindIndex(StateEventResponse needle) { + int FindIndex(MatrixEventResponse needle) { for (int i = 0; i < oldState.Count; i++) { var old = oldState[i]; if (old.Type == needle.Type && old.StateKey == needle.StateKey) diff --git a/LibMatrix/Extensions/JsonElementExtensions.cs b/LibMatrix/Extensions/JsonElementExtensions.cs
index dfec95b..9225f58 100644 --- a/LibMatrix/Extensions/JsonElementExtensions.cs +++ b/LibMatrix/Extensions/JsonElementExtensions.cs
@@ -8,7 +8,7 @@ namespace LibMatrix.Extensions; public static class JsonElementExtensions { public static bool FindExtraJsonElementFields(this JsonElement obj, Type objectType, string objectPropertyName) { if (objectPropertyName == "content" && objectType == typeof(JsonObject)) - objectType = typeof(StateEventResponse); + objectType = typeof(MatrixEventResponse); // if (t == typeof(JsonNode)) // return false; @@ -35,9 +35,9 @@ public static class JsonElementExtensions { continue; } - if (field.Name == "content" && (objectType == typeof(StateEventResponse) || objectType == typeof(StateEvent))) { + if (field.Name == "content" && (objectType == typeof(MatrixEventResponse) || objectType == typeof(MatrixEvent))) { unknownPropertyFound |= field.FindExtraJsonPropertyFieldsByValueKind( - StateEvent.GetStateEventType(obj.GetProperty("type").GetString()!), // We expect type to always be present + MatrixEvent.GetEventType(obj.GetProperty("type").GetString()!), // We expect type to always be present mappedProperty.PropertyType); continue; } diff --git a/LibMatrix/Extensions/MatrixHttpClient.Single.cs b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
index baa4a2c..cd82071 100644 --- a/LibMatrix/Extensions/MatrixHttpClient.Single.cs +++ b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
@@ -4,13 +4,14 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Net.Sockets; using System.Reflection; +using System.Security.Authentication; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; -using ArcaneLibs; using ArcaneLibs.Extensions; -using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests; namespace LibMatrix.Extensions; @@ -32,12 +33,13 @@ public class MatrixHttpClient { }; } catch (PlatformNotSupportedException e) { - Console.WriteLine("Failed to create HttpClient with connection pooling, continuing without connection pool!"); - Console.WriteLine("Original exception (safe to ignore!):"); - Console.WriteLine(e); + Console.WriteLine("HTTP connection pooling is not supported on this platform, continuing without connection pooling!"); + // Console.WriteLine("Original exception (safe to ignore!):"); + // Console.WriteLine(e); Client = new HttpClient { - DefaultRequestVersion = new Version(3, 0) + DefaultRequestVersion = new Version(3, 0), + Timeout = TimeSpan.FromDays(1) }; } catch (Exception e) { @@ -55,6 +57,20 @@ public class MatrixHttpClient { public Dictionary<string, string> AdditionalQueryParameters { get; set; } = new(); public Uri? BaseAddress { get; set; } + public static bool DefaultRetryOnNetworkError { get; set; } = true; + public static bool DefaultRetryOnMatrixError { get; set; } = true; + public bool RetryOnNetworkError { get; set; } = DefaultRetryOnNetworkError; + public bool RetryOnMatrixError { get; set; } = DefaultRetryOnMatrixError; + + public static int DefaultMinRetryIntervalMs { get; set; } = 1000; + public static int DefaultMaxRetryIntervalMs { get; set; } = 2000; + public static int DefaultMaxRetries { get; set; } = 20; + + public int MinRetryIntervalMs { get; set; } = DefaultMinRetryIntervalMs; + public int MaxRetryIntervalMs { get; set; } = DefaultMaxRetryIntervalMs; + public int MaxRetries { get; set; } = DefaultMaxRetries; + + private Dictionary<HttpRequestMessage, int> _retries = []; // default headers, not bound to client public HttpRequestHeaders DefaultRequestHeaders { get; set; } = @@ -101,22 +117,22 @@ public class MatrixHttpClient { responseMessage = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); } catch (Exception e) { - if (attempt >= 5) { - Console.WriteLine( - $"Failed to send request {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.Content?.Headers.ContentLength ?? 0)}):\n{e}"); - throw; - } - - if (e is TaskCanceledException or TimeoutException or HttpRequestException) { - if (request.Method == HttpMethod.Get && !cancellationToken.IsCancellationRequested) { - await Task.Delay(Random.Shared.Next(500, 2500), cancellationToken); - request.ResetSendStatus(); - return await SendUnhandledAsync(request, cancellationToken, attempt + 1); - } - } - else if (!e.ToString().StartsWith("TypeError: NetworkError")) - Console.WriteLine( - $"Failed to send request {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.Content?.Headers.ContentLength ?? 0)}):\n{e}"); + // if (attempt >= 5) { + // Console.WriteLine( + // $"Failed to send request {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.Content?.Headers.ContentLength ?? 0)}):\n{e}"); + // throw; + // } + // + // if (e is TaskCanceledException or TimeoutException or HttpRequestException) { + // if (request.Method == HttpMethod.Get && !cancellationToken.IsCancellationRequested) { + // await Task.Delay(Random.Shared.Next(100, 1000), cancellationToken); + // request.ResetSendStatus(); + // return await SendUnhandledAsync(request, cancellationToken, attempt + 1); + // } + // } + // else if (!e.ToString().StartsWith("TypeError: NetworkError")) + // Console.WriteLine( + // $"Failed to send request {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.Content?.Headers.ContentLength ?? 0)}):\n{e}"); throw; } @@ -144,15 +160,73 @@ public class MatrixHttpClient { "X-Content-Security-Policy", "Referrer-Policy", "X-Robots-Tag", - "Content-Security-Policy" + "Content-Security-Policy", + "Alt-Svc", + // evil + "CF-Cache-Status", + "CF-Ray", + "x-amz-request-id", + "x-do-app-origin", + "x-do-orig-status", + "x-rgw-object-type", + "Report-To" ])); return responseMessage; } public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) { - var responseMessage = await SendUnhandledAsync(request, cancellationToken); - if (responseMessage.IsSuccessStatusCode) return responseMessage; + _retries.TryAdd(request, MaxRetries); + HttpResponseMessage responseMessage; + try { + responseMessage = await SendUnhandledAsync(request, cancellationToken); + } + catch (HttpRequestException ex) { + if (RetryOnNetworkError) { + if (_retries[request]-- <= 0) throw; + // browser exceptions + if (ex.InnerException?.GetType().FullName == "System.Runtime.InteropServices.JavaScript.JSException") + Console.WriteLine($"Got JSException, likely a CORS error due to a reverse proxy misconfiguration and error, retrying ({_retries[request]} left)..."); + // native exceptions + else if (ex.InnerException is SocketException sockEx) + if (sockEx.SocketErrorCode == SocketError.HostNotFound) { + throw new LibMatrixNetworkException(ex) { + Error = $"Host {request.RequestUri?.Host ?? "(null)"} not found", + ErrorCode = LibMatrixNetworkException.ErrorCodes.RLM_NET_UNKNOWN_HOST + }; + } + else { } // empty + else if (ex.InnerException is AuthenticationException authEx) + if (authEx.Message.Contains("The remote certificate is invalid")) { + throw new LibMatrixNetworkException(ex) { + Error = ex.Message, + ErrorCode = LibMatrixNetworkException.ErrorCodes.RLM_NET_INVALID_REMOTE_CERTIFICATE + }; + } + else { } // empty + + else + Console.WriteLine(new { + ex.HttpRequestError, + ex.StatusCode, + ex.Data, + ex.Message, + InnerException = ex.InnerException?.ToString(), + InnerExceptionType = ex.InnerException?.GetType().FullName + }.ToJson()); + + await Task.Delay(Random.Shared.Next(MinRetryIntervalMs, MaxRetryIntervalMs), cancellationToken); + request.ResetSendStatus(); + return await SendAsync(request, cancellationToken); + } + + throw; + } + + if (responseMessage.IsSuccessStatusCode) { + _retries.Remove(request); + return responseMessage; + } //retry on gateway timeout // if (responseMessage.StatusCode == HttpStatusCode.GatewayTimeout) { @@ -169,31 +243,46 @@ public class MatrixHttpClient { }; // if (!content.StartsWith('{')) throw new InvalidDataException("Encountered invalid data:\n" + content); - if (!content.TrimStart().StartsWith('{')) { - responseMessage.EnsureSuccessStatusCode(); - throw new InvalidDataException("Encountered invalid data:\n" + content); - } - //we have a matrix error + if (content.TrimStart().StartsWith('{')) { + //we have a matrix error, most likely + MatrixException? ex; + try { + ex = JsonSerializer.Deserialize<MatrixException>(content); + } + catch (JsonException e) { + throw new LibMatrixException() { + ErrorCode = "M_INVALID_JSON", + Error = e.Message + "\nBody:\n" + await responseMessage.Content.ReadAsStringAsync(cancellationToken) + }; + } - MatrixException? ex; - try { - ex = JsonSerializer.Deserialize<MatrixException>(content); + Debug.Assert(ex != null, nameof(ex) + " != null"); + ex.RawContent = content; + // Console.WriteLine($"Failed to send request: {ex}"); + if (ex.ErrorCode == MatrixException.ErrorCodes.M_LIMIT_EXCEEDED) { + // if (ex.RetryAfterMs is null) throw ex!; + //we have a ratelimit error + await Task.Delay(ex.RetryAfterMs ?? responseMessage.Headers.RetryAfter?.Delta?.Milliseconds ?? MinRetryIntervalMs, cancellationToken); + request.ResetSendStatus(); + return await SendAsync(request, cancellationToken); + } + + throw ex; } - catch (JsonException e) { - throw new LibMatrixException() { - ErrorCode = "M_INVALID_JSON", - Error = e.Message + "\nBody:\n" + await responseMessage.Content.ReadAsStringAsync(cancellationToken) - }; + + if (responseMessage.StatusCode == HttpStatusCode.BadGateway) { + // spread out retries + if (RetryOnNetworkError) { + if (_retries[request]-- <= 0) throw new InvalidDataException("Encountered invalid data:\n" + content); + Console.WriteLine($"Got 502 Bad Gateway, retrying ({_retries[request]} left)..."); + await Task.Delay(Random.Shared.Next(MinRetryIntervalMs, MaxRetryIntervalMs), cancellationToken); + request.ResetSendStatus(); + return await SendAsync(request, cancellationToken); + } } - Debug.Assert(ex != null, nameof(ex) + " != null"); - ex.RawContent = content; - // Console.WriteLine($"Failed to send request: {ex}"); - if (ex.RetryAfterMs is null) throw ex!; - //we have a ratelimit error - await Task.Delay(ex.RetryAfterMs.Value, cancellationToken); - request.ResetSendStatus(); - return await SendAsync(request, cancellationToken); + responseMessage.EnsureSuccessStatusCode(); + throw new InvalidDataException("Encountered invalid data:\n" + content); } // GetAsync @@ -241,22 +330,16 @@ public class MatrixHttpClient { options = GetJsonSerializerOptions(options); var request = new HttpRequestMessage(HttpMethod.Put, requestUri); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Content = new StringContent(JsonSerializer.Serialize(value, value.GetType(), options), - Encoding.UTF8, "application/json"); + request.Content = JsonContent.Create(value, value.GetType(), new MediaTypeHeaderValue("application/json", "utf-8"), options); return await SendAsync(request, cancellationToken); } public async Task<HttpResponseMessage> PostAsJsonAsync<T>([StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, T value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) where T : notnull { - options ??= new JsonSerializerOptions(); - options.Converters.Add(new JsonFloatStringConverter()); - options.Converters.Add(new JsonDoubleStringConverter()); - options.Converters.Add(new JsonDecimalStringConverter()); - options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options = GetJsonSerializerOptions(options); var request = new HttpRequestMessage(HttpMethod.Post, requestUri); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - request.Content = new StringContent(JsonSerializer.Serialize(value, value.GetType(), options), - Encoding.UTF8, "application/json"); + request.Content = JsonContent.Create(value, value.GetType(), new MediaTypeHeaderValue("application/json", "utf-8"), options); return await SendAsync(request, cancellationToken); } @@ -286,9 +369,9 @@ public class MatrixHttpClient { return await SendAsync(request, cancellationToken); } - public async Task DeleteAsync(string url) { + public async Task<HttpResponseMessage> DeleteAsync(string url) { var request = new HttpRequestMessage(HttpMethod.Delete, url); - await SendAsync(request); + return await SendAsync(request); } public async Task<HttpResponseMessage> DeleteAsJsonAsync<T>(string url, T payload) { @@ -297,5 +380,66 @@ public class MatrixHttpClient { }; return await SendAsync(request); } + + public async Task<HttpResponseMessage> PostAsyncEnumerableAsJsonAsync<T>(string url, IAsyncEnumerable<T> payload, JsonSerializerOptions? options = null, + CancellationToken cancellationToken = default) { + options = GetJsonSerializerOptions(options); + var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Content = new AsyncEnumerableJsonContent<T>(payload, options); + return await SendAsync(request, cancellationToken); + } + + public async Task<HttpResponseMessage> PutAsyncEnumerableAsJsonAsync<T>(string url, IAsyncEnumerable<T> payload, JsonSerializerOptions? options = null, + CancellationToken cancellationToken = default) { + options = GetJsonSerializerOptions(options); + var request = new HttpRequestMessage(HttpMethod.Put, url); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + request.Content = new AsyncEnumerableJsonContent<T>(payload, options); + return await SendAsync(request, cancellationToken); + } +} + +public class AsyncEnumerableJsonContent<T>(IAsyncEnumerable<T> payload, JsonSerializerOptions? options) : HttpContent { + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context) { + await using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { + Indented = options?.WriteIndented ?? true, + Encoder = options?.Encoder, + IndentCharacter = options?.IndentCharacter ?? ' ', + IndentSize = options?.IndentSize ?? 4, + MaxDepth = options?.MaxDepth ?? 0, + NewLine = options?.NewLine ?? Environment.NewLine + }); + + writer.WriteStartArray(); + await writer.FlushAsync(); + await stream.FlushAsync(); + + await foreach (var item in payload) { + if (item is null) { + writer.WriteNullValue(); + } + else { + using var memoryStream = new MemoryStream(); + await JsonSerializer.SerializeAsync(memoryStream, item, item.GetType(), options); + + memoryStream.Position = 0; + var jsonBytes = memoryStream.ToArray(); + writer.WriteRawValue(jsonBytes, skipInputValidation: true); + } + + await writer.FlushAsync(); + await stream.FlushAsync(); + } + + writer.WriteEndArray(); + await writer.FlushAsync(); + await stream.FlushAsync(); + } + + protected override bool TryComputeLength(out long length) { + length = 0; + return false; + } } #endif \ No newline at end of file diff --git a/LibMatrix/Helpers/MessageBuilder.cs b/LibMatrix/Helpers/MessageBuilder.cs
index 5e2b1b7..f753bf7 100644 --- a/LibMatrix/Helpers/MessageBuilder.cs +++ b/LibMatrix/Helpers/MessageBuilder.cs
@@ -37,6 +37,10 @@ public class MessageBuilder(string msgType = "m.text", string format = "org.matr return this; } + public static string GetColoredBody(string color, string body) { + return $"<font color=\"{color}\">{body}</font>"; + } + public MessageBuilder WithColoredBody(string color, string body) { Content.Body += body; Content.FormattedBody += $"<font color=\"{color}\">{body}</font>"; @@ -91,9 +95,33 @@ public class MessageBuilder(string msgType = "m.text", string format = "org.matr return this; } - public MessageBuilder WithMention(string id, string? displayName = null) { - Content.Body += $"@{displayName ?? id}"; + public MessageBuilder WithMention(string id, string? displayName = null, string[]? vias = null, bool useIdInPlainText = false, bool useLinkInPlainText = false) { + if (!useLinkInPlainText) Content.Body += $"@{(useIdInPlainText ? id : displayName ?? id)}"; + else { + Content.Body += $"https://matrix.to/#/{id}"; + if (vias is { Length: > 0 }) Content.Body += $"?via={string.Join("&via=", vias)}"; + } + Content.FormattedBody += $"<a href=\"https://matrix.to/#/{id}\">{displayName ?? id}</a>"; + if (id == "@room") { + Content.Mentions ??= new(); + Content.Mentions.Room = true; + } + else if (id.StartsWith('@')) { + Content.Mentions ??= new(); + Content.Mentions.Users ??= new(); + Content.Mentions.Users.Add(id); + } + + return this; + } + + public MessageBuilder WithRoomMention() { + // Legacy push rules support + Content.Body += "@room"; + Content.FormattedBody += "@room"; + Content.Mentions ??= new(); + Content.Mentions.Room = true; return this; } diff --git a/LibMatrix/Helpers/MessageFormatter.cs b/LibMatrix/Helpers/MessageFormatter.cs
index 1b9b4f3..780ac0e 100644 --- a/LibMatrix/Helpers/MessageFormatter.cs +++ b/LibMatrix/Helpers/MessageFormatter.cs
@@ -30,8 +30,11 @@ public static class MessageFormatter { public static string HtmlFormatMention(string id, string? displayName = null) => $"<a href=\"https://matrix.to/#/{id}\">{displayName ?? id}</a>"; - public static string HtmlFormatMessageLink(string roomId, string eventId, string[]? servers = null, string? displayName = null) { - if (servers is not { Length: > 0 }) servers = new[] { roomId.Split(':', 2)[1] }; + public static string HtmlFormatMessageLink(string roomId, string eventId, string[] servers, string? displayName = null) { + if (servers is not { Length: > 0 }) + servers = roomId.Contains(':') + ? [roomId.Split(':', 2)[1]] + : throw new ArgumentException("Message links must contain a list of via's for v12+ rooms!", nameof(servers)); return $"<a href=\"https://matrix.to/#/{roomId}/{eventId}?via={string.Join("&via=", servers)}\">{displayName ?? eventId}</a>"; } diff --git a/LibMatrix/Helpers/RoomBuilder.cs b/LibMatrix/Helpers/RoomBuilder.cs new file mode 100644
index 0000000..a292f33 --- /dev/null +++ b/LibMatrix/Helpers/RoomBuilder.cs
@@ -0,0 +1,279 @@ +using System.Diagnostics; +using System.Runtime.Intrinsics.X86; +using System.Text.RegularExpressions; +using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.Homeservers; +using LibMatrix.Responses; +using LibMatrix.RoomTypes; +using LibMatrix.StructuredData; + +namespace LibMatrix.Helpers; + +public class RoomBuilder { + private static readonly string[] V12PlusRoomVersions = ["org.matrix.hydra.11", "12"]; + public bool SynapseAdminAutoAcceptLocalInvites { get; set; } + public string? Type { get; set; } + public string Version { get; set; } = "12"; + public RoomNameEventContent Name { get; set; } = new(); + public RoomTopicEventContent Topic { get; set; } = new(); + public RoomAvatarEventContent Avatar { get; set; } = new(); + public RoomCanonicalAliasEventContent CanonicalAlias { get; set; } = new(); + public string AliasLocalPart { get; set; } = string.Empty; + public bool IsFederatable { get; set; } = true; + public long OwnPowerLevel { get; set; } = MatrixConstants.MaxSafeJsonInteger; + + public RoomJoinRulesEventContent JoinRules { get; set; } = new() { + JoinRule = RoomJoinRulesEventContent.JoinRules.Public + }; + + public RoomHistoryVisibilityEventContent HistoryVisibility { get; set; } = new() { + HistoryVisibility = RoomHistoryVisibilityEventContent.HistoryVisibilityTypes.Shared + }; + + public RoomGuestAccessEventContent GuestAccess { get; set; } = new() { + GuestAccess = "forbidden" + }; + + public RoomServerAclEventContent ServerAcls { get; set; } = new() { + AllowIpLiterals = false + }; + + public RoomEncryptionEventContent Encryption { get; set; } = new(); + + /// <summary> + /// State events to be sent *before* room access is configured. Keep this small! + /// </summary> + public List<MatrixEvent> ImportantState { get; set; } = []; + + /// <summary> + /// State events to be sent *after* room access is configured, but before invites are sent. + /// </summary> + public List<MatrixEvent> InitialState { get; set; } = []; + + /// <summary> + /// Users to invite, with optional reason + /// </summary> + public Dictionary<string, string?> Invites { get; set; } = []; + + /// <summary> + /// Users to ban, with optional reason + /// </summary> + public Dictionary<string, string?> Bans { get; set; } = []; + + public RoomPowerLevelEventContent PowerLevels { get; set; } = new() { + EventsDefault = 0, + UsersDefault = 0, + Kick = 50, + Invite = 50, + Ban = 50, + Redact = 50, + StateDefault = 50, + NotificationsPl = new() { + Room = 50 + }, + Users = [], + Events = new Dictionary<string, long> { + { RoomAvatarEventContent.EventId, 50 }, + { RoomCanonicalAliasEventContent.EventId, 50 }, + { RoomEncryptionEventContent.EventId, 100 }, + { RoomHistoryVisibilityEventContent.EventId, 100 }, + { RoomGuestAccessEventContent.EventId, 100 }, + { RoomNameEventContent.EventId, 50 }, + { RoomPowerLevelEventContent.EventId, 100 }, + { RoomServerAclEventContent.EventId, 100 }, + { RoomTombstoneEventContent.EventId, 150 }, + { RoomPolicyServerEventContent.EventId, 100 }, + { RoomPinnedEventContent.EventId, 50 }, + // recommended extensions + { "im.vector.modular.widgets", 50 }, + // { "m.reaction", 0 }, // we probably don't want these to end up as room state + // - prevent calls + { "io.element.voice_broadcast_info", 50 }, + { "org.matrix.msc3401.call", 50 }, + { "org.matrix.msc3401.call.member", 50 }, + } + }; + + public Dictionary<string, object> AdditionalCreationContent { get; set; } = new(); + public List<string> AdditionalCreators { get; set; } = new(); + + public virtual async Task<GenericRoom> Create(AuthenticatedHomeserverGeneric homeserver) { + var crq = new CreateRoomRequest { + PowerLevelContentOverride = new() { + EventsDefault = 1000000, + UsersDefault = 1000000, + Kick = 1000000, + Invite = 1000000, + Ban = 1000000, + Redact = 1000000, + StateDefault = 1000000, + NotificationsPl = new() { + Room = 1000000 + }, + Users = new() { + { homeserver.WhoAmI.UserId, MatrixConstants.MaxSafeJsonInteger } + }, + Events = new Dictionary<string, long> { + { RoomAvatarEventContent.EventId, 1000000 }, + { RoomCanonicalAliasEventContent.EventId, 1000000 }, + { RoomEncryptionEventContent.EventId, 1000000 }, + { RoomHistoryVisibilityEventContent.EventId, 1000000 }, + { RoomGuestAccessEventContent.EventId, 1000000 }, + { RoomNameEventContent.EventId, 1000000 }, + { RoomPowerLevelEventContent.EventId, 1000000 }, + { RoomServerAclEventContent.EventId, 1000000 }, + { RoomTombstoneEventContent.EventId, 1000000 }, + { RoomPolicyServerEventContent.EventId, 1000000 } + }, + }, + Visibility = "private", + RoomVersion = Version + }; + + if (!string.IsNullOrWhiteSpace(Type)) + crq.CreationContent.Add("type", Type); + + if (!IsFederatable) + crq.CreationContent.Add("m.federate", false); + + AdditionalCreators.RemoveAll(string.IsNullOrWhiteSpace); + if (V12PlusRoomVersions.Contains(Version)) { + crq.PowerLevelContentOverride.Users.Remove(homeserver.WhoAmI.UserId); + PowerLevels.Users?.Remove(homeserver.WhoAmI.UserId); + if (AdditionalCreators is { Count: > 0 }) { + crq.CreationContent.Add("additional_creators", AdditionalCreators); + foreach (var user in AdditionalCreators) + PowerLevels.Users?.Remove(user); + } + } + + foreach (var kvp in AdditionalCreationContent) { + crq.CreationContent.Add(kvp.Key, kvp.Value); + } + + var room = await homeserver.CreateRoom(crq); + + Console.WriteLine("Press any key to continue..."); + Console.ReadKey(true); + await SetBasicRoomInfoAsync(room); + await SetStatesAsync(room, ImportantState); + await SetAccessAsync(room); + await SetStatesAsync(room, InitialState); + await SendInvites(room); + + return room; + } + + private async Task SendInvites(GenericRoom room) { + if (Invites.Count == 0) return; + + if (SynapseAdminAutoAcceptLocalInvites && room.Homeserver is AuthenticatedHomeserverSynapse synapse) { + var localJoinTasks = Invites.Where(u => UserId.Parse(u.Key).ServerName == synapse.ServerName).Select(async entry => { + var user = entry.Key; + var reason = entry.Value; + try { + var uhs = await synapse.Admin.GetHomeserverForUserAsync(user, TimeSpan.FromHours(1)); + var userRoom = uhs.GetRoom(room.RoomId); + await userRoom.JoinAsync([uhs.ServerName], reason); + await uhs.Logout(); + } + catch (MatrixException e) { + Console.WriteLine("Failed to auto-accept invite for {0} in {1}: {2}", user, room.RoomId, e.Message); + } + }).ToList(); + await Task.WhenAll(localJoinTasks); + } + + var inviteTasks = Invites.Select(async kvp => { + try { + await room.InviteUserAsync(kvp.Key, kvp.Value); + } + catch (MatrixException e) { + Console.Error.WriteLine("Failed to invite {0} to {1}: {2}", kvp.Key, room.RoomId, e.Message); + } + }); + + await Task.WhenAll(inviteTasks); + } + + private async Task SetStatesAsync(GenericRoom room, List<MatrixEvent> state) { + if (state.Count == 0) return; + await room.BulkSendEventsAsync(state); + // We chunk this up to try to avoid hitting reverse proxy timeouts + // foreach (var group in state.Chunk(chunkSize)) { + // var sw = Stopwatch.StartNew(); + // await room.BulkSendEventsAsync(group); + // if (sw.ElapsedMilliseconds > 5000) { + // chunkSize = Math.Max(chunkSize / 2, 1); + // Console.WriteLine($"Warning: Sending {group.Length} state events took {sw.ElapsedMilliseconds}ms, which is quite long. Reducing chunk size to {chunkSize}."); + // } + // } + // int chunkSize = 50; + // for (int i = 0; i < state.Count; i += chunkSize) { + // var chunk = state.Skip(i).Take(chunkSize).ToList(); + // if (chunk.Count == 0) continue; + // + // var sw = Stopwatch.StartNew(); + // await room.BulkSendEventsAsync(chunk, forceSyncInterval: chunk.Count + 1); + // Console.WriteLine($"Sent {chunk.Count} state events in {sw.ElapsedMilliseconds}ms. {state.Count - (i + chunk.Count)} remaining."); + // // if (sw.ElapsedMilliseconds > 45000) { + // // chunkSize = Math.Max(chunkSize / 3, 1); + // // Console.WriteLine($"Warning: Sending {chunk.Count} state events took {sw.ElapsedMilliseconds}ms, which is dangerously long. Reducing chunk size to {chunkSize}."); + // // } + // // else if (sw.ElapsedMilliseconds > 30000) { + // // chunkSize = Math.Max(chunkSize / 2, 1); + // // Console.WriteLine($"Warning: Sending {chunk.Count} state events took {sw.ElapsedMilliseconds}ms, which is quite long. Reducing chunk size to {chunkSize}."); + // // } + // // else if (sw.ElapsedMilliseconds < 10000) { + // // chunkSize = Math.Min((int)(chunkSize * 1.2), 1000); + // // Console.WriteLine($"Info: Sending {chunk.Count} state events took {sw.ElapsedMilliseconds}ms, increasing chunk size to {chunkSize}."); + // // } + // } + } + + private async Task SetBasicRoomInfoAsync(GenericRoom room) { + if (!string.IsNullOrWhiteSpace(Name.Name)) + await room.SendStateEventAsync(RoomNameEventContent.EventId, Name); + + if (!string.IsNullOrWhiteSpace(Topic.Topic)) + await room.SendStateEventAsync(RoomTopicEventContent.EventId, Topic); + + if (!string.IsNullOrWhiteSpace(Avatar.Url)) + await room.SendStateEventAsync(RoomAvatarEventContent.EventId, Avatar); + + if (!string.IsNullOrWhiteSpace(AliasLocalPart)) + CanonicalAlias.Alias = $"#{AliasLocalPart}:{room.Homeserver.ServerName}"; + + if (!string.IsNullOrWhiteSpace(CanonicalAlias.Alias)) { + await room.Homeserver.SetRoomAliasAsync(CanonicalAlias.Alias!, room.RoomId); + await room.SendStateEventAsync(RoomCanonicalAliasEventContent.EventId, CanonicalAlias); + } + + if (!string.IsNullOrWhiteSpace(Encryption.Algorithm)) + await room.SendStateEventAsync(RoomEncryptionEventContent.EventId, Encryption); + } + + private async Task SetAccessAsync(GenericRoom room) { + if (!V12PlusRoomVersions.Contains(Version)) + PowerLevels.Users![room.Homeserver.WhoAmI.UserId] = OwnPowerLevel; + else { + PowerLevels.Users!.Remove(room.Homeserver.WhoAmI.UserId); + foreach (var additionalCreator in AdditionalCreators) { + PowerLevels.Users!.Remove(additionalCreator); + } + } + + await room.SendStateEventAsync(RoomPowerLevelEventContent.EventId, PowerLevels); + + if (!string.IsNullOrWhiteSpace(HistoryVisibility.HistoryVisibility)) + await room.SendStateEventAsync(RoomHistoryVisibilityEventContent.EventId, HistoryVisibility); + + if (!string.IsNullOrWhiteSpace(JoinRules.JoinRuleValue)) + await room.SendStateEventAsync(RoomJoinRulesEventContent.EventId, JoinRules); + } +} + +public class MatrixConstants { + public const long MaxSafeJsonInteger = 9007199254740991L; // 2^53 - 1 +} \ No newline at end of file diff --git a/LibMatrix/Helpers/RoomUpgradeBuilder.cs b/LibMatrix/Helpers/RoomUpgradeBuilder.cs new file mode 100644
index 0000000..ced0ef3 --- /dev/null +++ b/LibMatrix/Helpers/RoomUpgradeBuilder.cs
@@ -0,0 +1,232 @@ +using System.Diagnostics; +using System.Reflection; +using System.Text.Json.Serialization; +using ArcaneLibs; +using LibMatrix.EventTypes; +using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State.Policy; +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.Homeservers; +using LibMatrix.RoomTypes; +using LibMatrix.StructuredData; + +namespace LibMatrix.Helpers; + +public class RoomUpgradeBuilder : RoomBuilder { + public RoomUpgradeOptions UpgradeOptions { get; set; } = new(); + public string OldRoomId { get; set; } = string.Empty; + public bool CanUpgrade { get; private set; } + public Dictionary<string, object> AdditionalTombstoneContent { get; set; } = new(); + + private List<Type> basePolicyTypes = []; + + public async Task ImportAsync(GenericRoom OldRoom) { + var sw = Stopwatch.StartNew(); + var total = 0; + + basePolicyTypes = ClassCollector<PolicyRuleEventContent>.ResolveFromAllAccessibleAssemblies().ToList(); + Console.WriteLine($"Found {basePolicyTypes.Count} policy types in {sw.ElapsedMilliseconds}ms"); + CanUpgrade = ( + (await OldRoom.GetPowerLevelsAsync())?.UserHasStatePermission(OldRoom.Homeserver.UserId, RoomTombstoneEventContent.EventId) + ?? (await OldRoom.GetRoomCreatorsAsync()).Contains(OldRoom.Homeserver.UserId) + ) + || (OldRoom.IsV12PlusRoomId && (await OldRoom.GetRoomCreatorsAsync()).Contains(OldRoom.Homeserver.UserId)); + + await foreach (var srcEvt in OldRoom.GetFullStateAsync()) { + total++; + if (srcEvt is null) continue; + var evt = srcEvt; + + if (UpgradeOptions.UpgradeUnstableValues) { + evt = UpgradeUnstableValues(evt); + } + + if (evt.StateKey == "") { + if (evt.Type == RoomCreateEventContent.EventId) + foreach (var (key, value) in evt.RawContent) { + if (key is "room_version" or "creator") continue; + if (key == "type") + Type = value!.GetValue<string>(); + else AdditionalCreationContent[key] = value; + } + else if (evt.Type == RoomNameEventContent.EventId) + Name = evt.ContentAs<RoomNameEventContent>()!; + else if (evt.Type == RoomTopicEventContent.EventId) + Topic = evt.ContentAs<RoomTopicEventContent>()!; + else if (evt.Type == RoomAvatarEventContent.EventId) + Avatar = evt.ContentAs<RoomAvatarEventContent>()!; + else if (evt.Type == RoomCanonicalAliasEventContent.EventId) { + CanonicalAlias = evt.ContentAs<RoomCanonicalAliasEventContent>()!; + AliasLocalPart = CanonicalAlias.Alias?.Split(':', 2).FirstOrDefault()?[1..] ?? string.Empty; + } + else if (evt.Type == RoomJoinRulesEventContent.EventId) + JoinRules = evt.ContentAs<RoomJoinRulesEventContent>()!; + else if (evt.Type == RoomHistoryVisibilityEventContent.EventId) + HistoryVisibility = evt.ContentAs<RoomHistoryVisibilityEventContent>()!; + else if (evt.Type == RoomGuestAccessEventContent.EventId) + GuestAccess = evt.ContentAs<RoomGuestAccessEventContent>()!; + else if (evt.Type == RoomServerAclEventContent.EventId) + ServerAcls = evt.ContentAs<RoomServerAclEventContent>()!; + else if (evt.Type == RoomPowerLevelEventContent.EventId) { + PowerLevels = evt.ContentAs<RoomPowerLevelEventContent>()!; + if (UpgradeOptions.InvitePowerlevelUsers && PowerLevels.Users != null) + foreach (var (userId, level) in PowerLevels.Users) + if (level > PowerLevels.UsersDefault) + Invites.Add(userId, "Room upgrade (had a power level)"); + } + else if (evt.Type == RoomEncryptionEventContent.EventId) + Encryption = evt.ContentAs<RoomEncryptionEventContent>(); + else if (evt.Type == RoomPinnedEventContent.EventId) ; // Discard as you can't cross reference pinned events + else + InitialState.Add(new() { + Type = evt.Type, + StateKey = evt.StateKey, + RawContent = evt.RawContent + }); + } + else if (evt.Type == RoomMemberEventContent.EventId) { + if (evt.TypedContent is RoomMemberEventContent { Membership: "join" or "invite" } invitedMember) { + if (UpgradeOptions.InviteMembers) + Invites.TryAdd(evt.StateKey!, invitedMember.Reason ?? "Room upgrade"); + else if (UpgradeOptions.InviteLocalMembers && UserId.Parse(evt.StateKey!).ServerName == OldRoom.Homeserver.ServerName) + Invites.TryAdd(evt.StateKey!, invitedMember.Reason ?? "Room upgrade (local user)"); + } + else if (UpgradeOptions.MigrateBans && evt.TypedContent is RoomMemberEventContent { Membership: "ban" } bannedMember) + Bans.TryAdd(evt.StateKey!, bannedMember.Reason); + } + else if (!UpgradeOptions.MigrateEmptyStateEvents && evt.RawContent.Count == 0) { } // skip empty state events + else if (basePolicyTypes.Contains(evt.MappedType)) ImportPolicyEventAsync(evt); + else + InitialState.Add(new() { + Type = evt.Type, + StateKey = evt.StateKey, + RawContent = evt.RawContent + }); + } + + Console.WriteLine($"Imported {total} state events from old room {OldRoom.RoomId} in {sw.ElapsedMilliseconds}ms"); + } + + private MatrixEventResponse UpgradeUnstableValues(MatrixEventResponse evt) { + if (evt.IsLegacyType) { + var oldType = evt.Type; + evt.Type = evt.MappedType.GetCustomAttributes<MatrixEventAttribute>().FirstOrDefault(x => !x.Legacy)!.EventName; + Console.WriteLine($"Upgraded event type from {oldType} to {evt.Type} for event {evt.EventId}"); + } + + if (evt.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) { + if (evt.RawContent["recommendation"]?.GetValue<string>() == "org.matrix.mjolnir.ban") { + evt.RawContent["recommendation"] = "m.ban"; + Console.WriteLine($"Upgraded recommendation from 'org.matrix.mjolnir.ban' to 'm.ban' for event {evt.EventId}"); + } + } + + return evt; + } + + private void ImportPolicyEventAsync(MatrixEventResponse evt) { + var msc4321Options = UpgradeOptions.Msc4321PolicyListUpgradeOptions; + if (msc4321Options is { Enable: true, UpgradeType: Msc4321PolicyListUpgradeOptions.Msc4321PolicyListUpgradeType.Transition }) + return; // this upgrade type doesnt copy policies + if (msc4321Options.Enable) { + evt.RawContent["org.matrix.msc4321.original_sender"] = evt.Sender; + evt.RawContent["org.matrix.msc4321.original_timestamp"] = evt.OriginServerTs; + evt.RawContent["org.matrix.msc4321.original_event_id"] = evt.EventId; + } + + InitialState.Add(new() { + Type = evt.Type, + StateKey = evt.StateKey, + RawContent = evt.RawContent + }); + } + + public override async Task<GenericRoom> Create(AuthenticatedHomeserverGeneric homeserver) { + var oldRoom = homeserver.GetRoom(OldRoomId); + // set the previous room relation + AdditionalCreationContent["predecessor"] = new { + room_id = OldRoomId, + // event_id = (await oldRoom.GetMessagesAsync(limit: 1)).Chunk.Last().EventId + }; + + if (UpgradeOptions.NoopUpgrade) { + AliasLocalPart = null; + CanonicalAlias = new(); + return await base.Create(homeserver); + } + + // prepare old room first... + if (!string.IsNullOrWhiteSpace(AliasLocalPart)) { + var aliasResult = await homeserver.ResolveRoomAliasAsync($"#{AliasLocalPart}:{homeserver.ServerName}"); + if (aliasResult?.RoomId == OldRoomId) + await homeserver.DeleteRoomAliasAsync($"#{AliasLocalPart}:{homeserver.ServerName}"); + else + throw new LibMatrixException() { + ErrorCode = LibMatrixException.ErrorCodes.M_UNSUPPORTED, + Error = $"Cannot upgrade room {OldRoomId} as it has an alias that is not the same as the one tracked by the server! Server says: {aliasResult.RoomId}" + }; + } + + var room = await base.Create(homeserver); + if (CanUpgrade || UpgradeOptions.ForceUpgrade) { + if (UpgradeOptions.RoomUpgradeNotice != null) { + var noticeContent = await UpgradeOptions.RoomUpgradeNotice(room); + await oldRoom.SendMessageEventAsync(noticeContent); + } + + var tombstoneContent = new RoomTombstoneEventContent { + Body = "This room has been upgraded to a new version.", + ReplacementRoom = room.RoomId + }; + + tombstoneContent.AdditionalData ??= []; + foreach (var (key, value) in AdditionalTombstoneContent) + tombstoneContent.AdditionalData[key] = value; + + await oldRoom.SendStateEventAsync(RoomTombstoneEventContent.EventId, tombstoneContent); + } + + return room; + } + + public class RoomUpgradeOptions { + public bool InviteMembers { get; set; } + public bool InviteLocalMembers { get; set; } + public bool InvitePowerlevelUsers { get; set; } + public bool MigrateBans { get; set; } + public bool MigrateEmptyStateEvents { get; set; } + public bool UpgradeUnstableValues { get; set; } + public bool ForceUpgrade { get; set; } + public bool NoopUpgrade { get; set; } + public Msc4321PolicyListUpgradeOptions Msc4321PolicyListUpgradeOptions { get; set; } = new(); + + [JsonIgnore] + public Func<GenericRoom, Task<RoomMessageEventContent>>? RoomUpgradeNotice { get; set; } = async newRoom => new MessageBuilder() + .WithRoomMention() + .WithNewline() + .WithBody("This room has been upgraded to a new version. This version of the room will be kept as an archive.") + .WithNewline() + .WithBody("You can join the new room by clicking the link below:") + .WithNewline() + .WithMention(newRoom.RoomId, await newRoom.GetNameOrFallbackAsync(), vias: (await newRoom.GetHomeserversInRoom()).ToArray(), useLinkInPlainText: true) + .Build(); + } + + public class Msc4321PolicyListUpgradeOptions { + public bool Enable { get; set; } = true; + public Msc4321PolicyListUpgradeType UpgradeType { get; set; } = Msc4321PolicyListUpgradeType.Move; + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum Msc4321PolicyListUpgradeType { + /// <summary> + /// Copy policies, unwatch old list + /// </summary> + Move, + + /// <summary> + /// Don't copy policies, watch both lists + /// </summary> + Transition + } + } +} \ No newline at end of file diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs
index 6f2cacc..ebe653c 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs
@@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging; namespace LibMatrix.Helpers; public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logger = null, IStorageProvider? storageProvider = null) { - private readonly Func<SyncResponse?, Task<SyncResponse?>> _msc4222EmulationSyncProcessor = new Msc4222EmulationSyncProcessor(homeserver).EmulateMsc4222; + private readonly Func<SyncResponse?, Task<SyncResponse?>> _msc4222EmulationSyncProcessor = new Msc4222EmulationSyncProcessor(homeserver, logger).EmulateMsc4222; private SyncFilter? _filter; private string? _namedFilterName; @@ -25,7 +25,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg public string? Since { get; set; } public int Timeout { get; set; } = 30000; public string? SetPresence { get; set; } - + /// <summary> /// Disabling this uses a technically slower code path, useful for checking whether delay comes from waiting for server or deserialising responses /// </summary> @@ -37,11 +37,11 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg field = value; if (value) { AsyncSyncPreprocessors.Add(_msc4222EmulationSyncProcessor); - Console.WriteLine($"Added MSC4222 emulation sync processor"); + logger?.LogInformation($"Added MSC4222 emulation sync processor"); } else { AsyncSyncPreprocessors.Remove(_msc4222EmulationSyncProcessor); - Console.WriteLine($"Removed MSC4222 emulation sync processor"); + logger?.LogInformation($"Removed MSC4222 emulation sync processor"); } } } = false; @@ -121,7 +121,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg } if (storageProvider is null) { - var res = await SyncAsyncInternal(cancellationToken, noDelay); + var res = await SyncAsyncInternal(cancellationToken, noDelay); if (res is null) return null; if (UseMsc4222StateAfter) res.Msc4222Method = SyncResponse.Msc4222SyncType.Server; @@ -186,13 +186,12 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg else { var httpResp = await homeserver.ClientHttpClient.GetAsync(url, cancellationToken ?? CancellationToken.None); if (httpResp is null) throw new NullReferenceException("Failed to send HTTP request"); - logger?.LogInformation("Got sync response: {} bytes, {} elapsed", httpResp.GetContentLength(), sw.Elapsed); + var receivedTime = sw.Elapsed; var deserializeSw = Stopwatch.StartNew(); - // var jsonResp = await httpResp.Content.ReadFromJsonAsync<JsonObject>(cancellationToken: cancellationToken ?? CancellationToken.None); - // var resp = jsonResp.Deserialize<SyncResponse>(); resp = await httpResp.Content.ReadFromJsonAsync(cancellationToken: cancellationToken ?? CancellationToken.None, jsonTypeInfo: SyncResponseSerializerContext.Default.SyncResponse); - logger?.LogInformation("Deserialized sync response: {} bytes, {} elapsed, {} total", httpResp.GetContentLength(), deserializeSw.Elapsed, sw.Elapsed); + logger?.LogInformation("Deserialized sync response: {} bytes, {} response time, {} deserialize time, {} total", httpResp.GetContentLength(), receivedTime, + deserializeSw.Elapsed, sw.Elapsed); } var timeToWait = MinimumDelay.Subtract(sw.Elapsed); @@ -299,9 +298,9 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg if (syncResponse.Rooms is { Join.Count: > 0 }) foreach (var updatedRoom in syncResponse.Rooms.Join) { if (updatedRoom.Value.Timeline is null) continue; - foreach (var stateEventResponse in updatedRoom.Value.Timeline.Events ?? []) { - stateEventResponse.RoomId = updatedRoom.Key; - var tasks = TimelineEventHandlers.Select(x => x(stateEventResponse)).ToList(); + foreach (var MatrixEventResponse in updatedRoom.Value.Timeline.Events ?? []) { + MatrixEventResponse.RoomId = updatedRoom.Key; + var tasks = TimelineEventHandlers.Select(x => x(MatrixEventResponse)).ToList(); await Task.WhenAll(tasks); } } @@ -320,12 +319,12 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg /// <summary> /// Event fired when a timeline event is received /// </summary> - public List<Func<StateEventResponse, Task>> TimelineEventHandlers { get; } = new(); + public List<Func<MatrixEventResponse, Task>> TimelineEventHandlers { get; } = new(); /// <summary> /// Event fired when an account data event is received /// </summary> - public List<Func<StateEventResponse, Task>> AccountDataReceivedHandlers { get; } = new(); + public List<Func<MatrixEventResponse, Task>> AccountDataReceivedHandlers { get; } = new(); /// <summary> /// Event fired when an exception is thrown diff --git a/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs b/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs
index 6cb42ca..c887f6e 100644 --- a/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs +++ b/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs
@@ -1,16 +1,18 @@ using System.Diagnostics; +using System.Timers; using ArcaneLibs.Extensions; using LibMatrix.Homeservers; using LibMatrix.Responses; +using Microsoft.Extensions.Logging; namespace LibMatrix.Helpers.SyncProcessors; -public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homeserver) { - private static bool StateEventsMatch(StateEventResponse a, StateEventResponse b) { +public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homeserver, ILogger? logger) { + private static bool StateEventsMatch(MatrixEventResponse a, MatrixEventResponse b) { return a.Type == b.Type && a.StateKey == b.StateKey; } - private static bool StateEventIsNewer(StateEventResponse a, StateEventResponse b) { + private static bool StateEventIsNewer(MatrixEventResponse a, MatrixEventResponse b) { return StateEventsMatch(a, b) && a.OriginServerTs < b.OriginServerTs; } @@ -22,12 +24,13 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese resp.Rooms.Join?.Any(x => x.Value.StateAfter is { Events.Count: > 0 }) == true || resp.Rooms.Leave?.Any(x => x.Value.StateAfter is { Events.Count: > 0 }) == true ) { - Console.WriteLine($"Msc4222EmulationSyncProcessor.EmulateMsc4222 determined that no emulation is needed in {sw.Elapsed}"); + logger?.Log(sw.ElapsedMilliseconds > 100 ? LogLevel.Warning : LogLevel.Debug, + "Msc4222EmulationSyncProcessor.EmulateMsc4222 determined that no emulation is needed in {elapsed}", sw.Elapsed); return resp; } resp = await EmulateMsc4222Internal(resp, sw); - + return SimpleSyncProcessors.FillRoomIds(resp); } @@ -42,14 +45,17 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese tasks.AddRange(resp.Rooms.Leave.Select(ProcessLeftRooms).ToList()); } - var tasksEnum = tasks.ToAsyncEnumerable(); + var tasksEnum = tasks.ToAsyncResultEnumerable(); await foreach (var wasModified in tasksEnum) { if (wasModified) { modified = true; } } - Console.WriteLine($"Msc4222EmulationSyncProcessor.EmulateMsc4222 processed {resp.Rooms?.Join?.Count}/{resp.Rooms?.Leave?.Count} rooms in {sw.Elapsed} (modified: {modified})"); + logger?.Log(sw.ElapsedMilliseconds > 100 ? LogLevel.Warning : LogLevel.Debug, + "Msc4222EmulationSyncProcessor.EmulateMsc4222 processed {joinCount}/{leaveCount} rooms in {elapsed} (modified: {modified})", + resp.Rooms?.Join?.Count ?? 0, resp.Rooms?.Leave?.Count ?? 0, sw.Elapsed, modified); + if (modified) resp.Msc4222Method = SyncResponse.Msc4222SyncType.Emulated; @@ -70,7 +76,7 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese Events = [] }; - var oldState = new List<StateEventResponse>(); + var oldState = new List<MatrixEventResponse>(); if (data.State is { Events.Count: > 0 }) { oldState.ReplaceBy(data.State.Events, StateEventIsNewer); } @@ -90,7 +96,7 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese } } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get timeline for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get timeline for room {roomId}, state may be incomplete!\n{exception}", roomId, e); } } @@ -103,12 +109,12 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese // .Join(oldState, x => (x.Type, x.StateKey), y => (y.Type, y.StateKey), (x, y) => x) .IntersectBy(oldState.Select(s => (s.Type, s.StateKey)), s => (s.Type, s.StateKey)) .ToList(); - + data.State = null; return true; } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get full state for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get full state for room {roomId}, state may be incomplete!\n{exception}", roomId, e); } var tasks = oldState @@ -117,12 +123,13 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese return await room.GetStateEventAsync(oldEvt.Type, oldEvt.StateKey!); } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get state event {oldEvt.Type}/{oldEvt.StateKey} for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get state event {type}/{stateKey} for room {roomId}, state may be incomplete!\n{exception}", + oldEvt.Type, oldEvt.StateKey, roomId, e); return oldEvt; } }); - var tasksEnum = tasks.ToAsyncEnumerable(); + var tasksEnum = tasks.ToAsyncResultEnumerable(); await foreach (var evt in tasksEnum) { data.StateAfter.Events.Add(evt); } @@ -150,10 +157,10 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese return true; } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get full state for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get full state for room {roomId}, state may be incomplete!\n{exception}", roomId, e); } - var oldState = new List<StateEventResponse>(); + var oldState = new List<MatrixEventResponse>(); if (data.State is { Events.Count: > 0 }) { oldState.ReplaceBy(data.State.Events, StateEventIsNewer); } @@ -173,7 +180,7 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese } } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get timeline for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get timeline for room {roomId}, state may be incomplete!\n{exception}", roomId, e); } } @@ -185,12 +192,13 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese return await room.GetStateEventAsync(oldEvt.Type, oldEvt.StateKey!); } catch (Exception e) { - Console.WriteLine($"Msc4222Emulation: Failed to get state event {oldEvt.Type}/{oldEvt.StateKey} for room {roomId}, state may be incomplete!\n{e}"); + logger?.LogWarning("Msc4222Emulation: Failed to get state event {type}/{stateKey} for room {roomId}, state may be incomplete!\n{exception}", + oldEvt.Type, oldEvt.StateKey, roomId, e); return oldEvt; } }); - var tasksEnum = tasks.ToAsyncEnumerable(); + var tasksEnum = tasks.ToAsyncResultEnumerable(); await foreach (var evt in tasksEnum) { data.StateAfter.Events.Add(evt); } diff --git a/LibMatrix/Helpers/SyncStateResolver.cs b/LibMatrix/Helpers/SyncStateResolver.cs
index f111c79..17c1a41 100644 --- a/LibMatrix/Helpers/SyncStateResolver.cs +++ b/LibMatrix/Helpers/SyncStateResolver.cs
@@ -625,7 +625,7 @@ public class SyncStateResolver(AuthenticatedHomeserverGeneric homeserver, ILogge return oldState; } - private static EventList? MergeEventListBy(EventList? oldState, EventList? newState, Func<StateEventResponse, StateEventResponse, bool> comparer) { + private static EventList? MergeEventListBy(EventList? oldState, EventList? newState, Func<MatrixEventResponse, MatrixEventResponse, bool> comparer) { if (newState is null) return oldState; if (oldState is null) { return newState; diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index 55899de..916780e 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -4,6 +4,7 @@ using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Web; +using ArcaneLibs.Collections; using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec; using LibMatrix.EventTypes.Spec.State.RoomInfo; @@ -13,6 +14,7 @@ using LibMatrix.Homeservers.Extensions.NamedCaches; using LibMatrix.Responses; using LibMatrix.RoomTypes; using LibMatrix.Services; +using LibMatrix.StructuredData; using LibMatrix.Utilities; namespace LibMatrix.Homeservers; @@ -145,7 +147,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { await Task.Delay(1000); } } - }).ToAsyncEnumerable(); + }).ToAsyncResultEnumerable(); await foreach (var result in tasks) if (result is not null) @@ -215,7 +217,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { if (preserveCustomRoomProfile) { var rooms = await GetJoinedRooms(); - var roomProfiles = rooms.Select(GetOwnRoomProfileWithIdAsync).ToAsyncEnumerable(); + var roomProfiles = rooms.Select(GetOwnRoomProfileWithIdAsync).ToAsyncResultEnumerable(); targetSyncCount = rooms.Count; await foreach (var (roomId, currentRoomProfile) in roomProfiles) try { @@ -288,14 +290,26 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { public async IAsyncEnumerable<KeyValuePair<string, RoomMemberEventContent>> GetRoomProfilesAsync() { var rooms = await GetJoinedRooms(); - var results = rooms.Select(GetOwnRoomProfileWithIdAsync).ToAsyncEnumerable(); + var results = rooms.Select(GetOwnRoomProfileWithIdAsync).ToAsyncResultEnumerable(); await foreach (var res in results) yield return res; } public async Task<RoomIdResponse> JoinRoomAsync(string roomId, List<string> homeservers = null, string? reason = null) { var joinUrl = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(roomId)}"; Console.WriteLine($"Calling {joinUrl} with {homeservers?.Count ?? 0} via's..."); - if (homeservers == null || homeservers.Count == 0) homeservers = new List<string> { roomId.Split(':')[1] }; + if (homeservers is not { Count: > 0 }) { + // Legacy room IDs: !abc:server.xyz + if (roomId.Contains(':')) + homeservers = [ServerName, roomId.Split(':')[1]]; + // v12+ room IDs: !<hash> + else { + homeservers = [ServerName]; + foreach (var room in await GetJoinedRooms()) { + homeservers.Add(await room.GetOriginHomeserverAsync()); + } + } + } + var fullJoinUrl = $"{joinUrl}?server_name=" + string.Join("&server_name=", homeservers); var res = await ClientHttpClient.PostAsJsonAsync(fullJoinUrl, new { reason @@ -397,15 +411,12 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { private Dictionary<string, string>? _namedFilterCache; private Dictionary<string, SyncFilter> _filterCache = new(); - public async Task<JsonObject?> GetCapabilitiesAsync() { - var res = await ClientHttpClient.GetAsync("/_matrix/client/v3/capabilities"); - if (!res.IsSuccessStatusCode) { - Console.WriteLine($"Failed to get capabilities: {await res.Content.ReadAsStringAsync()}"); - throw new InvalidDataException($"Failed to get capabilities: {await res.Content.ReadAsStringAsync()}"); - } + private static readonly SemaphoreCache<CapabilitiesResponse> CapabilitiesCache = new(); - return await res.Content.ReadFromJsonAsync<JsonObject>(); - } + public async Task<CapabilitiesResponse> GetCapabilitiesAsync() => + await CapabilitiesCache.GetOrAdd(ServerName, async () => + await ClientHttpClient.GetFromJsonAsync<CapabilitiesResponse>("/_matrix/client/v3/capabilities") + ); public class HsNamedCaches { internal HsNamedCaches(AuthenticatedHomeserverGeneric hs) { @@ -429,7 +440,8 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { try { // Console.WriteLine($"Trying authenticated media URL: {uri}"); var res = await ClientHttpClient.SendAsync(new() { - Method = HttpMethod.Head, + // Method = HttpMethod.Head, // This apparently doesn't work with Matrix-Media-Repo... + Method = HttpMethod.Get, RequestUri = (new Uri(mxcUri.ToDownloadUri(BaseUrl, filename, timeout), string.IsNullOrWhiteSpace(BaseUrl) ? UriKind.Relative : UriKind.Absolute)) }); if (res.IsSuccessStatusCode) { @@ -445,7 +457,8 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { try { // Console.WriteLine($"Trying legacy media URL: {uri}"); var res = await ClientHttpClient.SendAsync(new() { - Method = HttpMethod.Head, + // Method = HttpMethod.Head, + Method = HttpMethod.Get, RequestUri = new(mxcUri.ToLegacyDownloadUri(BaseUrl, filename, timeout), string.IsNullOrWhiteSpace(BaseUrl) ? UriKind.Relative : UriKind.Absolute) }); if (res.IsSuccessStatusCode) { @@ -574,8 +587,87 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { await SetAccountDataAsync(IgnoredUserListEventContent.EventId, ignoredUserList); } - private class CapabilitiesResponse { + public class CapabilitiesResponse { [JsonPropertyName("capabilities")] - public Dictionary<string, object>? Capabilities { get; set; } + public CapabilitiesContents Capabilities { get; set; } + + public class CapabilitiesContents { + [JsonPropertyName("m.3pid_changes")] + public BooleanCapability? ThreePidChanges { get; set; } + + [JsonPropertyName("m.change_password")] + public BooleanCapability? ChangePassword { get; set; } + + [JsonPropertyName("m.get_login_token")] + public BooleanCapability? GetLoginToken { get; set; } + + [JsonPropertyName("m.room_versions")] + public RoomVersionsCapability? RoomVersions { get; set; } + + [JsonPropertyName("m.set_avatar_url")] + public BooleanCapability? SetAvatarUrl { get; set; } + + [JsonPropertyName("m.set_displayname")] + public BooleanCapability? SetDisplayName { get; set; } + + [JsonPropertyName("gay.rory.bulk_send_events")] + public BooleanCapability? BulkSendEvents { get; set; } + + [JsonPropertyName("gay.rory.synapse_admin_extensions.room_list.query_events.v2")] + public BooleanCapability? SynapseRoomListQueryEventsV2 { get; set; } + + [JsonExtensionData] + public Dictionary<string, object>? AdditionalCapabilities { get; set; } + } + + public class BooleanCapability { + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + } + + public class RoomVersionsCapability { + [JsonPropertyName("default")] + public string? Default { get; set; } + + [JsonPropertyName("available")] + public Dictionary<string, string>? Available { get; set; } + } + } + +#region Room Directory/aliases + + public async Task SetRoomAliasAsync(string roomAlias, string roomId) { + var resp = await ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/directory/room/{HttpUtility.UrlEncode(roomAlias)}", new RoomIdResponse() { + RoomId = roomId + }); + if (!resp.IsSuccessStatusCode) { + Console.WriteLine($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}"); + throw new InvalidDataException($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}"); + } } + + public async Task DeleteRoomAliasAsync(string roomAlias) { + var resp = await ClientHttpClient.DeleteAsync("/_matrix/client/v3/directory/room/" + HttpUtility.UrlEncode(roomAlias)); + if (!resp.IsSuccessStatusCode) { + Console.WriteLine($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}"); + throw new InvalidDataException($"Failed to set room alias: {await resp.Content.ReadAsStringAsync()}"); + } + } + + public async Task<RoomAliasesResponse> GetLocalRoomAliasesAsync(string roomId) { + var resp = await ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{HttpUtility.UrlEncode(roomId)}/aliases"); + if (!resp.IsSuccessStatusCode) { + Console.WriteLine($"Failed to get room aliases: {await resp.Content.ReadAsStringAsync()}"); + throw new InvalidDataException($"Failed to get room aliases: {await resp.Content.ReadAsStringAsync()}"); + } + + return await resp.Content.ReadFromJsonAsync<RoomAliasesResponse>() ?? throw new Exception("Failed to get room aliases?"); + } + + public class RoomAliasesResponse { + [JsonPropertyName("aliases")] + public required List<string> Aliases { get; set; } + } + +#endregion } \ No newline at end of file diff --git a/LibMatrix/Homeservers/FederationClient.cs b/LibMatrix/Homeservers/FederationClient.cs
index 617b737..9760e20 100644 --- a/LibMatrix/Homeservers/FederationClient.cs +++ b/LibMatrix/Homeservers/FederationClient.cs
@@ -1,5 +1,5 @@ -using System.Text.Json.Serialization; using LibMatrix.Extensions; +using LibMatrix.Responses.Federation; using LibMatrix.Services; namespace LibMatrix.Homeservers; @@ -17,17 +17,7 @@ public class FederationClient { public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } public async Task<ServerVersionResponse> GetServerVersionAsync() => await HttpClient.GetFromJsonAsync<ServerVersionResponse>("/_matrix/federation/v1/version"); + public async Task<SignedObject<ServerKeysResponse>> GetServerKeysAsync() => await HttpClient.GetFromJsonAsync<SignedObject<ServerKeysResponse>>("/_matrix/key/v2/server"); } -public class ServerVersionResponse { - [JsonPropertyName("server")] - public required ServerInfo Server { get; set; } - public class ServerInfo { - [JsonPropertyName("name")] - public string Name { get; set; } - - [JsonPropertyName("version")] - public string Version { get; set; } - } -} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalRoomQueryFilter.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalRoomQueryFilter.cs
index b8929a0..97c4bbf 100644 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalRoomQueryFilter.cs +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalRoomQueryFilter.cs
@@ -1,27 +1,90 @@ namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters; public class SynapseAdminLocalRoomQueryFilter { - public string RoomIdContains { get; set; } = ""; - public string NameContains { get; set; } = ""; - public string CanonicalAliasContains { get; set; } = ""; - public string VersionContains { get; set; } = ""; - public string CreatorContains { get; set; } = ""; - public string EncryptionContains { get; set; } = ""; - public string JoinRulesContains { get; set; } = ""; - public string GuestAccessContains { get; set; } = ""; - public string HistoryVisibilityContains { get; set; } = ""; - - public bool Federatable { get; set; } = true; - public bool Public { get; set; } = true; - - public int JoinedMembersGreaterThan { get; set; } - public int JoinedMembersLessThan { get; set; } = int.MaxValue; - - public int JoinedLocalMembersGreaterThan { get; set; } - public int JoinedLocalMembersLessThan { get; set; } = int.MaxValue; - public int StateEventsGreaterThan { get; set; } - public int StateEventsLessThan { get; set; } = int.MaxValue; - - public bool CheckFederation { get; set; } - public bool CheckPublic { get; set; } + public StringFilter RoomId { get; set; } = new(); + public StringFilter Name { get; set; } = new(); + public StringFilter CanonicalAlias { get; set; } = new(); + public StringFilter Version { get; set; } = new(); + public StringFilter Creator { get; set; } = new(); + public StringFilter Encryption { get; set; } = new(); + public StringFilter JoinRules { get; set; } = new(); + public StringFilter GuestAccess { get; set; } = new(); + public StringFilter HistoryVisibility { get; set; } = new(); + public StringFilter RoomType { get; set; } = new(); + public StringFilter Topic { get; set; } = new(); + + public IntFilter JoinedMembers { get; set; } = new() { + GreaterThan = 0, + LessThan = int.MaxValue + }; + + public IntFilter JoinedLocalMembers { get; set; } = new() { + GreaterThan = 0, + LessThan = int.MaxValue + }; + + public IntFilter StateEvents { get; set; } = new() { + GreaterThan = 0, + LessThan = int.MaxValue + }; + + public BoolFilter Federation { get; set; } = new(); + public BoolFilter Public { get; set; } = new(); + public BoolFilter Tombstone { get; set; } = new(); +} + +public class OptionalFilter { + public bool Enabled { get; set; } +} + +public class StringFilter : OptionalFilter { + public bool CheckValueContains { get; set; } + public string? ValueContains { get; set; } + + public bool CheckValueEquals { get; set; } + public string? ValueEquals { get; set; } + + public bool Matches(string? value, StringComparison comparison = StringComparison.Ordinal) { + if (!Enabled) return true; + + if (CheckValueEquals) { + if (!string.Equals(value, ValueEquals, comparison)) return false; + } + + if (CheckValueContains && ValueContains != null) { + if (value != null && !value.Contains(ValueContains, comparison)) return false; + } + + return true; + } +} + +public class IntFilter : OptionalFilter { + public bool CheckGreaterThan { get; set; } + public int GreaterThan { get; set; } + public bool CheckLessThan { get; set; } + public int LessThan { get; set; } + + public bool Matches(int value) { + if (!Enabled) return true; + + if (CheckGreaterThan) { + if (value <= GreaterThan) return false; + } + + if (CheckLessThan) { + if (value >= LessThan) return false; + } + + return true; + } +} + +public class BoolFilter : OptionalFilter { + public bool Value { get; set; } + + public bool Matches(bool value) { + if (!Enabled) return true; + return value == Value; + } } \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs
index 62b291b..5a4acf7 100644 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs
@@ -1,27 +1,5 @@ namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters; public class SynapseAdminLocalUserQueryFilter { - public string UserIdContains { get; set; } = ""; - public string NameContains { get; set; } = ""; - public string CanonicalAliasContains { get; set; } = ""; - public string VersionContains { get; set; } = ""; - public string CreatorContains { get; set; } = ""; - public string EncryptionContains { get; set; } = ""; - public string JoinRulesContains { get; set; } = ""; - public string GuestAccessContains { get; set; } = ""; - public string HistoryVisibilityContains { get; set; } = ""; - public bool Federatable { get; set; } = true; - public bool Public { get; set; } = true; - - public int JoinedMembersGreaterThan { get; set; } - public int JoinedMembersLessThan { get; set; } = int.MaxValue; - - public int JoinedLocalMembersGreaterThan { get; set; } - public int JoinedLocalMembersLessThan { get; set; } = int.MaxValue; - public int StateEventsGreaterThan { get; set; } - public int StateEventsLessThan { get; set; } = int.MaxValue; - - public bool CheckFederation { get; set; } - public bool CheckPublic { get; set; } } \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/EventReportListResult.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/EventReportListResult.cs
index 10fc039..0f3ee56 100644 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/EventReportListResult.cs +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/EventReportListResult.cs
@@ -97,10 +97,10 @@ public class SynapseAdminEventReportListResult : SynapseNextTokenTotalCollection [JsonPropertyName("unsigned")] public JsonObject? Unsigned { get; set; } - // Extra... copied from StateEventResponse + // Extra... copied from MatrixEventResponse [JsonIgnore] - public Type MappedType => StateEvent.GetStateEventType(Type); + public Type MappedType => MatrixEvent.GetEventType(Type); [JsonIgnore] public bool IsLegacyType => MappedType.GetCustomAttributes<MatrixEventAttribute>().FirstOrDefault(x => x.EventName == Type)?.Legacy ?? false; @@ -128,7 +128,7 @@ public class SynapseAdminEventReportListResult : SynapseNextTokenTotalCollection // return null; // } try { - var mappedType = StateEvent.GetStateEventType(Type); + var mappedType = MatrixEvent.GetEventType(Type); if (mappedType == typeof(UnknownEventContent)) Console.WriteLine($"Warning: unknown event type '{Type}'"); var deserialisedContent = (EventContent)RawContent.Deserialize(mappedType, TypedContentSerializerOptions)!; diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/RoomListResult.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/RoomListResult.cs
index d84c89b..7006c07 100644 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/RoomListResult.cs +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/RoomListResult.cs
@@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using LibMatrix.EventTypes.Spec.State.RoomInfo; namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; @@ -60,5 +61,56 @@ public class SynapseAdminRoomListResult { [JsonPropertyName("state_events")] public int StateEvents { get; set; } + + [JsonPropertyName("gay.rory.synapse_admin_extensions.tombstone")] + public MatrixEventResponse? TombstoneEvent { get; set; } + + [JsonPropertyName("gay.rory.synapse_admin_extensions.create")] + public MatrixEventResponse? CreateEvent { get; set; } + + [JsonPropertyName("gay.rory.synapse_admin_extensions.topic")] + public MatrixEventResponse? TopicEvent { get; set; } + + public async Task<MatrixEventResponse?> GetCreateEventAsync(AuthenticatedHomeserverSynapse hs) { + if (CreateEvent != null) return CreateEvent; + + try { + var events = (await hs.Admin.GetRoomStateAsync(RoomId, RoomCreateEventContent.EventId)); + CreateEvent = events.Events.SingleOrDefault(x => x.StateKey == ""); + } + catch (Exception e) { + Console.WriteLine($"Failed to fetch room create event for {RoomId}: {e}"); + } + + return null; + } + + public async Task<MatrixEventResponse?> GetTombstoneEventAsync(AuthenticatedHomeserverSynapse hs) { + if (TombstoneEvent != null) return TombstoneEvent; + + try { + var events = (await hs.Admin.GetRoomStateAsync(RoomId, RoomTombstoneEventContent.EventId)); + TombstoneEvent = events.Events.SingleOrDefault(x => x.StateKey == ""); + } + catch (Exception e) { + Console.WriteLine($"Failed to fetch room tombstone event for {RoomId}: {e}"); + } + + return null; + } + + public async Task<MatrixEventResponse?> GetTopicEventAsync(AuthenticatedHomeserverSynapse hs) { + if (TopicEvent != null) return TopicEvent; + + try { + var events = await hs.Admin.GetRoomStateAsync(RoomId, RoomTopicEventContent.EventId); + TopicEvent = events.Events.SingleOrDefault(x => x.StateKey == ""); + } + catch (Exception e) { + Console.WriteLine($"Failed to fetch room topic event for {RoomId}: {e}"); + } + + return null; + } } } \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminRoomStateResult.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminRoomStateResult.cs
index ae36d4e..d9d5f1a 100644 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminRoomStateResult.cs +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminRoomStateResult.cs
@@ -4,5 +4,5 @@ namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; public class SynapseAdminRoomStateResult { [JsonPropertyName("state")] - public required List<StateEventResponse> Events { get; set; } + public required List<MatrixEventResponse> Events { get; set; } } \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs
index 777c04a..f839e20 100644 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs
@@ -1,21 +1,16 @@ // #define LOG_SKIP +using System.CodeDom.Compiler; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes; -using System.Net.Http.Json; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters; using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests; using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; using LibMatrix.Responses; -using LibMatrix.Filters; -using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters; -using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; -using LibMatrix.Responses; +using LibMatrix.StructuredData; namespace LibMatrix.Homeservers.ImplementationDetails.Synapse; @@ -27,24 +22,41 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH #region Rooms public async IAsyncEnumerable<SynapseAdminRoomListResult.SynapseAdminRoomListResultRoom> SearchRoomsAsync(int limit = int.MaxValue, int chunkLimit = 250, - string orderBy = "name", string dir = "f", string? searchTerm = null, SynapseAdminLocalRoomQueryFilter? localFilter = null) { + string orderBy = "name", string dir = "f", string? searchTerm = null, SynapseAdminLocalRoomQueryFilter? localFilter = null, + bool fetchTombstones = false, bool fetchTopics = false, bool fetchCreateEvents = false) { + if (localFilter != null) { + fetchTombstones |= localFilter.Tombstone.Enabled; + fetchTopics |= localFilter.Topic.Enabled; + fetchCreateEvents |= localFilter.RoomType.Enabled; + } + + var serverCaps = await authenticatedHomeserver.GetCapabilitiesAsync(); + var serverSupportsQueryEventsV2 = serverCaps.Capabilities.SynapseRoomListQueryEventsV2?.Enabled ?? false; + SynapseAdminRoomListResult? res = null; var i = 0; int? totalRooms = null; do { var url = $"/_synapse/admin/v1/rooms?limit={Math.Min(limit, chunkLimit)}&dir={dir}&order_by={orderBy}"; - if (!string.IsNullOrEmpty(searchTerm)) url += $"&search_term={searchTerm}"; + if (!string.IsNullOrEmpty(searchTerm)) url += $"&search_term={searchTerm}"; if (res?.NextBatch is not null) url += $"&from={res.NextBatch}"; + // nonstandard stuff + if (fetchTombstones) url += "&gay.rory.synapse_admin_extensions.include_tombstone=true&emma_include_tombstone=true"; + if (fetchTopics) url += "&gay.rory.synapse_admin_extensions.include_topic=true"; + if (fetchCreateEvents) url += "&gay.rory.synapse_admin_extensions.include_create_event=true"; + Console.WriteLine($"--- ADMIN Querying Room List with URL: {url} - Already have {i} items... ---"); res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminRoomListResult>(url); totalRooms ??= res.TotalRooms; // Console.WriteLine(res.ToJson(false)); + + List<SynapseAdminRoomListResult.SynapseAdminRoomListResultRoom> keep = []; foreach (var room in res.Rooms) { if (localFilter is not null) { - if (!string.IsNullOrWhiteSpace(localFilter.RoomIdContains) && !room.RoomId.Contains(localFilter.RoomIdContains, StringComparison.OrdinalIgnoreCase)) { + if (!localFilter.RoomId.Matches(room.RoomId, StringComparison.OrdinalIgnoreCase)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule roomid."); @@ -52,7 +64,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (!string.IsNullOrWhiteSpace(localFilter.NameContains) && room.Name?.Contains(localFilter.NameContains, StringComparison.OrdinalIgnoreCase) != true) { + if (!localFilter.Name.Matches(room.Name ?? "", StringComparison.OrdinalIgnoreCase)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule roomname."); @@ -60,8 +72,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (!string.IsNullOrWhiteSpace(localFilter.CanonicalAliasContains) && - room.CanonicalAlias?.Contains(localFilter.CanonicalAliasContains, StringComparison.OrdinalIgnoreCase) != true) { + if (!localFilter.CanonicalAlias.Matches(room.CanonicalAlias ?? "", StringComparison.OrdinalIgnoreCase)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule alias."); @@ -69,7 +80,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (!string.IsNullOrWhiteSpace(localFilter.VersionContains) && !room.Version.Contains(localFilter.VersionContains, StringComparison.OrdinalIgnoreCase)) { + if (!localFilter.Version.Matches(room.Version ?? "")) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule version."); @@ -77,7 +88,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (!string.IsNullOrWhiteSpace(localFilter.CreatorContains) && !room.Creator.Contains(localFilter.CreatorContains, StringComparison.OrdinalIgnoreCase)) { + if (!localFilter.Creator.Matches(room.Creator ?? "", StringComparison.OrdinalIgnoreCase)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule creator."); @@ -85,8 +96,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (!string.IsNullOrWhiteSpace(localFilter.EncryptionContains) && - room.Encryption?.Contains(localFilter.EncryptionContains, StringComparison.OrdinalIgnoreCase) != true) { + if (!localFilter.Encryption.Matches(room.Encryption ?? "", StringComparison.OrdinalIgnoreCase)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule encryption."); @@ -94,8 +104,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (!string.IsNullOrWhiteSpace(localFilter.JoinRulesContains) && - room.JoinRules?.Contains(localFilter.JoinRulesContains, StringComparison.OrdinalIgnoreCase) != true) { + if (!localFilter.JoinRules.Matches(room.JoinRules ?? "", StringComparison.OrdinalIgnoreCase)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule joinrules."); @@ -103,8 +112,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (!string.IsNullOrWhiteSpace(localFilter.GuestAccessContains) && - room.GuestAccess?.Contains(localFilter.GuestAccessContains, StringComparison.OrdinalIgnoreCase) != true) { + if (!localFilter.GuestAccess.Matches(room.GuestAccess ?? "", StringComparison.OrdinalIgnoreCase)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule guestaccess."); @@ -112,8 +120,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (!string.IsNullOrWhiteSpace(localFilter.HistoryVisibilityContains) && - room.HistoryVisibility?.Contains(localFilter.HistoryVisibilityContains, StringComparison.OrdinalIgnoreCase) != true) { + if (!localFilter.HistoryVisibility.Matches(room.HistoryVisibility ?? "", StringComparison.OrdinalIgnoreCase)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule history visibility."); @@ -121,7 +128,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (localFilter.CheckFederation && room.Federatable != localFilter.Federatable) { + if (!localFilter.Federation.Matches(room.Federatable)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule federation."); @@ -129,7 +136,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (localFilter.CheckPublic && room.Public != localFilter.Public) { + if (!localFilter.Public.Matches(room.Public)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule public."); @@ -137,15 +144,15 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (room.StateEvents < localFilter.StateEventsGreaterThan || room.StateEvents > localFilter.StateEventsLessThan) { + if (!localFilter.StateEvents.Matches(room.StateEvents)) { totalRooms--; #if LOG_SKIP - Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule joined local members."); + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule state events."); #endif continue; } - if (room.JoinedMembers < localFilter.JoinedMembersGreaterThan || room.JoinedMembers > localFilter.JoinedMembersLessThan) { + if (!localFilter.JoinedMembers.Matches(room.JoinedMembers)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule joined members: {localFilter.JoinedMembersGreaterThan} < {room.JoinedLocalMembers} < {localFilter.JoinedMembersLessThan}."); @@ -153,7 +160,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } - if (room.JoinedLocalMembers < localFilter.JoinedLocalMembersGreaterThan || room.JoinedLocalMembers > localFilter.JoinedLocalMembersLessThan) { + if (!localFilter.JoinedLocalMembers.Matches(room.JoinedLocalMembers)) { totalRooms--; #if LOG_SKIP Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule joined local members: {localFilter.JoinedLocalMembersGreaterThan} < {room.JoinedLocalMembers} < {localFilter.JoinedLocalMembersLessThan}."); @@ -161,28 +168,99 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH continue; } } - // if (contentSearch is not null && !string.IsNullOrEmpty(contentSearch) && - // !( - // room.Name?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true || - // room.CanonicalAlias?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true || - // room.Creator?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true - // ) - // ) { - // totalRooms--; - // continue; - // } i++; + keep.Add(room); + } + + var parallelisationLimit = new SemaphoreSlim(32, 32); + List<Task<(SynapseAdminRoomListResult.SynapseAdminRoomListResultRoom room, MatrixEventResponse?[] tasks)>> tasks = []; + + async Task<(SynapseAdminRoomListResult.SynapseAdminRoomListResultRoom room, MatrixEventResponse?[] tasks)> fillTask( + SynapseAdminRoomListResult.SynapseAdminRoomListResultRoom room) { + if (serverSupportsQueryEventsV2) return (room, []); + + var fillTasks = await Task.WhenAll(((Task<MatrixEventResponse?>?[]) [ + fetchTombstones && room.TombstoneEvent is null + ? parallelisationLimit.RunWithLockAsync(() => room.GetTombstoneEventAsync(authenticatedHomeserver)) + : null!, + fetchTopics && room.TopicEvent is null + ? parallelisationLimit.RunWithLockAsync(() => room.GetTopicEventAsync(authenticatedHomeserver)) + : null!, + fetchCreateEvents && room.CreateEvent is null + ? parallelisationLimit.RunWithLockAsync(() => room.GetCreateEventAsync(authenticatedHomeserver)) + : null!, + ]) + .Where(t => t != null)! + ); + return ( + room, + fillTasks + ); + } + + tasks.AddRange( + serverSupportsQueryEventsV2 + ? keep.Select(x => Task.FromResult((x, (MatrixEventResponse?[])[]))) + : keep.Select(fillTask) + ); + + // await Task.WhenAll(tasks); + + foreach (var taskRes in tasks) { + var (room, _) = await taskRes; + if (localFilter is not null) { + if (!localFilter.Tombstone.Matches(room.TombstoneEvent != null)) { + totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule tombstone."); +#endif + continue; + } + + if (!localFilter.RoomType.Matches(room.CreateEvent?.ContentAs<RoomCreateEventContent>()?.Type)) { + totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule room type."); +#endif + continue; + } + + if (!localFilter.Topic.Matches(room.TopicEvent?.ContentAs<RoomTopicEventContent>()?.Topic, StringComparison.OrdinalIgnoreCase)) { + totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule topic."); +#endif + continue; + } + } + yield return room; } } while (i < Math.Min(limit, totalRooms ?? limit)); } + public async Task<bool> CheckRoomKnownAsync(string roomId) { + try { + var createEvt = await GetRoomStateAsync(roomId, RoomCreateEventContent.EventId); + if (createEvt.Events.FirstOrDefault(e => e.StateKey == "") is null) + return false; + var members = await GetRoomMembersAsync(roomId, localOnly: true); + return members.Members.Count > 0; + } + catch (Exception e) { + if (e is HttpRequestException { StatusCode: System.Net.HttpStatusCode.NotFound }) return false; + if (e is MatrixException { ErrorCode: MatrixException.ErrorCodes.M_NOT_FOUND }) return false; + throw; + } + } + #endregion #region Users public async IAsyncEnumerable<SynapseAdminUserListResult.SynapseAdminUserListResultUser> SearchUsersAsync(int limit = int.MaxValue, int chunkLimit = 250, + string orderBy = "name", string dir = "f", SynapseAdminLocalUserQueryFilter? localFilter = null) { // TODO: implement filters string? from = null; @@ -190,6 +268,9 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH var url = new Uri("/_synapse/admin/v3/users", UriKind.Relative); url = url.AddQuery("limit", Math.Min(limit, chunkLimit).ToString()); if (!string.IsNullOrWhiteSpace(from)) url = url.AddQuery("from", from); + if (!string.IsNullOrWhiteSpace(orderBy)) url = url.AddQuery("order_by", orderBy); + if (!string.IsNullOrWhiteSpace(dir)) url = url.AddQuery("dir", dir); + Console.WriteLine($"--- ADMIN Querying User List with URL: {url} ---"); // TODO: implement URI methods in http client var res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminUserListResult>(url.ToString()); @@ -522,8 +603,13 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH $"/_synapse/admin/v2/rooms/delete_status/{deleteId}"); } - public async Task<SynapseAdminRoomMemberListResult> GetRoomMembersAsync(string roomId) { - return await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminRoomMemberListResult>($"/_synapse/admin/v1/rooms/{roomId.UrlEncode()}/members"); + public async Task<SynapseAdminRoomMemberListResult> GetRoomMembersAsync(string roomId, bool localOnly = false) { + var res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminRoomMemberListResult>($"/_synapse/admin/v1/rooms/{roomId.UrlEncode()}/members"); + if (localOnly) { + res.Members = res.Members.Where(m => m.EndsWith($":{authenticatedHomeserver.ServerName}")).ToList(); + } + + return res; } public async Task<SynapseAdminRoomStateResult> GetRoomStateAsync(string roomId, string? type = null) { diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs
index f0b35f9..af84be2 100644 --- a/LibMatrix/Homeservers/RemoteHomeServer.cs +++ b/LibMatrix/Homeservers/RemoteHomeServer.cs
@@ -3,6 +3,7 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Web; +using ArcaneLibs.Collections; using ArcaneLibs.Extensions; using LibMatrix.Extensions; using LibMatrix.Responses; @@ -28,7 +29,8 @@ public class RemoteHomeserver { Auth = new(this); } - private Dictionary<string, object> _profileCache { get; set; } = new(); + // private Dictionary<string, object> _profileCache { get; set; } = new(); + private SemaphoreCache<UserProfileResponse> _profileCache { get; set; } = new(); public string ServerNameOrUrl { get; } public string? Proxy { get; } @@ -40,27 +42,12 @@ public class RemoteHomeserver { public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } - public async Task<UserProfileResponse> GetProfileAsync(string mxid, bool useCache = false) { - if (mxid is null) throw new ArgumentNullException(nameof(mxid)); - if (useCache && _profileCache.TryGetValue(mxid, out var value)) { - if (value is SemaphoreSlim s) await s.WaitAsync(); - if (value is UserProfileResponse p) return p; - } - - _profileCache[mxid] = new SemaphoreSlim(1); - - var resp = await ClientHttpClient.GetAsync($"/_matrix/client/v3/profile/{HttpUtility.UrlEncode(mxid)}"); - var data = await resp.Content.ReadFromJsonAsync<UserProfileResponse>(); - if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data); - _profileCache[mxid] = data ?? throw new InvalidOperationException($"Could not get profile for {mxid}"); - - return data; - } - // TODO: Do we need to support retrieving individual profile properties? Is there any use for that besides just getting the full profile? + public async Task<UserProfileResponse> GetProfileAsync(string mxid) => + await ClientHttpClient.GetFromJsonAsync<UserProfileResponse>($"/_matrix/client/v3/profile/{HttpUtility.UrlEncode(mxid)}"); public async Task<ClientVersionsResponse> GetClientVersionsAsync() { - var resp = await ClientHttpClient.GetAsync($"/_matrix/client/versions"); + var resp = await ClientHttpClient.GetAsync("/_matrix/client/versions"); var data = await resp.Content.ReadFromJsonAsync<ClientVersionsResponse>(); if (!resp.IsSuccessStatusCode) Console.WriteLine("ClientVersions: " + data); return data ?? throw new InvalidOperationException("ClientVersionsResponse is null"); @@ -74,13 +61,27 @@ public class RemoteHomeserver { return data ?? throw new InvalidOperationException($"Could not resolve alias {alias}"); } - public Task<PublicRoomDirectoryResult> GetPublicRoomsAsync(int limit = 100, string? server = null, string? since = null) => - ClientHttpClient.GetFromJsonAsync<PublicRoomDirectoryResult>(buildUriWithParams("/_matrix/client/v3/publicRooms", (nameof(limit), true, limit), - (nameof(server), !string.IsNullOrWhiteSpace(server), server), (nameof(since), !string.IsNullOrWhiteSpace(since), since))); + public Task<PublicRoomDirectoryResult> GetPublicRoomsAsync(int limit = 100, string? server = null, string? since = null) { + var url = $"/_matrix/client/v3/publicRooms?limit={limit}"; + if (!string.IsNullOrWhiteSpace(server)) { + url += $"&server={server}"; + } - // TODO: move this somewhere else - private string buildUriWithParams(string url, params (string name, bool include, object? value)[] values) { - return url + "?" + string.Join("&", values.Where(x => x.include)); + if (!string.IsNullOrWhiteSpace(since)) { + url += $"&since={since}"; + } + + return ClientHttpClient.GetFromJsonAsync<PublicRoomDirectoryResult>(url); + } + + public async IAsyncEnumerable<PublicRoomDirectoryResult> EnumeratePublicRoomsAsync(int limit = int.MaxValue, string? server = null, string? since = null, int chunkSize = 100) { + PublicRoomDirectoryResult res; + do { + res = await GetPublicRoomsAsync(chunkSize, server, since); + yield return res; + if (res.NextBatch is null || res.NextBatch == since || res.Chunk.Count == 0) break; + since = res.NextBatch; + } while (limit > 0 && limit-- > 0); } #region Authentication @@ -117,9 +118,6 @@ public class RemoteHomeserver { #endregion - [Obsolete("This call uses the deprecated unauthenticated media endpoints, please switch to the relevant AuthenticatedHomeserver methods instead.", true)] - public virtual string? ResolveMediaUri(string? mxcUri) => null; - public UserInteractiveAuthClient Auth; } diff --git a/LibMatrix/LibMatrix.csproj b/LibMatrix/LibMatrix.csproj
index 62bb48f..7d4ca5c 100644 --- a/LibMatrix/LibMatrix.csproj +++ b/LibMatrix/LibMatrix.csproj
@@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <LangVersion>preview</LangVersion> @@ -12,15 +12,16 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.1"/> - <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1"/> + <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107"/> + <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107"/> <ProjectReference Include="..\LibMatrix.EventTypes\LibMatrix.EventTypes.csproj"/> </ItemGroup> <ItemGroup> - <!-- <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250313-104848" Condition="'$(Configuration)' == 'Release'" />--> - <!-- <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(Configuration)' == 'Debug'"/>--> + <!-- <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250313-104848" Condition="'$(Configuration)' == 'Release'" />--> + <!-- <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(Configuration)' == 'Debug'"/>--> <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj"/> + <PackageReference Include="ArcaneLibs" Version="*-*" Condition="'$(ContinuousIntegrationBuild)'=='true'"/> </ItemGroup> </Project> diff --git a/LibMatrix/LibMatrixNetworkException.cs b/LibMatrix/LibMatrixNetworkException.cs new file mode 100644
index 0000000..7be0f4e --- /dev/null +++ b/LibMatrix/LibMatrixNetworkException.cs
@@ -0,0 +1,33 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; +using ArcaneLibs.Extensions; +// ReSharper disable MemberCanBePrivate.Global + +namespace LibMatrix; + +public class LibMatrixNetworkException : Exception { + public LibMatrixNetworkException() : base() { } + public LibMatrixNetworkException(Exception httpRequestException) : base("A network error occurred", httpRequestException) { } + + [JsonPropertyName("errcode")] + public required string ErrorCode { get; set; } + + [JsonPropertyName("error")] + public required string Error { get; set; } + + public object GetAsObject() => new { errcode = ErrorCode, error = Error }; + public string GetAsJson() => GetAsObject().ToJson(ignoreNull: true); + + public override string Message => + $"{ErrorCode}: {ErrorCode switch { + ErrorCodes.RLM_NET_UNKNOWN_HOST => "The specified host could not be found.", + ErrorCodes.RLM_NET_INVALID_REMOTE_CERTIFICATE => "The remote server's TLS certificate is invalid or could not be verified.", + _ => $"Unknown error: {GetAsObject().ToJson(ignoreNull: true)}" + }}\nError: {Error}"; + + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Follows spec naming")] + public static class ErrorCodes { + public const string RLM_NET_UNKNOWN_HOST = "RLM_NET_UNKNOWN_HOST"; + public const string RLM_NET_INVALID_REMOTE_CERTIFICATE = "RLM_NET_INVALID_REMOTE_CERTIFICATE"; + } +} \ No newline at end of file diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs
index d9a6acd..b4dcc78 100644 --- a/LibMatrix/Responses/CreateRoomRequest.cs +++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -29,7 +29,7 @@ public class CreateRoomRequest { // public string Preset { get; set; } [JsonPropertyName("initial_state")] - public List<StateEvent>? InitialState { get; set; } + public List<MatrixEvent>? InitialState { get; set; } /// <summary> /// One of: ["public", "private"] @@ -42,24 +42,27 @@ public class CreateRoomRequest { public RoomPowerLevelEventContent? PowerLevelContentOverride { get; set; } [JsonPropertyName("creation_content")] - public JsonObject CreationContent { get; set; } = new(); + public Dictionary<string, object> CreationContent { get; set; } = new(); [JsonPropertyName("invite")] public List<string>? Invite { get; set; } + [JsonPropertyName("room_version")] + public string? RoomVersion { get; set; } + /// <summary> /// For use only when you can't use the CreationContent property /// </summary> - public StateEvent? this[string eventType, string eventKey = ""] { + public MatrixEvent? this[string eventType, string eventKey = ""] { get { var stateEvent = InitialState?.FirstOrDefault(x => x.Type == eventType && x.StateKey == eventKey); if (stateEvent == null) - InitialState?.Add(stateEvent = new StateEvent { + InitialState?.Add(stateEvent = new MatrixEvent { Type = eventType, StateKey = eventKey, TypedContent = (EventContent)Activator.CreateInstance( - StateEvent.KnownStateEventTypes.FirstOrDefault(x => + MatrixEvent.KnownEventTypes.FirstOrDefault(x => x.GetCustomAttributes<MatrixEventAttribute>()? .Any(y => y.EventName == eventType) ?? false) ?? typeof(UnknownEventContent) )! @@ -89,7 +92,7 @@ public class CreateRoomRequest { var request = new CreateRoomRequest { Name = name ?? "New public Room", Visibility = "public", - CreationContent = new JsonObject(), + CreationContent = new(), PowerLevelContentOverride = new RoomPowerLevelEventContent { EventsDefault = 0, UsersDefault = 0, @@ -119,7 +122,7 @@ public class CreateRoomRequest { } }, RoomAliasName = roomAliasName, - InitialState = new List<StateEvent>() + InitialState = new List<MatrixEvent>() }; return request; @@ -129,7 +132,7 @@ public class CreateRoomRequest { var request = new CreateRoomRequest { Name = name ?? "New private Room", Visibility = "private", - CreationContent = new JsonObject(), + CreationContent = new(), PowerLevelContentOverride = new RoomPowerLevelEventContent { EventsDefault = 0, UsersDefault = 0, @@ -159,7 +162,7 @@ public class CreateRoomRequest { } }, RoomAliasName = roomAliasName, - InitialState = new List<StateEvent>() + InitialState = new List<MatrixEvent>() }; return request; diff --git a/LibMatrix/EventIdResponse.cs b/LibMatrix/Responses/EventIdResponse.cs
index 6a04229..9e23210 100644 --- a/LibMatrix/EventIdResponse.cs +++ b/LibMatrix/Responses/EventIdResponse.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix; +namespace LibMatrix.Responses; public class EventIdResponse { [JsonPropertyName("event_id")] diff --git a/LibMatrix/Responses/Federation/ServerKeysResponse.cs b/LibMatrix/Responses/Federation/ServerKeysResponse.cs new file mode 100644
index 0000000..cb62e34 --- /dev/null +++ b/LibMatrix/Responses/Federation/ServerKeysResponse.cs
@@ -0,0 +1,55 @@ +using System.Diagnostics; +using System.Text.Json.Serialization; +using LibMatrix.Abstractions; + +namespace LibMatrix.Responses.Federation; + +public class ServerKeysResponse { + [JsonPropertyName("server_name")] + public string ServerName { get; set; } + + [JsonPropertyName("valid_until_ts")] + public ulong ValidUntilTs { get; set; } + + [JsonIgnore] + public DateTime ValidUntil { + get => DateTimeOffset.FromUnixTimeMilliseconds((long)ValidUntilTs).DateTime; + set => ValidUntilTs = (ulong)new DateTimeOffset(value).ToUnixTimeMilliseconds(); + } + + [JsonPropertyName("verify_keys")] + public Dictionary<string, CurrentVerifyKey> VerifyKeys { get; set; } = new(); + + [JsonIgnore] + public Dictionary<VersionedKeyId, CurrentVerifyKey> VerifyKeysById { + get => VerifyKeys.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value); + set => VerifyKeys = value.ToDictionary(key => (string)key.Key, key => key.Value); + } + + [JsonPropertyName("old_verify_keys")] + public Dictionary<string, ExpiredVerifyKey> OldVerifyKeys { get; set; } = new(); + + [JsonIgnore] + public Dictionary<VersionedKeyId, ExpiredVerifyKey> OldVerifyKeysById { + get => OldVerifyKeys.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value); + set => OldVerifyKeys = value.ToDictionary(key => (string)key.Key, key => key.Value); + } + + [DebuggerDisplay("{Key}")] + public class CurrentVerifyKey { + [JsonPropertyName("key")] + public string Key { get; set; } + } + + [DebuggerDisplay("{Key} (expired {Expired})")] + public class ExpiredVerifyKey : CurrentVerifyKey { + [JsonPropertyName("expired_ts")] + public ulong ExpiredTs { get; set; } + + [JsonIgnore] + public DateTime Expired { + get => DateTimeOffset.FromUnixTimeMilliseconds((long)ExpiredTs).DateTime; + set => ExpiredTs = (ulong)new DateTimeOffset(value).ToUnixTimeMilliseconds(); + } + } +} diff --git a/LibMatrix/Responses/Federation/ServerVersionResponse.cs b/LibMatrix/Responses/Federation/ServerVersionResponse.cs new file mode 100644
index 0000000..b09bdd0 --- /dev/null +++ b/LibMatrix/Responses/Federation/ServerVersionResponse.cs
@@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Responses.Federation; + +public class ServerVersionResponse { + [JsonPropertyName("server")] + public required ServerInfo Server { get; set; } + + public class ServerInfo { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("version")] + public string Version { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix/Responses/Federation/SignedObject.cs b/LibMatrix/Responses/Federation/SignedObject.cs new file mode 100644
index 0000000..3f6ffd6 --- /dev/null +++ b/LibMatrix/Responses/Federation/SignedObject.cs
@@ -0,0 +1,68 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using ArcaneLibs.Extensions; +using LibMatrix.Abstractions; +using LibMatrix.Homeservers; + +namespace LibMatrix.Responses.Federation; + +[JsonConverter(typeof(SignedObjectConverterFactory))] +public class SignedObject<T> { + [JsonPropertyName("signatures")] + public Dictionary<string, Dictionary<string, string>> Signatures { get; set; } = new(); + + [JsonIgnore] + public Dictionary<string, Dictionary<VersionedKeyId, string>> SignaturesById { + get => Signatures.ToDictionary(server => server.Key, server => server.Value.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value)); + set => Signatures = value.ToDictionary(server => server.Key, server => server.Value.ToDictionary(key => (string)key.Key, key => key.Value)); + } + + [JsonExtensionData] + public required JsonObject Content { get; set; } + + [JsonIgnore] + public T TypedContent { + get => Content.Deserialize<T>() ?? throw new JsonException("Failed to deserialize TypedContent from Content."); + set => Content = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(value)) ?? new JsonObject(); + } +} + +public class SignedObjectConverter<T> : JsonConverter<SignedObject<T>> { + public override SignedObject<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + var jsonObject = JsonSerializer.Deserialize<JsonObject>(ref reader, options); + if (jsonObject == null) { + throw new JsonException("Failed to deserialize SignedObject, JSON object is null."); + } + + var signatures = jsonObject["signatures"] ?? throw new JsonException("Failed to find 'signatures' property in JSON object."); + jsonObject.Remove("signatures"); + + var signedObject = new SignedObject<T> { + Content = jsonObject, + Signatures = signatures.Deserialize<Dictionary<string, Dictionary<string, string>>>() + ?? throw new JsonException("Failed to deserialize 'signatures' property into Dictionary<string, Dictionary<string, string>>.") + }; + + return signedObject; + } + + public override void Write(Utf8JsonWriter writer, SignedObject<T> value, JsonSerializerOptions options) { + var targetObj = value.Content.DeepClone(); + targetObj["signatures"] = value.Signatures.ToJsonNode(); + JsonSerializer.Serialize(writer, targetObj, options); + } +} + +internal class SignedObjectConverterFactory : JsonConverterFactory { + public override bool CanConvert(Type typeToConvert) { + if (!typeToConvert.IsGenericType) return false; + return typeToConvert.GetGenericTypeDefinition() == typeof(SignedObject<>); + } + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { + var wrappedType = typeToConvert.GetGenericArguments()[0]; + var converter = (JsonConverter)Activator.CreateInstance(typeof(SignedObjectConverter<>).MakeGenericType(wrappedType))!; + return converter; + } +} \ No newline at end of file diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs
index 2f78932..1944276 100644 --- a/LibMatrix/Responses/LoginResponse.cs +++ b/LibMatrix/Responses/LoginResponse.cs
@@ -19,11 +19,6 @@ public class LoginResponse { [JsonPropertyName("user_id")] public string UserId { get; set; } - - // public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedHomeserver(string? proxy = null) { - // var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver); - // await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(Homeserver, AccessToken, proxy); - // } } public class LoginRequest { diff --git a/LibMatrix/MessagesResponse.cs b/LibMatrix/Responses/MessagesResponse.cs
index 526da74..1b412fe 100644 --- a/LibMatrix/MessagesResponse.cs +++ b/LibMatrix/Responses/MessagesResponse.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix; +namespace LibMatrix.Responses; public class MessagesResponse { [JsonPropertyName("start")] @@ -10,8 +10,8 @@ public class MessagesResponse { public string? End { get; set; } [JsonPropertyName("chunk")] - public List<StateEventResponse> Chunk { get; set; } = new(); + public List<MatrixEventResponse> Chunk { get; set; } = new(); [JsonPropertyName("state")] - public List<StateEventResponse> State { get; set; } = new(); + public List<MatrixEventResponse> State { get; set; } = new(); } \ No newline at end of file diff --git a/LibMatrix/Responses/SyncResponse.cs b/LibMatrix/Responses/SyncResponse.cs
index d79e820..362ccc4 100644 --- a/LibMatrix/Responses/SyncResponse.cs +++ b/LibMatrix/Responses/SyncResponse.cs
@@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Text.Json.Serialization; using LibMatrix.EventTypes.Spec.State.RoomInfo; @@ -43,7 +44,7 @@ public class SyncResponse { // supporting classes public class PresenceDataStructure { [JsonPropertyName("events")] - public List<StateEventResponse>? Events { get; set; } + public List<MatrixEventResponse>? Events { get; set; } } public class RoomsDataStructure { @@ -115,13 +116,13 @@ public class SyncResponse { public class TimelineDataStructure : EventList { public TimelineDataStructure() { } - public TimelineDataStructure(List<StateEventResponse>? events, bool? limited) { + public TimelineDataStructure(List<MatrixEventResponse>? events, bool? limited) { Events = events; Limited = limited; } // [JsonPropertyName("events")] - // public List<StateEventResponse>? Events { get; set; } + // public List<MatrixEventResponse>? Events { get; set; } [JsonPropertyName("prev_batch")] public string? PrevBatch { get; set; } @@ -138,6 +139,7 @@ public class SyncResponse { public int HighlightCount { get; set; } } + [DebuggerDisplay("{JoinedMemberCount} joined, {InvitedMemberCount} invited, Heroes: {string.Join(\", \", Heroes ?? [])}")] public class SummaryDataStructure { [JsonPropertyName("m.heroes")] public List<string>? Heroes { get; set; } @@ -161,9 +163,9 @@ public class SyncResponse { AccountData?.Events?.Max(x => x.OriginServerTs) ?? 0, Presence?.Events?.Max(x => x.OriginServerTs) ?? 0, ToDevice?.Events?.Max(x => x.OriginServerTs) ?? 0, - Rooms?.Join?.Values?.Max(x => x.Timeline?.Events?.Max(y => y.OriginServerTs)) ?? 0, - Rooms?.Invite?.Values?.Max(x => x.InviteState?.Events?.Max(y => y.OriginServerTs)) ?? 0, - Rooms?.Leave?.Values?.Max(x => x.Timeline?.Events?.Max(y => y.OriginServerTs)) ?? 0 + Rooms?.Join?.Values.Max(x => x.Timeline?.Events?.Max(y => y.OriginServerTs)) ?? 0, + Rooms?.Invite?.Values.Max(x => x.InviteState?.Events?.Max(y => y.OriginServerTs)) ?? 0, + Rooms?.Leave?.Values.Max(x => x.Timeline?.Events?.Max(y => y.OriginServerTs)) ?? 0 ]).Max(); } diff --git a/LibMatrix/UserIdAndReason.cs b/LibMatrix/Responses/UserIdAndReason.cs
index 99c9eaf..176cf7c 100644 --- a/LibMatrix/UserIdAndReason.cs +++ b/LibMatrix/Responses/UserIdAndReason.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix; +namespace LibMatrix.Responses; internal class UserIdAndReason(string userId = null!, string reason = null!) { [JsonPropertyName("user_id")] diff --git a/LibMatrix/WhoAmIResponse.cs b/LibMatrix/Responses/WhoAmIResponse.cs
index 10fff35..db47152 100644 --- a/LibMatrix/WhoAmIResponse.cs +++ b/LibMatrix/Responses/WhoAmIResponse.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix; +namespace LibMatrix.Responses; public class WhoAmIResponse { [JsonPropertyName("user_id")] diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index bc1bc90..6d9a499 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -1,6 +1,5 @@ using System.Collections.Frozen; using System.Net.Http.Json; -using System.Security.Cryptography; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -12,6 +11,7 @@ using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Filters; using LibMatrix.Helpers; using LibMatrix.Homeservers; +using LibMatrix.Responses; namespace LibMatrix.RoomTypes; @@ -27,13 +27,13 @@ public class GenericRoom { public string RoomId { get; set; } - public async IAsyncEnumerable<StateEventResponse?> GetFullStateAsync() { - var result = Homeserver.ClientHttpClient.GetAsyncEnumerableFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/state"); + public async IAsyncEnumerable<MatrixEventResponse?> GetFullStateAsync() { + var result = Homeserver.ClientHttpClient.GetAsyncEnumerableFromJsonAsync<MatrixEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/state"); await foreach (var resp in result) yield return resp; } - public Task<List<StateEventResponse>> GetFullStateAsListAsync() => - Homeserver.ClientHttpClient.GetFromJsonAsync<List<StateEventResponse>>($"/_matrix/client/v3/rooms/{RoomId}/state"); + public Task<List<MatrixEventResponse>> GetFullStateAsListAsync() => + Homeserver.ClientHttpClient.GetFromJsonAsync<List<MatrixEventResponse>>($"/_matrix/client/v3/rooms/{RoomId}/state"); public async Task<T?> GetStateAsync<T>(string type, string stateKey = "") { if (string.IsNullOrEmpty(type)) throw new ArgumentNullException(nameof(type), "Event type must be specified"); @@ -63,20 +63,20 @@ public class GenericRoom { } } - public async Task<StateEventResponse> GetStateEventAsync(string type, string stateKey = "") { + public async Task<MatrixEventResponse> GetStateEventAsync(string type, string stateKey = "") { if (string.IsNullOrEmpty(type)) throw new ArgumentNullException(nameof(type), "Event type must be specified"); var url = $"/_matrix/client/v3/rooms/{RoomId}/state/{type}"; if (!string.IsNullOrEmpty(stateKey)) url += $"/{stateKey}"; url += "?format=event"; try { var resp = await Homeserver.ClientHttpClient.GetFromJsonAsync<JsonObject>(url); - if (resp["type"]?.GetValue<string>() != type) + if (resp["type"]?.GetValue<string>() != type || resp["state_key"]?.GetValue<string>() != stateKey) throw new LibMatrixException() { Error = "Homeserver returned event type does not match requested type, or server does not support passing `format`.", ErrorCode = LibMatrixException.ErrorCodes.M_UNSUPPORTED }; // throw new InvalidDataException("Returned event type does not match requested type, or server does not support passing `format`."); - return resp.Deserialize<StateEventResponse>(); + return resp.Deserialize<MatrixEventResponse>(); } catch (MatrixException e) { // if (e is not { ErrorCodode: "M_NOT_FOUND" }) { @@ -128,7 +128,7 @@ public class GenericRoom { } } - public async Task<StateEventResponse?> GetStateEventOrNullAsync(string type, string stateKey = "") { + public async Task<MatrixEventResponse?> GetStateEventOrNullAsync(string type, string stateKey = "") { try { return await GetStateEventAsync(type, stateKey); } @@ -220,7 +220,16 @@ public class GenericRoom { var joinUrl = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(RoomId)}"; var materialisedHomeservers = homeservers as string[] ?? homeservers?.ToArray() ?? []; - if (!materialisedHomeservers.Any()) materialisedHomeservers = [RoomId.Split(':', 2)[1]]; + if (!materialisedHomeservers.Any()) + if (RoomId.Contains(':')) + materialisedHomeservers = [Homeserver.ServerName, RoomId.Split(':')[1]]; + // v12+ room IDs: !<hash> + else { + materialisedHomeservers = [Homeserver.ServerName]; + foreach (var room in await Homeserver.GetJoinedRooms()) { + materialisedHomeservers.Add(await room.GetOriginHomeserverAsync()); + } + } Console.WriteLine($"Calling {joinUrl} with {materialisedHomeservers.Length} via(s)..."); @@ -232,13 +241,13 @@ public class GenericRoom { return await res.Content.ReadFromJsonAsync<RoomIdResponse>() ?? throw new Exception("Failed to join room?"); } - public async IAsyncEnumerable<StateEventResponse> GetMembersEnumerableAsync(string? membership = null) { + public async IAsyncEnumerable<MatrixEventResponse> GetMembersEnumerableAsync(string? membership = null) { var url = $"/_matrix/client/v3/rooms/{RoomId}/members"; var isMembershipSet = !string.IsNullOrWhiteSpace(membership); if (isMembershipSet) url += $"?membership={membership}"; var res = await Homeserver.ClientHttpClient.GetAsync(url); - var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { - TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default + var result = await JsonSerializer.DeserializeAsync<ChunkedMatrixEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { + TypeInfoResolver = ChunkedMatrixEventResponseSerializerContext.Default }); if (result is null) throw new Exception("Failed to deserialise members response"); @@ -250,18 +259,18 @@ public class GenericRoom { } } - public async Task<FrozenSet<StateEventResponse>> GetMembersListAsync(string? membership = null) { + public async Task<FrozenSet<MatrixEventResponse>> GetMembersListAsync(string? membership = null) { var url = $"/_matrix/client/v3/rooms/{RoomId}/members"; var isMembershipSet = !string.IsNullOrWhiteSpace(membership); if (isMembershipSet) url += $"?membership={membership}"; var res = await Homeserver.ClientHttpClient.GetAsync(url); - var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { - TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default + var result = await JsonSerializer.DeserializeAsync<ChunkedMatrixEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { + TypeInfoResolver = ChunkedMatrixEventResponseSerializerContext.Default }); if (result is null) throw new Exception("Failed to deserialise members response"); - var members = new List<StateEventResponse>(); + var members = new List<MatrixEventResponse>(); foreach (var resp in result.Chunk ?? []) { if (resp.Type != "m.room.member") continue; if (isMembershipSet && resp.RawContent?["membership"]?.GetValue<string>() != membership) continue; @@ -275,7 +284,7 @@ public class GenericRoom { await foreach (var evt in GetMembersEnumerableAsync(membership)) yield return evt.StateKey!; } - + public async Task<FrozenSet<string>> GetMemberIdsListAsync(string? membership = null) { var members = await GetMembersListAsync(membership); return members.Select(x => x.StateKey!).ToFrozenSet(); @@ -384,7 +393,7 @@ public class GenericRoom { new UserIdAndReason { UserId = userId, Reason = reason }); public async Task InviteUserAsync(string userId, string? reason = null, bool skipExisting = true) { - if (skipExisting && await GetStateOrNullAsync<RoomMemberEventContent>("m.room.member", userId) is not null) + if (skipExisting && await GetStateOrNullAsync<RoomMemberEventContent>("m.room.member", userId) is not { Membership: "leave" or "ban" or "join" }) return; await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/invite", new UserIdAndReason(userId, reason)); } @@ -394,12 +403,12 @@ public class GenericRoom { #region Events public async Task<EventIdResponse?> SendStateEventAsync(string eventType, object content) => - await (await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}", content)) + await (await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType.UrlEncode()}", content)) .Content.ReadFromJsonAsync<EventIdResponse>(); - public async Task<EventIdResponse?> SendStateEventAsync(string eventType, string stateKey, object content) => - await (await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType.UrlEncode()}/{stateKey.UrlEncode()}", content)) - .Content.ReadFromJsonAsync<EventIdResponse>(); + public async Task<EventIdResponse> SendStateEventAsync(string eventType, string stateKey, object content) => + (await (await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType.UrlEncode()}/{stateKey.UrlEncode()}", content)) + .Content.ReadFromJsonAsync<EventIdResponse>())!; public async Task<EventIdResponse> SendTimelineEventAsync(string eventType, TimelineEventContent content) { var res = await Homeserver.ClientHttpClient.PutAsJsonAsync( @@ -409,6 +418,14 @@ public class GenericRoom { return await res.Content.ReadFromJsonAsync<EventIdResponse>() ?? throw new Exception("Failed to send event"); } + public async Task<EventIdResponse> SendRawTimelineEventAsync(string eventType, JsonObject content) { + var res = await Homeserver.ClientHttpClient.PutAsJsonAsync( + $"/_matrix/client/v3/rooms/{RoomId}/send/{eventType}/" + Guid.NewGuid(), content, new JsonSerializerOptions { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }); + return await res.Content.ReadFromJsonAsync<EventIdResponse>() ?? throw new Exception("Failed to send event"); + } + public async Task<EventIdResponse> SendReactionAsync(string eventId, string key) => await SendTimelineEventAsync("m.reaction", new RoomMessageReactionEventContent() { RelatesTo = new() { @@ -461,8 +478,10 @@ public class GenericRoom { } } - public Task<StateEventResponse> GetEventAsync(string eventId) => - Homeserver.ClientHttpClient.GetFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}"); + public Task<MatrixEventResponse> GetEventAsync(string eventId, bool includeUnredactedContent = false) => + Homeserver.ClientHttpClient.GetFromJsonAsync<MatrixEventResponse>( + // .ToLower() on boolean here because this query param specifically on synapse is checked as a string rather than a boolean + $"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}?fi.mau.msc2815.include_unredacted_content={includeUnredactedContent.ToString().ToLower()}"); public async Task<EventIdResponse> RedactEventAsync(string eventToRedact, string? reason = null) { var data = new { reason }; @@ -578,7 +597,7 @@ public class GenericRoom { #endregion - public async IAsyncEnumerable<StateEventResponse> GetRelatedEventsAsync(string eventId, string? relationType = null, string? eventType = null, string? dir = "f", + public async IAsyncEnumerable<MatrixEventResponse> GetRelatedEventsAsync(string eventId, string? relationType = null, string? eventType = null, string? dir = "f", string? from = null, int? chunkLimit = 100, bool? recurse = null, string? to = null) { var path = $"/_matrix/client/v1/rooms/{RoomId}/relations/{HttpUtility.UrlEncode(eventId)}"; if (!string.IsNullOrEmpty(relationType)) path += $"/{relationType}"; @@ -593,19 +612,110 @@ public class GenericRoom { if (!string.IsNullOrEmpty(to)) uri = uri.AddQuery("to", to); // Console.WriteLine($"Getting related events from {uri}"); - var result = await Homeserver.ClientHttpClient.GetFromJsonAsync<RecursedBatchedChunkedStateEventResponse>(uri.ToString()); + var result = await Homeserver.ClientHttpClient.GetFromJsonAsync<RecursedBatchedChunkedMatrixEventResponse>(uri.ToString()); while (result!.Chunk.Count > 0) { foreach (var resp in result.Chunk) { yield return resp; } if (result.NextBatch is null) break; - result = await Homeserver.ClientHttpClient.GetFromJsonAsync<RecursedBatchedChunkedStateEventResponse>(uri.AddQuery("from", result.NextBatch).ToString()); + result = await Homeserver.ClientHttpClient.GetFromJsonAsync<RecursedBatchedChunkedMatrixEventResponse>(uri.AddQuery("from", result.NextBatch).ToString()); + } + } + + public async Task BulkSendEventsAsync(IEnumerable<MatrixEvent> events, int? forceSyncInterval = null) { + if ((await Homeserver.GetCapabilitiesAsync()).Capabilities.BulkSendEvents?.Enabled == true) { + var uri = $"/_matrix/client/unstable/gay.rory.bulk_send_events/rooms/{RoomId}/bulk_send_events?_libmatrix_txn_id={Guid.NewGuid()}"; + if (forceSyncInterval is not null) uri += $"&force_sync_interval={forceSyncInterval}"; + await Homeserver.ClientHttpClient.PostAsJsonAsync(uri, events); + } + else { + Console.WriteLine("Homeserver does not support bulk sending events, falling back to individual sends."); + foreach (var evt in events) + await ( + evt.StateKey == null + ? SendRawTimelineEventAsync(evt.Type, evt.RawContent!) + : SendStateEventAsync(evt.Type, evt.StateKey, evt.RawContent) + ); + } + } + + public async Task BulkSendEventsAsync(IAsyncEnumerable<MatrixEvent> events, int? forceSyncInterval = null) { + if ((await Homeserver.GetCapabilitiesAsync()).Capabilities.BulkSendEvents?.Enabled == true) { + var uri = $"/_matrix/client/unstable/gay.rory.bulk_send_events/rooms/{RoomId}/bulk_send_events?_libmatrix_txn_id={Guid.NewGuid()}"; + if (forceSyncInterval is not null) uri += $"&force_sync_interval={forceSyncInterval}"; + await Homeserver.ClientHttpClient.PostAsJsonAsync(uri, events); + } + else { + Console.WriteLine("Homeserver does not support bulk sending events, falling back to individual sends."); + await foreach (var evt in events) + await ( + evt.StateKey == null + ? SendRawTimelineEventAsync(evt.Type, evt.RawContent!) + : SendStateEventAsync(evt.Type, evt.StateKey, evt.RawContent) + ); } } public SpaceRoom AsSpace() => new SpaceRoom(Homeserver, RoomId); public PolicyRoom AsPolicyRoom() => new PolicyRoom(Homeserver, RoomId); + + /// <summary> + /// Unsafe: does not actually check if the room is v12, it just checks the room ID format as an estimation. + /// </summary> + public bool IsV12PlusRoomId => !RoomId.Contains(':'); + + /// <summary> + /// Gets the list of room creators for this room. + /// </summary> + /// <returns>A list of size 1 for v11 rooms and older, all creators for v12+</returns> + public async Task<List<string>> GetRoomCreatorsAsync() { + MatrixEventResponse createEvent; + if (IsV12PlusRoomId) { + createEvent = await GetEventAsync('$' + RoomId[1..]); + } + else { + createEvent = await GetStateEventAsync("m.room.create"); + } + + List<string> creators = [createEvent.Sender ?? throw new InvalidDataException("Create event has no sender")]; + + if (IsV12PlusRoomId && createEvent.TypedContent is RoomCreateEventContent { AdditionalCreators: { Count: > 0 } additionalCreators }) { + creators.AddRange(additionalCreators); + } + + return creators; + } + + public async Task<string> GetOriginHomeserverAsync() { + // pre-v12 room ID + if (RoomId.Contains(':')) { + var parts = RoomId.Split(':', 2); + if (parts.Length == 2) return parts[1]; + } + + // v12 room ID/fallback + var creators = await GetRoomCreatorsAsync(); + if (creators.Count == 0) { + throw new InvalidDataException("Room has no creators, cannot determine origin homeserver"); + } + + return creators[0].Split(':', 2)[1]; + } + + public async Task<List<string>> GetHomeserversInRoom() => (await GetMemberIdsListAsync("join")).Select(x => x.Split(':', 2)[1]).Distinct().ToList(); + + public async Task<bool> IsJoinedAsync() { + try { + var member = await GetStateOrNullAsync<RoomMemberEventContent>(RoomMemberEventContent.EventId, Homeserver.UserId); + return member?.Membership == "join"; + } + catch (MatrixException e) { + if (e.ErrorCode == "M_NOT_FOUND") return false; + if (e.ErrorCode == "M_FORBIDDEN") return false; + throw; + } + } } public class RoomIdResponse { diff --git a/LibMatrix/RoomTypes/PolicyRoom.cs b/LibMatrix/RoomTypes/PolicyRoom.cs
index c6eec63..e4fa6ae 100644 --- a/LibMatrix/RoomTypes/PolicyRoom.cs +++ b/LibMatrix/RoomTypes/PolicyRoom.cs
@@ -7,13 +7,13 @@ namespace LibMatrix.RoomTypes; public class PolicyRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : GenericRoom(homeserver, roomId) { public const string TypeName = "support.feline.policy.lists.msc.v1"; - + public static readonly FrozenSet<string> UserPolicyEventTypes = EventContent.GetMatchingEventTypes<UserPolicyRuleEventContent>().ToFrozenSet(); public static readonly FrozenSet<string> ServerPolicyEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>().ToFrozenSet(); public static readonly FrozenSet<string> RoomPolicyEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>().ToFrozenSet(); public static readonly FrozenSet<string> SpecPolicyEventTypes = [..UserPolicyEventTypes, ..ServerPolicyEventTypes, ..RoomPolicyEventTypes]; - public async IAsyncEnumerable<StateEventResponse> GetPoliciesAsync() { + public async IAsyncEnumerable<MatrixEventResponse> GetPoliciesAsync() { var fullRoomState = GetFullStateAsync(); await foreach (var eventResponse in fullRoomState) { if (SpecPolicyEventTypes.Contains(eventResponse!.Type)) { @@ -22,7 +22,7 @@ public class PolicyRoom(AuthenticatedHomeserverGeneric homeserver, string roomId } } - public async IAsyncEnumerable<StateEventResponse> GetUserPoliciesAsync() { + public async IAsyncEnumerable<MatrixEventResponse> GetUserPoliciesAsync() { var fullRoomState = GetPoliciesAsync(); await foreach (var eventResponse in fullRoomState) { if (UserPolicyEventTypes.Contains(eventResponse!.Type)) { @@ -31,7 +31,7 @@ public class PolicyRoom(AuthenticatedHomeserverGeneric homeserver, string roomId } } - public async IAsyncEnumerable<StateEventResponse> GetServerPoliciesAsync() { + public async IAsyncEnumerable<MatrixEventResponse> GetServerPoliciesAsync() { var fullRoomState = GetPoliciesAsync(); await foreach (var eventResponse in fullRoomState) { if (ServerPolicyEventTypes.Contains(eventResponse!.Type)) { @@ -40,7 +40,7 @@ public class PolicyRoom(AuthenticatedHomeserverGeneric homeserver, string roomId } } - public async IAsyncEnumerable<StateEventResponse> GetRoomPoliciesAsync() { + public async IAsyncEnumerable<MatrixEventResponse> GetRoomPoliciesAsync() { var fullRoomState = GetPoliciesAsync(); await foreach (var eventResponse in fullRoomState) { if (RoomPolicyEventTypes.Contains(eventResponse!.Type)) { diff --git a/LibMatrix/RoomTypes/SpaceRoom.cs b/LibMatrix/RoomTypes/SpaceRoom.cs
index 0c74be5..96abd77 100644 --- a/LibMatrix/RoomTypes/SpaceRoom.cs +++ b/LibMatrix/RoomTypes/SpaceRoom.cs
@@ -1,5 +1,6 @@ using ArcaneLibs.Extensions; using LibMatrix.Homeservers; +using LibMatrix.Responses; namespace LibMatrix.RoomTypes; diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs
index 36bc828..52aadd2 100644 --- a/LibMatrix/Services/HomeserverProviderService.cs +++ b/LibMatrix/Services/HomeserverProviderService.cs
@@ -2,6 +2,7 @@ using System.Net.Http.Json; using ArcaneLibs.Collections; using LibMatrix.Homeservers; using LibMatrix.Responses; +using LibMatrix.Responses.Federation; using Microsoft.Extensions.Logging; namespace LibMatrix.Services; @@ -16,7 +17,7 @@ public class HomeserverProviderService(ILogger<HomeserverProviderService> logger if (!enableClient && !enableServer) throw new ArgumentException("At least one of enableClient or enableServer must be true"); - return await AuthenticatedHomeserverCache.GetOrAdd($"{homeserver}{accessToken}{proxy}{impersonatedMxid}", async () => { + return await AuthenticatedHomeserverCache.GetOrAdd($"{homeserver}{accessToken}{proxy}{impersonatedMxid}{useGeneric}{enableClient}{enableServer}", async () => { var wellKnownUris = await hsResolver.ResolveHomeserverFromWellKnown(homeserver, enableClient, enableServer); var rhs = new RemoteHomeserver(homeserver, wellKnownUris, proxy); diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs
index 53cd2dd..ed1d2e3 100644 --- a/LibMatrix/Services/HomeserverResolverService.cs +++ b/LibMatrix/Services/HomeserverResolverService.cs
@@ -9,7 +9,9 @@ using Microsoft.Extensions.Logging.Abstractions; namespace LibMatrix.Services; public class HomeserverResolverService { - private readonly MatrixHttpClient _httpClient = new(); + private readonly MatrixHttpClient _httpClient = new() { + RetryOnNetworkError = false + }; private static readonly SemaphoreCache<WellKnownUris> WellKnownCache = new(); @@ -44,7 +46,17 @@ public class HomeserverResolverService { return res; }); } - + + private async Task<T?> GetFromJsonAsync<T>(string url) { + try { + return await _httpClient.GetFromJsonAsync<T>(url); + } + catch (Exception e) { + _logger.LogWarning(e, "Failed to get JSON from {url}", url); + return default; + } + } + private async Task<string?> _tryResolveClientEndpoint(string homeserver) { ArgumentNullException.ThrowIfNull(homeserver); _logger.LogTrace("Resolving client well-known: {homeserver}", homeserver); @@ -52,14 +64,20 @@ public class HomeserverResolverService { homeserver = homeserver.TrimEnd('/'); // check if homeserver has a client well-known if (homeserver.StartsWith("https://")) { - clientWellKnown = await _httpClient.TryGetFromJsonAsync<ClientWellKnown>($"{homeserver}/.well-known/matrix/client"); + clientWellKnown = await GetFromJsonAsync<ClientWellKnown>($"{homeserver}/.well-known/matrix/client"); + + if (clientWellKnown is null && await MatrixHttpClient.CheckSuccessStatus($"{homeserver}/_matrix/client/versions")) + return homeserver; } else if (homeserver.StartsWith("http://")) { - clientWellKnown = await _httpClient.TryGetFromJsonAsync<ClientWellKnown>($"{homeserver}/.well-known/matrix/client"); + clientWellKnown = await GetFromJsonAsync<ClientWellKnown>($"{homeserver}/.well-known/matrix/client"); + + if (clientWellKnown is null && await MatrixHttpClient.CheckSuccessStatus($"{homeserver}/_matrix/client/versions")) + return homeserver; } else { - clientWellKnown ??= await _httpClient.TryGetFromJsonAsync<ClientWellKnown>($"https://{homeserver}/.well-known/matrix/client"); - clientWellKnown ??= await _httpClient.TryGetFromJsonAsync<ClientWellKnown>($"http://{homeserver}/.well-known/matrix/client"); + clientWellKnown ??= await GetFromJsonAsync<ClientWellKnown>($"https://{homeserver}/.well-known/matrix/client"); + clientWellKnown ??= await GetFromJsonAsync<ClientWellKnown>($"http://{homeserver}/.well-known/matrix/client"); if (clientWellKnown is null) { if (await MatrixHttpClient.CheckSuccessStatus($"https://{homeserver}/_matrix/client/versions")) @@ -84,14 +102,14 @@ public class HomeserverResolverService { homeserver = homeserver.TrimEnd('/'); // check if homeserver has a server well-known if (homeserver.StartsWith("https://")) { - serverWellKnown = await _httpClient.TryGetFromJsonAsync<ServerWellKnown>($"{homeserver}/.well-known/matrix/server"); + serverWellKnown = await GetFromJsonAsync<ServerWellKnown>($"{homeserver}/.well-known/matrix/server"); } else if (homeserver.StartsWith("http://")) { - serverWellKnown = await _httpClient.TryGetFromJsonAsync<ServerWellKnown>($"{homeserver}/.well-known/matrix/server"); + serverWellKnown = await GetFromJsonAsync<ServerWellKnown>($"{homeserver}/.well-known/matrix/server"); } else { - serverWellKnown ??= await _httpClient.TryGetFromJsonAsync<ServerWellKnown>($"https://{homeserver}/.well-known/matrix/server"); - serverWellKnown ??= await _httpClient.TryGetFromJsonAsync<ServerWellKnown>($"http://{homeserver}/.well-known/matrix/server"); + serverWellKnown ??= await GetFromJsonAsync<ServerWellKnown>($"https://{homeserver}/.well-known/matrix/server"); + serverWellKnown ??= await GetFromJsonAsync<ServerWellKnown>($"http://{homeserver}/.well-known/matrix/server"); } _logger.LogInformation("Server well-known for {hs}: {json}", homeserver, serverWellKnown?.ToJson() ?? "null"); @@ -115,15 +133,6 @@ public class HomeserverResolverService { _logger.LogInformation("No server well-known for {server}...", homeserver); return null; } - - [Obsolete("Use authenticated media, available on AuthenticatedHomeserverGeneric", true)] - public async Task<string?> ResolveMediaUri(string homeserver, string mxc) { - if (homeserver is null) throw new ArgumentNullException(nameof(homeserver)); - if (mxc is null) throw new ArgumentNullException(nameof(mxc)); - if (!mxc.StartsWith("mxc://")) throw new InvalidDataException("mxc must start with mxc://"); - homeserver = (await ResolveHomeserverFromWellKnown(homeserver)).Client; - return mxc.Replace("mxc://", $"{homeserver}/_matrix/media/v3/download/"); - } public class WellKnownUris { public string? Client { get; set; } diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs
index 4c78347..c5e9d9c 100644 --- a/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs
@@ -34,17 +34,13 @@ public class WellKnownResolverService { WellKnownResolverConfiguration? config = null) { WellKnownRecords records = new(); _logger.LogDebug($"Resolving well-knowns for {homeserver}"); - if (includeClient && await _clientWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } clientResult) { - records.ClientWellKnown = clientResult; - } - - if (includeServer && await _serverWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } serverResult) { - records.ServerWellKnown = serverResult; - } - - if (includeSupport && await _supportWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } supportResult) { - records.SupportWellKnown = supportResult; - } + var clientTask = _clientWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration); + var serverTask = _serverWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration); + var supportTask = _supportWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration); + + if (includeClient && await clientTask is { } clientResult) records.ClientWellKnown = clientResult; + if (includeServer && await serverTask is { } serverResult) records.ServerWellKnown = serverResult; + if (includeSupport && await supportTask is { } supportResult) records.SupportWellKnown = supportResult; return records; } @@ -75,8 +71,10 @@ public class WellKnownResolverService { public struct WellKnownResolutionWarning { public WellKnownResolutionWarningType Type { get; set; } public string Message { get; set; } + [JsonIgnore] public Exception? Exception { get; set; } + public string? ExceptionMessage => Exception?.Message; [JsonConverter(typeof(JsonStringEnumConverter))] diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs
index f8de38d..f52b217 100644 --- a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs
@@ -14,8 +14,6 @@ public class ClientWellKnownResolver(ILogger<ClientWellKnownResolver> logger, We StoreNulls = false }; - private static readonly MatrixHttpClient HttpClient = new(); - public Task<WellKnownResolverService.WellKnownResolutionResult<ClientWellKnown>> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) { config ??= configuration; return ClientWellKnownCache.TryGetOrAdd(homeserver, async () => { diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs
index a99185c..a48d846 100644 --- a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs
@@ -1,6 +1,5 @@ using System.Text.Json.Serialization; using ArcaneLibs.Collections; -using LibMatrix.Extensions; using Microsoft.Extensions.Logging; using WellKnownType = LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ServerWellKnown; using ResultType = @@ -14,8 +13,6 @@ public class ServerWellKnownResolver(ILogger<ServerWellKnownResolver> logger, We StoreNulls = false }; - private static readonly MatrixHttpClient HttpClient = new(); - public Task<WellKnownResolverService.WellKnownResolutionResult<ServerWellKnown>> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) { config ??= configuration; return ClientWellKnownCache.TryGetOrAdd(homeserver, async () => { @@ -25,13 +22,11 @@ public class ServerWellKnownResolver(ILogger<ServerWellKnownResolver> logger, We await TryGetWellKnownFromUrl($"https://{homeserver}/.well-known/matrix/server", WellKnownResolverService.WellKnownSource.Https); if (result.Content != null) return result; - return result; }); } } - public class ServerWellKnown { [JsonPropertyName("m.server")] public string Homeserver { get; set; } diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs
index e2ac87e..861b584 100644 --- a/LibMatrix/StateEvent.cs +++ b/LibMatrix/StateEvent.cs
@@ -1,4 +1,5 @@ using System.Collections.Frozen; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; @@ -12,10 +13,10 @@ using LibMatrix.Extensions; namespace LibMatrix; -public class StateEvent { - public static FrozenSet<Type> KnownStateEventTypes { get; } = ClassCollector<EventContent>.ResolveFromAllAccessibleAssemblies().ToFrozenSet(); +public class MatrixEvent { + public static FrozenSet<Type> KnownEventTypes { get; } = ClassCollector<EventContent>.ResolveFromAllAccessibleAssemblies().ToFrozenSet(); - public static FrozenDictionary<string, Type> KnownStateEventTypesByName { get; } = KnownStateEventTypes.Aggregate( + public static FrozenDictionary<string, Type> KnownEventTypesByName { get; } = KnownEventTypes.Aggregate( new Dictionary<string, Type>(), (dict, type) => { var attrs = type.GetCustomAttributes<MatrixEventAttribute>(); @@ -28,11 +29,11 @@ public class StateEvent { return dict; }).OrderBy(x => x.Key).ToFrozenDictionary(); - public static Type GetStateEventType(string? type) => - string.IsNullOrWhiteSpace(type) ? typeof(UnknownEventContent) : KnownStateEventTypesByName.GetValueOrDefault(type) ?? typeof(UnknownEventContent); + public static Type GetEventType(string? type) => + string.IsNullOrWhiteSpace(type) ? typeof(UnknownEventContent) : KnownEventTypesByName.GetValueOrDefault(type) ?? typeof(UnknownEventContent); [JsonIgnore] - public Type MappedType => GetStateEventType(Type); + public Type MappedType => GetEventType(Type); [JsonIgnore] public bool IsLegacyType => MappedType.GetCustomAttributes<MatrixEventAttribute>().FirstOrDefault(x => x.EventName == Type)?.Legacy ?? false; @@ -57,7 +58,7 @@ public class StateEvent { public EventContent? TypedContent { get { try { - var mappedType = GetStateEventType(Type); + var mappedType = GetEventType(Type); if (mappedType == typeof(UnknownEventContent)) Console.WriteLine($"Warning: unknown event type '{Type}'"); var deserialisedContent = (EventContent)RawContent.Deserialize(mappedType, TypedContentSerializerOptions)!; @@ -120,14 +121,29 @@ public class StateEvent { [JsonIgnore] public string InternalContentTypeName => TypedContent?.GetType().Name ?? "null"; - public static bool TypeKeyPairMatches(StateEventResponse x, StateEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey; - public static bool Equals(StateEventResponse x, StateEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey && x.RawContent.Equals(y.RawContent); + public static bool TypeKeyPairMatches(MatrixEventResponse x, MatrixEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey; + public static bool Equals(MatrixEventResponse x, MatrixEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey && x.EventId == y.EventId; + + /// <summary> + /// Compares two state events for deep equality, including type, state key, and raw content. + /// If you trust the server, use Equals instead, as that compares by event ID instead of raw content. + /// </summary> + /// <param name="x"></param> + /// <param name="y"></param> + /// <returns></returns> + public static bool DeepEquals(MatrixEventResponse x, MatrixEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey && JsonNode.DeepEquals(x.RawContent, y.RawContent); } -public class StateEventResponse : StateEvent { +public class MatrixEventResponse : MatrixEvent { [JsonPropertyName("origin_server_ts")] public long? OriginServerTs { get; set; } + [JsonIgnore] + public DateTime? OriginServerTimestamp { + get => OriginServerTs.HasValue ? DateTimeOffset.FromUnixTimeMilliseconds(OriginServerTs.Value).UtcDateTime : DateTime.MinValue; + set => OriginServerTs = value is null ? null : new DateTimeOffset(value.Value).ToUnixTimeMilliseconds(); + } + [JsonPropertyName("room_id")] public string? RoomId { get; set; } @@ -162,26 +178,27 @@ public class StateEventResponse : StateEvent { } [JsonSourceGenerationOptions(WriteIndented = true)] -[JsonSerializable(typeof(ChunkedStateEventResponse))] -internal partial class ChunkedStateEventResponseSerializerContext : JsonSerializerContext; +[JsonSerializable(typeof(ChunkedMatrixEventResponse))] +internal partial class ChunkedMatrixEventResponseSerializerContext : JsonSerializerContext; +[DebuggerDisplay("{Events.Count} events")] public class EventList { public EventList() { } - public EventList(List<StateEventResponse>? events) { + public EventList(List<MatrixEventResponse>? events) { Events = events; } [JsonPropertyName("events")] - public List<StateEventResponse>? Events { get; set; } = new(); + public List<MatrixEventResponse>? Events { get; set; } = new(); } -public class ChunkedStateEventResponse { +public class ChunkedMatrixEventResponse { [JsonPropertyName("chunk")] - public List<StateEventResponse>? Chunk { get; set; } = new(); + public List<MatrixEventResponse>? Chunk { get; set; } = new(); } -public class PaginatedChunkedStateEventResponse : ChunkedStateEventResponse { +public class PaginatedChunkedMatrixEventResponse : ChunkedMatrixEventResponse { [JsonPropertyName("start")] public string? Start { get; set; } @@ -189,7 +206,7 @@ public class PaginatedChunkedStateEventResponse : ChunkedStateEventResponse { public string? End { get; set; } } -public class BatchedChunkedStateEventResponse : ChunkedStateEventResponse { +public class BatchedChunkedMatrixEventResponse : ChunkedMatrixEventResponse { [JsonPropertyName("next_batch")] public string? NextBatch { get; set; } @@ -197,7 +214,7 @@ public class BatchedChunkedStateEventResponse : ChunkedStateEventResponse { public string? PrevBatch { get; set; } } -public class RecursedBatchedChunkedStateEventResponse : BatchedChunkedStateEventResponse { +public class RecursedBatchedChunkedMatrixEventResponse : BatchedChunkedMatrixEventResponse { [JsonPropertyName("recursion_depth")] public int? RecursionDepth { get; set; } } @@ -218,7 +235,7 @@ public class StateEventContentPolymorphicTypeInfoResolver : DefaultJsonTypeInfoR IgnoreUnrecognizedTypeDiscriminators = true, UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType, - DerivedTypes = StateEvent.KnownStateEventTypesByName.Select(x => new JsonDerivedType(x.Value, x.Key)).ToList() + DerivedTypes = MatrixEvent.KnownEventTypesByName.Select(x => new JsonDerivedType(x.Value, x.Key)).ToList() // DerivedTypes = new ClassCollector<EventContent>() // .ResolveFromAllAccessibleAssemblies() diff --git a/LibMatrix/MxcUri.cs b/LibMatrix/StructuredData/MxcUri.cs
index 875ae53..82a9677 100644 --- a/LibMatrix/MxcUri.cs +++ b/LibMatrix/StructuredData/MxcUri.cs
@@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace LibMatrix; +namespace LibMatrix.StructuredData; public class MxcUri { public required string ServerName { get; set; } diff --git a/LibMatrix/StructuredData/UserId.cs b/LibMatrix/StructuredData/UserId.cs new file mode 100644
index 0000000..02b2e91 --- /dev/null +++ b/LibMatrix/StructuredData/UserId.cs
@@ -0,0 +1,27 @@ +namespace LibMatrix.StructuredData; + +public class UserId { + public required string ServerName { get; set; } + public required string LocalPart { get; set; } + + public static UserId Parse(string mxid) { + if (!mxid.StartsWith('@')) throw new ArgumentException("Matrix User IDs must start with '@'", nameof(mxid)); + var parts = mxid.Split(':', 2); + if (parts.Length != 2) throw new ArgumentException($"Invalid MXID '{mxid}' passed! MXIDs must exist of only 2 parts!", nameof(mxid)); + return new UserId { + LocalPart = parts[0][1..], + ServerName = parts[1] + }; + } + + public static implicit operator UserId(string mxid) => Parse(mxid); + public static implicit operator string(UserId mxid) => $"@{mxid.LocalPart}:{mxid.ServerName}"; + public static implicit operator (string, string)(UserId mxid) => (mxid.LocalPart, mxid.ServerName); + public static implicit operator UserId((string localPart, string serverName) mxid) => (mxid.localPart, mxid.serverName); + // public override string ToString() => $"mxc://{ServerName}/{MediaId}"; + + public void Deconstruct(out string serverName, out string localPart) { + serverName = ServerName; + localPart = LocalPart; + } +} \ No newline at end of file diff --git a/LibMatrix/deps.json b/LibMatrix/deps.json new file mode 100644
index 0000000..d6ba81b --- /dev/null +++ b/LibMatrix/deps.json
@@ -0,0 +1,12 @@ +[ + { + "pname": "Microsoft.Extensions.DependencyInjection.Abstractions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-1nh8z2nglCizQkl0iWwJ/au4BAuuBu0xghKHGBeTM1I=" + }, + { + "pname": "Microsoft.Extensions.Logging.Abstractions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-krml7WL+lF7oiYOvQ8NHQp7BVpHJrLIHhyxUgkHO+WE=" + } +] diff --git a/README.MD b/README.MD
index 31915d4..85a8137 100644 --- a/README.MD +++ b/README.MD
@@ -11,31 +11,4 @@ Probably add as a submodule for now? NuGet packaging still has to be implemented # Contributing -Any contribution is welcome, even if it's just documentation or recommended git practices! We're not too strict on code style, but we do have a few guidelines: -- Use spaces, not tabs -- Use 4 spaces for indentation -- Use the C# naming convention for variables, methods, etc. -- Wrap lines at 160 characters, though this value can be changed if it's too lean or strict -- Use the `#region` and `#endregion` directives to group code if you're adding utility functions - -```sh -# Prepare patch set -mkdir patches -git format-patch --output-directory "./patches" @{u}.. - -# Send patches -... -``` -You can send the patches to [@emma:rory.gay](https://matrix.to/#/@emma:rory.gay) or in the [Rory&::LibMatrix room](https://matrix.to/#/#libmatrix:rory.gay). - -### Developer utility commands - -Error reporting upon file save (may not work): -```sh -inotifywait -rmqe CLOSE_WRITE --include '.*\.cs$' . | while read l; do clear; dotnet build --property WarningLevel=0; done -``` - -Hot rebuild on file save: -```sh -dotnet watch run --no-hot-reload --property WarningLevel=0 -``` +See the [contributing guidelines](CONTRIBUTING.md) for more information. \ No newline at end of file diff --git a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
index 6878b44..20be560 100644 --- a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs +++ b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
@@ -10,15 +10,15 @@ namespace LibMatrix.Tests.Abstractions; public class HomeserverAbstraction(HomeserverProviderService _hsProvider, Config _config, ILogger<HomeserverAbstraction> _logger) { // private static readonly HomeserverResolverService _hsResolver = new HomeserverResolverService(NullLogger<HomeserverResolverService>.Instance); // private static readonly HomeserverProviderService _hsProvider = new HomeserverProviderService(NullLogger<HomeserverProviderService>.Instance, _hsResolver); - + private static AuthenticatedHomeserverGeneric? ConfiguredHomeserver { get; set; } private static readonly SemaphoreSlim _lock = new(1, 1); - + public async Task<AuthenticatedHomeserverGeneric> GetConfiguredHomeserver(ITestOutputHelper? testOutputHelper = null) { Assert.False(string.IsNullOrWhiteSpace(_config.TestHomeserver)); Assert.False(string.IsNullOrWhiteSpace(_config.TestUsername)); Assert.False(string.IsNullOrWhiteSpace(_config.TestPassword)); - + _logger.LogDebug("Using homeserver '{0}' with login '{1}' '{2}", _config.TestHomeserver, _config.TestUsername, _config.TestPassword); testOutputHelper?.WriteLine($"Using homeserver '{_config.TestHomeserver}' with login '{_config.TestUsername}' '{_config.TestPassword}'"); @@ -29,7 +29,7 @@ public class HomeserverAbstraction(HomeserverProviderService _hsProvider, Config } var rhs = await _hsProvider.GetRemoteHomeserver(_config.TestHomeserver); - + LoginResponse reg; try { reg = await rhs.LoginAsync(_config.TestUsername, _config.TestPassword); @@ -53,27 +53,27 @@ public class HomeserverAbstraction(HomeserverProviderService _hsProvider, Config Assert.False(string.IsNullOrWhiteSpace(_config.TestHomeserver)); var username = Guid.NewGuid().ToString(); var password = Guid.NewGuid().ToString(); - + _logger.LogDebug("Creating new homeserver '{0}' with login '{1}' '{2}'", _config.TestHomeserver, username, password); - + var rhs = await _hsProvider.GetRemoteHomeserver(_config.TestHomeserver); var reg = await rhs.RegisterAsync(username, password, "Unit tests!"); var hs = await _hsProvider.GetAuthenticatedWithToken(reg.Homeserver, reg.AccessToken); - + return hs; } public async IAsyncEnumerable<AuthenticatedHomeserverGeneric> GetNewHomeservers(int count = 1) { var createRandomUserTasks = Enumerable .Range(0, count) - .Select(_ => GetNewHomeserver()).ToAsyncEnumerable(); + .Select(_ => GetNewHomeserver()).ToAsyncResultEnumerable(); await foreach (var hs in createRandomUserTasks) yield return hs; } public async Task<(string username, string password, string token)> GetKnownCredentials() { Assert.False(string.IsNullOrWhiteSpace(_config.TestHomeserver)); var rhs = await _hsProvider.GetRemoteHomeserver(_config.TestHomeserver); - + var username = _config.TestUsername; var password = _config.TestPassword; var reg = await rhs.RegisterAsync(username, password, "Unit tests!"); diff --git a/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
index b1176ca..7c74d9f 100644 --- a/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs +++ b/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
@@ -14,29 +14,29 @@ public static class RoomAbstraction { // Visibility = CreateRoomVisibility.Public, RoomAliasName = Guid.NewGuid().ToString() }; - crq.InitialState ??= new List<StateEvent>(); - crq.InitialState.Add(new StateEvent() { + crq.InitialState ??= new List<MatrixEvent>(); + crq.InitialState.Add(new MatrixEvent() { Type = RoomTopicEventContent.EventId, StateKey = "", TypedContent = new RoomTopicEventContent() { Topic = "LibMatrix Test Room " + DateTime.Now.ToString("O") } }); - crq.InitialState.Add(new StateEvent() { + crq.InitialState.Add(new MatrixEvent() { Type = RoomNameEventContent.EventId, StateKey = "", TypedContent = new RoomNameEventContent() { Name = "LibMatrix Test Room " + DateTime.Now.ToString("O") } }); - crq.InitialState.Add(new StateEvent() { + crq.InitialState.Add(new MatrixEvent() { Type = RoomAvatarEventContent.EventId, StateKey = "", TypedContent = new RoomAvatarEventContent() { Url = "mxc://conduit.rory.gay/r9KiT0f9eQbv8pv4RxwBZFuzhfKjGWHx" } }); - crq.InitialState.Add(new StateEvent() { + crq.InitialState.Add(new MatrixEvent() { Type = RoomAliasEventContent.EventId, StateKey = "", TypedContent = new RoomAliasEventContent() { @@ -60,7 +60,7 @@ public static class RoomAbstraction { Name = $"LibMatrix Test Space ({roomCount} children)", // Visibility = CreateRoomVisibility.Public, RoomAliasName = Guid.NewGuid().ToString(), - InitialState = new List<StateEvent>() + InitialState = new List<MatrixEvent>() }; crq.CreationContentBaseType.Type = "m.space"; @@ -69,10 +69,10 @@ public static class RoomAbstraction { Name = $"LibMatrix Test Room {Guid.NewGuid()}", // Visibility = CreateRoomVisibility.Public, RoomAliasName = Guid.NewGuid().ToString() - })).ToAsyncEnumerable(); + })).ToAsyncResultEnumerable(); await foreach (var room in createRoomTasks) - crq.InitialState.Add(new StateEvent { + crq.InitialState.Add(new MatrixEvent { Type = "m.space.child", StateKey = room.RoomId, TypedContent = new SpaceChildEventContent() { @@ -85,7 +85,7 @@ public static class RoomAbstraction { if (addSpaces) for (var i = 0; i < roomCount; i++) { var space = await GetTestSpace(hs, roomCount - spaceSizeReduction, true, spaceSizeReduction); - crq.InitialState.Add(new StateEvent { + crq.InitialState.Add(new MatrixEvent { Type = "m.space.child", StateKey = space.RoomId, TypedContent = new SpaceChildEventContent() { diff --git a/Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs b/Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs
index e1da3d5..2a25056 100644 --- a/Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs +++ b/Tests/LibMatrix.Tests/DataTests/WhoAmITests.cs
@@ -1,3 +1,5 @@ +using LibMatrix.Responses; + namespace LibMatrix.Tests.DataTests; public static class WhoAmITests { diff --git a/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj b/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj
index 98c8101..234b978 100644 --- a/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj +++ b/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj
@@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> @@ -10,20 +10,20 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" /> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-rc.2.25502.107"/> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> - <PackageReference Include="xunit" Version="2.9.3" /> - <PackageReference Include="Xunit.Microsoft.DependencyInjection" Version="9.0.0" /> - <PackageReference Include="xunit.runner.visualstudio" Version="3.0.1"> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0"/> + <PackageReference Include="xunit" Version="2.9.3"/> + <PackageReference Include="Xunit.Microsoft.DependencyInjection" Version="9.2.2"/> + <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> - <PackageReference Include="coverlet.collector" Version="6.0.3"> + <PackageReference Include="coverlet.collector" Version="6.0.4"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> - <PackageReference Include="Xunit.SkippableFact" Version="1.5.23" /> + <PackageReference Include="Xunit.SkippableFact" Version="1.5.23"/> </ItemGroup> <ItemGroup> diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs
index 8419518..1b2271e 100644 --- a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs +++ b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs
@@ -14,11 +14,11 @@ public class RoomMembershipTests : TestBed<TestFixture> { public RoomMembershipTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) { _hsAbstraction = _fixture.GetService<HomeserverAbstraction>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverAbstraction)}"); } - + [Fact] public async Task GetMembersAsync() { - Assert.True(StateEvent.KnownStateEventTypes is { Count: > 0 }, "StateEvent.KnownStateEventTypes is empty!"); - Assert.True(StateEvent.KnownStateEventTypesByName is { Count: > 0 }, "StateEvent.KnownStateEventTypesByName is empty!"); + Assert.True(MatrixEvent.KnownEventTypes is { Count: > 0 }, "MatrixEvent.KnownEventTypes is empty!"); + Assert.True(MatrixEvent.KnownEventTypesByName is { Count: > 0 }, "MatrixEvent.KnownEventTypesByName is empty!"); var hs = await _hsAbstraction.GetConfiguredHomeserver(); var room = await RoomAbstraction.GetTestRoom(hs); @@ -44,7 +44,7 @@ public class RoomMembershipTests : TestBed<TestFixture> { } Assert.True(hitMembers, "No members were found in the room"); - + await room.LeaveAsync(); } @@ -62,7 +62,7 @@ public class RoomMembershipTests : TestBed<TestFixture> { Assert.NotNull(id); Assert.NotNull(id.RoomId); Assert.NotEmpty(id.RoomId); - + await room.LeaveAsync(); } @@ -95,7 +95,7 @@ public class RoomMembershipTests : TestBed<TestFixture> { Assert.NotNull(banState); Assert.Equal("leave", banState.Membership); Assert.Equal("test", banState.Reason); - + await room.LeaveAsync(); } @@ -109,7 +109,7 @@ public class RoomMembershipTests : TestBed<TestFixture> { var banState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId); Assert.NotNull(banState); Assert.Equal("ban", banState.Membership); - + await room.LeaveAsync(); } @@ -124,12 +124,12 @@ public class RoomMembershipTests : TestBed<TestFixture> { Assert.NotNull(banState); Assert.Equal("ban", banState.Membership); await room.UnbanAsync(hs2.UserId, "testing"); - + var unbanState = await room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs2.UserId); Assert.NotNull(unbanState); Assert.Equal("leave", unbanState.Membership); Assert.Equal("testing", unbanState.Reason); - + await room.LeaveAsync(); } @@ -157,17 +157,16 @@ public class RoomMembershipTests : TestBed<TestFixture> { tasks.Add(otherUser, room.InviteUserAsync(otherUser.UserId, "Unit test!")); } - await foreach (var otherUser in tasks.ToAsyncEnumerable()) { + await foreach (var otherUser in tasks.ToAsyncResultEnumerable()) { _testOutputHelper.WriteLine($"Joining {otherUser.UserId} to {room.RoomId}"); await otherUser.GetRoom(room.RoomId).JoinAsync(reason: "Unit test!"); } var states = await room.GetMembersListAsync(); Assert.Equal(count + 1, states.Count); - + await room.LeaveAsync(); - await foreach (var authenticatedHomeserverGeneric in otherUsers) - { + await foreach (var authenticatedHomeserverGeneric in otherUsers) { await authenticatedHomeserverGeneric.GetRoom(room.RoomId).LeaveAsync(); } } diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs
index 7801ed0..817eca9 100644 --- a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs +++ b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs
@@ -35,8 +35,8 @@ public class RoomTests : TestBed<TestFixture> { [Fact] public async Task GetMembersAsync() { - Assert.True(StateEvent.KnownStateEventTypes is { Count: > 0 }, "StateEvent.KnownStateEventTypes is empty!"); - Assert.True(StateEvent.KnownStateEventTypesByName is { Count: > 0 }, "StateEvent.KnownStateEventTypesByName is empty!"); + Assert.True(MatrixEvent.KnownEventTypes is { Count: > 0 }, "MatrixEvent.KnownEventTypes is empty!"); + Assert.True(MatrixEvent.KnownEventTypesByName is { Count: > 0 }, "MatrixEvent.KnownEventTypesByName is empty!"); var hs = await _hsAbstraction.GetConfiguredHomeserver(); var room = await RoomAbstraction.GetTestRoom(hs); @@ -240,7 +240,7 @@ public class RoomTests : TestBed<TestFixture> { }); await room.LeaveAsync(); - + await File.WriteAllTextAsync("test.json", messages.ToJson()); } @@ -268,7 +268,7 @@ public class RoomTests : TestBed<TestFixture> { await room.LeaveAsync(); } - + [Fact] public async Task SendMessageEventAsync() { var hs = await _hsAbstraction.GetConfiguredHomeserver(); @@ -276,13 +276,14 @@ public class RoomTests : TestBed<TestFixture> { Assert.NotNull(room); var res = await room.SendMessageEventAsync(new RoomMessageEventContent(body: "This test was written by Emma [it/its], member of the Rory& system." + - "\nIf you are reading this on matrix, it means the unit test for sending a message works!", messageType: "m.text")); + "\nIf you are reading this on matrix, it means the unit test for sending a message works!", + messageType: "m.text")); Assert.NotNull(res); Assert.NotNull(res.EventId); await room.LeaveAsync(); } - + [Fact] public async Task InviteUsersAsync() { var hs = await _hsAbstraction.GetConfiguredHomeserver(); @@ -298,7 +299,7 @@ public class RoomTests : TestBed<TestFixture> { Assert.NotNull(u.UserId); Assert.NotEmpty(u.UserId); }); - + await room.InviteUsersAsync(users.Select(u => u.UserId)); var members = await room.GetMembersListAsync(); Assert.NotNull(members); @@ -310,7 +311,7 @@ public class RoomTests : TestBed<TestFixture> { Assert.NotEmpty(m.StateKey); }); Assert.All(users, u => Assert.Contains(u.UserId, members.Select(m => m.StateKey))); - + await room.LeaveAsync(); } } \ No newline at end of file diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs
index be8076a..d59920d 100644 --- a/Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs +++ b/Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs
@@ -52,14 +52,14 @@ public class SpaceTests : TestBed<TestFixture> { }); await space.AddChildByIdAsync(child.RoomId); - + //validate children var children = space.GetChildrenAsync().ToBlockingEnumerable().ToList(); Assert.NotNull(children); Assert.NotEmpty(children); Assert.Single(children, x => x.RoomId == child.RoomId); } - + [Fact] public async Task GetChildrenAsync() { var hs = await _hsAbstraction.GetConfiguredHomeserver(); @@ -68,11 +68,11 @@ public class SpaceTests : TestBed<TestFixture> { Name = "Test child" }); return room; - }).ToAsyncEnumerable().ToBlockingEnumerable().ToList(); - + }).ToAsyncResultEnumerable().ToBlockingEnumerable().ToList(); + var crq = new CreateRoomRequest() { Name = "Test space", - InitialState = expectedChildren.Select(c => new StateEvent() { + InitialState = expectedChildren.Select(c => new MatrixEvent() { Type = "m.space.child", StateKey = c.RoomId, TypedContent = new SpaceChildEventContent() { @@ -89,8 +89,7 @@ public class SpaceTests : TestBed<TestFixture> { Assert.NotNull(children); Assert.NotEmpty(children); Assert.Equal(expectedChildren.Count, children.Count); - foreach (var expectedChild in expectedChildren) - { + foreach (var expectedChild in expectedChildren) { Assert.Single(children, x => x.RoomId == expectedChild.RoomId); } } diff --git a/Utilities/LibMatrix.DebugDataValidationApi/LibMatrix.DebugDataValidationApi.csproj b/Utilities/LibMatrix.DebugDataValidationApi/LibMatrix.DebugDataValidationApi.csproj
index ee4fd58..c077e4e 100644 --- a/Utilities/LibMatrix.DebugDataValidationApi/LibMatrix.DebugDataValidationApi.csproj +++ b/Utilities/LibMatrix.DebugDataValidationApi/LibMatrix.DebugDataValidationApi.csproj
@@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <InvariantGlobalization>true</InvariantGlobalization> @@ -9,8 +9,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.1" /> - <PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" /> + <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-rc.2.25502.107"/> + <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6"/> </ItemGroup> <ItemGroup> diff --git a/Utilities/LibMatrix.DevTestBot/Bot/DevTestBot.cs b/Utilities/LibMatrix.DevTestBot/Bot/DevTestBot.cs
index 3bb0c25..da4522e 100644 --- a/Utilities/LibMatrix.DevTestBot/Bot/DevTestBot.cs +++ b/Utilities/LibMatrix.DevTestBot/Bot/DevTestBot.cs
@@ -48,7 +48,7 @@ public class DevTestBot : IHostedService { // foreach (var room in await hs.GetJoinedRooms()) { // if(room.RoomId is "!OGEhHVWSdvArJzumhm:matrix.org") continue; - // foreach (var stateEvent in await room.GetStateAsync<List<StateEvent>>("")) { + // foreach (var stateEvent in await room.GetStateAsync<List<MatrixEvent>>("")) { // var _ = stateEvent.GetType; // } // _logger.LogInformation($"Got room state for {room.RoomId}!"); diff --git a/Utilities/LibMatrix.DevTestBot/Bot/PingTestBot.cs b/Utilities/LibMatrix.DevTestBot/Bot/PingTestBot.cs
index e992e3c..345d755 100644 --- a/Utilities/LibMatrix.DevTestBot/Bot/PingTestBot.cs +++ b/Utilities/LibMatrix.DevTestBot/Bot/PingTestBot.cs
@@ -63,7 +63,7 @@ public class PingTestBot : IHostedService { // foreach (var room in await hs.GetJoinedRooms()) { // if(room.RoomId is "!OGEhHVWSdvArJzumhm:matrix.org") continue; - // foreach (var stateEvent in await room.GetStateAsync<List<StateEvent>>("")) { + // foreach (var stateEvent in await room.GetStateAsync<List<MatrixEvent>>("")) { // var _ = stateEvent.GetType; // } // _logger.LogInformation($"Got room state for {room.RoomId}!"); diff --git a/Utilities/LibMatrix.DevTestBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs b/Utilities/LibMatrix.DevTestBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs
index fdc7717..1339f23 100644 --- a/Utilities/LibMatrix.DevTestBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs +++ b/Utilities/LibMatrix.DevTestBot/Bot/StartupTasks/ServerRoomSizeCalulator.cs
@@ -42,7 +42,7 @@ public class ServerRoomSizeCalulator : IHostedService { var roomSize = stateList.Count; if (roomSize > 10000) await File.AppendAllLinesAsync("large_rooms.txt", new[] { $"{{ \"{room.RoomId}\", {roomSize} }}," }, cancellationToken); - var roomHs = room.RoomId.Split(":")[1]; + var roomHs = await room.GetOriginHomeserverAsync(); if (totalRoomSize.ContainsKey(roomHs)) totalRoomSize[roomHs] += roomSize; else diff --git a/Utilities/LibMatrix.DevTestBot/LibMatrix.DevTestBot.csproj b/Utilities/LibMatrix.DevTestBot/LibMatrix.DevTestBot.csproj
index acff3e2..9b0e94e 100644 --- a/Utilities/LibMatrix.DevTestBot/LibMatrix.DevTestBot.csproj +++ b/Utilities/LibMatrix.DevTestBot/LibMatrix.DevTestBot.csproj
@@ -1,36 +1,36 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <OutputType>Exe</OutputType> - <TargetFramework>net9.0</TargetFramework> - <LangVersion>preview</LangVersion> - <ImplicitUsings>enable</ImplicitUsings> - <Nullable>enable</Nullable> - <PublishAot>false</PublishAot> - <InvariantGlobalization>true</InvariantGlobalization> - <RootNamespace>LibMatrix.ExampleBot</RootNamespace> - <!-- <PublishTrimmed>true</PublishTrimmed>--> - <!-- <PublishReadyToRun>true</PublishReadyToRun>--> - <!-- <PublishSingleFile>true</PublishSingleFile>--> - <!-- <PublishReadyToRunShowWarnings>true</PublishReadyToRunShowWarnings>--> - <!-- <PublishTrimmedShowLinkerSizeComparison>true</PublishTrimmedShowLinkerSizeComparison>--> - <!-- <PublishTrimmedShowLinkerSizeComparisonWarnings>true</PublishTrimmedShowLinkerSizeComparisonWarnings>--> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="ArcaneLibs.StringNormalisation" Version="1.0.0-preview.20250419-174711" Condition="'$(Configuration)' == 'Release'" /> - <ProjectReference Include="..\..\ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj" Condition="'$(Configuration)' == 'Debug'"/> - <ProjectReference Include="..\..\LibMatrix\LibMatrix.csproj"/> - <ProjectReference Include="..\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj" /> - </ItemGroup> - - <ItemGroup> - <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1"/> - </ItemGroup> - - <ItemGroup> - <Content Include="appsettings*.json"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </Content> - </ItemGroup> -</Project> +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net10.0</TargetFramework> + <LangVersion>preview</LangVersion> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <PublishAot>false</PublishAot> + <InvariantGlobalization>true</InvariantGlobalization> + <RootNamespace>LibMatrix.ExampleBot</RootNamespace> + <!-- <PublishTrimmed>true</PublishTrimmed>--> + <!-- <PublishReadyToRun>true</PublishReadyToRun>--> + <!-- <PublishSingleFile>true</PublishSingleFile>--> + <!-- <PublishReadyToRunShowWarnings>true</PublishReadyToRunShowWarnings>--> + <!-- <PublishTrimmedShowLinkerSizeComparison>true</PublishTrimmedShowLinkerSizeComparison>--> + <!-- <PublishTrimmedShowLinkerSizeComparisonWarnings>true</PublishTrimmedShowLinkerSizeComparisonWarnings>--> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="ArcaneLibs.StringNormalisation" Version="1.0.0-preview.20251005-232225" Condition="'$(Configuration)' == 'Release'"/> + <ProjectReference Include="..\..\ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj" Condition="'$(Configuration)' == 'Debug'"/> + <ProjectReference Include="..\..\LibMatrix\LibMatrix.csproj"/> + <ProjectReference Include="..\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj"/> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-rc.2.25502.107"/> + </ItemGroup> + + <ItemGroup> + <Content Include="appsettings*.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + </ItemGroup> +</Project> diff --git a/Utilities/LibMatrix.E2eeTestKit/LibMatrix.E2eeTestKit.csproj b/Utilities/LibMatrix.E2eeTestKit/LibMatrix.E2eeTestKit.csproj
index 78cdb67..65d9242 100644 --- a/Utilities/LibMatrix.E2eeTestKit/LibMatrix.E2eeTestKit.csproj +++ b/Utilities/LibMatrix.E2eeTestKit/LibMatrix.E2eeTestKit.csproj
@@ -1,23 +1,23 @@ -<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> - - <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> - <Nullable>enable</Nullable> - <ImplicitUsings>enable</ImplicitUsings> - <ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" /> - <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.1" PrivateAssets="all" /> - </ItemGroup> - - <ItemGroup> - <ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\..\LibMatrix\LibMatrix.csproj" /> - </ItemGroup> - -</Project> +<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> + + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>enable</ImplicitUsings> + <ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0-rc.2.25502.107"/> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.0-rc.2.25502.107" PrivateAssets="all"/> + </ItemGroup> + + <ItemGroup> + <ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js"/> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\LibMatrix\LibMatrix.csproj"/> + </ItemGroup> + +</Project> diff --git a/Utilities/LibMatrix.FederationTest/.gitignore b/Utilities/LibMatrix.FederationTest/.gitignore new file mode 100644
index 0000000..4a96773 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/.gitignore
@@ -0,0 +1 @@ +.keys/ diff --git a/Utilities/LibMatrix.FederationTest/Controllers/RemoteServerPingController.cs b/Utilities/LibMatrix.FederationTest/Controllers/RemoteServerPingController.cs new file mode 100644
index 0000000..8d3a5ea --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/RemoteServerPingController.cs
@@ -0,0 +1,79 @@ +using LibMatrix.Federation; +using LibMatrix.Federation.Extensions; +using LibMatrix.FederationTest.Services; +using LibMatrix.FederationTest.Utilities; +using LibMatrix.Services; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers; + +[ApiController] +public class RemoteServerPingController(FederationTestConfiguration config, FederationKeyStore keyStore, HomeserverResolverService hsResolver) : ControllerBase { + [HttpGet] + [Route("/ping/{serverName}")] + public async Task<object> PingRemoteServer(string serverName) { + Dictionary<string, object> responseMessage = []; + var hsResolveResult = await hsResolver.ResolveHomeserverFromWellKnown(serverName, enableClient: false); + responseMessage["resolveResult"] = hsResolveResult; + + if (!string.IsNullOrWhiteSpace(hsResolveResult.Server)) { + try { + var ownKey = keyStore.GetCurrentSigningKey(); + var hs = new AuthenticatedFederationClient(hsResolveResult.Server, new() { + PrivateKey = , + OriginServerName = null + }); + var keys = await hs.GetServerKeysAsync(); + responseMessage["version"] = await hs.GetServerVersionAsync(); + responseMessage["keys"] = keys; + + responseMessage["keysAreValid"] = keys.SignaturesById[serverName].ToDictionary( + sig => (string)sig.Key, + sig => keys.ValidateSignature(serverName, sig.Key, Ed25519Utils.LoadPublicKeyFromEncoded(keys.TypedContent.VerifyKeysById[sig.Key].Key)) + ); + } + catch (Exception ex) { + responseMessage["error"] = new { + error = "Failed to connect to remote server", + message = ex.Message, + st = ex.StackTrace, + }; + return responseMessage; + } + } + + return responseMessage; + } + + [HttpPost] + [Route("/ping/")] + public async IAsyncEnumerable<KeyValuePair<string, object>> PingRemoteServers([FromBody] List<string>? serverNames) { + Dictionary<string, object> responseMessage = []; + + if (serverNames == null || !serverNames.Any()) { + responseMessage["error"] = "No server names provided"; + yield return responseMessage.First(); + yield break; + } + + var results = serverNames!.Select(s => (s, PingRemoteServer(s))).ToList(); + foreach (var result in results) { + var (serverName, pingResult) = result; + try { + responseMessage[serverName] = await pingResult; + if (results.Where(x => !x.Item2.IsCompleted).Select(x => x.s).ToList() is { } servers and not { Count: 0 }) + Console.WriteLine($"INFO | Waiting for servers: {string.Join(", ", servers)}"); + } + catch (Exception ex) { + responseMessage[serverName] = new { + error = "Failed to ping remote server", + message = ex.Message, + st = ex.StackTrace, + }; + } + + yield return new KeyValuePair<string, object>(serverName, responseMessage[serverName]); + // await Response.Body.FlushAsync(); + } + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs new file mode 100644
index 0000000..6516415 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationKeysController.cs
@@ -0,0 +1,42 @@ +using LibMatrix.Abstractions; +using LibMatrix.Federation.Extensions; +using LibMatrix.FederationTest.Services; +using LibMatrix.Homeservers; +using LibMatrix.Responses.Federation; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers.Spec; + +[ApiController] +[Route("_matrix/key/v2/")] +public class FederationKeysController(FederationTestConfiguration config, FederationKeyStore keyStore) { + static FederationKeysController() { + Console.WriteLine("INFO | FederationKeysController initialized."); + } + + private static SignedObject<ServerKeysResponse>? _cachedServerKeysResponse; + private static SemaphoreSlim _serverKeyCacheLock = new SemaphoreSlim(1, 1); + + [HttpGet("server")] + public async Task<SignedObject<ServerKeysResponse>> GetServerKeys() { + await _serverKeyCacheLock.WaitAsync(); + if (_cachedServerKeysResponse == null || _cachedServerKeysResponse.TypedContent.ValidUntil < DateTime.Now + TimeSpan.FromSeconds(30)) { + var keys = keyStore.GetCurrentSigningKey(); + _cachedServerKeysResponse = new ServerKeysResponse() { + ValidUntil = DateTime.Now + TimeSpan.FromMinutes(1), + ServerName = config.ServerName, + OldVerifyKeys = [], + VerifyKeysById = new() { + { + new() { Algorithm = "ed25519", KeyId = "0" }, new ServerKeysResponse.CurrentVerifyKey() { + Key = keys.publicKey.ToUnpaddedBase64(), + } + } + } + }.Sign(config.ServerName, new VersionedKeyId() { Algorithm = "ed25519", KeyId = "0" }, keys.privateKey); + } + _serverKeyCacheLock.Release(); + + return _cachedServerKeysResponse; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs new file mode 100644
index 0000000..d146cfd --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/FederationVersionController.cs
@@ -0,0 +1,19 @@ +using LibMatrix.Homeservers; +using LibMatrix.Responses.Federation; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers.Spec; + +[ApiController] +[Route("_matrix/federation/v1/")] +public class FederationVersionController : ControllerBase { + [HttpGet("version")] + public ServerVersionResponse GetVersion() { + return new ServerVersionResponse { + Server = new() { + Name = "LibMatrix.Federation", + Version = "0.0.0", + } + }; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs b/Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs new file mode 100644
index 0000000..b91868c --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/Spec/WellKnownController.cs
@@ -0,0 +1,19 @@ +using LibMatrix.Services.WellKnownResolver.WellKnownResolvers; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers.Spec; + +[ApiController] +[Route(".well-known/")] +public class WellKnownController(ILogger<WellKnownController> logger) : ControllerBase { + static WellKnownController() { + Console.WriteLine("INFO | WellKnownController initialized."); + } + [HttpGet("matrix/server")] + public ServerWellKnown GetMatrixServerWellKnown() { + // {Request.Headers["X-Forwarded-Proto"].FirstOrDefault(Request.Scheme)}:// + return new() { + Homeserver = $"{Request.Headers["X-Forwarded-Host"].FirstOrDefault(Request.Host.Host)}:{Request.Headers["X-Forwarded-Port"].FirstOrDefault("443")}", + }; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Controllers/TestController.cs b/Utilities/LibMatrix.FederationTest/Controllers/TestController.cs new file mode 100644
index 0000000..9c0981d --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Controllers/TestController.cs
@@ -0,0 +1,52 @@ +using System.Text.Json.Nodes; +using LibMatrix.Abstractions; +using LibMatrix.Extensions; +using LibMatrix.Federation; +using LibMatrix.Federation.Extensions; +using LibMatrix.FederationTest.Services; +using LibMatrix.Homeservers; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.FederationTest.Controllers; + +[ApiController] +public class TestController(FederationTestConfiguration config, FederationKeyStore keyStore) : ControllerBase { + static TestController() { + Console.WriteLine("INFO | TestController initialized."); + } + + [HttpGet("/test")] + public async Task<JsonObject> GetTest() { + var hc = new MatrixHttpClient() { + BaseAddress = new Uri("https://matrix.rory.gay") + }; + + var keyId = new VersionedKeyId() { + Algorithm = "ed25519", + KeyId = "0" + }; + + var signatureData = new XMatrixAuthorizationScheme.XMatrixRequestSignature() { + Method = "GET", + Uri = "/_matrix/federation/v1/user/devices/@emma:rory.gay", + OriginServerName = config.ServerName, + DestinationServerName = "rory.gay" + } + .Sign(config.ServerName, keyId, keyStore.GetCurrentSigningKey().privateKey); + + var signature = signatureData.Signatures[config.ServerName][keyId]; + var headerValue = new XMatrixAuthorizationScheme.XMatrixAuthorizationHeader() { + Origin = config.ServerName, + Destination = "rory.gay", + Key = keyId, + Signature = signature + }.ToHeaderValue(); + + var req = new HttpRequestMessage(HttpMethod.Get, "/_matrix/federation/v1/user/devices/@emma:rory.gay"); + req.Headers.Add("Authorization", headerValue); + + var response = await hc.SendAsync(req); + var content = await response.Content.ReadFromJsonAsync<JsonObject>(); + return content!; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/LibMatrix.FederationTest.csproj b/Utilities/LibMatrix.FederationTest/LibMatrix.FederationTest.csproj new file mode 100644
index 0000000..445eafe --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/LibMatrix.FederationTest.csproj
@@ -0,0 +1,18 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>net10.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>enable</ImplicitUsings> + <NoDefaultLaunchSettingsFile>True</NoDefaultLaunchSettingsFile> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-rc.2.25502.107"/> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\LibMatrix.Federation\LibMatrix.Federation.csproj"/> + </ItemGroup> + +</Project> diff --git a/Utilities/LibMatrix.FederationTest/Program.cs b/Utilities/LibMatrix.FederationTest/Program.cs new file mode 100644
index 0000000..18d3421 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Program.cs
@@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; +using LibMatrix.FederationTest.Services; +using LibMatrix.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers() + .AddJsonOptions(options => { + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.WriteIndented = true; + // options.JsonSerializerOptions.DefaultBufferSize = ; + }).AddMvcOptions(o => { o.SuppressOutputFormatterBuffering = true; }); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); +builder.Services.AddHttpLogging(options => { + options.LoggingFields = Microsoft.AspNetCore.HttpLogging.HttpLoggingFields.All; + options.RequestHeaders.Add("X-Forwarded-Proto"); + options.RequestHeaders.Add("X-Forwarded-Host"); + options.RequestHeaders.Add("X-Forwarded-Port"); +}); + +builder.Services.AddRoryLibMatrixServices(); +builder.Services.AddSingleton<FederationTestConfiguration>(); +builder.Services.AddSingleton<FederationKeyStore>(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (true || app.Environment.IsDevelopment()) { + app.MapOpenApi(); +} + +// app.UseAuthorization(); + +app.MapControllers(); +// app.UseHttpLogging(); + +app.Run(); \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs b/Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs new file mode 100644
index 0000000..e916703 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Services/FederationKeyStore.cs
@@ -0,0 +1,54 @@ +using System.Text.Json; +using LibMatrix.Abstractions; +using LibMatrix.FederationTest.Utilities; +using Org.BouncyCastle.Crypto.Parameters; + +namespace LibMatrix.FederationTest.Services; + +public class FederationKeyStore(FederationTestConfiguration config) { + static FederationKeyStore() { + Console.WriteLine("INFO | FederationKeyStore initialized."); + } + + private static (Ed25519PrivateKeyParameters privateKey, Ed25519PublicKeyParameters publicKey) currentKeyPair = default; + + public class PrivateKeyCollection { + + public required VersionedHomeserverPrivateKey CurrentSigningKey { get; set; } + } + + public PrivateKeyCollection GetCurrentSigningKey() { + if(!Directory.Exists(config.KeyStorePath)) Directory.CreateDirectory(config.KeyStorePath); + var privateKeyPath = Path.Combine(config.KeyStorePath, "private-keys.json"); + + if (!File.Exists(privateKeyPath)) { + var keyPair = InternalGetSigningKey(); + var privateKey = new VersionedHomeserverPrivateKey { + PrivateKey = keyPair.privateKey.GetEncoded().ToUnpaddedBase64(), + }; + File.WriteAllText(privateKeyPath, privateKey.ToJson()); + } + + return JsonSerializer.Deserialize<PrivateKeyCollection>() + } + + private (Ed25519PrivateKeyParameters privateKey, Ed25519PublicKeyParameters publicKey) InternalGetSigningKey() { + if (currentKeyPair != default) { + return currentKeyPair; + } + + if(!Directory.Exists(config.KeyStorePath)) Directory.CreateDirectory(config.KeyStorePath); + + var privateKeyPath = Path.Combine(config.KeyStorePath, "signing.key"); + if (!File.Exists(privateKeyPath)) { + var keyPair = Ed25519Utils.GenerateKeyPair(); + File.WriteAllBytes(privateKeyPath, keyPair.privateKey.GetEncoded()); + return keyPair; + } + + var privateKeyBytes = File.ReadAllBytes(privateKeyPath); + var privateKey = Ed25519Utils.LoadPrivateKeyFromEncoded(privateKeyBytes); + var publicKey = privateKey.GeneratePublicKey(); + return currentKeyPair = (privateKey, publicKey); + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Services/FederationTestConfiguration.cs b/Utilities/LibMatrix.FederationTest/Services/FederationTestConfiguration.cs new file mode 100644
index 0000000..353ddf5 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Services/FederationTestConfiguration.cs
@@ -0,0 +1,10 @@ +namespace LibMatrix.FederationTest.Services; + +public class FederationTestConfiguration { + public FederationTestConfiguration(IConfiguration configurationSection) { + configurationSection.GetRequiredSection("FederationTest").Bind(this); + } + + public string ServerName { get; set; } = "localhost"; + public string KeyStorePath { get; set; } = "./.keys"; +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs b/Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs new file mode 100644
index 0000000..7714fee --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/Utilities/Ed25519Utils.cs
@@ -0,0 +1,28 @@ +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace LibMatrix.FederationTest.Utilities; + +public class Ed25519Utils { + public static (Ed25519PrivateKeyParameters privateKey, Ed25519PublicKeyParameters publicKey) GenerateKeyPair() { + Console.WriteLine("Generating new Ed25519 key pair!"); + var keyPairGen = new Ed25519KeyPairGenerator(); + keyPairGen.Init(new Ed25519KeyGenerationParameters(new SecureRandom())); + var keyPair = keyPairGen.GenerateKeyPair(); + + var privateKey = (Ed25519PrivateKeyParameters)keyPair.Private; + var publicKey = (Ed25519PublicKeyParameters)keyPair.Public; + + return (privateKey, publicKey); + } + + public static Ed25519PublicKeyParameters LoadPublicKeyFromEncoded(string key) { + var keyBytes = UnpaddedBase64.Decode(key); + return new Ed25519PublicKeyParameters(keyBytes, 0); + } + + public static Ed25519PrivateKeyParameters LoadPrivateKeyFromEncoded(byte[] key) { + return new Ed25519PrivateKeyParameters(key, 0); + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.FederationTest/appsettings.Development.json b/Utilities/LibMatrix.FederationTest/appsettings.Development.json new file mode 100644
index 0000000..b6c6151 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/appsettings.Development.json
@@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Information", + "Microsoft.AspNetCore.Routing": "Warning" + } + }, + "FederationTest": { + "ServerName": "libmatrix-fed-test.rory.gay", + "KeyStorePath": "./.keys" + } +} diff --git a/Utilities/LibMatrix.FederationTest/appsettings.json b/Utilities/LibMatrix.FederationTest/appsettings.json new file mode 100644
index 0000000..10f68b8 --- /dev/null +++ b/Utilities/LibMatrix.FederationTest/appsettings.json
@@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/LegacyController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/LegacyController.cs
index 245770e..4c4e970 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/LegacyController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/LegacyController.cs
@@ -42,7 +42,7 @@ public class LegacyController(ILogger<LegacyController> logger, TokenService tok room_id = room.RoomId, state = room.State.ToList(), visibility = "public", - messages = new PaginatedChunkedStateEventResponse() { + messages = new PaginatedChunkedMatrixEventResponse() { Chunk = timelineChunk, End = timelineChunk.Last().EventId, Start = timelineChunk.Count >= limit ? timelineChunk.First().EventId : null diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomAccountDataController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomAccountDataController.cs
index bac803f..7bab143 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomAccountDataController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomAccountDataController.cs
@@ -38,7 +38,7 @@ public class RoomAccountDataController(ILogger<RoomAccountDataController> logger if (!room.AccountData.ContainsKey(user.UserId)) room.AccountData[user.UserId] = new(); - room.AccountData[user.UserId].Add(new StateEventResponse() { + room.AccountData[user.UserId].Add(new MatrixEventResponse() { Type = "m.fully_read", StateKey = user.UserId, RawContent = new() { @@ -46,7 +46,7 @@ public class RoomAccountDataController(ILogger<RoomAccountDataController> logger } }); - room.AccountData[user.UserId].Add(new StateEventResponse() { + room.AccountData[user.UserId].Add(new MatrixEventResponse() { Type = "m.read", StateKey = user.UserId, RawContent = new() { @@ -54,7 +54,7 @@ public class RoomAccountDataController(ILogger<RoomAccountDataController> logger } }); - room.AccountData[user.UserId].Add(new StateEventResponse() { + room.AccountData[user.UserId].Add(new MatrixEventResponse() { Type = "m.read.private", StateKey = user.UserId, RawContent = new() { diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomMembersController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomMembersController.cs
index 6c57cc4..f599e5e 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomMembersController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomMembersController.cs
@@ -13,7 +13,7 @@ public class RoomMembersController( RoomStore roomStore, PaginationTokenResolverService paginationTokenResolver) : ControllerBase { [HttpGet("members")] - public async Task<List<StateEventResponse>> GetMembers(string roomId, string? at = null, string? membership = null, string? not_membership = null) { + public async Task<List<MatrixEventResponse>> GetMembers(string roomId, string? at = null, string? membership = null, string? not_membership = null) { var token = tokenService.GetAccessTokenOrNull(HttpContext); if (token == null) throw new MatrixException() { @@ -44,7 +44,7 @@ public class RoomMembersController( members = members.Where(x => (x.TypedContent as RoomMemberEventContent)?.Membership != not_membership).ToList(); if (at != null) { - StateEventResponse? evt = null; + MatrixEventResponse? evt = null; if (at.StartsWith('$')) evt = await paginationTokenResolver.ResolveTokenToEvent(at, room); diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs
index 74c70a3..bc17b06 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomStateController.cs
@@ -2,6 +2,7 @@ using System.Collections.Frozen; using System.Text.Json.Nodes; using LibMatrix.HomeserverEmulator.Extensions; using LibMatrix.HomeserverEmulator.Services; +using LibMatrix.Responses; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers.Rooms; @@ -10,7 +11,7 @@ namespace LibMatrix.HomeserverEmulator.Controllers.Rooms; [Route("/_matrix/client/{version}/rooms/{roomId}/state")] public class RoomStateController(ILogger<RoomStateController> logger, TokenService tokenService, UserStore userStore, RoomStore roomStore) : ControllerBase { [HttpGet("")] - public async Task<FrozenSet<StateEventResponse>> GetState(string roomId) { + public async Task<FrozenSet<MatrixEventResponse>> GetState(string roomId) { var token = tokenService.GetAccessTokenOrNull(HttpContext); if (token == null) throw new MatrixException() { @@ -103,7 +104,7 @@ public class RoomStateController(ILogger<RoomStateController> logger, TokenServi ErrorCode = "M_NOT_FOUND", Error = "Room not found" }; - var evt = room.SetStateInternal(new StateEvent() { Type = eventType, StateKey = stateKey, RawContent = request }.ToStateEvent(user, room)); + var evt = room.SetStateInternal(new MatrixEvent() { Type = eventType, StateKey = stateKey, RawContent = request }.ToStateEvent(user, room)); evt.Type = eventType; evt.StateKey = stateKey; return new EventIdResponse() { diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs
index 61195b8..d05554c 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs
@@ -35,7 +35,7 @@ public class RoomTimelineController( Error = "Room not found" }; - var evt = new StateEvent() { + var evt = new MatrixEvent() { RawContent = content, Type = eventType }.ToStateEvent(user, room); @@ -100,7 +100,7 @@ public class RoomTimelineController( } [HttpGet("event/{eventId}")] - public async Task<StateEventResponse> GetEvent(string roomId, string eventId) { + public async Task<MatrixEventResponse> GetEvent(string roomId, string eventId) { var token = tokenService.GetAccessToken(HttpContext); var user = await userStore.GetUserByToken(token); @@ -128,7 +128,7 @@ public class RoomTimelineController( } [HttpGet("relations/{eventId}")] - public async Task<RecursedBatchedChunkedStateEventResponse> GetRelations(string roomId, string eventId, [FromQuery] string? dir = "b", [FromQuery] string? from = null, + public async Task<RecursedBatchedChunkedMatrixEventResponse> GetRelations(string roomId, string eventId, [FromQuery] string? dir = "b", [FromQuery] string? from = null, [FromQuery] int? limit = 100, [FromQuery] bool? recurse = false, [FromQuery] string? to = null) { var token = tokenService.GetAccessToken(HttpContext); var user = await userStore.GetUserByToken(token); @@ -161,7 +161,7 @@ public class RoomTimelineController( } [HttpGet("relations/{eventId}/{relationType}")] - public async Task<RecursedBatchedChunkedStateEventResponse> GetRelations(string roomId, string eventId, string relationType, [FromQuery] string? dir = "b", + public async Task<RecursedBatchedChunkedMatrixEventResponse> GetRelations(string roomId, string eventId, string relationType, [FromQuery] string? dir = "b", [FromQuery] string? from = null, [FromQuery] int? limit = 100, [FromQuery] bool? recurse = false, [FromQuery] string? to = null) { var token = tokenService.GetAccessToken(HttpContext); var user = await userStore.GetUserByToken(token); @@ -194,7 +194,7 @@ public class RoomTimelineController( } [HttpGet("relations/{eventId}/{relationType}/{eventType}")] - public async Task<RecursedBatchedChunkedStateEventResponse> GetRelations(string roomId, string eventId, string relationType, string eventType, [FromQuery] string? dir = "b", + public async Task<RecursedBatchedChunkedMatrixEventResponse> GetRelations(string roomId, string eventId, string relationType, string eventType, [FromQuery] string? dir = "b", [FromQuery] string? from = null, [FromQuery] int? limit = 100, [FromQuery] bool? recurse = false, [FromQuery] string? to = null) { var token = tokenService.GetAccessToken(HttpContext); var user = await userStore.GetUserByToken(token); @@ -226,7 +226,7 @@ public class RoomTimelineController( }; } - private async Task<IEnumerable<StateEventResponse>> GetRelationsInternal(string roomId, string eventId, string dir, string? from, int? limit, bool? recurse, string? to) { + private async Task<IEnumerable<MatrixEventResponse>> GetRelationsInternal(string roomId, string eventId, string dir, string? from, int? limit, bool? recurse, string? to) { var room = roomStore.GetRoomById(roomId); var evt = room.Timeline.SingleOrDefault(x => x.EventId == eventId); if (evt == null) @@ -254,7 +254,7 @@ public class RoomTimelineController( private void InternalSendMessage(RoomStore.Room room, RoomMessageEventContent content) { logger.LogInformation("Sending internal message: {content}", content.Body); - room.Timeline.Add(new StateEventResponse() { + room.Timeline.Add(new MatrixEventResponse() { Type = RoomMessageEventContent.EventId, TypedContent = content, // Sender = $"@hse:{tokenService.GenerateServerName(HttpContext)}", @@ -265,7 +265,7 @@ public class RoomTimelineController( }); } - private async Task HandleHseCommand(StateEventResponse evt, RoomStore.Room room, UserStore.User user) { + private async Task HandleHseCommand(MatrixEventResponse evt, RoomStore.Room room, UserStore.User user) { logger.LogWarning("Handling HSE command for {0}: {1}", user.UserId, evt.RawContent.ToJson(false, true)); try { var msgContent = evt.TypedContent as RoomMessageEventContent; @@ -332,7 +332,7 @@ public class RoomTimelineController( if (Random.Shared.Next(100) > 75) { crq.CreationContent["type"] = "m.space"; foreach (var item in Random.Shared.GetItems(roomStore._rooms.ToArray(), 50)) { - crq.InitialState!.Add(new StateEvent() { + crq.InitialState!.Add(new MatrixEvent() { Type = "m.space.child", StateKey = item.RoomId, TypedContent = new SpaceChildEventContent() { @@ -384,7 +384,7 @@ public class RoomTimelineController( } } - private async Task HandleImportNhekoProfilesCommand(string[] args, StateEventResponse evt, RoomStore.Room room, UserStore.User user) { + private async Task HandleImportNhekoProfilesCommand(string[] args, MatrixEventResponse evt, RoomStore.Room room, UserStore.User user) { var msgContent = evt.TypedContent as RoomMessageEventContent; var parts = msgContent.Body.Split('\n'); @@ -422,7 +422,7 @@ public class RoomTimelineController( } } - private async Task HandleImportCommand(string[] args, StateEventResponse evt, RoomStore.Room room, UserStore.User user) { + private async Task HandleImportCommand(string[] args, MatrixEventResponse evt, RoomStore.Room room, UserStore.User user) { var roomId = args[0]; var profile = args[1]; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs
index 40f3667..339e686 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs
@@ -1,6 +1,7 @@ using System.Text.Json.Serialization; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.HomeserverEmulator.Services; +using LibMatrix.Responses; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs
index 93e4b4f..495f9e3 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs
@@ -1,6 +1,7 @@ using System.Text.Json.Serialization; using LibMatrix.Homeservers; using LibMatrix.Responses; +using LibMatrix.Responses.Federation; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Extensions/EventExtensions.cs b/Utilities/LibMatrix.HomeserverEmulator/Extensions/EventExtensions.cs
index d938b1b..15e41d4 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Extensions/EventExtensions.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Extensions/EventExtensions.cs
@@ -3,8 +3,8 @@ using LibMatrix.HomeserverEmulator.Services; namespace LibMatrix.HomeserverEmulator.Extensions; public static class EventExtensions { - public static StateEventResponse ToStateEvent(this StateEvent stateEvent, UserStore.User user, RoomStore.Room room) { - return new StateEventResponse { + public static MatrixEventResponse ToStateEvent(this StateEvent stateEvent, UserStore.User user, RoomStore.Room room) { + return new MatrixEventResponse { RawContent = stateEvent.RawContent, EventId = "$" + string.Join("", Random.Shared.GetItems("abcdefghijklmnopqrstuvwxyzABCDEFGHIJLKMNOPQRSTUVWXYZ0123456789".ToCharArray(), 100)), RoomId = room.RoomId, @@ -15,7 +15,7 @@ public static class EventExtensions { }; } - public static List<StateEventResponse> GetCalculatedState(this IEnumerable<StateEventResponse> events) { + public static List<MatrixEventResponse> GetCalculatedState(this IEnumerable<MatrixEventResponse> events) { return events.Where(s => s.StateKey != null) .OrderByDescending(s => s.OriginServerTs) .DistinctBy(x => (x.Type, x.StateKey)) diff --git a/Utilities/LibMatrix.HomeserverEmulator/LibMatrix.HomeserverEmulator.csproj b/Utilities/LibMatrix.HomeserverEmulator/LibMatrix.HomeserverEmulator.csproj
index 5178012..6f32b58 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/LibMatrix.HomeserverEmulator.csproj +++ b/Utilities/LibMatrix.HomeserverEmulator/LibMatrix.HomeserverEmulator.csproj
@@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <InvariantGlobalization>true</InvariantGlobalization> @@ -10,1025 +10,1025 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="EasyCompressor.LZMA" Version="2.0.2" /> - <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.1" /> - <PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" /> + <PackageReference Include="EasyCompressor.LZMA" Version="2.1.0"/> + <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-rc.2.25502.107"/> + <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6"/> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\LibMatrix\LibMatrix.csproj"/> -<!-- <ProjectReference Include="..\..\Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj"/>--> + <!-- <ProjectReference Include="..\..\Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj"/>--> </ItemGroup> <ItemGroup> - <Folder Include="data\users\" /> + <Folder Include="data\users\"/> </ItemGroup> <ItemGroup> - <_ContentIncludedByDefault Remove="data\rooms\!009948f3-9034-4a24-a748-a6391dd886bd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!00e1caa3-d6bf-4a0a-9793-0cebe26d4a96.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!011ed14b-f495-4c52-9f0f-b0fd0bd72f26.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0151df7a-3b06-4048-8b1c-11ea88987ceb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!017179e2-c121-49c2-ad06-197f4cf39155.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!01830986-341e-4622-a51e-6368a6064c2b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!018a12f5-8ed7-4dbc-9285-2165ae1ca39a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!01951dc5-d87a-4b27-b444-809f4907755b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!01d8efb7-52b3-461d-a95e-f16ca6ca5888.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!01f2c6dd-1bab-40e6-ac62-fcb5abf15e73.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!025c937f-13fd-445f-af46-69bda3d08d02.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!02d2dd0c-e57b-4454-a197-046d81f8d7bb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!02f00a13-d09a-4b42-888f-f6651d7882b8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!031b3eb4-d25d-47ef-b3ce-e06f6f4f154a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!032dd423-f2d3-4f3f-9b8d-10febd9de20b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!03ba6761-0bb4-4bfa-b895-6e4428795410.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!03ce824c-450b-4d13-a8a5-43a6ba4f1da6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!03cfb867-3155-4ef4-8e2a-0939e6837ec2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!03e4b54c-c41e-46e0-b47d-a6486fe524bc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!044df1bc-13a1-4496-9ba3-bcf60d45fbb4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0482a9e8-a7a1-4e66-a98d-1735758f8013.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!04ae0100-c074-4a0b-bb29-5c88b3199fb4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0511d862-353b-4cb9-9392-04eb3d6b756a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!054b1613-1757-438b-bfe9-73b501390617.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!05877fed-59a3-430b-9b04-10d70ca9aef1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!05d2ac50-d014-4e72-93b1-d8e18ee4cf8c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!05e4883f-4784-4a25-9748-d9d8ce4d314e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!06475f48-8175-45e9-8cf0-15741f27ad8f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!06b3a783-949c-42d3-a4d4-813273e3bab0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!07b2ed26-caba-440e-8cb7-172e311c1d78.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!07e9491b-f799-4552-90e7-242413ea69cf.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!08602758-2108-45e5-8f08-baf57f8e90df.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!086b0484-a1ed-4c04-93f7-2f73009570fa.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!08fc4a55-9107-442b-961e-5353203623e0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!091cf52d-7932-4510-974b-c41ef0d05787.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!093a2862-3c0f-487a-bf43-6ae4d7ab9278.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!09925e93-ec19-49bd-ba19-e7b0fd0bd029.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!09f14b3a-21d0-41ed-bb86-1a602b07370e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0a34a93d-db6a-47cf-a5cc-8f9354435ea2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0a71f415-ec9a-40df-b300-c3fb79d4dd13.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0ab184df-be0f-4877-80f8-0aa34d5ad00b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0ad9691a-d4e9-4f4a-8f48-86a8adad7f43.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0afbbfb2-caf0-4829-a7f0-06472c16cb4b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0b6f3cb8-37ed-4a7a-b95c-5d299ac73755.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0ba81dce-f4d6-40fb-84fd-0df52347eb99.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0bd9becb-401f-465a-8b55-f2f20b4cdb78.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0c1f5b9c-4b80-4c83-989d-3dbcffa6f74a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0d7ece98-e347-4082-a9a4-d5e4cd594bf0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0d877be3-7545-49ca-9941-191e3299b7fe.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0dd1c03a-0455-4ffd-9ecb-5777039d4165.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0e3ca29e-0270-4ab9-8b07-c76f4662dcd0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0ea8696f-f527-4e11-916a-69dd9e086d9e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0f08c6a1-47aa-436f-9a44-3fdbd5aff348.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0f6a7cc3-2f2e-49fd-a953-401c9a16c38d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0f9fcf53-412d-452c-bcea-43d6dbb3d705.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0fb1c6b5-8bb9-47e8-8d9b-a126cb5eff7d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0fb79121-1e83-4fe9-8bf9-f8a5ed364efa.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!0fc9886d-ecbf-4525-8f5c-a43fc4dc1b58.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1000acc2-a562-41f0-9f40-6eaf73b99f66.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!100204b5-f690-42b9-a49b-3fc3df91bdb2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!101265f1-eacb-4185-9470-269bf51b1829.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!108535e1-632b-489a-a9e8-e2fcf60d8c83.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!109f5a13-3a9f-4ced-a4a4-b0191a84b7d5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!10c5ff39-6ac2-42a8-8fae-292f01a5cb32.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!111e8be3-72e1-4200-83aa-241c6e98764d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!11381a2e-0b1e-4a56-b53f-0dd2cf65a187.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!114243be-a9f3-4909-bb03-8171eacdf1ee.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!116a3988-1763-45ec-b24c-ebb9e57faf4e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!11a149c7-ee5c-4ada-9145-052976d3ab18.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1214a2eb-57ea-4662-8b32-742678cf130f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!13207273-2393-45a6-8cc9-7c4fbbbc9736.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!133f8b66-d9d5-44e4-95e9-dde76cceee0d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!13ed7c78-7160-4ebd-a84f-7124479d0b3b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!14586508-6e3a-4bc3-a8ac-dbcb177310de.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1459a8ab-3aa5-4619-bb94-7d636f9a4576.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!14cb8826-1b94-4106-b530-0f9609d93679.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1524fc2d-6985-44d4-9d77-08f02e2df93a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1528dc79-229f-4a0a-9433-ca0d8fe81fdc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!15d52bea-102a-4352-92a5-f2817e879083.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!15f05e3c-4694-403a-a95e-10def22cb013.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!161bbd52-f6e8-4ade-a139-27209aab5ca7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!164a1783-6218-4335-aa82-b9db00aa4cf9.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1680277b-9540-43b3-813c-83bce9c2581d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!169d4aca-5385-4df3-afc4-5cc3f94708d3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!16a88afc-09dd-45f8-8202-98bb7a88cef2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!16c80534-e338-45d3-beaf-0e4e93695abb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!16d4b0c1-9bd3-408c-bfb9-48bb8ef7b679.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1709e1bd-5539-40fd-9124-0bfb5c62341f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!17308b64-da60-4966-bf85-409df16d5ec4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1749eb5f-f7ea-4eb0-8a77-7243123bbbf8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1761367d-a128-4ad9-9c7a-acb5a4e5a1b6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!176f30db-9b87-4a06-b3f3-895bd8f41448.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!178abf28-9ac4-432e-bac8-9c75f800fbfa.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!17e1e888-b9e2-4aca-85a2-48e5d1d0b35c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!186cbe4b-c6b2-4e2d-ad85-931c8d078185.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!189c6c4a-fa84-4fb4-bdfc-ab25e959e243.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!18c0440c-5c3e-4c80-8397-95658769c20c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1909d1c2-18e6-4f41-b6d4-01a0350ecb4c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!191141f8-0556-43e3-b982-142854e40385.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1940357b-f2b8-45b9-a830-d35e7f4f46f1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!19ad183e-18f5-41a2-837f-5c440047a0c7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!19b0fc7b-0359-4aa3-9597-29a97145e7cd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!19cb0696-af13-4036-b000-7aa03bef9541.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!19e9f51d-278c-412f-9ecf-cb96a83282c7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1a0767ef-4897-4a87-8d95-9c1446b07780.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1a1e71ce-5f6a-4a9f-a3cf-8f95189eae24.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1a995531-85bc-4f90-b36e-29a1e8620e7e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1b2eb324-6682-4ea3-8377-2e17da2fd996.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1b3c2d02-b76a-449d-a458-15baf8254298.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1b60b136-19a4-4551-b682-b02a6cea94d4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1bc97814-3491-455b-8fc2-ca2916f77813.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1bdf6ed0-cac5-48d2-84ed-83b7f5ef3a42.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1c0450bd-8cb2-487f-9160-3ca95543410f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1c18b32b-c3b2-48ee-8562-bdbc1697adab.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1c273675-9267-46e1-901e-50460328d95a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1ce174af-bd5e-4ae5-ac1e-b71eb4b44594.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1cff0104-9ffa-460c-ae4a-4a4c17e2784f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1d3e4977-6df2-4f69-b8b8-e13ccffdec71.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1d4821a3-f202-46f4-aabf-7fa88387283c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1d6dc82d-978f-4f7f-8514-12f099d4e699.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1e3463cd-408a-4f85-b79f-5f1f9a2f4160.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1e96e1e2-e2d9-4547-90c2-c9311fdc1ffc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1ed2a1ee-ed46-4444-967a-3ad686fee01f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1ee34f83-373b-4508-94a9-3ab61873ecd6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1f1e1492-eef4-4dc3-a5bc-ff2aac6fd10c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1fd36873-80cc-4d2d-95e3-11e3df524dac.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1ff577ed-8eed-41be-a839-2e9985d4ee5b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!1ffe979c-e5c4-4db4-b0d1-c0e615cb76cc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!202568b3-39a9-41a2-a040-bc8845e50449.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!20da7437-06fd-43b3-b5ed-e2f87198daad.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!20f5dbc1-e343-428a-8a76-781193037198.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!210b0c40-0cb3-49bd-8ef5-028cf01499de.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!216fa2ed-abd6-4d1c-b87a-4dba953da630.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2170b28b-1c11-411c-ad8e-1b4d884b509b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!221ecfe5-5d11-44e5-a7f3-ab71ce8c9774.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!231333d5-f5c4-4df4-9d09-814338ff0b36.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!235e6b94-0c1b-409b-881f-f293d356bcd8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2363d819-977b-465a-81a5-d16ab55a2e22.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!23766876-4d3e-421f-a26c-abb822473a1f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!23a77580-70a5-4f71-bf88-383caa4edaba.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!23c45ba3-cd4f-4fa2-b129-948a350d28a9.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!23faa183-1aed-4a7e-937a-6b46768c9e16.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2449949d-7cfe-4680-99d7-97167eea6b52.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!24b54660-10aa-44be-a54b-c7b571d0427c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!24e064e4-b388-4bc9-b379-88e485deab8a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!253ea427-a360-46fa-a1bd-e319055a9bf5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!25f695b3-ee00-4161-ad85-209101aa600c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!25f6ff1e-e9bb-47b9-a9ca-f72f7c5ac797.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!26089b3d-c318-4d35-af3e-255eef32bc7e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!267a5f9e-1dac-4b5f-89cf-d0f1741aedf1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!26879a0f-d760-440b-be38-c70ba711e016.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!26a15ed6-2da6-4796-8526-d63385e62189.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!270af0c6-1496-46af-91f6-9977aeefeef1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!27897886-242b-4f28-a79c-9c71b787a956.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!27adc88d-bda5-4888-b6bd-9691f55f3a3b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2830fdb4-947f-4f7a-aac1-c793f84c2d7b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!285a2bfa-8e59-4613-bc4f-f0300ed990a7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!293d9933-de29-455f-9a89-aa1b131afc2b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!296bf7a9-68fc-47b7-9c6c-eab873a25565.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2987c88d-23fe-43bc-99bf-a57fd0f13675.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2987f9ed-ae51-42f2-b0c6-6ae736646360.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!29ecae78-49d0-4b07-98ab-ab75527f68be.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2a763c96-c63b-4332-9ed6-dc7e842a0440.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2af2278f-aa89-43d8-88bb-f20480ff9c58.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2b1e1b17-babd-4e87-a119-16ceefa61be3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2bbf11fc-ff3d-476a-beb6-4f0b515c93ea.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2bd0be39-ea9f-4c56-9faf-cdf5b1f3c079.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2bec4090-07ec-440a-908a-a722168fb4e7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2c2e3e2e-50d8-4440-86a9-9047ff752cdf.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2ca6f22d-34b9-4ca5-ad14-c96abc6c17e8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2ce029f3-f023-44af-94dd-06390e68198d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2ce31ed9-b818-4b70-86bc-2e41a7d71b3d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2ce9adde-2a0b-4a87-bfee-74f262cc73fc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2cfe6d51-ddea-4915-8e5f-21a74df2bfac.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2d00f9bc-0fd3-4da9-a254-a90e61c15067.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2d87b8b1-0ca6-416c-acba-3349b6a2a514.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2dac26f3-01e8-4aa6-817e-2a89cc59290d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2dc32058-e0b1-4e46-8c5f-a46a60fe85ad.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2e0a9a01-5bc5-432e-81c7-378a9b35ead4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2e1facae-b5c7-4a96-ad59-a15a28da15a4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2e5640d9-8e1a-42e9-b630-a869ea6c44c1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2eaf153a-d5a2-40b3-9d59-a0424133003a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2ee4350b-006e-4d2b-9b71-87b8ac202e6e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2ee9c481-dbbf-4230-8d4c-d8dc11bec46c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2f61cf24-2da9-4050-8d71-ff96cfd478c8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2f709dd0-2acf-4553-9d3f-1b3e7d7e9a3b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2fe91768-c1ff-4360-9dc0-ea052da98d4a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!2ffad0b7-fa7f-4b88-97c9-54830b4f460c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3047fc05-26c5-4649-8fc5-6d83afd9d42c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!31197c74-10be-46d2-b36a-e2b96d22d57b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3142892e-219c-470d-80f3-0d7e5c34150f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!31a3f8cf-614b-4b08-a61e-52290f3a197b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!31cf85c8-3c6d-492e-b43f-4d0a8c2a055a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!31d505c2-817f-4e4a-88bb-710ca54869ed.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!31f9f8d9-e3d5-4c77-805c-3cdcd4d1a140.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!321a0118-d05e-4d95-901f-b7929e65628c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!324aac37-6820-45e3-8b14-21a21334a718.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3288ee08-56ae-4371-8fcc-fae09bb29053.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!33d6862e-cf55-499d-963f-93a9e393cc5d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!33ed93c1-91f9-4a3c-b55d-35c927a034d1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!344ceb77-d29b-49b0-a328-0104c35f233f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!34a06da6-fecf-401e-9dbc-1a072b0da618.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!34b6371c-977d-410d-b3c8-920b1fda6b84.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!34c9fb4b-912e-4d0c-a593-cc072b07376d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!34f7fd18-78f4-47fe-b0e9-28f0181aef28.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!35334b1b-0748-4ae6-b290-2b9d952a0cc6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!353ec49e-5261-49d9-b701-0073818dd29a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!35d3f72b-29c9-42ca-adb0-89302521cc58.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!35dc7a95-211f-4383-a123-466555ca192d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!35ebd4e9-69a9-44d2-a152-426330bac118.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!36132c8e-a7eb-4a04-866c-b97d95fd51d6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!362d1839-0159-465d-9a81-faf0f4b9a223.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!365e92dc-d8ea-4e86-8d42-0aacb460e98f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3780fe5d-358d-4b32-8f29-fba7cdada78c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!379a776d-54c8-4eee-bef4-0492e1b05e3a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3866c302-ce01-4ffb-8b7b-4d83bab72ca4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!38cfc692-cd7b-43b0-b242-4d4445b750d8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!391d087a-62d9-4a38-ab28-d58aa9c6e970.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!39392d41-09c3-4a2f-90c2-52a8805ea39c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!394c788f-640f-4ac9-9d54-10607998b486.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3964e3d1-7f92-45e8-b24b-7ab15013891d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!39bccce2-b78c-4cf9-8428-308793b32e71.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3a446fce-8d08-4ce7-819a-f2f316dc0153.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3a5b7b5e-a7d1-4515-977c-59f968f7c132.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3a6a92be-939f-4820-9366-dc485d46dc4c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3b08bd24-dd41-4fe6-8a73-5e696201f5ac.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3baa7569-6cd3-4a38-a9d9-be6bda89f5ef.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3be0678a-feb2-47ab-8b1c-bbc4d78dc171.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3c0a0580-8152-4bdf-a511-26ba9f32857b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3c19888e-ab9d-408d-90ad-4bb92b2bdbf2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3c353341-5051-40b5-b97c-ad7284dfda13.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3c6b7be3-d937-4f2b-a251-14b0814af9ee.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3c95c9ef-6ca0-4f22-a4c2-82581f5ce566.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3cf1cd63-6e1d-41f8-b38b-99919923445a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3d69d242-b996-4912-b637-b2ff675b019d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3d69d419-4f1f-420f-a86d-8be471e522e5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3dd5ace5-70f3-443f-a77b-f2b3efc8b6a4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3ddd3d20-ae93-4448-8b65-38ce0cdf64b3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3ead0dc7-f117-4478-a8e1-28bd33969d65.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3fd47b87-b7f0-4aac-87f5-7370282cadca.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!3fe3728d-6531-4b01-ab58-bbd17cc8edca.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!401b32b9-cd18-4da1-ab56-02470a6ca453.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!40cf93b8-ac5e-47a1-ae71-998ad27ca189.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!40e14dc3-d16b-400d-8c13-dbf2d5ca90ee.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!40ef037d-369d-4e0b-9dfe-582bc9f73525.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!40f5792b-73a0-48fa-82cf-c13e0b8469d2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!41a0a70a-a5e1-41cd-bad4-dc1b371172fb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!41a7775c-cf14-4060-b897-ebba44d643ac.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!41bf7a42-68a4-4df3-8006-cf8524d3dae6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!41fb0617-119f-4fa3-b813-ff80a5c6b817.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!42a66b22-60c7-45db-a17d-f78822e758b0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!42dbf858-050f-49c6-82f9-5bfecbe77cfb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!43510534-019c-43e9-a9fe-ae868411967c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4383cd84-9f31-4779-a798-a1a237d3efc9.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!43d322d3-42a7-4f27-ade1-7886fe5c955e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!442e840f-fc88-4fd3-8932-4943c8753360.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!44684574-21b6-4ca1-a8cc-2fe11e459ca5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!44b00d92-c007-493a-85c8-ee0dd43fbfe0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!44d3ca18-311a-44a8-9fff-8912c7ecac60.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!451aff08-e181-45b6-a78a-c594f0fa056c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!452cbd9e-1d72-4e84-83dd-b42449f05baf.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!45600fdd-f784-42f6-a514-7f1e34e5c745.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!45afbb30-3ab4-43ab-90ac-f5b902c70557.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!45daabc6-cd56-4052-b6eb-8663f8b8dbe2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!45ff7d79-4e27-40d2-93ee-8b24f78e89ec.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!461b5b97-1d25-4171-bb20-aa925d72bce6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!462b0be0-065f-44e3-96a6-3e67179d6674.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!46430d1f-b496-48d0-b5bc-5de79cadf175.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!46962d6e-92b2-4245-8a03-b10d38546911.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!46efb743-ab87-4ac7-9682-c3fcd15ed92c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!47199b5c-57c0-4aa4-bbfb-20d5c70212af.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4719ae4d-008f-4191-89e8-6c15f31c1fb7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4723262f-5574-4a31-bb74-57ec1e0651ab.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4776dea5-e0fd-4421-be38-c671999842e3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!478f85ae-4468-49ef-8d22-4d4d05570fae.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!47ef9d8c-24ad-44a4-ba40-526716184725.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!48f5f5eb-6fd5-480c-9658-c23e2952b81c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4909420b-44e9-4a00-9c97-3b8d61a0a1ef.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4917c167-f8bc-4416-bafc-37d2fa05537d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!491dcbbb-5398-4520-a6a8-71ff45dfbb49.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!49636e8f-be76-4105-ab9e-90e9bc6855d3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!496f12b0-20f6-4388-a078-74e31c9c8db8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!49a0ed75-a6ab-4005-ace4-a9cf6351cbcd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!49a133d9-a891-4d85-b46d-da7b7160c1f6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4a43c56b-93f7-4ce2-9cc9-eadb58f38487.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4a65073e-7651-45d6-b501-e9437434fcac.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4a850743-1c6a-4f08-a580-a2ec5d91845c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4b1244f7-ca4e-4833-b500-c9a377dbb7ca.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4b15fb46-8496-49c5-8f6d-bf9c7daa6793.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4b33efd9-b324-40c9-bfdc-3c1970b86195.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4b358236-7bd5-4fe1-be79-5b6d0985e112.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4b466e87-444d-437b-a830-14c0cb6fe2f4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4b6f50dd-a934-44a5-9925-004e6a89a779.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4b82a600-3b76-4865-a422-517b6155034c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4bf9b456-2028-42d5-bec8-53f046eae7a7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4c0351ae-e696-40a6-857d-45b82fc4f9a6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4c262d60-7c86-47ad-b6b7-17e3ee5d2b65.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4c2c29be-4591-4ff7-acd0-bb5f13225315.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4c65cfdc-3c2c-469f-b204-a71f50ee3a11.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4c6d07db-ed39-41a5-b489-89f77ae4c67f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4cd1a8da-c3fa-4feb-ba5e-b93131f33729.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4d180222-335e-4c40-8e61-abd709ddbfa0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4d528a08-13d1-4d0a-8f69-d870f1245a9a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4da181a0-9cee-44ce-a2db-9acdb2154fac.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4dbdaa18-bf2a-46a7-9fa5-272a503fcd57.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4e0cc5d6-eca3-4cd2-af5a-0a5e472a9a40.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4e42d7d0-b511-42ec-8888-858bd10b4413.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4e478383-798e-4f22-b4a9-919b0609267f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4eec4377-98a0-40fa-ba06-be2777d6a463.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4f68be5d-749f-4ae2-bf17-ff248c6c47b0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4f863192-0991-4618-975e-55772df49772.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!4ff69980-0724-49c8-b885-ee5c86aee9f1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!500d38f4-f55e-4091-8bbe-806c9bbec2df.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!50340cb9-b71e-4748-9dbd-80d5e2ab4533.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!508ade04-1815-49b3-bd31-81a9da598277.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!50a458ad-1ab0-4da8-8175-b90e1f987b09.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5107d457-b1a4-464a-93ce-452a5aaab204.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!514dbafe-7499-410b-aa6b-9a3cfc39ed94.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!527b3e69-4a28-4cac-a358-c204444f499d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!528ca094-7692-4a01-b1a3-0790926d5544.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!528ce4fb-8249-4d3f-9743-a1dc9bba8829.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5296546d-7aab-4e5d-9d4a-027966246645.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!529c7af7-691e-4480-b88d-f4c424c30610.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!52a3ae7e-0653-46b1-8052-8cf1cf17da95.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!52a3e47b-0213-4872-9bfa-94ac62c92698.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!52c624d8-2ed5-40eb-b3ca-cfc19cdc8e71.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!52d1bfcd-3d9a-4469-8fa1-413491ca75ac.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!52d49ea6-a9b2-4332-b712-46a9fe47e7cf.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!530618be-416e-4281-bdf4-432cca2ba154.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!53205a54-7175-4adf-9362-9e83d27a1414.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!53219c8d-ab96-486c-8d72-32c08d8d1e14.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!536e266b-8b25-4a1b-a5f5-6778399c90b4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!53869421-9475-4607-9ef9-efd4af0d3b98.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!53ed8360-33f8-4539-9f27-ebee01d3cfdc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!541ac3af-938d-4d2d-87ec-d40a7d51f949.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!54385af9-0e23-44dd-bc4d-2bf6362da26c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5457ed85-c48c-4ccf-8905-44751b42225f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!545e67e1-1744-4937-9bf1-a9666a903a0f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!54cf9e45-e833-4e90-9fef-7b49da74dd8a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5540a57e-b315-407a-ba5a-e41be9436b3b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!555a5486-947b-414e-a500-5248395bcd97.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!55cc236a-7120-4b8d-b990-afb7f6386e2d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!56624f0c-b9d6-4107-8c1b-e238ab6d1182.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!56805a47-39e7-4b6f-9f1c-d6762a70e655.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5691c070-0e79-44c6-8c01-2fb0031df8af.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!56a13e14-6a26-443e-ad58-84c7f8703ca5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!56eea411-09d9-4339-b21e-dcbceca22a8b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!570f1dfe-8829-4593-bef0-f42230b519ef.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!580a90be-62cd-4679-bdd7-1b28f9934b0d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!580f5147-ea97-432d-ad40-42cfcb3cf2e6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5890f4c0-88ad-4800-8232-d67b3da9b9d8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!58e03112-fd39-4e87-9b8b-fe8e9a92cc94.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!590ae097-0b16-4a6c-8a3d-fbfe6d56224a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!593c6e1e-de58-4cbc-a933-9eeedad35cc4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5994b78b-a10e-4a43-87b4-9c2c313f3c9d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!59e04135-5a7e-4c38-91e8-6e3213c384df.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5a165c73-27a7-449a-9a8a-94c377e78d92.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5a176a47-1bae-411f-86c4-5ce9a434878c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5a6465c4-ca0a-4ff5-a3c9-60be0e2485ee.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5a8bf716-15e5-40a6-8d4e-d4c6cdef49cd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5a9e4fd6-0206-4fcd-85e2-aeb0414c223a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5ab45d5f-316c-4519-8552-30222f670430.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5b777c93-d41b-448c-b14c-4f8e6bdf0956.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5be6fb36-dffc-4ffe-a553-f164714f6370.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5c0c8af0-e2e6-436f-867c-ea652a0c8a4d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5c191dd2-a708-4bec-be43-12a392460ffc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5c6d36dd-07fc-4421-bcf5-48008c49b592.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5cb2c113-40fe-45c2-97db-786ac0003a69.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5cd92388-abf1-40ff-82c8-f8b448680114.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5cf9dec3-bc79-4d2b-b12d-49132108abc2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5d022dfa-9ac2-4396-9683-f6f32c8afffd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5d65c2b7-a08d-40d2-ac79-2675933e0594.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5d743f7c-884e-4ab5-ab60-caa95ab2a935.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5db3bc2b-39d2-4ecf-8c70-e009243c0e1e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5ddf0dd3-5782-444e-9610-093b736ddfc8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5dfb804d-d608-4b1f-9cd5-d3bd8a4d767c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5e439239-b615-4827-822c-d2c16adf909f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5e61773b-5593-4752-867e-7c0555d89b35.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5e79c9b9-c28a-4cd4-a1e5-29c32987a56e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5e97d736-2d04-4c28-9692-bc22b6a2fcfc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5e9eb177-ead9-4476-8418-567c01b4ab87.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5ea34f5c-48ba-4100-b56b-0592cb44a691.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5f3f177c-9e7a-4471-8c2b-b9c82196ac29.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5f64dd66-fec7-4e6a-a356-ed786534481c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5f975c45-c190-4aa1-9f04-20301c3ce827.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5fed32d8-3433-4fef-a31a-d07dd279f505.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!5ff2de80-5117-4132-be8e-fb640c017658.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!608b89e8-29cc-408c-94a0-332f4d5fa220.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!60b22989-525c-43d6-a2e5-d4763c7c35cd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!60b51119-3f74-4ee7-8382-022b876627dc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!60c64de3-a206-4ec7-a471-6231e2a87ad8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6103ef62-4d3f-4c45-b8c5-917479681fe4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6114533a-571b-4678-bd70-282e33652305.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!61334577-4c4b-4f15-a044-4e5b37ca99fb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6161938b-41a6-490b-9fc8-80549584b3c3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6162d9db-7522-44e7-aa6b-5cf919d931c7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6163f333-883f-47e6-b9cd-2ab8f58253ec.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!616f742e-7a02-4b87-9ca8-ad1ab6424ada.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!61a27993-72d2-4f6b-9f33-fc05b83b9a92.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!61b1bd3c-6cb4-43ba-850c-4f008020cf75.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!61b2b409-7935-4a49-a77b-b8385651afb1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!623b5349-f07d-4124-a018-ea4203f0f584.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!623c857d-300e-4b82-a7c9-841ad3ff5720.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!625b9cb6-df51-4d54-953c-f0d903286af9.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!627ac2fa-8384-4c2d-b14a-57cadffa9307.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!629fa0a6-d0de-46cb-8670-c3b78b5b026c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!62ecec36-777b-4e75-a8e2-ecc1b5acc989.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!636119e3-4107-441a-bb92-ac39aa04e28a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!63821479-398e-4f03-bef1-e050ee0cc7e5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!63d0ca04-40c0-465e-b31f-52c008241771.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!63d4aa81-eadf-4d81-913b-64e26851d093.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!63f904f6-a1cc-474a-a43b-06ec3d07191e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6443867d-f5b3-4a85-8df1-57aaac003dd8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!648fe408-e54a-40e1-b148-b97162fd6c61.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!64954fab-f87f-4cb7-9bf0-88829f91b25f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!649e9af2-3160-4a44-9bd7-ecc909f51f69.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!64a42e40-2bd3-4b35-915c-2034abd3fb37.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!64fd2ab6-fdad-4299-8278-61769de3c386.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!655e242b-68d7-4c73-b053-5a398ef9daa0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!66097526-6373-42e8-8d7f-9930899d42ad.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6613b5ce-70b0-4286-824e-6cdd5504e5d2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!663e92df-0e24-483d-aad0-7f8745b7eedc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!66d37076-cdc3-4a7d-82fb-2d53dd9a083d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!66ea76e7-2fec-4397-bada-48783193a2f0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!67345f81-971a-481d-92a5-c93a89168247.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!674e4ac2-fa73-4707-b8ae-5c179c076c51.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6794ef03-6d4d-4c47-8285-a37cdba4d64e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!67cd0076-0a88-470a-be4f-796239108eef.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!67d57362-ce3e-496b-929b-a8da87cd3862.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!68422c19-788b-4dea-acfb-5e49f4800990.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!68467b77-4a4b-408a-ada2-41ac4613dffc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!68b476dd-95a5-4e4e-b202-35b81e5a3731.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!694b53f0-000e-4c58-8f80-0ef9076cbeb8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!69906978-ce3e-4f70-b89a-380c69b13982.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!69bb2ff8-6db7-40d8-b45a-ef101cd969fa.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!69e5c80f-58f2-4615-8044-86af98bf3086.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!69eef5e8-ab54-4eb0-8e69-5198a4dd45d1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6a3216f7-5afd-42a4-b021-02dfb397321e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6a54ee0a-5567-4e89-ac6d-c2902bc4fdf8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6a5ecdeb-e183-45b3-8a30-7bbc3545aa27.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6a9002da-8c11-4e6f-8ad3-15d79111ddcd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6b81c752-01e9-458f-b261-bf8d4688166b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6b8a143f-ccc9-472e-b077-bcc0e044fe36.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6b9cc525-e7ea-4fe0-aa3a-5ffd31d5faad.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6bcb38c1-4958-4e3a-bdd5-32f52b58a3f7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6bf138cc-5bfa-4fd0-9c04-c2d5f072ccd5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6d840bfb-0045-463a-b226-727c278f155f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6d93ea42-f79c-4385-bd9b-5e0fba1a5909.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6de3378e-baae-45a3-8a84-03ec11148bc3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6e49ac34-422c-42d2-9c77-305b68a0ac54.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6e7c7434-fd9f-42ae-8cc4-aba1e2ff18aa.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6e9d7e45-865c-4867-9f4d-0e96f3921c35.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6ee08cd7-3540-489f-9c6b-674b298737d6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6f7a879e-b2cf-4a20-bec5-5c09de93c4ad.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!6f829ba4-b4e3-433c-a860-8e186e30ac85.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!70c4999f-08e3-4aea-8973-ebc3389c5e35.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!70ddde73-376f-4562-a097-b1424f2cd70e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!70edd37a-7220-437c-9619-26748a492a3a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!714e6e75-7832-45ef-af06-bf66039166f8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!719db094-4cb1-4cb7-be57-3bbbf6e34ec7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!72315498-eb3b-44a3-a008-c2d46463daeb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!724a70f3-b99a-457c-a074-b5899daaa4d5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7285b815-f6cf-4cb8-904a-1fcee299d77e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7295f5f1-1342-408f-99d8-d2c10bc86f23.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!736868b2-3f5a-4688-b391-790b7d5fc5e3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!73ad57ab-b839-4fec-9af8-814115c27e68.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!73fbafd6-ca4d-4f7d-bf00-b2c878f36891.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!73ff3e24-d56f-4bd4-91d1-e95725bb5139.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!74eb5bd8-4757-4263-9a6c-e77c9c2ae6f7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!756e9840-5e66-4d10-b93b-7a7b6cf38832.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7571dd3c-e54a-4c39-87c0-33cbaeaf9648.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!75d61410-1198-40d6-9a2b-3c5da0c3ac92.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!75e6c3a8-5e47-491b-82f3-0c7000df0f2c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!760b828c-374b-466c-b27d-39b04a2af257.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!761ee42c-bb77-43bd-ac52-46a1a00a833e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!76fa27b9-c343-4c79-b500-7821f787592b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!76fb2de5-6c22-45ad-a984-e15fd2f4b93c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7767a63d-7556-4b2d-a91b-485df33a6889.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7784e064-fdab-429a-8505-042653461c7a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!77dc5e61-c8e0-4be7-b604-4e1e2ed5fafc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!77ebb6f7-97d9-4846-91b4-208396eb89e5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!781a5078-228d-4a04-8371-0071970a913f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!783efa63-029a-48bc-9d7f-789811a1c43e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!786de287-e4ac-4269-90ee-0f8d1c99e2c3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!787d6c56-a077-447d-958a-1183108265af.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!794406d4-3ad0-4436-af85-3320115799fb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!79519d6d-83b9-4bd8-bbe9-f85296401068.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!79da8d0b-947a-44f4-8b63-213d4a276eba.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7a2d5031-ff86-41f2-bb18-204ddfd631aa.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7a452301-05b9-4114-b5dd-ed8de24c97ad.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7a48212d-d1a2-4680-9bff-f6dc2e322c03.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7a4d1809-bf26-4ee3-8c9b-8de0470fb883.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7a67a249-01d7-4cd2-b885-c18781d1b2a0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7a9dfabc-f519-4adb-ba00-2dd6c2827e08.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7ab35026-dfda-471f-baee-3c0c8ea03eeb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7ae7061a-0931-438a-b905-c1e54f48c474.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7afb9033-4f15-421a-981e-f4161d006e85.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7b59eec6-53fc-4b9d-8c5b-6f3ecc5a4ebe.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7b7d15a8-aa1c-4044-a759-2947b9131324.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7bc0b3a0-f830-43b7-81a7-a31c52fafe16.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7bd17e98-bf49-45ab-b1a7-bd6d85035a07.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7bd5547f-aac6-469d-892a-53f3f9f71d0b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7c4f45e1-02d4-4e48-ba3e-4a637f3790db.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7ccaf774-35b4-4116-ba95-69316b85dd35.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7d27f50f-1d49-4293-834f-3663e16a98cb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7d42d75a-7c2b-4fa7-9031-c7cb005f1967.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7d488e6f-f188-46aa-a140-22fd49e22f0b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7d7f7373-0ad2-460d-9b46-c2d57eca8e04.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7d823240-f3bc-40b8-9e7f-c6f1b77918e7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7d980a7a-6d46-4034-8f1a-d119b4350f05.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7df9eacf-941a-4828-a262-6d132c095dcd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7e4d28ed-c284-4354-a018-78072c374052.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7e79d4b3-fbee-487a-93bb-786d259d8c10.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7e8eda49-46ee-435e-9a90-717192068160.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7f3ec66a-08ea-4461-ad43-d5309502ad88.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7f515865-7563-459f-9f89-ec4649ccf6da.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7f9e76ad-7803-44fc-ad43-24c1fd7f2914.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7fe13852-666c-49eb-a91f-da36122a1794.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7feecbb9-b6db-40a7-9e06-05a025a39d7c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!7ff3ec0a-f857-467f-bf42-242056982142.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!80213af7-e194-429b-9a88-043c0c556e4d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!80336cab-19d9-4cc6-a81c-b34063b8b87d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!809abe8b-7091-4df1-9229-1749ceda56c1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!80e7ff58-53e1-473a-9a29-ff5e4f033f1d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!80f697d5-8f52-467b-9526-f4709689c5a2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!810287ef-4020-42c4-9a00-2c22552b409f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8179f1be-f633-4867-b554-055946a9c9fe.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!819c4318-5c18-41ad-8bf7-7148cbdc94ca.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!81bf0e44-2afc-4adb-8906-b912d7324dea.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!81c501b2-21cc-43d6-bc92-a4619ebc3d02.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8240981d-9292-4c82-b263-3d478bff5064.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!826ff981-e71a-42c8-97b2-95e36dcf2b5b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!828c8579-9c75-43b7-aef3-3436516361df.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!82aca1f3-e6a0-4eb0-9850-0b4225383cdf.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!82e303df-50e3-4d96-a685-ff4f8b4b2ebd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!82e9aa2e-4cec-4325-8901-5db37fdfd0c7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!82f61d00-9565-412d-8dbd-71677ce2b9bd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!82fd4e98-e5f6-4b4e-a63e-778f37d95e70.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8309da6c-6419-4d42-9700-c510705f6611.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!830e080b-c885-44af-ad2f-06ca60fcbf8d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!83531791-e6c8-46cb-81b5-474e0a2aa330.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!83ec3f44-cd1f-4300-8af7-d2b5c6aa1379.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!846d6318-3637-49f6-ad26-3ad5f7a72e6b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!848aede3-f8aa-4f24-a4d7-6ee8f5862ea3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!84baf477-3e1d-4af9-afcd-a1c6480dd897.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!84cba453-dc0a-4db0-8663-bebe9b729d42.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!84d82be2-4f5a-4a4b-9709-e81253736f47.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!851fef2f-538f-4b64-ad62-46d399c0b9fe.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!85529642-880e-43e6-b628-5d83ee0a3499.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8569d782-7a72-4a08-ac99-48364a0664ee.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!85724c05-e2bf-45b8-859e-c27ba22d6c06.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8610348f-6220-48be-af80-c2e3e3bcaadb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8614e7a3-8af0-45f8-bab1-f3c5b3c988aa.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8666776a-5c94-4480-aba5-fa03c9610b60.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!86aab75b-c414-4ee9-a248-69481d8970d5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!86c30f5a-174a-4ead-900a-01b8f6549833.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!86c3b3e4-dd53-4362-97b0-f813406b7e93.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8708eb0d-45fa-44a1-a933-daa7f037d6fb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8769b30e-93fd-4878-8630-6cd79bf37e3f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!87b12842-e6cf-4edb-adeb-d04d9052ffbc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!87bf5cff-f62e-4f7f-9bcb-f095bd027ad5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!880f5cac-8b76-4be0-b6a4-9a5b1a76d7cb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8863ecac-3c8f-4c49-9651-9daf581c3661.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!887df1bd-70a8-4ee7-acaa-14944486a0f9.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!888fadf9-58d1-4147-bac8-e192d87728db.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!88c986fd-f3dc-4fb1-a36b-2c31aac6e82e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!893de46f-fd7c-45b4-b161-64875029bed7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!897dfd06-82e4-46a4-9534-05f09078c8f0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!89824cb3-e607-486b-aed9-a56ae41b6a28.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!89a36790-14d5-47a8-954e-cd8df0cc735f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8a4ce707-c4d8-4416-8111-03670b92da13.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8a5b653f-2bc6-416e-ba2d-881b9f891e32.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8a5e4f44-5a35-464c-b465-5310afbb21be.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8ae39bc3-8ce8-4e10-9baf-34771e6af458.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8af94adb-44cd-44f4-8d94-c6d9a495c826.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8b371195-6702-439f-950f-f71e2ee233bd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8b7bd3b0-5063-4881-88bc-5dbdd029202e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8bc1e90e-c2ea-4a08-ac4a-c1bbf7421f30.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8c74a87c-03ad-4f75-b928-b7c9e01c0cea.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8d00edda-9300-4834-acb9-b4fccb581886.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8d3461c2-0ba7-41d6-bdcc-3b1f37109334.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8d8a7616-affe-4ed2-9d41-d5b9a3a5ad4b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8dc2a80b-7e5f-4eaf-9b7a-4b59b5979078.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8e345f62-0a2b-4399-9815-4420681fdcd6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8e583211-dd76-438e-b980-c64e99a3db2d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8e6157b9-51dc-467e-be1a-ed2ccc50cb7d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8e8a5d26-9841-491d-80dc-9a1ed1a03ca4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8edc03ed-f541-4147-b780-dc5d771bb2db.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8f913ee0-31a1-439c-9f91-b476b6cc8595.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8f9a93d3-3222-469a-8c25-1ca5bc7e98e1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!8fb8ac48-f2b6-45b8-98bd-daa519d242dc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!918fb788-49a9-45ff-afed-7e5a274a00dd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!91a34035-db1e-471a-b69c-ad3c1e7f86ab.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!91c7d204-3bae-4d5b-ac03-015206ff86df.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!91da77d8-35ec-4894-b17c-dd9b787abcfd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!91fcd66a-a3d5-4452-a7c4-5bc8c7634727.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!933fc143-21d1-41a1-b2e4-1259485693e2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!93407142-ad96-4e20-a7b0-23781ea069b5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!93579f05-47b3-428e-954f-637ca1a635cf.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!93c6717d-79a9-4f5a-b447-a4d148ce55e3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!940c863f-ca7a-418d-86ad-0f561a7a5cc7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!958ce060-b2f6-49dc-96cc-7203cb6e4048.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!95c24965-59d9-4197-b203-9d28fef41c32.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!95c24d4a-510b-40f9-8703-e82ce2aacf17.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!95ce6440-c5a3-4c78-a40f-0a6bdbe8f11c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9640253d-cbe6-4e63-832f-c8c5862646d3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!96b66bf2-632e-499b-b51a-dffe3d74b7cc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!976989b0-3036-499e-ae81-1614a027db98.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!976fd366-5337-46ce-b252-cf7afa0c3368.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!978060e7-a3b5-48c8-b2cd-d186fcd1c9b6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9799cb4e-dcba-465f-9c75-49f3ef5f1acd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!97abdb0a-a490-4528-8b7c-0f3dcf7b4325.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!984d09f3-a2f6-46b6-9fa8-61860a74f85a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!98c77d4e-fccb-45c2-8a02-6ee2a5415fa2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!98f30fc1-da53-403e-9e2d-6034b50c2f9c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9903fc3e-0984-4dce-aa04-3f4dac0f541f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9918eae5-18bb-4aca-8963-a0fe59d9e781.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!991f85e0-6f1e-4d5c-8019-d856349e14b2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!99770653-f825-43f4-b0f5-edf87aa4023d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!999bd22f-b7b0-494d-b5e9-4b7c824d3382.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9a0f8e15-86e1-4e7a-8b9e-961cafc45629.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9a66a615-7ae6-4952-ae92-efaafb819cbc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9b54e996-fda5-4e27-af77-d876eb7f9f7e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9b56b840-1819-409e-b6fa-66919471c651.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9b70d17b-8976-4368-afb3-51df6e632860.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9bd82993-74a9-453f-9764-1b3ad5ac7f9c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9c385667-6e21-4ee9-b010-f5dfbce33062.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9c38ece0-a2f3-4ad8-a171-5e8ffc161350.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9c5a1e44-8cdc-426a-bc9b-aa6f6c74e3da.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9c719a62-7841-4e25-8b93-ee59f7c20233.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9c7fac0d-445e-4a84-b7d5-45ff59c027e8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9cacdd4f-d708-4357-9ac3-175f64b7b163.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9d64cdc2-c11a-4185-af80-377fd2ffde6f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9d72af86-5de2-457e-b03c-8e8c9acf1d72.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9d991242-911f-4b3d-9d3e-657c09dabd11.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9ddb6a8c-45a9-41e6-99da-eff96797c186.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9de8324c-ce97-4a13-9962-0057c558bd19.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9df30072-b3c9-404d-b773-bf9ef89858ef.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9df59639-d274-42c3-bac2-93809e5baa22.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9e2765f1-9e81-4164-926a-776c17efcee8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9e5359b0-024f-4ebe-969f-672a199c5b73.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9e62b6f9-23ca-47dc-adef-22c908593a21.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9e806174-f27a-4fd0-b419-73a20fb1bc17.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9f04c67d-e38f-4ab3-8cf6-4dcc2d4dcd7f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9f52feb2-65ae-4652-9fb9-d7efdd78a259.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!9f95db47-94b6-43b9-9094-f523295f14a4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a0574842-95ef-4402-b003-514936196fc6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a07c9e8a-27ad-4a53-9aa1-b4cc17a031f3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a0d73922-5fe4-4631-b0f3-0b6724d66e17.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a0d9e00f-87e7-4168-ae85-472f7e162dda.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a1025da4-a811-4068-8416-16202f7e899c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a1dc84cb-462f-4183-95f8-5203c4080be5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a2116151-c4ee-4f0e-8d25-cd582c2a2751.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a2248fa0-e9a8-4742-94ee-00b919cd5368.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a25a6e7a-e7fa-467b-b26e-0b80d16e9617.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a2e8690c-17ac-492e-a0e0-b56fa854f91c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a31148c2-7b4c-467f-91fc-0362b8a340f2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a339bd4d-dec7-44b9-873f-a28fc6b451bc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a3555a97-c6cc-45ed-bb72-b20061cd7e58.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a36b9b6c-f204-42fb-9cdb-e71c6207e82e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a38286fb-fbc1-45a7-ac24-1b30f49c2f9e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a3d2b5e3-2de6-42e3-bcfb-67dfe8e5ae1b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a3d2d4f0-d27a-4dff-a16e-cf8f6ab078c6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a440aa7e-13c3-47b5-a707-7c825260ab2a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a45a3767-fa8f-4450-9d3f-4b620878039d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a4a49991-8cff-4045-8cd2-c9fa5ccc6ed7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a4e3366d-7408-4a05-825b-4510bce62b4a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a5194fad-bbaa-4f01-b5b4-78f3347398b1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a5274826-976e-42ba-9cb5-439c4f780b99.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a5abe2dd-6ba4-4c54-9401-9b88fa83cf39.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a5c46de7-eeef-431a-8b35-b71fcf81d12e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a5fa0d25-4d76-444f-81e8-00028f2c07f4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a6a4b783-b5c5-40b0-8237-052c81bb7c38.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a6bdf3c4-5b6a-4eeb-8eca-bdc14058a8ae.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a6c5b19f-560b-4e98-acbf-e6646194fe2a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a6dfa64d-5f6d-4d3e-b92a-bb78fc41db89.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a6f4c62c-1ae7-4bb9-902d-8ee9b282c72d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a70a38d9-cf3e-4e5b-8501-c964ab7cf20d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a76461bb-23c8-4344-a932-34f602d873bf.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a764ac61-393f-4d3d-a45d-314768f99380.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a7710298-8c29-421e-a758-2de643aec29f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a77ec1c5-4d57-439c-921d-8e116de97214.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a7b1cfec-7d6a-4a88-9c21-db876816ab34.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a7cb2ce6-4777-4e26-b420-deff47f05fdc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a7d07d48-5235-45ef-b673-a38f2289db4b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a7d3ed16-2c24-4b3e-8cdc-d36826460fae.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!a8fd042e-ce3c-446e-beca-00aef4418597.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!aa2d7528-a27a-47ca-ba4b-72fd12946f17.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!aa45208c-ebc2-428d-8af7-e893f7378169.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!aaffe828-6a01-4a5f-a081-04f7606df81a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ab61fbf0-ccfe-4ada-8772-dff5c3a02247.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ab90ed9b-50e3-4031-9393-24fe8e992fd7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!acad28d3-9f1b-442e-bc4e-dd7622f91fae.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!acca7ef4-f7d3-4b7f-8c9d-ab00dd4b6645.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ad0c5018-db28-4851-a03f-d94497e059f1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ad258585-b8b1-4a5d-a67c-c2e6d7d95be2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ad38071e-4ff5-4fbc-bb94-3f22963b44c1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ae865b74-d384-47cf-bda0-e30c42c99d2e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ae8d8426-24c3-4465-822f-8abe89e9712d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!aee0f24c-933d-459c-9a3a-a6e2bb506e05.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!af3abfac-532e-4a28-8214-3468dfa95e5d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!af40349f-0b62-474e-90e8-d5f6142245fc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!aff2e91e-3b3c-4cac-bba9-6f2876dbf441.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b078e4f2-a8f1-4792-9fa3-03f2ffbb1fc2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b0c2ebf1-94c8-449c-9c0c-ebb237de45e2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b111a966-160c-480f-b0d9-a17e14777e4a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b12bbc43-f6c0-4115-89c1-69635bc76306.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b1e32c37-a825-4f3a-9c2b-766cc0ce82d3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b241102d-a7c9-40ab-adbb-ed4b3d80e489.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b25b1552-cf53-4079-9710-20163fb0a69a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b2767de4-2c85-4923-aa2d-57997602c958.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b27f17da-3788-477e-a9d9-666316736922.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b2a5072b-65f8-4e01-acdf-4575d9707dd3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b2e0de4b-42c8-4e71-a4f3-141e77a9700c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b2eb4fdb-5e67-49ed-b181-ebc9cad9418c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b34de8cd-f3c8-4478-ace1-c10f9bff4e96.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b3acf88b-80d0-44c0-abee-6dec4fbdedbe.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b3c3ed1c-8b2a-4884-96c9-ad76bf6e7735.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b3f6d482-5e18-42de-a0f2-baa60d02ab8c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b441a095-ab08-4c8d-8771-1a678336e46d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b554a46a-e88b-411d-8362-e4b63783a693.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b5772f29-ab76-4fd8-90b6-8841dcc0c4b4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b5f6dc35-fdf4-465b-bc91-3d4dda471752.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b6df2f90-8cf9-4bb1-8ec9-71a0c89f6943.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b7bb67df-d7b5-413c-9483-34b3547127ad.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b7ffcc98-4fca-4103-8c68-8020671345f2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b800cf2f-bd31-45ce-90ef-11044a266ccb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b8d90e48-18fb-4764-b31b-4773c77ce0b3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b922d766-d56f-4800-b6fb-3c81caf0a38b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b96639ae-9710-4803-8b39-9df0ba604ee2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b972ff9e-0f0d-4ec4-9846-3a09ab769eea.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b9760753-53dc-4463-827f-7ab25328d939.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b9a089be-25ca-40b5-8a57-a69956ebd8a1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!b9fb2acf-d5f8-49f4-bcf4-3bcc576a3b62.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ba401d49-e243-43b7-9f45-c82b6ce3814a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ba4f8e33-4721-4f51-ba69-41bef6d7d8a6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ba53504e-701f-4420-9c34-75998e4f512e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bb7ba32c-d61e-42b7-b8ae-e8c4386847ac.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bb7de660-24fc-4d72-88b1-03203e16fc38.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bb813719-a991-4ad4-b6c4-54ea1121a0fa.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bc138419-ee0e-4ba8-ad06-7b229e104dc8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bc3ab790-1d1a-498f-b870-668c925a54de.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bc66e53d-231b-4888-a46a-4ce01002dede.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bc7bc0bc-1144-4c19-a084-1d30057d9a83.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bcc03393-28e7-43db-9650-2b79bdcef35e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bd070c4c-3406-435a-8c86-c4ec56075fff.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bd79c444-2083-490e-85f9-95f701276c1c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bd946a3d-2de3-4d86-9c7d-3250c3120271.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bdf8a01d-b98d-4e22-9035-8ef6cf97f755.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!be1db8f7-31a2-48f8-9696-af290e33c783.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!be4cf1c9-df85-40c1-868e-aaee070ee786.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!be610b60-599b-492e-8ec5-f8f3e6586c7c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bf0d5113-12a6-4428-ad9f-ef42cab9fbb2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bf5f197d-ff0d-49ac-b23a-71a9f24002a0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bf6a8ef8-9dc8-4e6e-b373-d2823c1891f8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bf7194ed-55ae-4e1a-92bd-8157c6996fb6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bf7b453f-f0d3-4ebe-bd90-a9d65386a9e8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bf85b825-183a-458e-9fd3-eaf4a415fc9d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!bfda8075-aef9-4372-8c0e-971b78f31c3e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c046ab99-345e-4da0-a3c8-0b8e28c8413e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c0471a4b-bafd-4a59-9d5b-9733bae2570d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c06eea48-21ea-4ff8-9e0e-4f65c2ca27be.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c09d2c8f-3484-4711-aa25-83072d1c3e14.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c0a19744-d67b-4a63-a084-45a654dbd375.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c134c4a0-ffd1-43ad-a9c9-bf5f4a2d53cd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c1436235-896b-4e82-b146-5b5e592c497d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c18984db-4fde-4f92-bdb2-3dd6b3999e6d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c195e1d3-1885-4ebe-894a-e0904ebe926e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c1a8707b-63d7-4936-b36c-d05ac2ebaa2c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c250d917-ee64-4e90-a6d2-651f2b2a2d88.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c26c228e-7235-4c28-a843-5813a313708b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c2aca3c6-d019-4b90-a7ae-bf52243a87be.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c3001c06-6095-4a91-949e-7679ea3dc2b1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c3348754-0a43-49fc-9c6a-9d3d5919bc38.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c3621a39-9518-4245-a40e-0bc8301bccd3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c4a6bd63-fd2e-4e1a-8f4c-bffff882cfc5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c594a37d-d3ff-425c-bacd-ec27840e072f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c5bd5714-fd0a-408a-8e35-fd915039e3f8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c5d427c8-f341-498b-b223-a3181e4d063a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c5f3ce90-7bc9-4566-9122-26b76f7a5b82.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c60655c1-3c28-4abe-acbf-4a1f75d0c2b1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c60e70cb-b5d3-4a55-b901-c0674e5eeb2b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c6368aa1-bd5e-4079-bcf0-b703ef01efb7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c6afec49-f553-42a7-a1e5-649f03c1a9c4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c6d378ad-cfa5-46f5-971d-2a5c4f98a58b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c78375ba-9d68-4232-855a-dc2caff25dff.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c7902733-21c6-4766-bb64-eaeeecd4db89.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c7dccfe0-bf72-41a0-b1d6-90c2f19fecc1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c7e9ef5c-6997-4cbf-83a0-3f4989843881.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c7f047f0-2e9f-4ecb-8162-63fc1896df62.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c85ad497-2928-448e-85d7-5b79822db861.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c87c6ea4-9637-48ad-9e42-0c7ba695d282.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c8d11a3f-4490-48c9-be9d-676feae9eb93.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c8e6fc0c-3c97-44f2-9a28-ddd498ae66cf.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!c9c8f194-886f-47ba-9fe6-9ff098a4f4d6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ca867141-5304-46a8-a2b4-c067b725af37.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ca89c627-c193-4f39-8364-4238cf3cc840.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cb3632d2-c7aa-4600-a10a-b7a16fce03fb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cb82ab3d-591d-494a-9979-a2727bc5626f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cb95e28e-7611-4243-98eb-bcb5d91a1440.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cb9659d4-0c9a-442d-b61d-ae5d6f068dc3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cbb66134-f183-47d2-8147-530f332ef71e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cc4f6155-951c-4d35-ab65-a1aaef2f4a18.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cc710b9d-e9c6-43c3-8199-3a1d1565cb85.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cc781b54-415b-4ced-8fc1-83dadb298354.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ccbdcb78-905b-4a3f-bf84-b574d700d827.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cd044782-4b57-4b87-a422-69263d7f8265.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cd518557-08a0-4081-83f5-d97655d92682.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cd8acfbf-98e3-4c8e-b357-ed2e91f6dab3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cde74e59-5080-4b65-8e79-01dfba4c6d18.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ce0216dd-9102-4f47-8846-d7ebeb0dcd00.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ce6c2e13-491c-4382-a3e8-6c4c05100ac3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ce7014c2-83e0-4e99-8bb2-046bdf39f048.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ce711e5a-ff55-46ab-81ae-38e10a068802.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cec78c8d-372b-4d8e-8c8e-a57272f65076.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cf510c51-11a0-4849-a72d-fbd19eed48bd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cf5f7944-dfdc-4e66-9e9c-37efe31eb558.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cfa0fb00-d325-48af-9b8e-0b6b4511b870.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!cfc61302-0225-45d3-aaab-97482ea12090.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d045de20-8e6f-4bc4-bd08-55a26d7c7389.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d053e218-2768-4ddf-bede-ad442f09b640.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d0ed4630-bb46-4a2f-adc4-e80b0b5fff96.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d10775bf-c0b8-44b2-8f66-b08087d04a2b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d18be333-3e3e-435b-89dd-427bc6363062.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d22bee42-784c-4514-9cf8-8deb26d59a47.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d23be372-cb1b-4c05-8bd2-0ee06410b4b2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d265564e-e623-4abb-a4d8-190370fb3b38.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d26ddf63-75fc-48d2-ac42-07d671ebce7f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d30ca369-1e7d-40bb-9d0d-1c8c3660e954.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d3273daf-a9b0-4959-b260-01432187448c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d33bb1c7-ac15-4216-a505-2d76262518c4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d383a4e1-6299-46cc-8663-02a9023cf9c1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d3be721c-bac2-4e28-bfdb-08aff335c0a6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d438f303-39d9-4192-9086-9f80d2758a95.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d49393c0-b749-4021-9014-e1547d92b706.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d4a89799-91c9-48db-ad06-f3f9596be9ce.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d4bf9709-af5b-4911-803c-044c4fd55e2b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d4c36ca5-37b3-4524-88d2-8dccfb78fdce.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d539f896-49b2-4f3c-91f4-a09f3880a084.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d5ad52e6-adef-4978-ad2e-9e8752b50862.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d5dce4bf-1f4b-4749-84d8-b810cc8dd696.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d61b5f7c-12e5-4bb1-a68a-7ef989aa2c4c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d6a53b5d-bddf-4ae8-a209-886e16e72ce9.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d6bacf38-0765-4261-95a2-71c10352de6c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d6c44000-0ed2-41d3-9ffa-9f15695279b1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d6d26b7e-be16-4927-acfd-e7caf6eb21bb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d6d4a170-d644-4302-80d1-8f6131713b1c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d74867ce-7807-45a8-b030-35d6e64cfbe0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d74bca5a-1521-442e-87f3-3752bce676d5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d7d39ea5-a57a-4c36-99a6-8dc9a6cf3997.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d8039263-c790-42e0-9259-10161305b10c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d8c19a32-07b5-4616-bf6c-0664d2c6023d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d9118d53-0acb-47f9-9475-d5ec4f8d3bc1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d933cf65-9047-4d4f-81bc-8abcd6c338ea.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d95e5f97-1a22-4ae2-b554-5dee707b4895.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!d983e058-2c7e-4711-ad92-6331e6b822c3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!da61e076-280d-4532-b76b-df3e49a99c39.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!dac02dbb-8cc7-4792-b96a-de2f6bc5375c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!dae742c7-dfb0-446a-a509-58e99e25c836.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!db4f2d65-2d09-4d2e-a25b-f25b4419e87a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!db9b1115-3dc3-4144-849f-f460b51d9d75.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!dbd402bd-9985-4b34-98ff-221fe63786f6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!dbf45ed5-7b39-4a55-9a32-3647a375b3ac.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!dcf54a40-5ee2-45ec-9466-6acceda82493.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ddf1bd0b-814b-4282-86b3-9be7f428218d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ddfed76b-dd25-46e4-a375-dcba0138c06f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!de5b1570-b28e-494b-83d1-416e5cdea3e3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!de5bb326-f9d5-4b47-b72b-c242dc2001f3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!decab043-01e1-4e48-b3d5-4906937c4b05.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!decc785c-12d2-4519-91b5-7ee133c243fd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ded508f6-716f-47fa-af07-1bc51e02011e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!def0c346-1d37-4fb5-94f9-6a60adfcaf28.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!df4406bf-991b-4629-80bb-81b398b6efc4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!df63f51f-0b81-499b-92e6-bec76301b2a7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!dfdab292-e4e1-4ec8-9f33-5772b72e94ec.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e0a9a21a-5531-48e0-811a-a0c500d74773.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e0ba39a4-0ff0-4d7a-a9ef-a912a7eec9a5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e1b15766-67e9-4def-97fe-f85df1534ec5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e2100b51-5cd7-4fb2-9f4a-9fa53ca36631.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e25d2c90-e67e-4de1-80ef-4c3d2fc8d390.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e25e8fe0-0255-4540-9671-97a405fe5c9a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e28ba2cb-9086-4232-84c2-23edeefc39e3.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e2c7f584-bcbe-43b7-8afb-67ce6d20c12c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e2fa5556-3258-4e0e-9c89-f864f172be90.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e2fbeb66-bd5e-40b0-a8c7-fe3aa89c9322.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e380a8d3-316b-432f-aa9c-7828f76db67c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e3a1236e-d962-4a23-8a9e-1408e3bf2a3f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e488ffdd-f384-41ca-bd3a-4b39bb94d612.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e4ae4070-6afd-4265-8346-369fa1152975.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e4b752c0-eff0-401d-a638-d04e2e3b6d9e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e4fe59c3-ea66-42ed-ae48-f79502a29089.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e511c2da-ac81-46bb-80a2-cb6c96f8bdc7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e54a68fb-35ee-463e-adc3-3860b0009430.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e57f9d10-e29b-4d35-937c-ffd3d4c1f836.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e5af2e4c-6ebe-406b-8546-ed74864bc2f6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e65f0abb-6c8e-461d-9179-63f7d12f17bd.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e6cd4d3f-8fb9-4863-ba38-ba11eda1678c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e7662a5e-a03f-4cf1-b146-24016265f243.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e784d386-7e5a-46c8-9eed-c82b7b76f8f7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e79d0c3f-b28e-436a-8548-291c44971bc0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e86555a7-663b-4015-82fc-8419d18e4dfc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e865fa7d-3ef3-43c2-b795-d7800aba783f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e8f4f551-a625-4629-a761-fc8a8c43155e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e93d0da0-2748-4684-bf03-f172d66308e7.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!e9586638-033d-4bf7-bf12-1dc34049ea68.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ea24a093-a472-489b-8e23-54969e6e5301.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ea4529ac-405a-46a0-a3da-b7b3626ff077.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ea47d3eb-792a-4ba4-97c9-69f0a3c57731.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!eabdab62-a67f-4278-832b-798b43f74084.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!eb377b96-8b92-498e-a9a0-29649def6c09.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ebb7bca0-6d0d-483c-a5da-8604b9247ce6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ebbc7459-8a49-4551-815a-0cd82d4bff76.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ebf932bb-81e8-42a1-80f4-130ca6c63579.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ec199ae7-14da-43ec-9ae2-fd642f9b494d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ec326a41-3e29-42a8-b03a-0aa67a291a6f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ecbdbee3-c469-46f0-840f-2c8defa0d489.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ecd9f0ab-3e20-457c-8f1f-21fbac6b4580.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ecdd7831-ba15-4137-93e2-14339d7b0075.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ed44b30c-87cc-44e2-b2b9-d980d1804dd2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ed889161-7f2b-4e35-a2db-172ea6bd6b8b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ed9760e9-39fa-45a3-904e-89a003b24346.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ed9d3267-95c0-4fec-b687-882c98e0a990.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!edabb2ee-09b1-460f-b718-04c6578b2530.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!edadd699-5e5e-49be-a4c9-7eee94036a03.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ee0b072d-cb98-486d-95bc-a205e861ca4b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ee5a8c9c-e03c-46f5-821d-5109413a2f67.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ee65ca66-0d4e-4a8b-a7f9-825b05a6b1d5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ee96c3b4-e8dd-483e-ab60-eb2d3187332a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!eeb86305-cd5e-4a08-a573-17f3f316cc84.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!eef8b7e4-8616-41d8-88c7-0cc316fdab15.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ef30f611-473d-471a-87a2-76e5ebf87e81.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ef6ad6cd-ef80-43a4-97ae-46fec076933c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f077b013-67c4-4390-bda5-b818e4369b2d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f08a25f5-8a88-41f7-8a85-bcd166612307.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f0a3f6ab-cd54-4820-9378-648c1167e803.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f0ab6959-16c0-43a1-a779-1061dd46318a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f0bf3c1c-0589-4007-b6cc-e70bc76f4539.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f0ea32a1-2c37-44a3-8266-e2983739b247.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f0ed4b69-505c-4423-ad4a-f588edf85983.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f0ee301e-1140-4b84-ac93-3ceede0f5090.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f1144f3d-ed1e-42c9-aaaa-8cfc936d4961.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f1206f9b-00ec-4479-9de4-19eb62ec2ca2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f187a57f-b9ec-4409-8533-7672b56a4437.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f1fb620c-967c-4f30-878a-e17fb1ede7ac.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f244a993-0ae3-4091-b866-7a2ef326ea73.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f2956010-fcaa-4474-947a-3d77d889e3af.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f337e441-1f89-401e-b9d5-560a7f210011.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f35a9de0-3bf9-49f7-806c-d30d192a8c7d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f3790c7f-c6d9-49bf-b2ce-636236e0d46c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f3a60a02-30a7-468b-81bf-13add7646fd8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f3fd25f4-4ec1-452a-a724-9d39c00f2904.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f443ea90-2902-44fb-bbbc-66d97e3cc67c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f4558fd1-8e24-456d-b283-902f6ff29a10.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f45b1d36-ff13-412f-b17f-a64ee194df2a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f464e756-7e8a-4b18-8785-9d5f54ac25d5.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f470fb58-ca47-41cb-acdf-a615f0b82d3f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f48693d8-60e4-49b6-820e-ad6150c26965.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f4ad97d6-f54e-4d7f-bf27-61d7a4eec94c.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f4e70d71-1c25-4407-840c-0442c50fd16b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f591cd2c-4da9-44e2-ab78-4802e06bca19.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f5dbd7a0-4fcc-4497-a23d-3d6f52c1e62b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f5fd4678-6a69-493b-9a10-d66d03bc3f4a.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f60c1baf-033d-46ba-be73-dd9cce98ad58.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f61575c7-93e2-4e91-8cd8-48a6b7c95b9e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f69039d7-90b4-42b4-9950-bfd155095924.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f69253de-b613-4026-829d-b2586ed27986.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f6984628-b745-447c-ad5a-f70b6434ebc6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f6b83ee2-2cf5-49c2-873a-22ebbd5e64d2.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f6e0edc0-0912-4242-9f8d-96df82e93ae9.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f7a624dc-40ea-4412-bf05-e5aed8ba75df.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f7e17e14-747f-4ba7-bdfc-be68d898fb53.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f865a40b-c6f7-4db2-9327-ff48a524100e.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f9623569-329b-44d7-b71e-b8afb4a9e5d8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f96597bc-ca74-410c-9eab-41b5055d7f77.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!f9a24a87-d4c7-4d36-ba82-f953ec8d5ca0.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fa291d6c-434e-4ba4-abee-b26bc573a954.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fa65f5d0-cbb3-4953-85ee-fafe5b155b2f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fa97b46a-3a11-46cb-977f-e898ac1fc544.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fb8501d7-1fda-460b-b9ae-344e976f2f6d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fb918d1b-2419-4242-adba-f947d84b0f1b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fbd80379-e48b-42c6-9936-37d42f4ad4f1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fbe34067-7807-4f3b-b228-a17cc23edb78.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fbed909d-3d2b-45fc-b05d-2daf52a3c1c4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fc2334f2-952b-4b8a-9d38-ff42f758ee64.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fc346ac3-47a9-4a56-97c1-465e582c261d.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fcabe18c-b801-4c93-a700-1c39b428957f.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fcf53b6c-9b21-480b-8a7a-7200f00e83d1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fd2fd43d-f4c7-4e0e-b31c-4a9a2232b5d6.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fd389908-05d5-4505-b350-598878fe9120.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fd9cf1a1-4bcb-437f-b481-8b85c704e7dc.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fe2b9d32-94cd-436c-97bf-bc44006fc7e1.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!fe5a6674-ac51-46a6-ba84-928ec8fdf6bb.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ff20bf31-83b5-46ef-9ae5-2c15be948c22.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ff30dfd7-0c9c-47c7-b992-4fbef3b97bed.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ff76473e-e874-424f-84b4-496ae7814131.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ff95f473-3648-45cd-886c-8c700018d78b.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ffa69c54-30d9-4777-bfe9-a5a3309b60a4.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ffbaeac3-6204-4714-b014-2b6e4a3d2a21.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ffc46a54-d0dd-4a3c-bd21-cd89d8a31b69.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ffda8b8c-baf0-4938-bda5-12d30ef39fe8.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ffe1fed4-8e49-4584-8f74-a1f9a58b70ed.json" /> - <_ContentIncludedByDefault Remove="data\rooms\!ffffc7e7-4b1e-40a9-9f24-ebd95267e758.json" /> - <_ContentIncludedByDefault Remove="data\users\@emma:hse.localhost\tokens.json" /> - <_ContentIncludedByDefault Remove="data\users\@emma:hse.localhost\user.json" /> + <_ContentIncludedByDefault Remove="data\rooms\!009948f3-9034-4a24-a748-a6391dd886bd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!00e1caa3-d6bf-4a0a-9793-0cebe26d4a96.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!011ed14b-f495-4c52-9f0f-b0fd0bd72f26.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0151df7a-3b06-4048-8b1c-11ea88987ceb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!017179e2-c121-49c2-ad06-197f4cf39155.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!01830986-341e-4622-a51e-6368a6064c2b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!018a12f5-8ed7-4dbc-9285-2165ae1ca39a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!01951dc5-d87a-4b27-b444-809f4907755b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!01d8efb7-52b3-461d-a95e-f16ca6ca5888.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!01f2c6dd-1bab-40e6-ac62-fcb5abf15e73.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!025c937f-13fd-445f-af46-69bda3d08d02.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!02d2dd0c-e57b-4454-a197-046d81f8d7bb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!02f00a13-d09a-4b42-888f-f6651d7882b8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!031b3eb4-d25d-47ef-b3ce-e06f6f4f154a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!032dd423-f2d3-4f3f-9b8d-10febd9de20b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!03ba6761-0bb4-4bfa-b895-6e4428795410.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!03ce824c-450b-4d13-a8a5-43a6ba4f1da6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!03cfb867-3155-4ef4-8e2a-0939e6837ec2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!03e4b54c-c41e-46e0-b47d-a6486fe524bc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!044df1bc-13a1-4496-9ba3-bcf60d45fbb4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0482a9e8-a7a1-4e66-a98d-1735758f8013.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!04ae0100-c074-4a0b-bb29-5c88b3199fb4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0511d862-353b-4cb9-9392-04eb3d6b756a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!054b1613-1757-438b-bfe9-73b501390617.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!05877fed-59a3-430b-9b04-10d70ca9aef1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!05d2ac50-d014-4e72-93b1-d8e18ee4cf8c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!05e4883f-4784-4a25-9748-d9d8ce4d314e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!06475f48-8175-45e9-8cf0-15741f27ad8f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!06b3a783-949c-42d3-a4d4-813273e3bab0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!07b2ed26-caba-440e-8cb7-172e311c1d78.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!07e9491b-f799-4552-90e7-242413ea69cf.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!08602758-2108-45e5-8f08-baf57f8e90df.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!086b0484-a1ed-4c04-93f7-2f73009570fa.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!08fc4a55-9107-442b-961e-5353203623e0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!091cf52d-7932-4510-974b-c41ef0d05787.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!093a2862-3c0f-487a-bf43-6ae4d7ab9278.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!09925e93-ec19-49bd-ba19-e7b0fd0bd029.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!09f14b3a-21d0-41ed-bb86-1a602b07370e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0a34a93d-db6a-47cf-a5cc-8f9354435ea2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0a71f415-ec9a-40df-b300-c3fb79d4dd13.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0ab184df-be0f-4877-80f8-0aa34d5ad00b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0ad9691a-d4e9-4f4a-8f48-86a8adad7f43.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0afbbfb2-caf0-4829-a7f0-06472c16cb4b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0b6f3cb8-37ed-4a7a-b95c-5d299ac73755.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0ba81dce-f4d6-40fb-84fd-0df52347eb99.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0bd9becb-401f-465a-8b55-f2f20b4cdb78.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0c1f5b9c-4b80-4c83-989d-3dbcffa6f74a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0d7ece98-e347-4082-a9a4-d5e4cd594bf0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0d877be3-7545-49ca-9941-191e3299b7fe.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0dd1c03a-0455-4ffd-9ecb-5777039d4165.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0e3ca29e-0270-4ab9-8b07-c76f4662dcd0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0ea8696f-f527-4e11-916a-69dd9e086d9e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0f08c6a1-47aa-436f-9a44-3fdbd5aff348.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0f6a7cc3-2f2e-49fd-a953-401c9a16c38d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0f9fcf53-412d-452c-bcea-43d6dbb3d705.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0fb1c6b5-8bb9-47e8-8d9b-a126cb5eff7d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0fb79121-1e83-4fe9-8bf9-f8a5ed364efa.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!0fc9886d-ecbf-4525-8f5c-a43fc4dc1b58.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1000acc2-a562-41f0-9f40-6eaf73b99f66.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!100204b5-f690-42b9-a49b-3fc3df91bdb2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!101265f1-eacb-4185-9470-269bf51b1829.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!108535e1-632b-489a-a9e8-e2fcf60d8c83.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!109f5a13-3a9f-4ced-a4a4-b0191a84b7d5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!10c5ff39-6ac2-42a8-8fae-292f01a5cb32.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!111e8be3-72e1-4200-83aa-241c6e98764d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!11381a2e-0b1e-4a56-b53f-0dd2cf65a187.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!114243be-a9f3-4909-bb03-8171eacdf1ee.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!116a3988-1763-45ec-b24c-ebb9e57faf4e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!11a149c7-ee5c-4ada-9145-052976d3ab18.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1214a2eb-57ea-4662-8b32-742678cf130f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!13207273-2393-45a6-8cc9-7c4fbbbc9736.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!133f8b66-d9d5-44e4-95e9-dde76cceee0d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!13ed7c78-7160-4ebd-a84f-7124479d0b3b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!14586508-6e3a-4bc3-a8ac-dbcb177310de.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1459a8ab-3aa5-4619-bb94-7d636f9a4576.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!14cb8826-1b94-4106-b530-0f9609d93679.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1524fc2d-6985-44d4-9d77-08f02e2df93a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1528dc79-229f-4a0a-9433-ca0d8fe81fdc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!15d52bea-102a-4352-92a5-f2817e879083.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!15f05e3c-4694-403a-a95e-10def22cb013.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!161bbd52-f6e8-4ade-a139-27209aab5ca7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!164a1783-6218-4335-aa82-b9db00aa4cf9.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1680277b-9540-43b3-813c-83bce9c2581d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!169d4aca-5385-4df3-afc4-5cc3f94708d3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!16a88afc-09dd-45f8-8202-98bb7a88cef2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!16c80534-e338-45d3-beaf-0e4e93695abb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!16d4b0c1-9bd3-408c-bfb9-48bb8ef7b679.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1709e1bd-5539-40fd-9124-0bfb5c62341f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!17308b64-da60-4966-bf85-409df16d5ec4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1749eb5f-f7ea-4eb0-8a77-7243123bbbf8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1761367d-a128-4ad9-9c7a-acb5a4e5a1b6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!176f30db-9b87-4a06-b3f3-895bd8f41448.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!178abf28-9ac4-432e-bac8-9c75f800fbfa.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!17e1e888-b9e2-4aca-85a2-48e5d1d0b35c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!186cbe4b-c6b2-4e2d-ad85-931c8d078185.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!189c6c4a-fa84-4fb4-bdfc-ab25e959e243.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!18c0440c-5c3e-4c80-8397-95658769c20c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1909d1c2-18e6-4f41-b6d4-01a0350ecb4c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!191141f8-0556-43e3-b982-142854e40385.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1940357b-f2b8-45b9-a830-d35e7f4f46f1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!19ad183e-18f5-41a2-837f-5c440047a0c7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!19b0fc7b-0359-4aa3-9597-29a97145e7cd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!19cb0696-af13-4036-b000-7aa03bef9541.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!19e9f51d-278c-412f-9ecf-cb96a83282c7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1a0767ef-4897-4a87-8d95-9c1446b07780.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1a1e71ce-5f6a-4a9f-a3cf-8f95189eae24.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1a995531-85bc-4f90-b36e-29a1e8620e7e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1b2eb324-6682-4ea3-8377-2e17da2fd996.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1b3c2d02-b76a-449d-a458-15baf8254298.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1b60b136-19a4-4551-b682-b02a6cea94d4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1bc97814-3491-455b-8fc2-ca2916f77813.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1bdf6ed0-cac5-48d2-84ed-83b7f5ef3a42.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1c0450bd-8cb2-487f-9160-3ca95543410f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1c18b32b-c3b2-48ee-8562-bdbc1697adab.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1c273675-9267-46e1-901e-50460328d95a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1ce174af-bd5e-4ae5-ac1e-b71eb4b44594.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1cff0104-9ffa-460c-ae4a-4a4c17e2784f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1d3e4977-6df2-4f69-b8b8-e13ccffdec71.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1d4821a3-f202-46f4-aabf-7fa88387283c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1d6dc82d-978f-4f7f-8514-12f099d4e699.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1e3463cd-408a-4f85-b79f-5f1f9a2f4160.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1e96e1e2-e2d9-4547-90c2-c9311fdc1ffc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1ed2a1ee-ed46-4444-967a-3ad686fee01f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1ee34f83-373b-4508-94a9-3ab61873ecd6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1f1e1492-eef4-4dc3-a5bc-ff2aac6fd10c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1fd36873-80cc-4d2d-95e3-11e3df524dac.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1ff577ed-8eed-41be-a839-2e9985d4ee5b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!1ffe979c-e5c4-4db4-b0d1-c0e615cb76cc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!202568b3-39a9-41a2-a040-bc8845e50449.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!20da7437-06fd-43b3-b5ed-e2f87198daad.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!20f5dbc1-e343-428a-8a76-781193037198.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!210b0c40-0cb3-49bd-8ef5-028cf01499de.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!216fa2ed-abd6-4d1c-b87a-4dba953da630.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2170b28b-1c11-411c-ad8e-1b4d884b509b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!221ecfe5-5d11-44e5-a7f3-ab71ce8c9774.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!231333d5-f5c4-4df4-9d09-814338ff0b36.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!235e6b94-0c1b-409b-881f-f293d356bcd8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2363d819-977b-465a-81a5-d16ab55a2e22.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!23766876-4d3e-421f-a26c-abb822473a1f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!23a77580-70a5-4f71-bf88-383caa4edaba.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!23c45ba3-cd4f-4fa2-b129-948a350d28a9.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!23faa183-1aed-4a7e-937a-6b46768c9e16.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2449949d-7cfe-4680-99d7-97167eea6b52.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!24b54660-10aa-44be-a54b-c7b571d0427c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!24e064e4-b388-4bc9-b379-88e485deab8a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!253ea427-a360-46fa-a1bd-e319055a9bf5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!25f695b3-ee00-4161-ad85-209101aa600c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!25f6ff1e-e9bb-47b9-a9ca-f72f7c5ac797.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!26089b3d-c318-4d35-af3e-255eef32bc7e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!267a5f9e-1dac-4b5f-89cf-d0f1741aedf1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!26879a0f-d760-440b-be38-c70ba711e016.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!26a15ed6-2da6-4796-8526-d63385e62189.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!270af0c6-1496-46af-91f6-9977aeefeef1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!27897886-242b-4f28-a79c-9c71b787a956.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!27adc88d-bda5-4888-b6bd-9691f55f3a3b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2830fdb4-947f-4f7a-aac1-c793f84c2d7b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!285a2bfa-8e59-4613-bc4f-f0300ed990a7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!293d9933-de29-455f-9a89-aa1b131afc2b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!296bf7a9-68fc-47b7-9c6c-eab873a25565.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2987c88d-23fe-43bc-99bf-a57fd0f13675.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2987f9ed-ae51-42f2-b0c6-6ae736646360.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!29ecae78-49d0-4b07-98ab-ab75527f68be.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2a763c96-c63b-4332-9ed6-dc7e842a0440.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2af2278f-aa89-43d8-88bb-f20480ff9c58.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2b1e1b17-babd-4e87-a119-16ceefa61be3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2bbf11fc-ff3d-476a-beb6-4f0b515c93ea.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2bd0be39-ea9f-4c56-9faf-cdf5b1f3c079.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2bec4090-07ec-440a-908a-a722168fb4e7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2c2e3e2e-50d8-4440-86a9-9047ff752cdf.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2ca6f22d-34b9-4ca5-ad14-c96abc6c17e8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2ce029f3-f023-44af-94dd-06390e68198d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2ce31ed9-b818-4b70-86bc-2e41a7d71b3d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2ce9adde-2a0b-4a87-bfee-74f262cc73fc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2cfe6d51-ddea-4915-8e5f-21a74df2bfac.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2d00f9bc-0fd3-4da9-a254-a90e61c15067.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2d87b8b1-0ca6-416c-acba-3349b6a2a514.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2dac26f3-01e8-4aa6-817e-2a89cc59290d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2dc32058-e0b1-4e46-8c5f-a46a60fe85ad.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2e0a9a01-5bc5-432e-81c7-378a9b35ead4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2e1facae-b5c7-4a96-ad59-a15a28da15a4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2e5640d9-8e1a-42e9-b630-a869ea6c44c1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2eaf153a-d5a2-40b3-9d59-a0424133003a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2ee4350b-006e-4d2b-9b71-87b8ac202e6e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2ee9c481-dbbf-4230-8d4c-d8dc11bec46c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2f61cf24-2da9-4050-8d71-ff96cfd478c8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2f709dd0-2acf-4553-9d3f-1b3e7d7e9a3b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2fe91768-c1ff-4360-9dc0-ea052da98d4a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!2ffad0b7-fa7f-4b88-97c9-54830b4f460c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3047fc05-26c5-4649-8fc5-6d83afd9d42c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!31197c74-10be-46d2-b36a-e2b96d22d57b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3142892e-219c-470d-80f3-0d7e5c34150f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!31a3f8cf-614b-4b08-a61e-52290f3a197b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!31cf85c8-3c6d-492e-b43f-4d0a8c2a055a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!31d505c2-817f-4e4a-88bb-710ca54869ed.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!31f9f8d9-e3d5-4c77-805c-3cdcd4d1a140.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!321a0118-d05e-4d95-901f-b7929e65628c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!324aac37-6820-45e3-8b14-21a21334a718.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3288ee08-56ae-4371-8fcc-fae09bb29053.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!33d6862e-cf55-499d-963f-93a9e393cc5d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!33ed93c1-91f9-4a3c-b55d-35c927a034d1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!344ceb77-d29b-49b0-a328-0104c35f233f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!34a06da6-fecf-401e-9dbc-1a072b0da618.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!34b6371c-977d-410d-b3c8-920b1fda6b84.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!34c9fb4b-912e-4d0c-a593-cc072b07376d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!34f7fd18-78f4-47fe-b0e9-28f0181aef28.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!35334b1b-0748-4ae6-b290-2b9d952a0cc6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!353ec49e-5261-49d9-b701-0073818dd29a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!35d3f72b-29c9-42ca-adb0-89302521cc58.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!35dc7a95-211f-4383-a123-466555ca192d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!35ebd4e9-69a9-44d2-a152-426330bac118.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!36132c8e-a7eb-4a04-866c-b97d95fd51d6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!362d1839-0159-465d-9a81-faf0f4b9a223.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!365e92dc-d8ea-4e86-8d42-0aacb460e98f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3780fe5d-358d-4b32-8f29-fba7cdada78c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!379a776d-54c8-4eee-bef4-0492e1b05e3a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3866c302-ce01-4ffb-8b7b-4d83bab72ca4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!38cfc692-cd7b-43b0-b242-4d4445b750d8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!391d087a-62d9-4a38-ab28-d58aa9c6e970.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!39392d41-09c3-4a2f-90c2-52a8805ea39c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!394c788f-640f-4ac9-9d54-10607998b486.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3964e3d1-7f92-45e8-b24b-7ab15013891d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!39bccce2-b78c-4cf9-8428-308793b32e71.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3a446fce-8d08-4ce7-819a-f2f316dc0153.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3a5b7b5e-a7d1-4515-977c-59f968f7c132.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3a6a92be-939f-4820-9366-dc485d46dc4c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3b08bd24-dd41-4fe6-8a73-5e696201f5ac.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3baa7569-6cd3-4a38-a9d9-be6bda89f5ef.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3be0678a-feb2-47ab-8b1c-bbc4d78dc171.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3c0a0580-8152-4bdf-a511-26ba9f32857b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3c19888e-ab9d-408d-90ad-4bb92b2bdbf2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3c353341-5051-40b5-b97c-ad7284dfda13.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3c6b7be3-d937-4f2b-a251-14b0814af9ee.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3c95c9ef-6ca0-4f22-a4c2-82581f5ce566.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3cf1cd63-6e1d-41f8-b38b-99919923445a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3d69d242-b996-4912-b637-b2ff675b019d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3d69d419-4f1f-420f-a86d-8be471e522e5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3dd5ace5-70f3-443f-a77b-f2b3efc8b6a4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3ddd3d20-ae93-4448-8b65-38ce0cdf64b3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3ead0dc7-f117-4478-a8e1-28bd33969d65.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3fd47b87-b7f0-4aac-87f5-7370282cadca.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!3fe3728d-6531-4b01-ab58-bbd17cc8edca.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!401b32b9-cd18-4da1-ab56-02470a6ca453.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!40cf93b8-ac5e-47a1-ae71-998ad27ca189.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!40e14dc3-d16b-400d-8c13-dbf2d5ca90ee.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!40ef037d-369d-4e0b-9dfe-582bc9f73525.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!40f5792b-73a0-48fa-82cf-c13e0b8469d2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!41a0a70a-a5e1-41cd-bad4-dc1b371172fb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!41a7775c-cf14-4060-b897-ebba44d643ac.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!41bf7a42-68a4-4df3-8006-cf8524d3dae6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!41fb0617-119f-4fa3-b813-ff80a5c6b817.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!42a66b22-60c7-45db-a17d-f78822e758b0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!42dbf858-050f-49c6-82f9-5bfecbe77cfb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!43510534-019c-43e9-a9fe-ae868411967c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4383cd84-9f31-4779-a798-a1a237d3efc9.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!43d322d3-42a7-4f27-ade1-7886fe5c955e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!442e840f-fc88-4fd3-8932-4943c8753360.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!44684574-21b6-4ca1-a8cc-2fe11e459ca5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!44b00d92-c007-493a-85c8-ee0dd43fbfe0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!44d3ca18-311a-44a8-9fff-8912c7ecac60.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!451aff08-e181-45b6-a78a-c594f0fa056c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!452cbd9e-1d72-4e84-83dd-b42449f05baf.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!45600fdd-f784-42f6-a514-7f1e34e5c745.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!45afbb30-3ab4-43ab-90ac-f5b902c70557.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!45daabc6-cd56-4052-b6eb-8663f8b8dbe2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!45ff7d79-4e27-40d2-93ee-8b24f78e89ec.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!461b5b97-1d25-4171-bb20-aa925d72bce6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!462b0be0-065f-44e3-96a6-3e67179d6674.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!46430d1f-b496-48d0-b5bc-5de79cadf175.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!46962d6e-92b2-4245-8a03-b10d38546911.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!46efb743-ab87-4ac7-9682-c3fcd15ed92c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!47199b5c-57c0-4aa4-bbfb-20d5c70212af.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4719ae4d-008f-4191-89e8-6c15f31c1fb7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4723262f-5574-4a31-bb74-57ec1e0651ab.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4776dea5-e0fd-4421-be38-c671999842e3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!478f85ae-4468-49ef-8d22-4d4d05570fae.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!47ef9d8c-24ad-44a4-ba40-526716184725.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!48f5f5eb-6fd5-480c-9658-c23e2952b81c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4909420b-44e9-4a00-9c97-3b8d61a0a1ef.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4917c167-f8bc-4416-bafc-37d2fa05537d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!491dcbbb-5398-4520-a6a8-71ff45dfbb49.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!49636e8f-be76-4105-ab9e-90e9bc6855d3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!496f12b0-20f6-4388-a078-74e31c9c8db8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!49a0ed75-a6ab-4005-ace4-a9cf6351cbcd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!49a133d9-a891-4d85-b46d-da7b7160c1f6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4a43c56b-93f7-4ce2-9cc9-eadb58f38487.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4a65073e-7651-45d6-b501-e9437434fcac.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4a850743-1c6a-4f08-a580-a2ec5d91845c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4b1244f7-ca4e-4833-b500-c9a377dbb7ca.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4b15fb46-8496-49c5-8f6d-bf9c7daa6793.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4b33efd9-b324-40c9-bfdc-3c1970b86195.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4b358236-7bd5-4fe1-be79-5b6d0985e112.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4b466e87-444d-437b-a830-14c0cb6fe2f4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4b6f50dd-a934-44a5-9925-004e6a89a779.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4b82a600-3b76-4865-a422-517b6155034c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4bf9b456-2028-42d5-bec8-53f046eae7a7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4c0351ae-e696-40a6-857d-45b82fc4f9a6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4c262d60-7c86-47ad-b6b7-17e3ee5d2b65.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4c2c29be-4591-4ff7-acd0-bb5f13225315.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4c65cfdc-3c2c-469f-b204-a71f50ee3a11.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4c6d07db-ed39-41a5-b489-89f77ae4c67f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4cd1a8da-c3fa-4feb-ba5e-b93131f33729.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4d180222-335e-4c40-8e61-abd709ddbfa0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4d528a08-13d1-4d0a-8f69-d870f1245a9a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4da181a0-9cee-44ce-a2db-9acdb2154fac.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4dbdaa18-bf2a-46a7-9fa5-272a503fcd57.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4e0cc5d6-eca3-4cd2-af5a-0a5e472a9a40.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4e42d7d0-b511-42ec-8888-858bd10b4413.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4e478383-798e-4f22-b4a9-919b0609267f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4eec4377-98a0-40fa-ba06-be2777d6a463.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4f68be5d-749f-4ae2-bf17-ff248c6c47b0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4f863192-0991-4618-975e-55772df49772.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!4ff69980-0724-49c8-b885-ee5c86aee9f1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!500d38f4-f55e-4091-8bbe-806c9bbec2df.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!50340cb9-b71e-4748-9dbd-80d5e2ab4533.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!508ade04-1815-49b3-bd31-81a9da598277.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!50a458ad-1ab0-4da8-8175-b90e1f987b09.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5107d457-b1a4-464a-93ce-452a5aaab204.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!514dbafe-7499-410b-aa6b-9a3cfc39ed94.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!527b3e69-4a28-4cac-a358-c204444f499d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!528ca094-7692-4a01-b1a3-0790926d5544.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!528ce4fb-8249-4d3f-9743-a1dc9bba8829.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5296546d-7aab-4e5d-9d4a-027966246645.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!529c7af7-691e-4480-b88d-f4c424c30610.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!52a3ae7e-0653-46b1-8052-8cf1cf17da95.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!52a3e47b-0213-4872-9bfa-94ac62c92698.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!52c624d8-2ed5-40eb-b3ca-cfc19cdc8e71.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!52d1bfcd-3d9a-4469-8fa1-413491ca75ac.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!52d49ea6-a9b2-4332-b712-46a9fe47e7cf.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!530618be-416e-4281-bdf4-432cca2ba154.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!53205a54-7175-4adf-9362-9e83d27a1414.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!53219c8d-ab96-486c-8d72-32c08d8d1e14.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!536e266b-8b25-4a1b-a5f5-6778399c90b4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!53869421-9475-4607-9ef9-efd4af0d3b98.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!53ed8360-33f8-4539-9f27-ebee01d3cfdc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!541ac3af-938d-4d2d-87ec-d40a7d51f949.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!54385af9-0e23-44dd-bc4d-2bf6362da26c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5457ed85-c48c-4ccf-8905-44751b42225f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!545e67e1-1744-4937-9bf1-a9666a903a0f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!54cf9e45-e833-4e90-9fef-7b49da74dd8a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5540a57e-b315-407a-ba5a-e41be9436b3b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!555a5486-947b-414e-a500-5248395bcd97.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!55cc236a-7120-4b8d-b990-afb7f6386e2d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!56624f0c-b9d6-4107-8c1b-e238ab6d1182.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!56805a47-39e7-4b6f-9f1c-d6762a70e655.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5691c070-0e79-44c6-8c01-2fb0031df8af.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!56a13e14-6a26-443e-ad58-84c7f8703ca5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!56eea411-09d9-4339-b21e-dcbceca22a8b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!570f1dfe-8829-4593-bef0-f42230b519ef.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!580a90be-62cd-4679-bdd7-1b28f9934b0d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!580f5147-ea97-432d-ad40-42cfcb3cf2e6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5890f4c0-88ad-4800-8232-d67b3da9b9d8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!58e03112-fd39-4e87-9b8b-fe8e9a92cc94.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!590ae097-0b16-4a6c-8a3d-fbfe6d56224a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!593c6e1e-de58-4cbc-a933-9eeedad35cc4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5994b78b-a10e-4a43-87b4-9c2c313f3c9d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!59e04135-5a7e-4c38-91e8-6e3213c384df.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5a165c73-27a7-449a-9a8a-94c377e78d92.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5a176a47-1bae-411f-86c4-5ce9a434878c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5a6465c4-ca0a-4ff5-a3c9-60be0e2485ee.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5a8bf716-15e5-40a6-8d4e-d4c6cdef49cd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5a9e4fd6-0206-4fcd-85e2-aeb0414c223a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5ab45d5f-316c-4519-8552-30222f670430.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5b777c93-d41b-448c-b14c-4f8e6bdf0956.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5be6fb36-dffc-4ffe-a553-f164714f6370.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5c0c8af0-e2e6-436f-867c-ea652a0c8a4d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5c191dd2-a708-4bec-be43-12a392460ffc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5c6d36dd-07fc-4421-bcf5-48008c49b592.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5cb2c113-40fe-45c2-97db-786ac0003a69.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5cd92388-abf1-40ff-82c8-f8b448680114.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5cf9dec3-bc79-4d2b-b12d-49132108abc2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5d022dfa-9ac2-4396-9683-f6f32c8afffd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5d65c2b7-a08d-40d2-ac79-2675933e0594.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5d743f7c-884e-4ab5-ab60-caa95ab2a935.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5db3bc2b-39d2-4ecf-8c70-e009243c0e1e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5ddf0dd3-5782-444e-9610-093b736ddfc8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5dfb804d-d608-4b1f-9cd5-d3bd8a4d767c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5e439239-b615-4827-822c-d2c16adf909f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5e61773b-5593-4752-867e-7c0555d89b35.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5e79c9b9-c28a-4cd4-a1e5-29c32987a56e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5e97d736-2d04-4c28-9692-bc22b6a2fcfc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5e9eb177-ead9-4476-8418-567c01b4ab87.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5ea34f5c-48ba-4100-b56b-0592cb44a691.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5f3f177c-9e7a-4471-8c2b-b9c82196ac29.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5f64dd66-fec7-4e6a-a356-ed786534481c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5f975c45-c190-4aa1-9f04-20301c3ce827.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5fed32d8-3433-4fef-a31a-d07dd279f505.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!5ff2de80-5117-4132-be8e-fb640c017658.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!608b89e8-29cc-408c-94a0-332f4d5fa220.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!60b22989-525c-43d6-a2e5-d4763c7c35cd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!60b51119-3f74-4ee7-8382-022b876627dc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!60c64de3-a206-4ec7-a471-6231e2a87ad8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6103ef62-4d3f-4c45-b8c5-917479681fe4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6114533a-571b-4678-bd70-282e33652305.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!61334577-4c4b-4f15-a044-4e5b37ca99fb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6161938b-41a6-490b-9fc8-80549584b3c3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6162d9db-7522-44e7-aa6b-5cf919d931c7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6163f333-883f-47e6-b9cd-2ab8f58253ec.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!616f742e-7a02-4b87-9ca8-ad1ab6424ada.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!61a27993-72d2-4f6b-9f33-fc05b83b9a92.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!61b1bd3c-6cb4-43ba-850c-4f008020cf75.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!61b2b409-7935-4a49-a77b-b8385651afb1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!623b5349-f07d-4124-a018-ea4203f0f584.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!623c857d-300e-4b82-a7c9-841ad3ff5720.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!625b9cb6-df51-4d54-953c-f0d903286af9.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!627ac2fa-8384-4c2d-b14a-57cadffa9307.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!629fa0a6-d0de-46cb-8670-c3b78b5b026c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!62ecec36-777b-4e75-a8e2-ecc1b5acc989.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!636119e3-4107-441a-bb92-ac39aa04e28a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!63821479-398e-4f03-bef1-e050ee0cc7e5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!63d0ca04-40c0-465e-b31f-52c008241771.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!63d4aa81-eadf-4d81-913b-64e26851d093.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!63f904f6-a1cc-474a-a43b-06ec3d07191e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6443867d-f5b3-4a85-8df1-57aaac003dd8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!648fe408-e54a-40e1-b148-b97162fd6c61.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!64954fab-f87f-4cb7-9bf0-88829f91b25f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!649e9af2-3160-4a44-9bd7-ecc909f51f69.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!64a42e40-2bd3-4b35-915c-2034abd3fb37.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!64fd2ab6-fdad-4299-8278-61769de3c386.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!655e242b-68d7-4c73-b053-5a398ef9daa0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!66097526-6373-42e8-8d7f-9930899d42ad.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6613b5ce-70b0-4286-824e-6cdd5504e5d2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!663e92df-0e24-483d-aad0-7f8745b7eedc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!66d37076-cdc3-4a7d-82fb-2d53dd9a083d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!66ea76e7-2fec-4397-bada-48783193a2f0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!67345f81-971a-481d-92a5-c93a89168247.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!674e4ac2-fa73-4707-b8ae-5c179c076c51.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6794ef03-6d4d-4c47-8285-a37cdba4d64e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!67cd0076-0a88-470a-be4f-796239108eef.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!67d57362-ce3e-496b-929b-a8da87cd3862.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!68422c19-788b-4dea-acfb-5e49f4800990.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!68467b77-4a4b-408a-ada2-41ac4613dffc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!68b476dd-95a5-4e4e-b202-35b81e5a3731.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!694b53f0-000e-4c58-8f80-0ef9076cbeb8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!69906978-ce3e-4f70-b89a-380c69b13982.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!69bb2ff8-6db7-40d8-b45a-ef101cd969fa.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!69e5c80f-58f2-4615-8044-86af98bf3086.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!69eef5e8-ab54-4eb0-8e69-5198a4dd45d1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6a3216f7-5afd-42a4-b021-02dfb397321e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6a54ee0a-5567-4e89-ac6d-c2902bc4fdf8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6a5ecdeb-e183-45b3-8a30-7bbc3545aa27.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6a9002da-8c11-4e6f-8ad3-15d79111ddcd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6b81c752-01e9-458f-b261-bf8d4688166b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6b8a143f-ccc9-472e-b077-bcc0e044fe36.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6b9cc525-e7ea-4fe0-aa3a-5ffd31d5faad.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6bcb38c1-4958-4e3a-bdd5-32f52b58a3f7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6bf138cc-5bfa-4fd0-9c04-c2d5f072ccd5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6d840bfb-0045-463a-b226-727c278f155f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6d93ea42-f79c-4385-bd9b-5e0fba1a5909.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6de3378e-baae-45a3-8a84-03ec11148bc3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6e49ac34-422c-42d2-9c77-305b68a0ac54.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6e7c7434-fd9f-42ae-8cc4-aba1e2ff18aa.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6e9d7e45-865c-4867-9f4d-0e96f3921c35.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6ee08cd7-3540-489f-9c6b-674b298737d6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6f7a879e-b2cf-4a20-bec5-5c09de93c4ad.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!6f829ba4-b4e3-433c-a860-8e186e30ac85.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!70c4999f-08e3-4aea-8973-ebc3389c5e35.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!70ddde73-376f-4562-a097-b1424f2cd70e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!70edd37a-7220-437c-9619-26748a492a3a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!714e6e75-7832-45ef-af06-bf66039166f8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!719db094-4cb1-4cb7-be57-3bbbf6e34ec7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!72315498-eb3b-44a3-a008-c2d46463daeb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!724a70f3-b99a-457c-a074-b5899daaa4d5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7285b815-f6cf-4cb8-904a-1fcee299d77e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7295f5f1-1342-408f-99d8-d2c10bc86f23.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!736868b2-3f5a-4688-b391-790b7d5fc5e3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!73ad57ab-b839-4fec-9af8-814115c27e68.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!73fbafd6-ca4d-4f7d-bf00-b2c878f36891.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!73ff3e24-d56f-4bd4-91d1-e95725bb5139.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!74eb5bd8-4757-4263-9a6c-e77c9c2ae6f7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!756e9840-5e66-4d10-b93b-7a7b6cf38832.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7571dd3c-e54a-4c39-87c0-33cbaeaf9648.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!75d61410-1198-40d6-9a2b-3c5da0c3ac92.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!75e6c3a8-5e47-491b-82f3-0c7000df0f2c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!760b828c-374b-466c-b27d-39b04a2af257.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!761ee42c-bb77-43bd-ac52-46a1a00a833e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!76fa27b9-c343-4c79-b500-7821f787592b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!76fb2de5-6c22-45ad-a984-e15fd2f4b93c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7767a63d-7556-4b2d-a91b-485df33a6889.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7784e064-fdab-429a-8505-042653461c7a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!77dc5e61-c8e0-4be7-b604-4e1e2ed5fafc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!77ebb6f7-97d9-4846-91b4-208396eb89e5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!781a5078-228d-4a04-8371-0071970a913f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!783efa63-029a-48bc-9d7f-789811a1c43e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!786de287-e4ac-4269-90ee-0f8d1c99e2c3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!787d6c56-a077-447d-958a-1183108265af.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!794406d4-3ad0-4436-af85-3320115799fb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!79519d6d-83b9-4bd8-bbe9-f85296401068.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!79da8d0b-947a-44f4-8b63-213d4a276eba.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7a2d5031-ff86-41f2-bb18-204ddfd631aa.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7a452301-05b9-4114-b5dd-ed8de24c97ad.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7a48212d-d1a2-4680-9bff-f6dc2e322c03.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7a4d1809-bf26-4ee3-8c9b-8de0470fb883.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7a67a249-01d7-4cd2-b885-c18781d1b2a0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7a9dfabc-f519-4adb-ba00-2dd6c2827e08.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7ab35026-dfda-471f-baee-3c0c8ea03eeb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7ae7061a-0931-438a-b905-c1e54f48c474.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7afb9033-4f15-421a-981e-f4161d006e85.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7b59eec6-53fc-4b9d-8c5b-6f3ecc5a4ebe.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7b7d15a8-aa1c-4044-a759-2947b9131324.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7bc0b3a0-f830-43b7-81a7-a31c52fafe16.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7bd17e98-bf49-45ab-b1a7-bd6d85035a07.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7bd5547f-aac6-469d-892a-53f3f9f71d0b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7c4f45e1-02d4-4e48-ba3e-4a637f3790db.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7ccaf774-35b4-4116-ba95-69316b85dd35.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7d27f50f-1d49-4293-834f-3663e16a98cb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7d42d75a-7c2b-4fa7-9031-c7cb005f1967.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7d488e6f-f188-46aa-a140-22fd49e22f0b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7d7f7373-0ad2-460d-9b46-c2d57eca8e04.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7d823240-f3bc-40b8-9e7f-c6f1b77918e7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7d980a7a-6d46-4034-8f1a-d119b4350f05.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7df9eacf-941a-4828-a262-6d132c095dcd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7e4d28ed-c284-4354-a018-78072c374052.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7e79d4b3-fbee-487a-93bb-786d259d8c10.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7e8eda49-46ee-435e-9a90-717192068160.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7f3ec66a-08ea-4461-ad43-d5309502ad88.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7f515865-7563-459f-9f89-ec4649ccf6da.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7f9e76ad-7803-44fc-ad43-24c1fd7f2914.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7fe13852-666c-49eb-a91f-da36122a1794.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7feecbb9-b6db-40a7-9e06-05a025a39d7c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!7ff3ec0a-f857-467f-bf42-242056982142.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!80213af7-e194-429b-9a88-043c0c556e4d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!80336cab-19d9-4cc6-a81c-b34063b8b87d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!809abe8b-7091-4df1-9229-1749ceda56c1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!80e7ff58-53e1-473a-9a29-ff5e4f033f1d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!80f697d5-8f52-467b-9526-f4709689c5a2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!810287ef-4020-42c4-9a00-2c22552b409f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8179f1be-f633-4867-b554-055946a9c9fe.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!819c4318-5c18-41ad-8bf7-7148cbdc94ca.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!81bf0e44-2afc-4adb-8906-b912d7324dea.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!81c501b2-21cc-43d6-bc92-a4619ebc3d02.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8240981d-9292-4c82-b263-3d478bff5064.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!826ff981-e71a-42c8-97b2-95e36dcf2b5b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!828c8579-9c75-43b7-aef3-3436516361df.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!82aca1f3-e6a0-4eb0-9850-0b4225383cdf.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!82e303df-50e3-4d96-a685-ff4f8b4b2ebd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!82e9aa2e-4cec-4325-8901-5db37fdfd0c7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!82f61d00-9565-412d-8dbd-71677ce2b9bd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!82fd4e98-e5f6-4b4e-a63e-778f37d95e70.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8309da6c-6419-4d42-9700-c510705f6611.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!830e080b-c885-44af-ad2f-06ca60fcbf8d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!83531791-e6c8-46cb-81b5-474e0a2aa330.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!83ec3f44-cd1f-4300-8af7-d2b5c6aa1379.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!846d6318-3637-49f6-ad26-3ad5f7a72e6b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!848aede3-f8aa-4f24-a4d7-6ee8f5862ea3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!84baf477-3e1d-4af9-afcd-a1c6480dd897.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!84cba453-dc0a-4db0-8663-bebe9b729d42.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!84d82be2-4f5a-4a4b-9709-e81253736f47.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!851fef2f-538f-4b64-ad62-46d399c0b9fe.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!85529642-880e-43e6-b628-5d83ee0a3499.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8569d782-7a72-4a08-ac99-48364a0664ee.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!85724c05-e2bf-45b8-859e-c27ba22d6c06.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8610348f-6220-48be-af80-c2e3e3bcaadb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8614e7a3-8af0-45f8-bab1-f3c5b3c988aa.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8666776a-5c94-4480-aba5-fa03c9610b60.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!86aab75b-c414-4ee9-a248-69481d8970d5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!86c30f5a-174a-4ead-900a-01b8f6549833.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!86c3b3e4-dd53-4362-97b0-f813406b7e93.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8708eb0d-45fa-44a1-a933-daa7f037d6fb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8769b30e-93fd-4878-8630-6cd79bf37e3f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!87b12842-e6cf-4edb-adeb-d04d9052ffbc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!87bf5cff-f62e-4f7f-9bcb-f095bd027ad5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!880f5cac-8b76-4be0-b6a4-9a5b1a76d7cb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8863ecac-3c8f-4c49-9651-9daf581c3661.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!887df1bd-70a8-4ee7-acaa-14944486a0f9.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!888fadf9-58d1-4147-bac8-e192d87728db.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!88c986fd-f3dc-4fb1-a36b-2c31aac6e82e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!893de46f-fd7c-45b4-b161-64875029bed7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!897dfd06-82e4-46a4-9534-05f09078c8f0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!89824cb3-e607-486b-aed9-a56ae41b6a28.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!89a36790-14d5-47a8-954e-cd8df0cc735f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8a4ce707-c4d8-4416-8111-03670b92da13.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8a5b653f-2bc6-416e-ba2d-881b9f891e32.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8a5e4f44-5a35-464c-b465-5310afbb21be.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8ae39bc3-8ce8-4e10-9baf-34771e6af458.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8af94adb-44cd-44f4-8d94-c6d9a495c826.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8b371195-6702-439f-950f-f71e2ee233bd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8b7bd3b0-5063-4881-88bc-5dbdd029202e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8bc1e90e-c2ea-4a08-ac4a-c1bbf7421f30.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8c74a87c-03ad-4f75-b928-b7c9e01c0cea.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8d00edda-9300-4834-acb9-b4fccb581886.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8d3461c2-0ba7-41d6-bdcc-3b1f37109334.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8d8a7616-affe-4ed2-9d41-d5b9a3a5ad4b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8dc2a80b-7e5f-4eaf-9b7a-4b59b5979078.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8e345f62-0a2b-4399-9815-4420681fdcd6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8e583211-dd76-438e-b980-c64e99a3db2d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8e6157b9-51dc-467e-be1a-ed2ccc50cb7d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8e8a5d26-9841-491d-80dc-9a1ed1a03ca4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8edc03ed-f541-4147-b780-dc5d771bb2db.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8f913ee0-31a1-439c-9f91-b476b6cc8595.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8f9a93d3-3222-469a-8c25-1ca5bc7e98e1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!8fb8ac48-f2b6-45b8-98bd-daa519d242dc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!918fb788-49a9-45ff-afed-7e5a274a00dd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!91a34035-db1e-471a-b69c-ad3c1e7f86ab.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!91c7d204-3bae-4d5b-ac03-015206ff86df.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!91da77d8-35ec-4894-b17c-dd9b787abcfd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!91fcd66a-a3d5-4452-a7c4-5bc8c7634727.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!933fc143-21d1-41a1-b2e4-1259485693e2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!93407142-ad96-4e20-a7b0-23781ea069b5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!93579f05-47b3-428e-954f-637ca1a635cf.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!93c6717d-79a9-4f5a-b447-a4d148ce55e3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!940c863f-ca7a-418d-86ad-0f561a7a5cc7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!958ce060-b2f6-49dc-96cc-7203cb6e4048.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!95c24965-59d9-4197-b203-9d28fef41c32.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!95c24d4a-510b-40f9-8703-e82ce2aacf17.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!95ce6440-c5a3-4c78-a40f-0a6bdbe8f11c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9640253d-cbe6-4e63-832f-c8c5862646d3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!96b66bf2-632e-499b-b51a-dffe3d74b7cc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!976989b0-3036-499e-ae81-1614a027db98.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!976fd366-5337-46ce-b252-cf7afa0c3368.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!978060e7-a3b5-48c8-b2cd-d186fcd1c9b6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9799cb4e-dcba-465f-9c75-49f3ef5f1acd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!97abdb0a-a490-4528-8b7c-0f3dcf7b4325.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!984d09f3-a2f6-46b6-9fa8-61860a74f85a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!98c77d4e-fccb-45c2-8a02-6ee2a5415fa2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!98f30fc1-da53-403e-9e2d-6034b50c2f9c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9903fc3e-0984-4dce-aa04-3f4dac0f541f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9918eae5-18bb-4aca-8963-a0fe59d9e781.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!991f85e0-6f1e-4d5c-8019-d856349e14b2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!99770653-f825-43f4-b0f5-edf87aa4023d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!999bd22f-b7b0-494d-b5e9-4b7c824d3382.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9a0f8e15-86e1-4e7a-8b9e-961cafc45629.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9a66a615-7ae6-4952-ae92-efaafb819cbc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9b54e996-fda5-4e27-af77-d876eb7f9f7e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9b56b840-1819-409e-b6fa-66919471c651.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9b70d17b-8976-4368-afb3-51df6e632860.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9bd82993-74a9-453f-9764-1b3ad5ac7f9c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9c385667-6e21-4ee9-b010-f5dfbce33062.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9c38ece0-a2f3-4ad8-a171-5e8ffc161350.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9c5a1e44-8cdc-426a-bc9b-aa6f6c74e3da.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9c719a62-7841-4e25-8b93-ee59f7c20233.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9c7fac0d-445e-4a84-b7d5-45ff59c027e8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9cacdd4f-d708-4357-9ac3-175f64b7b163.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9d64cdc2-c11a-4185-af80-377fd2ffde6f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9d72af86-5de2-457e-b03c-8e8c9acf1d72.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9d991242-911f-4b3d-9d3e-657c09dabd11.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9ddb6a8c-45a9-41e6-99da-eff96797c186.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9de8324c-ce97-4a13-9962-0057c558bd19.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9df30072-b3c9-404d-b773-bf9ef89858ef.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9df59639-d274-42c3-bac2-93809e5baa22.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9e2765f1-9e81-4164-926a-776c17efcee8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9e5359b0-024f-4ebe-969f-672a199c5b73.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9e62b6f9-23ca-47dc-adef-22c908593a21.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9e806174-f27a-4fd0-b419-73a20fb1bc17.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9f04c67d-e38f-4ab3-8cf6-4dcc2d4dcd7f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9f52feb2-65ae-4652-9fb9-d7efdd78a259.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!9f95db47-94b6-43b9-9094-f523295f14a4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a0574842-95ef-4402-b003-514936196fc6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a07c9e8a-27ad-4a53-9aa1-b4cc17a031f3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a0d73922-5fe4-4631-b0f3-0b6724d66e17.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a0d9e00f-87e7-4168-ae85-472f7e162dda.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a1025da4-a811-4068-8416-16202f7e899c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a1dc84cb-462f-4183-95f8-5203c4080be5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a2116151-c4ee-4f0e-8d25-cd582c2a2751.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a2248fa0-e9a8-4742-94ee-00b919cd5368.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a25a6e7a-e7fa-467b-b26e-0b80d16e9617.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a2e8690c-17ac-492e-a0e0-b56fa854f91c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a31148c2-7b4c-467f-91fc-0362b8a340f2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a339bd4d-dec7-44b9-873f-a28fc6b451bc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a3555a97-c6cc-45ed-bb72-b20061cd7e58.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a36b9b6c-f204-42fb-9cdb-e71c6207e82e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a38286fb-fbc1-45a7-ac24-1b30f49c2f9e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a3d2b5e3-2de6-42e3-bcfb-67dfe8e5ae1b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a3d2d4f0-d27a-4dff-a16e-cf8f6ab078c6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a440aa7e-13c3-47b5-a707-7c825260ab2a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a45a3767-fa8f-4450-9d3f-4b620878039d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a4a49991-8cff-4045-8cd2-c9fa5ccc6ed7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a4e3366d-7408-4a05-825b-4510bce62b4a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a5194fad-bbaa-4f01-b5b4-78f3347398b1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a5274826-976e-42ba-9cb5-439c4f780b99.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a5abe2dd-6ba4-4c54-9401-9b88fa83cf39.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a5c46de7-eeef-431a-8b35-b71fcf81d12e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a5fa0d25-4d76-444f-81e8-00028f2c07f4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a6a4b783-b5c5-40b0-8237-052c81bb7c38.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a6bdf3c4-5b6a-4eeb-8eca-bdc14058a8ae.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a6c5b19f-560b-4e98-acbf-e6646194fe2a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a6dfa64d-5f6d-4d3e-b92a-bb78fc41db89.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a6f4c62c-1ae7-4bb9-902d-8ee9b282c72d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a70a38d9-cf3e-4e5b-8501-c964ab7cf20d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a76461bb-23c8-4344-a932-34f602d873bf.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a764ac61-393f-4d3d-a45d-314768f99380.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a7710298-8c29-421e-a758-2de643aec29f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a77ec1c5-4d57-439c-921d-8e116de97214.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a7b1cfec-7d6a-4a88-9c21-db876816ab34.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a7cb2ce6-4777-4e26-b420-deff47f05fdc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a7d07d48-5235-45ef-b673-a38f2289db4b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a7d3ed16-2c24-4b3e-8cdc-d36826460fae.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!a8fd042e-ce3c-446e-beca-00aef4418597.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!aa2d7528-a27a-47ca-ba4b-72fd12946f17.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!aa45208c-ebc2-428d-8af7-e893f7378169.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!aaffe828-6a01-4a5f-a081-04f7606df81a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ab61fbf0-ccfe-4ada-8772-dff5c3a02247.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ab90ed9b-50e3-4031-9393-24fe8e992fd7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!acad28d3-9f1b-442e-bc4e-dd7622f91fae.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!acca7ef4-f7d3-4b7f-8c9d-ab00dd4b6645.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ad0c5018-db28-4851-a03f-d94497e059f1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ad258585-b8b1-4a5d-a67c-c2e6d7d95be2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ad38071e-4ff5-4fbc-bb94-3f22963b44c1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ae865b74-d384-47cf-bda0-e30c42c99d2e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ae8d8426-24c3-4465-822f-8abe89e9712d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!aee0f24c-933d-459c-9a3a-a6e2bb506e05.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!af3abfac-532e-4a28-8214-3468dfa95e5d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!af40349f-0b62-474e-90e8-d5f6142245fc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!aff2e91e-3b3c-4cac-bba9-6f2876dbf441.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b078e4f2-a8f1-4792-9fa3-03f2ffbb1fc2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b0c2ebf1-94c8-449c-9c0c-ebb237de45e2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b111a966-160c-480f-b0d9-a17e14777e4a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b12bbc43-f6c0-4115-89c1-69635bc76306.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b1e32c37-a825-4f3a-9c2b-766cc0ce82d3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b241102d-a7c9-40ab-adbb-ed4b3d80e489.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b25b1552-cf53-4079-9710-20163fb0a69a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b2767de4-2c85-4923-aa2d-57997602c958.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b27f17da-3788-477e-a9d9-666316736922.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b2a5072b-65f8-4e01-acdf-4575d9707dd3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b2e0de4b-42c8-4e71-a4f3-141e77a9700c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b2eb4fdb-5e67-49ed-b181-ebc9cad9418c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b34de8cd-f3c8-4478-ace1-c10f9bff4e96.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b3acf88b-80d0-44c0-abee-6dec4fbdedbe.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b3c3ed1c-8b2a-4884-96c9-ad76bf6e7735.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b3f6d482-5e18-42de-a0f2-baa60d02ab8c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b441a095-ab08-4c8d-8771-1a678336e46d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b554a46a-e88b-411d-8362-e4b63783a693.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b5772f29-ab76-4fd8-90b6-8841dcc0c4b4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b5f6dc35-fdf4-465b-bc91-3d4dda471752.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b6df2f90-8cf9-4bb1-8ec9-71a0c89f6943.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b7bb67df-d7b5-413c-9483-34b3547127ad.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b7ffcc98-4fca-4103-8c68-8020671345f2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b800cf2f-bd31-45ce-90ef-11044a266ccb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b8d90e48-18fb-4764-b31b-4773c77ce0b3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b922d766-d56f-4800-b6fb-3c81caf0a38b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b96639ae-9710-4803-8b39-9df0ba604ee2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b972ff9e-0f0d-4ec4-9846-3a09ab769eea.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b9760753-53dc-4463-827f-7ab25328d939.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b9a089be-25ca-40b5-8a57-a69956ebd8a1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!b9fb2acf-d5f8-49f4-bcf4-3bcc576a3b62.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ba401d49-e243-43b7-9f45-c82b6ce3814a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ba4f8e33-4721-4f51-ba69-41bef6d7d8a6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ba53504e-701f-4420-9c34-75998e4f512e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bb7ba32c-d61e-42b7-b8ae-e8c4386847ac.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bb7de660-24fc-4d72-88b1-03203e16fc38.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bb813719-a991-4ad4-b6c4-54ea1121a0fa.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bc138419-ee0e-4ba8-ad06-7b229e104dc8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bc3ab790-1d1a-498f-b870-668c925a54de.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bc66e53d-231b-4888-a46a-4ce01002dede.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bc7bc0bc-1144-4c19-a084-1d30057d9a83.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bcc03393-28e7-43db-9650-2b79bdcef35e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bd070c4c-3406-435a-8c86-c4ec56075fff.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bd79c444-2083-490e-85f9-95f701276c1c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bd946a3d-2de3-4d86-9c7d-3250c3120271.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bdf8a01d-b98d-4e22-9035-8ef6cf97f755.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!be1db8f7-31a2-48f8-9696-af290e33c783.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!be4cf1c9-df85-40c1-868e-aaee070ee786.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!be610b60-599b-492e-8ec5-f8f3e6586c7c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bf0d5113-12a6-4428-ad9f-ef42cab9fbb2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bf5f197d-ff0d-49ac-b23a-71a9f24002a0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bf6a8ef8-9dc8-4e6e-b373-d2823c1891f8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bf7194ed-55ae-4e1a-92bd-8157c6996fb6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bf7b453f-f0d3-4ebe-bd90-a9d65386a9e8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bf85b825-183a-458e-9fd3-eaf4a415fc9d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!bfda8075-aef9-4372-8c0e-971b78f31c3e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c046ab99-345e-4da0-a3c8-0b8e28c8413e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c0471a4b-bafd-4a59-9d5b-9733bae2570d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c06eea48-21ea-4ff8-9e0e-4f65c2ca27be.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c09d2c8f-3484-4711-aa25-83072d1c3e14.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c0a19744-d67b-4a63-a084-45a654dbd375.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c134c4a0-ffd1-43ad-a9c9-bf5f4a2d53cd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c1436235-896b-4e82-b146-5b5e592c497d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c18984db-4fde-4f92-bdb2-3dd6b3999e6d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c195e1d3-1885-4ebe-894a-e0904ebe926e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c1a8707b-63d7-4936-b36c-d05ac2ebaa2c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c250d917-ee64-4e90-a6d2-651f2b2a2d88.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c26c228e-7235-4c28-a843-5813a313708b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c2aca3c6-d019-4b90-a7ae-bf52243a87be.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c3001c06-6095-4a91-949e-7679ea3dc2b1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c3348754-0a43-49fc-9c6a-9d3d5919bc38.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c3621a39-9518-4245-a40e-0bc8301bccd3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c4a6bd63-fd2e-4e1a-8f4c-bffff882cfc5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c594a37d-d3ff-425c-bacd-ec27840e072f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c5bd5714-fd0a-408a-8e35-fd915039e3f8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c5d427c8-f341-498b-b223-a3181e4d063a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c5f3ce90-7bc9-4566-9122-26b76f7a5b82.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c60655c1-3c28-4abe-acbf-4a1f75d0c2b1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c60e70cb-b5d3-4a55-b901-c0674e5eeb2b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c6368aa1-bd5e-4079-bcf0-b703ef01efb7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c6afec49-f553-42a7-a1e5-649f03c1a9c4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c6d378ad-cfa5-46f5-971d-2a5c4f98a58b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c78375ba-9d68-4232-855a-dc2caff25dff.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c7902733-21c6-4766-bb64-eaeeecd4db89.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c7dccfe0-bf72-41a0-b1d6-90c2f19fecc1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c7e9ef5c-6997-4cbf-83a0-3f4989843881.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c7f047f0-2e9f-4ecb-8162-63fc1896df62.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c85ad497-2928-448e-85d7-5b79822db861.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c87c6ea4-9637-48ad-9e42-0c7ba695d282.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c8d11a3f-4490-48c9-be9d-676feae9eb93.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c8e6fc0c-3c97-44f2-9a28-ddd498ae66cf.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!c9c8f194-886f-47ba-9fe6-9ff098a4f4d6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ca867141-5304-46a8-a2b4-c067b725af37.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ca89c627-c193-4f39-8364-4238cf3cc840.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cb3632d2-c7aa-4600-a10a-b7a16fce03fb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cb82ab3d-591d-494a-9979-a2727bc5626f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cb95e28e-7611-4243-98eb-bcb5d91a1440.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cb9659d4-0c9a-442d-b61d-ae5d6f068dc3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cbb66134-f183-47d2-8147-530f332ef71e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cc4f6155-951c-4d35-ab65-a1aaef2f4a18.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cc710b9d-e9c6-43c3-8199-3a1d1565cb85.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cc781b54-415b-4ced-8fc1-83dadb298354.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ccbdcb78-905b-4a3f-bf84-b574d700d827.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cd044782-4b57-4b87-a422-69263d7f8265.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cd518557-08a0-4081-83f5-d97655d92682.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cd8acfbf-98e3-4c8e-b357-ed2e91f6dab3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cde74e59-5080-4b65-8e79-01dfba4c6d18.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ce0216dd-9102-4f47-8846-d7ebeb0dcd00.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ce6c2e13-491c-4382-a3e8-6c4c05100ac3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ce7014c2-83e0-4e99-8bb2-046bdf39f048.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ce711e5a-ff55-46ab-81ae-38e10a068802.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cec78c8d-372b-4d8e-8c8e-a57272f65076.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cf510c51-11a0-4849-a72d-fbd19eed48bd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cf5f7944-dfdc-4e66-9e9c-37efe31eb558.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cfa0fb00-d325-48af-9b8e-0b6b4511b870.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!cfc61302-0225-45d3-aaab-97482ea12090.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d045de20-8e6f-4bc4-bd08-55a26d7c7389.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d053e218-2768-4ddf-bede-ad442f09b640.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d0ed4630-bb46-4a2f-adc4-e80b0b5fff96.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d10775bf-c0b8-44b2-8f66-b08087d04a2b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d18be333-3e3e-435b-89dd-427bc6363062.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d22bee42-784c-4514-9cf8-8deb26d59a47.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d23be372-cb1b-4c05-8bd2-0ee06410b4b2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d265564e-e623-4abb-a4d8-190370fb3b38.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d26ddf63-75fc-48d2-ac42-07d671ebce7f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d30ca369-1e7d-40bb-9d0d-1c8c3660e954.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d3273daf-a9b0-4959-b260-01432187448c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d33bb1c7-ac15-4216-a505-2d76262518c4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d383a4e1-6299-46cc-8663-02a9023cf9c1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d3be721c-bac2-4e28-bfdb-08aff335c0a6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d438f303-39d9-4192-9086-9f80d2758a95.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d49393c0-b749-4021-9014-e1547d92b706.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d4a89799-91c9-48db-ad06-f3f9596be9ce.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d4bf9709-af5b-4911-803c-044c4fd55e2b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d4c36ca5-37b3-4524-88d2-8dccfb78fdce.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d539f896-49b2-4f3c-91f4-a09f3880a084.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d5ad52e6-adef-4978-ad2e-9e8752b50862.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d5dce4bf-1f4b-4749-84d8-b810cc8dd696.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d61b5f7c-12e5-4bb1-a68a-7ef989aa2c4c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d6a53b5d-bddf-4ae8-a209-886e16e72ce9.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d6bacf38-0765-4261-95a2-71c10352de6c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d6c44000-0ed2-41d3-9ffa-9f15695279b1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d6d26b7e-be16-4927-acfd-e7caf6eb21bb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d6d4a170-d644-4302-80d1-8f6131713b1c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d74867ce-7807-45a8-b030-35d6e64cfbe0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d74bca5a-1521-442e-87f3-3752bce676d5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d7d39ea5-a57a-4c36-99a6-8dc9a6cf3997.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d8039263-c790-42e0-9259-10161305b10c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d8c19a32-07b5-4616-bf6c-0664d2c6023d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d9118d53-0acb-47f9-9475-d5ec4f8d3bc1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d933cf65-9047-4d4f-81bc-8abcd6c338ea.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d95e5f97-1a22-4ae2-b554-5dee707b4895.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!d983e058-2c7e-4711-ad92-6331e6b822c3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!da61e076-280d-4532-b76b-df3e49a99c39.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!dac02dbb-8cc7-4792-b96a-de2f6bc5375c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!dae742c7-dfb0-446a-a509-58e99e25c836.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!db4f2d65-2d09-4d2e-a25b-f25b4419e87a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!db9b1115-3dc3-4144-849f-f460b51d9d75.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!dbd402bd-9985-4b34-98ff-221fe63786f6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!dbf45ed5-7b39-4a55-9a32-3647a375b3ac.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!dcf54a40-5ee2-45ec-9466-6acceda82493.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ddf1bd0b-814b-4282-86b3-9be7f428218d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ddfed76b-dd25-46e4-a375-dcba0138c06f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!de5b1570-b28e-494b-83d1-416e5cdea3e3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!de5bb326-f9d5-4b47-b72b-c242dc2001f3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!decab043-01e1-4e48-b3d5-4906937c4b05.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!decc785c-12d2-4519-91b5-7ee133c243fd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ded508f6-716f-47fa-af07-1bc51e02011e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!def0c346-1d37-4fb5-94f9-6a60adfcaf28.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!df4406bf-991b-4629-80bb-81b398b6efc4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!df63f51f-0b81-499b-92e6-bec76301b2a7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!dfdab292-e4e1-4ec8-9f33-5772b72e94ec.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e0a9a21a-5531-48e0-811a-a0c500d74773.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e0ba39a4-0ff0-4d7a-a9ef-a912a7eec9a5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e1b15766-67e9-4def-97fe-f85df1534ec5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e2100b51-5cd7-4fb2-9f4a-9fa53ca36631.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e25d2c90-e67e-4de1-80ef-4c3d2fc8d390.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e25e8fe0-0255-4540-9671-97a405fe5c9a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e28ba2cb-9086-4232-84c2-23edeefc39e3.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e2c7f584-bcbe-43b7-8afb-67ce6d20c12c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e2fa5556-3258-4e0e-9c89-f864f172be90.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e2fbeb66-bd5e-40b0-a8c7-fe3aa89c9322.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e380a8d3-316b-432f-aa9c-7828f76db67c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e3a1236e-d962-4a23-8a9e-1408e3bf2a3f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e488ffdd-f384-41ca-bd3a-4b39bb94d612.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e4ae4070-6afd-4265-8346-369fa1152975.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e4b752c0-eff0-401d-a638-d04e2e3b6d9e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e4fe59c3-ea66-42ed-ae48-f79502a29089.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e511c2da-ac81-46bb-80a2-cb6c96f8bdc7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e54a68fb-35ee-463e-adc3-3860b0009430.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e57f9d10-e29b-4d35-937c-ffd3d4c1f836.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e5af2e4c-6ebe-406b-8546-ed74864bc2f6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e65f0abb-6c8e-461d-9179-63f7d12f17bd.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e6cd4d3f-8fb9-4863-ba38-ba11eda1678c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e7662a5e-a03f-4cf1-b146-24016265f243.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e784d386-7e5a-46c8-9eed-c82b7b76f8f7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e79d0c3f-b28e-436a-8548-291c44971bc0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e86555a7-663b-4015-82fc-8419d18e4dfc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e865fa7d-3ef3-43c2-b795-d7800aba783f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e8f4f551-a625-4629-a761-fc8a8c43155e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e93d0da0-2748-4684-bf03-f172d66308e7.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!e9586638-033d-4bf7-bf12-1dc34049ea68.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ea24a093-a472-489b-8e23-54969e6e5301.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ea4529ac-405a-46a0-a3da-b7b3626ff077.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ea47d3eb-792a-4ba4-97c9-69f0a3c57731.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!eabdab62-a67f-4278-832b-798b43f74084.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!eb377b96-8b92-498e-a9a0-29649def6c09.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ebb7bca0-6d0d-483c-a5da-8604b9247ce6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ebbc7459-8a49-4551-815a-0cd82d4bff76.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ebf932bb-81e8-42a1-80f4-130ca6c63579.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ec199ae7-14da-43ec-9ae2-fd642f9b494d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ec326a41-3e29-42a8-b03a-0aa67a291a6f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ecbdbee3-c469-46f0-840f-2c8defa0d489.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ecd9f0ab-3e20-457c-8f1f-21fbac6b4580.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ecdd7831-ba15-4137-93e2-14339d7b0075.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ed44b30c-87cc-44e2-b2b9-d980d1804dd2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ed889161-7f2b-4e35-a2db-172ea6bd6b8b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ed9760e9-39fa-45a3-904e-89a003b24346.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ed9d3267-95c0-4fec-b687-882c98e0a990.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!edabb2ee-09b1-460f-b718-04c6578b2530.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!edadd699-5e5e-49be-a4c9-7eee94036a03.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ee0b072d-cb98-486d-95bc-a205e861ca4b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ee5a8c9c-e03c-46f5-821d-5109413a2f67.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ee65ca66-0d4e-4a8b-a7f9-825b05a6b1d5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ee96c3b4-e8dd-483e-ab60-eb2d3187332a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!eeb86305-cd5e-4a08-a573-17f3f316cc84.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!eef8b7e4-8616-41d8-88c7-0cc316fdab15.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ef30f611-473d-471a-87a2-76e5ebf87e81.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ef6ad6cd-ef80-43a4-97ae-46fec076933c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f077b013-67c4-4390-bda5-b818e4369b2d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f08a25f5-8a88-41f7-8a85-bcd166612307.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f0a3f6ab-cd54-4820-9378-648c1167e803.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f0ab6959-16c0-43a1-a779-1061dd46318a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f0bf3c1c-0589-4007-b6cc-e70bc76f4539.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f0ea32a1-2c37-44a3-8266-e2983739b247.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f0ed4b69-505c-4423-ad4a-f588edf85983.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f0ee301e-1140-4b84-ac93-3ceede0f5090.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f1144f3d-ed1e-42c9-aaaa-8cfc936d4961.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f1206f9b-00ec-4479-9de4-19eb62ec2ca2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f187a57f-b9ec-4409-8533-7672b56a4437.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f1fb620c-967c-4f30-878a-e17fb1ede7ac.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f244a993-0ae3-4091-b866-7a2ef326ea73.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f2956010-fcaa-4474-947a-3d77d889e3af.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f337e441-1f89-401e-b9d5-560a7f210011.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f35a9de0-3bf9-49f7-806c-d30d192a8c7d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f3790c7f-c6d9-49bf-b2ce-636236e0d46c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f3a60a02-30a7-468b-81bf-13add7646fd8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f3fd25f4-4ec1-452a-a724-9d39c00f2904.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f443ea90-2902-44fb-bbbc-66d97e3cc67c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f4558fd1-8e24-456d-b283-902f6ff29a10.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f45b1d36-ff13-412f-b17f-a64ee194df2a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f464e756-7e8a-4b18-8785-9d5f54ac25d5.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f470fb58-ca47-41cb-acdf-a615f0b82d3f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f48693d8-60e4-49b6-820e-ad6150c26965.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f4ad97d6-f54e-4d7f-bf27-61d7a4eec94c.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f4e70d71-1c25-4407-840c-0442c50fd16b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f591cd2c-4da9-44e2-ab78-4802e06bca19.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f5dbd7a0-4fcc-4497-a23d-3d6f52c1e62b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f5fd4678-6a69-493b-9a10-d66d03bc3f4a.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f60c1baf-033d-46ba-be73-dd9cce98ad58.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f61575c7-93e2-4e91-8cd8-48a6b7c95b9e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f69039d7-90b4-42b4-9950-bfd155095924.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f69253de-b613-4026-829d-b2586ed27986.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f6984628-b745-447c-ad5a-f70b6434ebc6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f6b83ee2-2cf5-49c2-873a-22ebbd5e64d2.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f6e0edc0-0912-4242-9f8d-96df82e93ae9.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f7a624dc-40ea-4412-bf05-e5aed8ba75df.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f7e17e14-747f-4ba7-bdfc-be68d898fb53.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f865a40b-c6f7-4db2-9327-ff48a524100e.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f9623569-329b-44d7-b71e-b8afb4a9e5d8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f96597bc-ca74-410c-9eab-41b5055d7f77.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!f9a24a87-d4c7-4d36-ba82-f953ec8d5ca0.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fa291d6c-434e-4ba4-abee-b26bc573a954.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fa65f5d0-cbb3-4953-85ee-fafe5b155b2f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fa97b46a-3a11-46cb-977f-e898ac1fc544.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fb8501d7-1fda-460b-b9ae-344e976f2f6d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fb918d1b-2419-4242-adba-f947d84b0f1b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fbd80379-e48b-42c6-9936-37d42f4ad4f1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fbe34067-7807-4f3b-b228-a17cc23edb78.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fbed909d-3d2b-45fc-b05d-2daf52a3c1c4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fc2334f2-952b-4b8a-9d38-ff42f758ee64.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fc346ac3-47a9-4a56-97c1-465e582c261d.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fcabe18c-b801-4c93-a700-1c39b428957f.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fcf53b6c-9b21-480b-8a7a-7200f00e83d1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fd2fd43d-f4c7-4e0e-b31c-4a9a2232b5d6.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fd389908-05d5-4505-b350-598878fe9120.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fd9cf1a1-4bcb-437f-b481-8b85c704e7dc.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fe2b9d32-94cd-436c-97bf-bc44006fc7e1.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!fe5a6674-ac51-46a6-ba84-928ec8fdf6bb.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ff20bf31-83b5-46ef-9ae5-2c15be948c22.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ff30dfd7-0c9c-47c7-b992-4fbef3b97bed.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ff76473e-e874-424f-84b4-496ae7814131.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ff95f473-3648-45cd-886c-8c700018d78b.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ffa69c54-30d9-4777-bfe9-a5a3309b60a4.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ffbaeac3-6204-4714-b014-2b6e4a3d2a21.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ffc46a54-d0dd-4a3c-bd21-cd89d8a31b69.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ffda8b8c-baf0-4938-bda5-12d30ef39fe8.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ffe1fed4-8e49-4584-8f74-a1f9a58b70ed.json"/> + <_ContentIncludedByDefault Remove="data\rooms\!ffffc7e7-4b1e-40a9-9f24-ebd95267e758.json"/> + <_ContentIncludedByDefault Remove="data\users\@emma:hse.localhost\tokens.json"/> + <_ContentIncludedByDefault Remove="data\users\@emma:hse.localhost\user.json"/> </ItemGroup> </Project> diff --git a/Utilities/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs b/Utilities/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs
index 0603a2d..7324bd8 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs
@@ -35,7 +35,7 @@ public class PaginationTokenResolverService(ILogger<PaginationTokenResolverServi } } - public Task<StateEventResponse?> ResolveTokenToEvent(string token, RoomStore.Room room) { + public Task<MatrixEventResponse?> ResolveTokenToEvent(string token, RoomStore.Room room) { if (token.StartsWith('$')) { //we have an event ID logger.LogTrace("ResolveTokenToEvent(EventId({token}), Room({room})): searching for event...", token, room.RoomId); @@ -43,7 +43,7 @@ public class PaginationTokenResolverService(ILogger<PaginationTokenResolverServi var evt = room.Timeline.SingleOrDefault(x => x.EventId == token); if (evt is not null) return Task.FromResult(evt); logger.LogTrace("ResolveTokenToEvent({token}, Room({room})): event not in requested room...", token, room.RoomId); - return Task.FromResult<StateEventResponse?>(null); + return Task.FromResult<MatrixEventResponse?>(null); } else { // we have a sync token diff --git a/Utilities/LibMatrix.HomeserverEmulator/Services/RoomStore.cs b/Utilities/LibMatrix.HomeserverEmulator/Services/RoomStore.cs
index b6fe7c2..232863f 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Services/RoomStore.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Services/RoomStore.cs
@@ -51,7 +51,7 @@ public class RoomStore { public Room CreateRoom(CreateRoomRequest request, UserStore.User? user = null) { var room = new Room(roomId: $"!{Guid.NewGuid().ToString()}"); - var newCreateEvent = new StateEvent() { + var newCreateEvent = new MatrixEvent() { Type = RoomCreateEventContent.EventId, RawContent = new() }; @@ -78,7 +78,7 @@ public class RoomStore { } if (!string.IsNullOrWhiteSpace(request.Name)) - room.SetStateInternal(new StateEvent() { + room.SetStateInternal(new MatrixEvent() { Type = RoomNameEventContent.EventId, TypedContent = new RoomNameEventContent() { Name = request.Name @@ -86,7 +86,7 @@ public class RoomStore { }); if (!string.IsNullOrWhiteSpace(request.RoomAliasName)) - room.SetStateInternal(new StateEvent() { + room.SetStateInternal(new MatrixEvent() { Type = RoomCanonicalAliasEventContent.EventId, TypedContent = new RoomCanonicalAliasEventContent() { Alias = $"#{request.RoomAliasName}:localhost" @@ -112,10 +112,10 @@ public class RoomStore { public class Room : NotifyPropertyChanged { private CancellationTokenSource _debounceCts = new(); - private ObservableCollection<StateEventResponse> _timeline; - private ObservableDictionary<string, List<StateEventResponse>> _accountData; + private ObservableCollection<MatrixEventResponse> _timeline; + private ObservableDictionary<string, List<MatrixEventResponse>> _accountData; private ObservableDictionary<string, ReadMarkersData> _readMarkers; - private FrozenSet<StateEventResponse> _stateCache; + private FrozenSet<MatrixEventResponse> _stateCache; private int _timelineHash; public Room(string roomId) { @@ -129,9 +129,9 @@ public class RoomStore { public string RoomId { get; set; } - public FrozenSet<StateEventResponse> State => _timelineHash == _timeline.GetHashCode() ? _stateCache : RebuildState(); + public FrozenSet<MatrixEventResponse> State => _timelineHash == _timeline.GetHashCode() ? _stateCache : RebuildState(); - public ObservableCollection<StateEventResponse> Timeline { + public ObservableCollection<MatrixEventResponse> Timeline { get => _timeline; set { if (Equals(value, _timeline)) return; @@ -140,7 +140,7 @@ public class RoomStore { // we dont want to do this as it's rebuilt when the state is accessed // if (args.Action == NotifyCollectionChangedAction.Add) { - // foreach (StateEventResponse state in args.NewItems) { + // foreach (MatrixEventResponse state in args.NewItems) { // if (state.StateKey is not null) // // we want state to be deduplicated by type and key, and we want the latest state to be the one that is returned // RebuildState(); @@ -154,7 +154,7 @@ public class RoomStore { } } - public ObservableDictionary<string, List<StateEventResponse>> AccountData { + public ObservableDictionary<string, List<MatrixEventResponse>> AccountData { get => _accountData; set { if (Equals(value, _accountData)) return; @@ -164,7 +164,7 @@ public class RoomStore { } } - public ImmutableList<StateEventResponse> JoinedMembers => + public ImmutableList<MatrixEventResponse> JoinedMembers => State.Where(s => s is { Type: RoomMemberEventContent.EventId, TypedContent: RoomMemberEventContent { Membership: "join" } }).ToImmutableList(); public ObservableDictionary<string, ReadMarkersData> ReadMarkers { @@ -177,8 +177,8 @@ public class RoomStore { } } - internal StateEventResponse SetStateInternal(StateEvent request, string? senderId = null, UserStore.User? user = null) { - var state = request as StateEventResponse ?? new StateEventResponse() { + internal MatrixEventResponse SetStateInternal(MatrixEvent request, string? senderId = null, UserStore.User? user = null) { + var state = request as MatrixEventResponse ?? new MatrixEventResponse() { Type = request.Type, StateKey = request.StateKey ?? "", EventId = "$" + Guid.NewGuid().ToString(), @@ -195,7 +195,7 @@ public class RoomStore { return state; } - public StateEventResponse AddUser(string userId) { + public MatrixEventResponse AddUser(string userId) { var state = SetStateInternal(new() { Type = RoomMemberEventContent.EventId, StateKey = userId, @@ -245,15 +245,15 @@ public class RoomStore { private SemaphoreSlim stateRebuildSemaphore = new(1, 1); - private FrozenSet<StateEventResponse> RebuildState() { + private FrozenSet<MatrixEventResponse> RebuildState() { stateRebuildSemaphore.Wait(); while (true) try { Console.WriteLine($"Rebuilding state for room {RoomId}"); // ReSharper disable once RedundantEnumerableCastCall - This sometimes happens when the collection is modified during enumeration - List<StateEventResponse>? timeline = null; + List<MatrixEventResponse>? timeline = null; lock (_timeline) { - timeline = Timeline.OfType<StateEventResponse>().ToList(); + timeline = Timeline.OfType<MatrixEventResponse>().ToList(); } foreach (var evt in timeline) { @@ -284,7 +284,7 @@ public class RoomStore { } } - public List<StateEventResponse> GetRoomsByMember(string userId) { + public List<MatrixEventResponse> GetRoomsByMember(string userId) { // return _rooms // // .Where(r => r.State.Any(s => s.Type == RoomMemberEventContent.EventId && s.StateKey == userId)) // .Select(r => (Room: r, MemberEvent: r.State.SingleOrDefault(s => s.Type == RoomMemberEventContent.EventId && s.StateKey == userId))) diff --git a/Utilities/LibMatrix.HomeserverEmulator/Services/UserStore.cs b/Utilities/LibMatrix.HomeserverEmulator/Services/UserStore.cs
index 7f211e3..124a082 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Services/UserStore.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Services/UserStore.cs
@@ -75,19 +75,18 @@ public class UserStore { UserId = $"@{localPart}:{_config.ServerName}", IsGuest = kind == "guest", AccountData = new() { - new StateEventResponse() { + new MatrixEventResponse() { Type = "im.vector.analytics", RawContent = new JsonObject() { ["pseudonymousAnalyticsOptIn"] = false }, }, - new StateEventResponse() { + new MatrixEventResponse() { Type = "im.vector.web.settings", RawContent = new JsonObject() { ["developerMode"] = true, ["alwaysShowTimestamps"] = true, ["SpotlightSearch.showNsfwPublicRooms"] = true, - } }, new() { @@ -135,7 +134,7 @@ public class UserStore { private ObservableDictionary<string, SessionInfo> _accessTokens; private ObservableDictionary<string, SyncFilter> _filters; private ObservableDictionary<string, object> _profile; - private ObservableCollection<StateEventResponse> _accountData; + private ObservableCollection<MatrixEventResponse> _accountData; private ObservableDictionary<string, RoomKeysResponse> _roomKeys; private ObservableDictionary<string, LoginResponse> _authorizedSessions; @@ -174,7 +173,7 @@ public class UserStore { } } - public ObservableCollection<StateEventResponse> AccountData { + public ObservableCollection<MatrixEventResponse> AccountData { get => _accountData; set { if (value == _accountData) return; diff --git a/Utilities/LibMatrix.JsonSerializerContextGenerator/LibMatrix.JsonSerializerContextGenerator.csproj b/Utilities/LibMatrix.JsonSerializerContextGenerator/LibMatrix.JsonSerializerContextGenerator.csproj
index 486b6b6..37c7989 100644 --- a/Utilities/LibMatrix.JsonSerializerContextGenerator/LibMatrix.JsonSerializerContextGenerator.csproj +++ b/Utilities/LibMatrix.JsonSerializerContextGenerator/LibMatrix.JsonSerializerContextGenerator.csproj
@@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <OutputType>exe</OutputType> diff --git a/Utilities/LibMatrix.TestDataGenerator/Bot/DataFetcher.cs b/Utilities/LibMatrix.TestDataGenerator/Bot/DataFetcher.cs
index 66b8a03..8d76f06 100644 --- a/Utilities/LibMatrix.TestDataGenerator/Bot/DataFetcher.cs +++ b/Utilities/LibMatrix.TestDataGenerator/Bot/DataFetcher.cs
@@ -34,7 +34,7 @@ public class DataFetcher(AuthenticatedHomeserverGeneric hs, ILogger<DataFetcher> await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: "Fetching room data...")); - var roomAliasTasks = rooms.Select(room => room.GetCanonicalAliasAsync()).ToAsyncEnumerable(); + var roomAliasTasks = rooms.Select(room => room.GetCanonicalAliasAsync()).ToAsyncResultEnumerable(); List<Task<(string, string)>> aliasResolutionTasks = new(); await foreach (var @event in roomAliasTasks) if (@event?.Alias != null) { @@ -45,7 +45,7 @@ public class DataFetcher(AuthenticatedHomeserverGeneric hs, ILogger<DataFetcher> }, cancellationToken)); } - var aliasResolutionTaskEnumerator = aliasResolutionTasks.ToAsyncEnumerable(); + var aliasResolutionTaskEnumerator = aliasResolutionTasks.ToAsyncResultEnumerable(); await foreach (var result in aliasResolutionTaskEnumerator) await _logRoom.SendMessageEventAsync(new RoomMessageEventContent(body: $"Resolved room alias {result.Item1} to {result.Item2}!")); } diff --git a/Utilities/LibMatrix.TestDataGenerator/LibMatrix.TestDataGenerator.csproj b/Utilities/LibMatrix.TestDataGenerator/LibMatrix.TestDataGenerator.csproj
index b054aba..1678502 100644 --- a/Utilities/LibMatrix.TestDataGenerator/LibMatrix.TestDataGenerator.csproj +++ b/Utilities/LibMatrix.TestDataGenerator/LibMatrix.TestDataGenerator.csproj
@@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> <LangVersion>preview</LangVersion> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> @@ -17,7 +17,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1"/> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-rc.2.25502.107"/> </ItemGroup> <ItemGroup> <Content Include="appsettings*.json"> diff --git a/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs b/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs
index c6abde2..4da6df2 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Interfaces/CommandContext.cs
@@ -1,12 +1,13 @@ using LibMatrix.EventTypes.Spec; using LibMatrix.Homeservers; +using LibMatrix.Responses; using LibMatrix.RoomTypes; namespace LibMatrix.Utilities.Bot.Interfaces; public class CommandContext { public required GenericRoom Room { get; set; } - public required StateEventResponse MessageEvent { get; set; } + public required MatrixEventResponse MessageEvent { get; set; } public string MessageContentWithoutReply => (MessageEvent.TypedContent as RoomMessageEventContent)! diff --git a/Utilities/LibMatrix.Utilities.Bot/Interfaces/RoomInviteContext.cs b/Utilities/LibMatrix.Utilities.Bot/Interfaces/RoomInviteContext.cs
index 380c1c7..c5ffc7c 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Interfaces/RoomInviteContext.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Interfaces/RoomInviteContext.cs
@@ -7,7 +7,7 @@ namespace LibMatrix.Utilities.Bot.Interfaces; public class RoomInviteContext { public required string RoomId { get; init; } public required AuthenticatedHomeserverGeneric Homeserver { get; init; } - public required StateEventResponse MemberEvent { get; init; } + public required MatrixEventResponse MemberEvent { get; init; } public required SyncResponse.RoomsDataStructure.InvitedRoomDataStructure InviteData { get; init; } public async Task<string> TryGetInviterNameAsync() { diff --git a/Utilities/LibMatrix.Utilities.Bot/LibMatrix.Utilities.Bot.csproj b/Utilities/LibMatrix.Utilities.Bot/LibMatrix.Utilities.Bot.csproj
index bbb0a65..80d43be 100644 --- a/Utilities/LibMatrix.Utilities.Bot/LibMatrix.Utilities.Bot.csproj +++ b/Utilities/LibMatrix.Utilities.Bot/LibMatrix.Utilities.Bot.csproj
@@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net10.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <LangVersion>preview</LangVersion> @@ -9,12 +9,13 @@ <ItemGroup> <ProjectReference Include="..\..\LibMatrix\LibMatrix.csproj"/> + <PackageReference Include="LibMatrix" Version="*-*" Condition="'$(ContinuousIntegrationBuild)'=='true'"/> </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.1" /> - <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" /> - <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1" /> + <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107"/> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-rc.2.25502.107"/> + <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107"/> </ItemGroup> diff --git a/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs b/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs
index 4c6b462..5b697de 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs
@@ -25,7 +25,7 @@ public class CommandListenerHostedService( private FrozenSet<ICommand> _commands = null!; private Task? _listenerTask; - private CancellationTokenSource _cts = new(); + private readonly CancellationTokenSource _cts = new(); private long _startupTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); /// <summary>Triggered when the application host is ready to start the service.</summary> @@ -41,7 +41,7 @@ public class CommandListenerHostedService( private async Task? Run(CancellationToken cancellationToken) { logger.LogInformation("Starting command listener!"); - var filter = await hs.NamedCaches.FilterCache.GetOrSetValueAsync("gay.rory.libmatrix.utilities.bot.command_listener_syncfilter.dev3" + (config.SelfCommandsOnly), + var filter = await hs.NamedCaches.FilterCache.GetOrSetValueAsync("gay.rory.libmatrix.utilities.bot.command_listener_syncfilter.dev4" + (config.SelfCommandsOnly), new SyncFilter() { AccountData = new SyncFilter.EventFilter(notTypes: ["*"], limit: 1), Presence = new SyncFilter.EventFilter(notTypes: ["*"]), @@ -49,9 +49,11 @@ public class CommandListenerHostedService( AccountData = new SyncFilter.RoomFilter.StateFilter(notTypes: ["*"]), Ephemeral = new SyncFilter.RoomFilter.StateFilter(notTypes: ["*"]), State = new SyncFilter.RoomFilter.StateFilter(notTypes: ["*"]), - Timeline = new SyncFilter.RoomFilter.StateFilter(types: ["m.room.message"], + Timeline = new SyncFilter.RoomFilter.StateFilter( + types: ["m.room.message"], notSenders: config.SelfCommandsOnly ? null : [hs.WhoAmI.UserId], - senders: config.SelfCommandsOnly ? [hs.WhoAmI.UserId] : null + senders: config.SelfCommandsOnly ? [hs.WhoAmI.UserId] : null, + limit: config.SelfCommandsOnly ? 1 : null ), } }); @@ -61,7 +63,6 @@ public class CommandListenerHostedService( Timeout = config.SyncConfiguration.Timeout ?? 30_000, MinimumDelay = config.SyncConfiguration.MinimumSyncTime ?? TimeSpan.Zero, SetPresence = config.SyncConfiguration.Presence ?? botConfig.Presence, - }; syncHelper.SyncReceivedHandlers.Add(async sync => { @@ -118,7 +119,7 @@ public class CommandListenerHostedService( await _cts.CancelAsync(); } - private async Task<string?> GetUsedPrefix(StateEventResponse evt) { + private async Task<string?> GetUsedPrefix(MatrixEventResponse evt) { var messageContent = evt.TypedContent as RoomMessageEventContent; var message = messageContent!.BodyWithoutReplyFallback; var prefix = config.Prefixes.OrderByDescending(x => x.Length).FirstOrDefault(message.StartsWith); @@ -138,7 +139,7 @@ public class CommandListenerHostedService( return prefix; } - private async Task<CommandResult> InvokeCommand(StateEventResponse evt, string usedPrefix) { + private async Task<CommandResult> InvokeCommand(MatrixEventResponse evt, string usedPrefix) { var message = evt.TypedContent as RoomMessageEventContent; var room = hs.GetRoom(evt.RoomId!); diff --git a/Utilities/LibMatrix.Utilities.Bot/deps.json b/Utilities/LibMatrix.Utilities.Bot/deps.json new file mode 100644
index 0000000..da8051b --- /dev/null +++ b/Utilities/LibMatrix.Utilities.Bot/deps.json
@@ -0,0 +1,142 @@ +[ + { + "pname": "Microsoft.Extensions.Configuration", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-Bxb3LNyZsDlGyYxBjDnUIgj8ZfIDAb0fJqbBdGRocPY=" + }, + { + "pname": "Microsoft.Extensions.Configuration.Abstractions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-224s03xAtaxcp6T0D17a/aK2qGkPlajGS1THd6HXV8A=" + }, + { + "pname": "Microsoft.Extensions.Configuration.Binder", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-gPBNpr7vAB08NXWESFwt3G/b83ds/RDw17QN/op57kM=" + }, + { + "pname": "Microsoft.Extensions.Configuration.CommandLine", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-kZdKla41vPzV6XdCfz0ObTQW54Z4oVn17J5V8zouGa8=" + }, + { + "pname": "Microsoft.Extensions.Configuration.EnvironmentVariables", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-dNZaIOJ2TV9Chj9fIDshZTP8UbIvsCqEkQNv65iuX+8=" + }, + { + "pname": "Microsoft.Extensions.Configuration.FileExtensions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-X5e1bqK6OhslICJQQGA1HORX94rJBV1f3RMRZ4chwaE=" + }, + { + "pname": "Microsoft.Extensions.Configuration.Json", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-C6NixHkhPrNCX44U2KhJUlDbry1drXwKTKjTI5sDw5I=" + }, + { + "pname": "Microsoft.Extensions.Configuration.UserSecrets", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-tfmsy6K0UoSK0dh36XLypPm6cjJy0xyU9Pgm5YpKV+o=" + }, + { + "pname": "Microsoft.Extensions.DependencyInjection", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-LhtRtPoZbwgZrfaaFa2MNDK2TDsZby7T0UtlE2pqhwk=" + }, + { + "pname": "Microsoft.Extensions.DependencyInjection.Abstractions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-1nh8z2nglCizQkl0iWwJ/au4BAuuBu0xghKHGBeTM1I=" + }, + { + "pname": "Microsoft.Extensions.Diagnostics", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-LAKtLFZMBJ6qPp/h9rKbRCxk6lT2OsNQLqeKutIO5Go=" + }, + { + "pname": "Microsoft.Extensions.Diagnostics.Abstractions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-tLxHtLTe1KsvE00xgXlTfL4WrtURuzZyVT6hl5Kdx9g=" + }, + { + "pname": "Microsoft.Extensions.FileProviders.Abstractions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-UpNf7I8nhNDhEIIxZ3TD3EHJxBlAFLGB8qIrXvahZSQ=" + }, + { + "pname": "Microsoft.Extensions.FileProviders.Physical", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-WcY2e493OhzTx2YKUMpWZctrxmvPhW6pvB4zRPcRhBA=" + }, + { + "pname": "Microsoft.Extensions.FileSystemGlobbing", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-tgcgrmF89f+UZkBwdQEdEJlnJ+DnhPHM6E7zo5wfAdc=" + }, + { + "pname": "Microsoft.Extensions.Hosting", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-6psfqOUKyucgKUZx5sNtTFjiDPyXIDNatPWtTrqSz2I=" + }, + { + "pname": "Microsoft.Extensions.Hosting.Abstractions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-mPojrILhm+IhpZj8b0tGnosAxMorekKtC/6otju6qaI=" + }, + { + "pname": "Microsoft.Extensions.Logging", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-HUDcfhht8zuN4g1Ku0YbUKQzM1tIv5qK9tUt1EWACFU=" + }, + { + "pname": "Microsoft.Extensions.Logging.Abstractions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-krml7WL+lF7oiYOvQ8NHQp7BVpHJrLIHhyxUgkHO+WE=" + }, + { + "pname": "Microsoft.Extensions.Logging.Configuration", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-nmwnKAorvZ49MurYaBBooxEpNZIbCtNcgYqZg83mS0M=" + }, + { + "pname": "Microsoft.Extensions.Logging.Console", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-+h4QtdYmFnXZ0ul7lYT/mq6ioidurFhN+neh413MWPU=" + }, + { + "pname": "Microsoft.Extensions.Logging.Debug", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-7imEDh57muZuPU0gqj4ZwbLwG7ElqC/M2vQNLE0m/kY=" + }, + { + "pname": "Microsoft.Extensions.Logging.EventLog", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-qWTH3yQ9OJdOC63gFR/j1kMWwwCphqmD4pOLEC00Ncg=" + }, + { + "pname": "Microsoft.Extensions.Logging.EventSource", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-3KkZT6gfLRHOcVb7GUvRC6os5JN5ftRT5Yyhz8XcjWA=" + }, + { + "pname": "Microsoft.Extensions.Options", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-0gis7GC+wzUJiWlP1EPi0vCrWDrV8sU6KHmt4WkI5bQ=" + }, + { + "pname": "Microsoft.Extensions.Options.ConfigurationExtensions", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-woY7sow2/WfzCN1K9IaJe1EtYuz/LZZhPvlU9b70Q+I=" + }, + { + "pname": "Microsoft.Extensions.Primitives", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-jvjZK/c8TGYIUA4zw7yR9uAFJmw90YE7TD3+DaxX9Ls=" + }, + { + "pname": "System.Diagnostics.EventLog", + "version": "10.0.0-rc.2.25502.107", + "hash": "sha256-WGW3a4boNTJcuPNKT0RH7G7K3HGatXqmmBIRIjHTKN4=" + } +] diff --git a/flake.lock b/flake.lock new file mode 100644
index 0000000..5627b33 --- /dev/null +++ b/flake.lock
@@ -0,0 +1,116 @@ +{ + "nodes": { + "arcanelibs": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1763321335, + "narHash": "sha256-cjNR58eGCHYtL+78anpUdxDg/Y/5oermkY4XWJQ5ybI=", + "owner": "TheArcaneBrony", + "repo": "ArcaneLibs", + "rev": "412a14c2ad2fed85066155d1060c35cbe23d6c91", + "type": "github" + }, + "original": { + "owner": "TheArcaneBrony", + "repo": "ArcaneLibs", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1762977756, + "narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "arcanelibs": "arcanelibs", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644
index 0000000..3349b45 --- /dev/null +++ b/flake.nix
@@ -0,0 +1,104 @@ +{ + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + inputs.flake-utils.url = "github:numtide/flake-utils"; + inputs.arcanelibs.url = "github:TheArcaneBrony/ArcaneLibs"; + inputs.arcanelibs.inputs.nixpkgs.follows = "nixpkgs"; + + outputs = + { + self, + nixpkgs, + flake-utils, + arcanelibs, + }: + let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + rVersion = + let + rev = self.sourceInfo.shortRev or self.sourceInfo.dirtyShortRev; + date = builtins.substring 0 8 self.sourceInfo.lastModifiedDate; + time = builtins.substring 8 6 self.sourceInfo.lastModifiedDate; + in + "preview.${date}-${time}+${rev}"; + + makeNupkg = + { + name, + nugetDeps ? null, + projectReferences ? [ ], + projectFile ? "${name}/${name}.csproj", + }@args: + pkgs.buildDotnetModule rec { + inherit projectReferences nugetDeps projectFile; + + pname = "${name}"; + version = "1.0.0-" + rVersion; + dotnetPackFlags = [ + "--include-symbols" + "--include-source" + "--version-suffix ${rVersion}" + ]; + # dotnetFlags = [ "-v:diag" ]; + dotnet-sdk = pkgs.dotnet-sdk_10; + dotnet-runtime = pkgs.dotnet-aspnetcore_10; + src = ./.; + packNupkg = true; + meta = with pkgs.lib; { + description = "Rory&::LibMatrix"; + homepage = "https://cgit.rory.gay/matrix/LibMatrix.git"; + license = licenses.agpl3Plus; + maintainers = with maintainers; [ RorySys ]; + }; + }; + in + { + packages.x86_64-linux = + let + # HACKHACK: trim version string until nuget learns to deal with semver properly + # See: https://github.com/NuGet/Home/issues/14628 + ArcaneLibs = arcanelibs.packages."${pkgs.stdenv.hostPlatform.system}".ArcaneLibs.overrideAttrs (old: { + __intentionallyOverridingVersion = true; + version = builtins.substring 0 29 old.version; # "1.0.0-preview-20251106-123456"; + }); + LibMatrix = self.packages."${pkgs.stdenv.hostPlatform.system}".LibMatrix.overrideAttrs (old: { + __intentionallyOverridingVersion = true; + version = builtins.substring 0 29 old.version; # "1.0.0-preview-20251106-123456"; + }); + in + { + LibMatrix = makeNupkg { + name = "LibMatrix"; + nugetDeps = LibMatrix/deps.json; + projectReferences = [ ArcaneLibs ]; + }; + LibMatrix-EventTypes = makeNupkg { + name = "LibMatrix.EventTypes"; + projectReferences = [ + ArcaneLibs + # LibMatrix + ]; + }; + LibMatrix-Federation = makeNupkg { + name = "LibMatrix.Federation"; + nugetDeps = LibMatrix.Federation/deps.json; + projectReferences = [ + ArcaneLibs + LibMatrix + ]; + }; + LibMatrix-Bot-Utils = makeNupkg { + name = "LibMatrix.Utilities.Bot"; + nugetDeps = Utilities/LibMatrix.Utilities.Bot/deps.json; + projectFile = "Utilities/LibMatrix.Utilities.Bot/LibMatrix.Utilities.Bot.csproj"; + projectReferences = [ + ArcaneLibs + LibMatrix + ]; + }; + }; + checks = pkgs.lib.attrsets.unionOfDisjoint { + # Actual checks + } self.packages; + }; + +} diff --git a/scripts/fetchdeps.sh b/scripts/fetchdeps.sh new file mode 100755
index 0000000..a93ff53 --- /dev/null +++ b/scripts/fetchdeps.sh
@@ -0,0 +1,5 @@ +#! /usr/bin/env sh +for p in `nix flake show --json | jq '.packages."x86_64-linux" | keys[]' -r` +do + nix build .\#${p}.passthru.fetch-deps && ./result ./${p//-/\.}/deps.json +done diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100755
index 0000000..2fd910f --- /dev/null +++ b/scripts/publish.sh
@@ -0,0 +1,20 @@ +BASEDIR="$PWD" +rm ./result* *.nupkg + +for p in `nix flake show --json | jq '.packages."x86_64-linux" | keys[]' -r` +do + nix build .\#${p} -j4 -L --out-link ./result-${p//-/\.} & +done +wait + +for p in result*/share/nuget/packages/*/*/.unpacked +do + PNAME=$(basename `realpath "${p}/../.."`) + PRNAME=$(basename $(cd "${p}/../../../../../.." && echo $PWD)) + echo $PNAME: $PRNAME + cd "${p}" || continue + zip -db -ds 32k -9 -r "${BASEDIR}/${PNAME//./-}.nupkg" * + cd - + dotnet nuget push *.nupkg -k ${NUGET_KEY} --source https://api.nuget.org/v3/index.json --skip-duplicate + rm -rfv "${PRNAME}" "${PNAME//./-}.nupkg" +done \ No newline at end of file