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/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..d75b19f 100644
--- a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
@@ -35,7 +35,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 +51,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 +82,46 @@ 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("?", "."));
public bool IsGlobRule() =>
- Entity != null
+ !string.IsNullOrWhiteSpace(Entity)
&& (Entity.Contains('*') || Entity.Contains('?'));
- public bool EntityMatches(string entity) =>
- Entity != null
- && (
- Entity == entity
- || (
- IsGlobRule()
- ? GetEntityRegex()!.IsMatch(entity)
- : entity == Entity
- )
- );
+ public bool EntityMatches(string entity) {
+ if (string.IsNullOrWhiteSpace(entity)) return false;
+
+ 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;
+ }
+
+ 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;
}
}
@@ -130,6 +136,13 @@ public static class PolicyRecommendationTypes {
/// Mute this user
/// </summary>
public static string Mute = "support.feline.policy.recommendation_mute"; //stable prefix: m.mute, msc pending
+
+ public static string Takedown = "m.takedown"; //unstable prefix: org.matrix.msc4204.takedown
+}
+
+public class PolicyHash {
+ [JsonPropertyName("sha256")]
+ public string? Sha256 { get; set; }
}
// public class PolicySchemaDefinition {
diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
index 16cfcb0..8edf4a7 100644
--- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
+++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
@@ -8,4 +8,10 @@ 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";
+ }
}
\ 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.Federation/AuthenticatedFederationClient.cs b/LibMatrix.Federation/AuthenticatedFederationClient.cs
new file mode 100644
index 0000000..6f8d44b
--- /dev/null
+++ b/LibMatrix.Federation/AuthenticatedFederationClient.cs
@@ -0,0 +1,20 @@
+using LibMatrix.Homeservers;
+
+namespace LibMatrix.Federation;
+
+public class AuthenticatedFederationClient : FederationClient {
+
+ public class AuthenticatedFederationConfiguration {
+
+ }
+ public AuthenticatedFederationClient(string federationEndpoint, AuthenticatedFederationConfiguration config, string? proxy = null) : base(federationEndpoint, proxy)
+ {
+
+ }
+
+ // public async Task<UserDeviceListResponse> GetUserDevicesAsync(string userId) {
+ // var response = await GetAsync<UserDeviceListResponse>($"/_matrix/federation/v1/user/devices/{userId}", accessToken);
+ // 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..69baf58
--- /dev/null
+++ b/LibMatrix.Federation/Extensions/Ed25519Extensions.cs
@@ -0,0 +1,8 @@
+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());
+}
\ 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..78086bb
--- /dev/null
+++ b/LibMatrix.Federation/LibMatrix.Federation.csproj
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net9.0</TargetFramework>
+ <LangVersion>preview</LangVersion>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\LibMatrix\LibMatrix.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="BouncyCastle.Cryptography" Version="2.6.1" />
+ <PackageReference Include="Microsoft.Extensions.Primitives" Version="10.0.0-preview.5.25277.114" />
+ </ItemGroup>
+
+</Project>
diff --git a/LibMatrix.Federation/Utilities/JsonSigning.cs b/LibMatrix.Federation/Utilities/JsonSigning.cs
new file mode 100644
index 0000000..c727cde
--- /dev/null
+++ b/LibMatrix.Federation/Utilities/JsonSigning.cs
@@ -0,0 +1,108 @@
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+using ArcaneLibs.Extensions;
+using LibMatrix.Extensions;
+using LibMatrix.FederationTest.Utilities;
+using LibMatrix.Homeservers;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math.EC.Rfc8032;
+
+namespace LibMatrix.Federation.Utilities;
+
+public static class JsonSigning { }
+
+[JsonConverter(typeof(SignedObjectConverterFactory))]
+public class SignedObject<T> {
+ [JsonPropertyName("signatures")]
+ public Dictionary<string, Dictionary<string, string>> Signatures { get; set; } = new();
+
+ [JsonIgnore]
+ public Dictionary<string, Dictionary<ServerKeysResponse.VersionedKeyId, string>> VerifyKeysById {
+ get => Signatures.ToDictionary(server => server.Key, server => server.Value.ToDictionary(key => (ServerKeysResponse.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();
+ }
+}
+
+// Content needs to be merged at toplevel
+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;
+ }
+}
+
+public static class ObjectExtensions {
+ public static SignedObject<T> Sign<T>(this SignedObject<T> content, string serverName, string keyName, Ed25519PrivateKeyParameters key) {
+ var signResult = Sign(content.Content, 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 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;
+ }
+}
\ No newline at end of file
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..fc402b7
--- /dev/null
+++ b/LibMatrix.Federation/XMatrixAuthorizationScheme.cs
@@ -0,0 +1,71 @@
+using System.Net;
+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.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/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/MatrixHttpClient.Single.cs b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
index baa4a2c..671566f 100644
--- a/LibMatrix/Extensions/MatrixHttpClient.Single.cs
+++ b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
@@ -101,22 +101,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;
}
@@ -286,9 +286,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) {
diff --git a/LibMatrix/Helpers/MessageBuilder.cs b/LibMatrix/Helpers/MessageBuilder.cs
index 5e2b1b7..6f55739 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>";
@@ -94,6 +98,16 @@ public class MessageBuilder(string msgType = "m.text", string format = "org.matr
public MessageBuilder WithMention(string id, string? displayName = null) {
Content.Body += $"@{displayName ?? id}";
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;
}
diff --git a/LibMatrix/Helpers/RoomBuilder.cs b/LibMatrix/Helpers/RoomBuilder.cs
new file mode 100644
index 0000000..bef7568
--- /dev/null
+++ b/LibMatrix/Helpers/RoomBuilder.cs
@@ -0,0 +1,173 @@
+using System.Runtime.Intrinsics.X86;
+using LibMatrix.EventTypes.Spec.State.RoomInfo;
+using LibMatrix.Homeservers;
+using LibMatrix.Responses;
+using LibMatrix.RoomTypes;
+
+namespace LibMatrix.Helpers;
+
+public class RoomBuilder {
+ public string? Type { get; set; }
+ public string Version { get; set; } = "11";
+ 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
+ };
+
+ /// <summary>
+ /// State events to be sent *before* room access is configured. Keep this small!
+ /// </summary>
+ public List<StateEvent> ImportantState { get; set; } = [];
+
+ /// <summary>
+ /// State events to be sent *after* room access is configured, but before invites are sent.
+ /// </summary>
+ public List<StateEvent> InitialState { get; set; } = [];
+
+ /// <summary>
+ /// Users to invite, with optional reason
+ /// </summary>
+ public Dictionary<string, string?> Invites { get; set; } = new();
+
+ public RoomPowerLevelEventContent PowerLevels { get; init; } = 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 },
+ { RoomNameEventContent.EventId, 50 },
+ { RoomPowerLevelEventContent.EventId, 100 },
+ { RoomServerAclEventContent.EventId, 100 },
+ { RoomTombstoneEventContent.EventId, 100 },
+ { RoomPolicyServerEventContent.EventId, 100 }
+ }
+ };
+
+ public 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 Dictionary<string, long>() {
+ { homeserver.WhoAmI.UserId, MatrixConstants.MaxSafeJsonInteger }
+ },
+ Events = new Dictionary<string, long> {
+ { RoomAvatarEventContent.EventId, 1000000 },
+ { RoomCanonicalAliasEventContent.EventId, 1000000 },
+ { RoomEncryptionEventContent.EventId, 1000000 },
+ { RoomHistoryVisibilityEventContent.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);
+
+ var room = await homeserver.CreateRoom(crq);
+
+ 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;
+
+ 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<StateEvent> state) {
+ foreach (var ev in state) {
+ await (string.IsNullOrWhiteSpace(ev.StateKey)
+ ? room.SendStateEventAsync(ev.Type, ev.RawContent)
+ : room.SendStateEventAsync(ev.Type, ev.StateKey, ev.RawContent));
+ }
+ }
+
+ 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);
+ }
+ }
+
+ private async Task SetAccessAsync(GenericRoom room) {
+ PowerLevels.Users![room.Homeserver.WhoAmI.UserId] = OwnPowerLevel;
+ 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/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs
index 6488464..c8e2928 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;
@@ -24,8 +24,8 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg
public string? Since { get; set; }
public int Timeout { get; set; } = 30000;
- public string? SetPresence { get; set; } = "online";
-
+ 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;
@@ -168,9 +168,11 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg
var sw = Stopwatch.StartNew();
if (_filterIsDirty) await UpdateFilterAsync();
- var url = $"/_matrix/client/v3/sync?timeout={Timeout}&set_presence={SetPresence}&full_state={(FullState ? "true" : "false")}";
+ var url = $"/_matrix/client/v3/sync?timeout={Timeout}";
+ if (!string.IsNullOrWhiteSpace(SetPresence)) url += $"&set_presence={SetPresence}";
if (!string.IsNullOrWhiteSpace(Since)) url += $"&since={Since}";
if (_filterId is not null) url += $"&filter={_filterId}";
+ if (FullState) url += "&full_state=true";
if (UseMsc4222StateAfter) url += "&org.matrix.msc4222.use_state_after=true&use_state_after=true"; // We use both unstable and stable names for compatibility
// logger?.LogInformation("SyncHelper: Calling: {}", url);
@@ -184,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);
@@ -209,6 +210,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg
Console.WriteLine(e);
logger?.LogError(e, "Failed to sync!\n{}", e.ToString());
await Task.WhenAll(ExceptionHandlers.Select(x => x.Invoke(e)).ToList());
+ if (e is MatrixException { ErrorCode: MatrixException.ErrorCodes.M_UNKNOWN_TOKEN }) throw;
}
return null;
diff --git a/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs b/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs
index 6cb42ca..e34b5cf 100644
--- a/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs
+++ b/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs
@@ -1,11 +1,13 @@
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) {
+public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homeserver, ILogger? logger) {
private static bool StateEventsMatch(StateEventResponse a, StateEventResponse b) {
return a.Type == b.Type && a.StateKey == b.StateKey;
}
@@ -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);
}
@@ -49,7 +52,10 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese
}
}
- 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;
@@ -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,7 +123,8 @@ 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;
}
});
@@ -150,7 +157,7 @@ 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>();
@@ -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,7 +192,8 @@ 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;
}
});
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index 55899de..5fd3311 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -578,4 +578,41 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
[JsonPropertyName("capabilities")]
public Dictionary<string, object>? Capabilities { 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..a2cb12d 100644
--- a/LibMatrix/Homeservers/FederationClient.cs
+++ b/LibMatrix/Homeservers/FederationClient.cs
@@ -1,6 +1,7 @@
using System.Text.Json.Serialization;
using LibMatrix.Extensions;
using LibMatrix.Services;
+using Microsoft.VisualBasic.CompilerServices;
namespace LibMatrix.Homeservers;
@@ -17,6 +18,70 @@ 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<ServerKeysResponse> GetServerKeysAsync() => await HttpClient.GetFromJsonAsync<ServerKeysResponse>("/_matrix/key/v2/server");
+}
+
+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);
+ }
+
+ 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 class CurrentVerifyKey {
+ [JsonPropertyName("key")]
+ public string Key { get; set; }
+ }
+
+ 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();
+ }
+ }
}
public class ServerVersionResponse {
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/SynapseAdminApiClient.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs
index 777c04a..cee3d8d 100644
--- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs
+++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs
@@ -183,6 +183,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH
#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 +191,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());
diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs
index d9a6acd..6933622 100644
--- a/LibMatrix/Responses/CreateRoomRequest.cs
+++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -47,6 +47,8 @@ public class CreateRoomRequest {
[JsonPropertyName("invite")]
public List<string>? Invite { get; set; }
+ public string? RoomVersion { get; set; }
+
/// <summary>
/// For use only when you can't use the CreationContent property
/// </summary>
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/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..4912add 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")]
diff --git a/LibMatrix/Responses/SyncResponse.cs b/LibMatrix/Responses/SyncResponse.cs
index 47fb753..d79e820 100644
--- a/LibMatrix/Responses/SyncResponse.cs
+++ b/LibMatrix/Responses/SyncResponse.cs
@@ -167,7 +167,7 @@ public class SyncResponse {
]).Max();
}
- [JsonConverter(typeof(JsonStringEnumConverter))]
+ [JsonConverter(typeof(JsonStringEnumConverter<Msc4222SyncType>))]
public enum Msc4222SyncType {
None,
Server,
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 93736a3..fd4db4d 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;
@@ -232,8 +232,11 @@ public class GenericRoom {
return await res.Content.ReadFromJsonAsync<RoomIdResponse>() ?? throw new Exception("Failed to join room?");
}
- public async IAsyncEnumerable<StateEventResponse> GetMembersEnumerableAsync(bool joinedOnly = true) {
- var res = await Homeserver.ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members");
+ public async IAsyncEnumerable<StateEventResponse> 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
});
@@ -242,13 +245,16 @@ public class GenericRoom {
foreach (var resp in result.Chunk ?? []) {
if (resp.Type != "m.room.member") continue;
- if (joinedOnly && resp.RawContent?["membership"]?.GetValue<string>() != "join") continue;
+ if (isMembershipSet && resp.RawContent?["membership"]?.GetValue<string>() != membership) continue;
yield return resp;
}
}
- public async Task<FrozenSet<StateEventResponse>> GetMembersListAsync(bool joinedOnly = true) {
- var res = await Homeserver.ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members");
+ public async Task<FrozenSet<StateEventResponse>> 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
});
@@ -258,13 +264,23 @@ public class GenericRoom {
var members = new List<StateEventResponse>();
foreach (var resp in result.Chunk ?? []) {
if (resp.Type != "m.room.member") continue;
- if (joinedOnly && resp.RawContent?["membership"]?.GetValue<string>() != "join") continue;
+ if (isMembershipSet && resp.RawContent?["membership"]?.GetValue<string>() != membership) continue;
members.Add(resp);
}
return members.ToFrozenSet();
}
+ public async IAsyncEnumerable<string> GetMemberIdsEnumerableAsync(string? membership = null) {
+ 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();
+ }
+
#region Utility shortcuts
public Task<EventIdResponse> SendMessageEventAsync(RoomMessageEventContent content) =>
@@ -393,6 +409,15 @@ public class GenericRoom {
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() {
+ RelationType = "m.annotation",
+ EventId = eventId,
+ Key = key
+ }
+ });
+
public async Task<EventIdResponse?> SendFileAsync(string fileName, Stream fileStream, string messageType = "m.file", string contentType = "application/octet-stream") {
var url = await Homeserver.UploadFile(fileName, fileStream);
var content = new RoomMessageEventContent() {
@@ -436,8 +461,8 @@ public class GenericRoom {
}
}
- public Task<StateEventResponse> GetEventAsync(string eventId) =>
- Homeserver.ClientHttpClient.GetFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}");
+ public Task<StateEventResponse> GetEventAsync(string eventId, bool includeUnredactedContent = false) =>
+ Homeserver.ClientHttpClient.GetFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}?fi.mau.msc2815.include_unredacted_content={includeUnredactedContent}");
public async Task<EventIdResponse> RedactEventAsync(string eventToRedact, string? reason = null) {
var data = new { reason };
@@ -586,4 +611,4 @@ public class GenericRoom {
public class RoomIdResponse {
[JsonPropertyName("room_id")]
public string RoomId { get; set; }
-}
+}
\ No newline at end of file
diff --git a/LibMatrix/RoomTypes/SpaceRoom.cs b/LibMatrix/RoomTypes/SpaceRoom.cs
index 4563ed3..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;
@@ -17,7 +18,7 @@ public class SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId)
}
public async Task<EventIdResponse> AddChildAsync(GenericRoom room) {
- var members = room.GetMembersEnumerableAsync(true);
+ var members = room.GetMembersEnumerableAsync("join");
Dictionary<string, int> memberCountByHs = new();
await foreach (var member in members) {
var server = member.StateKey.Split(':')[1];
diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs
index 53cd2dd..94a3826 100644
--- a/LibMatrix/Services/HomeserverResolverService.cs
+++ b/LibMatrix/Services/HomeserverResolverService.cs
@@ -44,7 +44,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 +62,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 +100,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");
diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs
index ef760e1..af25805 100644
--- a/LibMatrix/StateEvent.cs
+++ b/LibMatrix/StateEvent.cs
@@ -100,13 +100,10 @@ public class StateEvent {
[JsonPropertyName("replaces_state")]
public string? ReplacesState { get; set; }
- private JsonObject? _rawContent;
-
[JsonPropertyName("content")]
- public JsonObject? RawContent {
- get => _rawContent;
- set => _rawContent = value;
- }
+ // [field: AllowNull, MaybeNull]
+ [NotNull]
+ public JsonObject? RawContent { get; set; }
//debug
[JsonIgnore]
@@ -122,6 +119,18 @@ 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.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(StateEventResponse x, StateEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey && JsonNode.DeepEquals(x.RawContent, y.RawContent);
}
public class StateEventResponse : StateEvent {
|