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
|