diff options
Diffstat (limited to '')
-rw-r--r-- | LibMatrix.EventTypes/EventContent.cs | 16 | ||||
-rw-r--r-- | LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs | 1 | ||||
-rw-r--r-- | LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs | 4 | ||||
-rw-r--r-- | LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs | 4 | ||||
-rw-r--r-- | LibMatrix.MxApiExtensions/LibMatrix.MxApiExtensions.csproj | 13 | ||||
-rw-r--r-- | LibMatrix.sln | 86 | ||||
-rw-r--r-- | LibMatrix/Extensions/CanonicalJsonSerializer.cs | 91 | ||||
-rw-r--r-- | LibMatrix/Extensions/MatrixHttpClient.Single.cs | 16 | ||||
-rw-r--r-- | LibMatrix/Extensions/UnicodeJsonEncoder.cs | 173 | ||||
-rw-r--r-- | LibMatrix/Helpers/HomeserverWeightEstimation.cs | 58 | ||||
-rw-r--r-- | LibMatrix/Helpers/MessageBuilder.cs | 12 | ||||
-rw-r--r-- | LibMatrix/Helpers/SyncHelper.cs | 8 | ||||
-rw-r--r-- | LibMatrix/LibMatrix.csproj | 5 | ||||
-rw-r--r-- | LibMatrix/Responses/SyncResponse.cs | 2 | ||||
-rw-r--r-- | LibMatrix/RoomTypes/GenericRoom.cs | 58 | ||||
-rw-r--r-- | LibMatrix/RoomTypes/SpaceRoom.cs | 4 | ||||
-rw-r--r-- | LibMatrix/Services/ServiceInstaller.cs | 10 | ||||
-rw-r--r-- | LibMatrix/StateEvent.cs | 41 |
18 files changed, 461 insertions, 141 deletions
diff --git a/LibMatrix.EventTypes/EventContent.cs b/LibMatrix.EventTypes/EventContent.cs index c582cf2..a837252 100644 --- a/LibMatrix.EventTypes/EventContent.cs +++ b/LibMatrix.EventTypes/EventContent.cs @@ -1,10 +1,20 @@ +using System.Reflection; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; namespace LibMatrix.EventTypes; -public abstract class EventContent; +public abstract class EventContent { + public static List<string> GetMatchingEventTypes<T>() where T : EventContent { + var type = typeof(T); + var eventTypes = new List<string>(); + foreach (var attr in type.GetCustomAttributes<MatrixEventAttribute>(true)) { + eventTypes.Add(attr.EventName); + } + return eventTypes; + } +} public class UnknownEventContent : TimelineEventContent; @@ -37,6 +47,10 @@ public abstract class TimelineEventContent : EventContent { [JsonPropertyName("rel_type")] public string? RelationType { get; set; } + // used for reactions + [JsonPropertyName("key")] + public string? Key { get; set; } + public class EventInReplyTo { [JsonPropertyName("event_id")] public string? EventId { get; set; } diff --git a/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs index ae893f8..9602bf3 100644 --- a/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs +++ b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs @@ -29,6 +29,7 @@ public class RoomMessageEventContent : TimelineEventContent { [JsonPropertyName("url")] public string? Url { get; set; } + [JsonPropertyName("filename")] public string? FileName { get; set; } [JsonPropertyName("info")] diff --git a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs index 89e2fdb..5bfd77b 100644 --- a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs @@ -1,5 +1,7 @@ +using System.Security.Cryptography; using System.Text.Json.Serialization; using ArcaneLibs.Attributes; +using ArcaneLibs.Extensions; namespace LibMatrix.EventTypes.Spec.State.Policy; @@ -87,6 +89,8 @@ public abstract class PolicyRuleEventContent : EventContent { Expiry = ((DateTimeOffset)value).ToUnixTimeMilliseconds(); } } + + public string GetDraupnir2StateKey() => Convert.ToBase64String(SHA256.HashData($"{Entity}{Recommendation}".AsBytes().ToArray())); } public static class PolicyRecommendationTypes { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs index c619d0e..f26b8e5 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs @@ -15,8 +15,8 @@ public class RoomCreateEventContent : EventContent { [JsonPropertyName("m.federate")] public bool? Federate { get; set; } - [JsonPropertyName("predecessor")] - public RoomCreatePredecessor? Predecessor { get; set; } + // [JsonPropertyName("predecessor")] + // public RoomCreatePredecessor? Predecessor { get; set; } [JsonPropertyName("type")] public string? Type { get; set; } diff --git a/LibMatrix.MxApiExtensions/LibMatrix.MxApiExtensions.csproj b/LibMatrix.MxApiExtensions/LibMatrix.MxApiExtensions.csproj deleted file mode 100644 index 4df0c6b..0000000 --- a/LibMatrix.MxApiExtensions/LibMatrix.MxApiExtensions.csproj +++ /dev/null @@ -1,13 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> - <ImplicitUsings>enable</ImplicitUsings> - <Nullable>enable</Nullable> - </PropertyGroup> - - <ItemGroup> - <Folder Include="Classes\" /> - </ItemGroup> - -</Project> diff --git a/LibMatrix.sln b/LibMatrix.sln index c068216..3294f77 100644 --- a/LibMatrix.sln +++ b/LibMatrix.sln @@ -5,22 +5,14 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix", "LibMatrix\LibMatrix.csproj", "{2A07D7DA-7B8F-432D-8AD3-9679B58A7C19}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExampleBots", "ExampleBots", "{840309F0-435B-43A7-8471-8C2BE643889D}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{A6345ECE-4C5E-400F-9130-886E343BF314}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.MxApiExtensions", "LibMatrix.MxApiExtensions\LibMatrix.MxApiExtensions.csproj", "{32D9616B-91BB-4B43-97C6-2C3840C12EA6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.ExampleBot", "ExampleBots\LibMatrix.ExampleBot\LibMatrix.ExampleBot.csproj", "{1B1B2197-61FB-416F-B6C8-845F2E5A0442}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModerationBot", "ExampleBots\ModerationBot\ModerationBot.csproj", "{8F0A820E-F6AE-45A2-970E-7A3759693919}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DebugDataValidationApi", "Utilities\LibMatrix.DebugDataValidationApi\LibMatrix.DebugDataValidationApi.csproj", "{35DF9A1A-D988-4225-AFA3-06BB8EDEB559}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Utilities.Bot", "Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj", "{3ACF2613-E23F-42C2-925E-0BB4FC3AB1F7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluralContactBotPoC", "ExampleBots\PluralContactBotPoC\PluralContactBotPoC.csproj", "{FB8FE4EB-B53B-464B-A5FD-9BF9D0F3EF9B}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BFE16D8E-EFC5-49F6-9854-DB001309B3B4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Tests", "Tests\LibMatrix.Tests\LibMatrix.Tests.csproj", "{345934FF-CA81-4A4B-B137-9F198102C65F}" @@ -37,6 +29,24 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "Lib EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "Tests\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{D44DB78D-9BAD-4AB6-A054-839ECA9D68D2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Blazor.Components", "ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj", "{ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Legacy", "ArcaneLibs\ArcaneLibs.Legacy\ArcaneLibs.Legacy.csproj", "{F94D35FF-E90E-4B86-B26E-A8E46EB54BDD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Logging", "ArcaneLibs\ArcaneLibs.Logging\ArcaneLibs.Logging.csproj", "{711D1579-9228-47BA-9CB3-C237F7E8F403}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.StringNormalisation", "ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj", "{87270607-F95C-4EED-AE69-57666863EFDF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "ArcaneLibs\ArcaneLibs.Timings\ArcaneLibs.Timings.csproj", "{5EE6E02C-A63F-4864-91F3-7375B4C50189}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLib.Tests", "ArcaneLibs\ArcaneLib.Tests\ArcaneLib.Tests.csproj", "{E6BF8A7E-92F6-4761-A793-1EC709F7E2BF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.E2eeTestKit", "Utilities\LibMatrix.E2eeTestKit\LibMatrix.E2eeTestKit.csproj", "{A1066F8F-CE8A-4F60-9EFB-553C9E794435}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -54,14 +64,6 @@ Global {32D9616B-91BB-4B43-97C6-2C3840C12EA6}.Debug|Any CPU.Build.0 = Debug|Any CPU {32D9616B-91BB-4B43-97C6-2C3840C12EA6}.Release|Any CPU.ActiveCfg = Release|Any CPU {32D9616B-91BB-4B43-97C6-2C3840C12EA6}.Release|Any CPU.Build.0 = Release|Any CPU - {1B1B2197-61FB-416F-B6C8-845F2E5A0442}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B1B2197-61FB-416F-B6C8-845F2E5A0442}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B1B2197-61FB-416F-B6C8-845F2E5A0442}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B1B2197-61FB-416F-B6C8-845F2E5A0442}.Release|Any CPU.Build.0 = Release|Any CPU - {8F0A820E-F6AE-45A2-970E-7A3759693919}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8F0A820E-F6AE-45A2-970E-7A3759693919}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8F0A820E-F6AE-45A2-970E-7A3759693919}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8F0A820E-F6AE-45A2-970E-7A3759693919}.Release|Any CPU.Build.0 = Release|Any CPU {35DF9A1A-D988-4225-AFA3-06BB8EDEB559}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {35DF9A1A-D988-4225-AFA3-06BB8EDEB559}.Debug|Any CPU.Build.0 = Debug|Any CPU {35DF9A1A-D988-4225-AFA3-06BB8EDEB559}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -70,10 +72,6 @@ Global {3ACF2613-E23F-42C2-925E-0BB4FC3AB1F7}.Debug|Any CPU.Build.0 = Debug|Any CPU {3ACF2613-E23F-42C2-925E-0BB4FC3AB1F7}.Release|Any CPU.ActiveCfg = Release|Any CPU {3ACF2613-E23F-42C2-925E-0BB4FC3AB1F7}.Release|Any CPU.Build.0 = Release|Any CPU - {FB8FE4EB-B53B-464B-A5FD-9BF9D0F3EF9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FB8FE4EB-B53B-464B-A5FD-9BF9D0F3EF9B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FB8FE4EB-B53B-464B-A5FD-9BF9D0F3EF9B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FB8FE4EB-B53B-464B-A5FD-9BF9D0F3EF9B}.Release|Any CPU.Build.0 = Release|Any CPU {345934FF-CA81-4A4B-B137-9F198102C65F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {345934FF-CA81-4A4B-B137-9F198102C65F}.Debug|Any CPU.Build.0 = Debug|Any CPU {345934FF-CA81-4A4B-B137-9F198102C65F}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -98,17 +96,59 @@ Global {D44DB78D-9BAD-4AB6-A054-839ECA9D68D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {D44DB78D-9BAD-4AB6-A054-839ECA9D68D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {D44DB78D-9BAD-4AB6-A054-839ECA9D68D2}.Release|Any CPU.Build.0 = Release|Any CPU + {ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C}.Release|Any CPU.Build.0 = Release|Any CPU + {F94D35FF-E90E-4B86-B26E-A8E46EB54BDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F94D35FF-E90E-4B86-B26E-A8E46EB54BDD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F94D35FF-E90E-4B86-B26E-A8E46EB54BDD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F94D35FF-E90E-4B86-B26E-A8E46EB54BDD}.Release|Any CPU.Build.0 = Release|Any CPU + {711D1579-9228-47BA-9CB3-C237F7E8F403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {711D1579-9228-47BA-9CB3-C237F7E8F403}.Debug|Any CPU.Build.0 = Debug|Any CPU + {711D1579-9228-47BA-9CB3-C237F7E8F403}.Release|Any CPU.ActiveCfg = Release|Any CPU + {711D1579-9228-47BA-9CB3-C237F7E8F403}.Release|Any CPU.Build.0 = Release|Any CPU + {87270607-F95C-4EED-AE69-57666863EFDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87270607-F95C-4EED-AE69-57666863EFDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87270607-F95C-4EED-AE69-57666863EFDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87270607-F95C-4EED-AE69-57666863EFDF}.Release|Any CPU.Build.0 = Release|Any CPU + {5EE6E02C-A63F-4864-91F3-7375B4C50189}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5EE6E02C-A63F-4864-91F3-7375B4C50189}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5EE6E02C-A63F-4864-91F3-7375B4C50189}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5EE6E02C-A63F-4864-91F3-7375B4C50189}.Release|Any CPU.Build.0 = Release|Any CPU + {7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF}.Release|Any CPU.Build.0 = Release|Any CPU + {E6BF8A7E-92F6-4761-A793-1EC709F7E2BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6BF8A7E-92F6-4761-A793-1EC709F7E2BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6BF8A7E-92F6-4761-A793-1EC709F7E2BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6BF8A7E-92F6-4761-A793-1EC709F7E2BF}.Release|Any CPU.Build.0 = Release|Any CPU + {6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5}.Release|Any CPU.Build.0 = Release|Any CPU + {A1066F8F-CE8A-4F60-9EFB-553C9E794435}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1066F8F-CE8A-4F60-9EFB-553C9E794435}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1066F8F-CE8A-4F60-9EFB-553C9E794435}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1066F8F-CE8A-4F60-9EFB-553C9E794435}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution - {1B1B2197-61FB-416F-B6C8-845F2E5A0442} = {840309F0-435B-43A7-8471-8C2BE643889D} - {8F0A820E-F6AE-45A2-970E-7A3759693919} = {840309F0-435B-43A7-8471-8C2BE643889D} {35DF9A1A-D988-4225-AFA3-06BB8EDEB559} = {A6345ECE-4C5E-400F-9130-886E343BF314} {3ACF2613-E23F-42C2-925E-0BB4FC3AB1F7} = {A6345ECE-4C5E-400F-9130-886E343BF314} - {FB8FE4EB-B53B-464B-A5FD-9BF9D0F3EF9B} = {840309F0-435B-43A7-8471-8C2BE643889D} {345934FF-CA81-4A4B-B137-9F198102C65F} = {BFE16D8E-EFC5-49F6-9854-DB001309B3B4} {0B9B34D1-9362-45A9-9C21-816FD6959110} = {BFE16D8E-EFC5-49F6-9854-DB001309B3B4} {4D9B5227-48DC-4A30-9263-AFB51DC01ABB} = {A6345ECE-4C5E-400F-9130-886E343BF314} {13A797D1-7E13-4789-A167-8628B1641AC0} = {01A126FE-9D50-40F2-817B-E55F4065EA76} {D44DB78D-9BAD-4AB6-A054-839ECA9D68D2} = {BFE16D8E-EFC5-49F6-9854-DB001309B3B4} + {ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C} = {01A126FE-9D50-40F2-817B-E55F4065EA76} + {F94D35FF-E90E-4B86-B26E-A8E46EB54BDD} = {01A126FE-9D50-40F2-817B-E55F4065EA76} + {711D1579-9228-47BA-9CB3-C237F7E8F403} = {01A126FE-9D50-40F2-817B-E55F4065EA76} + {87270607-F95C-4EED-AE69-57666863EFDF} = {01A126FE-9D50-40F2-817B-E55F4065EA76} + {5EE6E02C-A63F-4864-91F3-7375B4C50189} = {01A126FE-9D50-40F2-817B-E55F4065EA76} + {7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF} = {01A126FE-9D50-40F2-817B-E55F4065EA76} + {E6BF8A7E-92F6-4761-A793-1EC709F7E2BF} = {01A126FE-9D50-40F2-817B-E55F4065EA76} + {6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5} = {A6345ECE-4C5E-400F-9130-886E343BF314} + {A1066F8F-CE8A-4F60-9EFB-553C9E794435} = {A6345ECE-4C5E-400F-9130-886E343BF314} EndGlobalSection EndGlobal diff --git a/LibMatrix/Extensions/CanonicalJsonSerializer.cs b/LibMatrix/Extensions/CanonicalJsonSerializer.cs new file mode 100644 index 0000000..a6fbcf4 --- /dev/null +++ b/LibMatrix/Extensions/CanonicalJsonSerializer.cs @@ -0,0 +1,91 @@ +using System.Collections.Frozen; +using System.Reflection; +using System.Security.Cryptography; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using System.Text.Unicode; +using ArcaneLibs.Extensions; + +namespace LibMatrix.Extensions; + +public static class CanonicalJsonSerializer { + // TODO: Alphabetise dictionaries + private static JsonSerializerOptions _options => new() { + WriteIndented = false, + Encoder = UnicodeJsonEncoder.Singleton, + }; + + private static readonly FrozenSet<PropertyInfo> JsonSerializerOptionsProperties = typeof(JsonSerializerOptions) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.SetMethod != null && x.GetMethod != null) + .ToFrozenSet(); + + private static JsonSerializerOptions MergeOptions(JsonSerializerOptions? inputOptions) { + var newOptions = _options; + if (inputOptions == null) + return newOptions; + + foreach (var property in JsonSerializerOptionsProperties) { + if(property.Name == nameof(JsonSerializerOptions.Encoder)) + continue; + if (property.Name == nameof(JsonSerializerOptions.WriteIndented)) + continue; + + var value = property.GetValue(inputOptions); + // if (value == null) + // continue; + property.SetValue(newOptions, value); + } + + return newOptions; + } + +#region STJ API + + public static String Serialize<TValue>(TValue value, JsonSerializerOptions? options = null) { + var newOptions = MergeOptions(options); + + return System.Text.Json.JsonSerializer.SerializeToNode(value, options) // We want to allow passing custom converters for eg. double/float -> string here... + .SortProperties()! + .CanonicalizeNumbers()! + .ToJsonString(newOptions); + + + // System.Text.Json.JsonSerializer.SerializeToNode(System.Text.Json.JsonSerializer.Deserialize<dynamic>("{\n \"a\": -0,\n \"b\": 1e10\n}")).ToJsonString(); + + } + + public static String Serialize(object value, Type inputType, JsonSerializerOptions? options = null) => JsonSerializer.Serialize(value, inputType, _options); + // public static String Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => JsonSerializer.Serialize(value, jsonTypeInfo, _options); + // public static String Serialize(Object value, JsonTypeInfo jsonTypeInfo) + +#endregion + + private static partial class JsonExtensions { + public static Action<JsonTypeInfo> AlphabetizeProperties(Type type) { + return typeInfo => { + if (typeInfo.Kind != JsonTypeInfoKind.Object || !type.IsAssignableFrom(typeInfo.Type)) + return; + AlphabetizeProperties()(typeInfo); + }; + } + + public static Action<JsonTypeInfo> AlphabetizeProperties() { + return static typeInfo => { + if (typeInfo.Kind == JsonTypeInfoKind.Dictionary) { } + + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + } +} \ No newline at end of file diff --git a/LibMatrix/Extensions/MatrixHttpClient.Single.cs b/LibMatrix/Extensions/MatrixHttpClient.Single.cs index 39eb7e5..4145a16 100644 --- a/LibMatrix/Extensions/MatrixHttpClient.Single.cs +++ b/LibMatrix/Extensions/MatrixHttpClient.Single.cs @@ -2,6 +2,7 @@ // #define SYNC_HTTPCLIENT // Only allow one request as a time, for debugging using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Net; using System.Net.Http.Headers; using System.Reflection; using System.Security.Cryptography.X509Certificates; @@ -73,12 +74,15 @@ public class MatrixHttpClient { await _rateLimitSemaphore.WaitAsync(cancellationToken); #endif - Console.WriteLine($"Sending {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.Content?.Headers.ContentLength ?? 0)})"); + Console.WriteLine($"Sending {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.GetContentLength())})"); if (request.RequestUri is null) throw new NullReferenceException("RequestUri is null"); if (!request.RequestUri.IsAbsoluteUri) request.RequestUri = new Uri(BaseAddress, request.RequestUri); foreach (var (key, value) in AdditionalQueryParameters) request.RequestUri = request.RequestUri.AddQuery(key, value); - foreach (var (key, value) in DefaultRequestHeaders) request.Headers.Add(key, value); + foreach (var (key, value) in DefaultRequestHeaders) { + if (request.Headers.Contains(key)) continue; + request.Headers.Add(key, value); + } request.Options.Set(new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse"), true); @@ -106,7 +110,13 @@ public class MatrixHttpClient { public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) { var responseMessage = await SendUnhandledAsync(request, cancellationToken); if (responseMessage.IsSuccessStatusCode) return responseMessage; - + + //retry on gateway timeout + if (responseMessage.StatusCode == HttpStatusCode.GatewayTimeout) { + request.ResetSendStatus(); + return await SendAsync(request, cancellationToken); + } + //error handling var content = await responseMessage.Content.ReadAsStringAsync(cancellationToken); if (content.Length == 0) diff --git a/LibMatrix/Extensions/UnicodeJsonEncoder.cs b/LibMatrix/Extensions/UnicodeJsonEncoder.cs new file mode 100644 index 0000000..ae58263 --- /dev/null +++ b/LibMatrix/Extensions/UnicodeJsonEncoder.cs @@ -0,0 +1,173 @@ +// LibMatrix: File sourced from https://github.com/dotnet/runtime/pull/87147/files under the MIT license. + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; +using System.Text.Encodings.Web; + +namespace LibMatrix.Extensions; + +internal sealed class UnicodeJsonEncoder : JavaScriptEncoder +{ + internal static readonly UnicodeJsonEncoder Singleton = new UnicodeJsonEncoder(); + + private readonly bool _preferHexEscape; + private readonly bool _preferUppercase; + + public UnicodeJsonEncoder() + : this(preferHexEscape: false, preferUppercase: false) + { + } + + public UnicodeJsonEncoder(bool preferHexEscape, bool preferUppercase) + { + _preferHexEscape = preferHexEscape; + _preferUppercase = preferUppercase; + } + + public override int MaxOutputCharactersPerInputCharacter => 6; // "\uXXXX" for a single char ("\uXXXX\uYYYY" [12 chars] for supplementary scalar value) + + public override unsafe int FindFirstCharacterToEncode(char* text, int textLength) + { + for (int index = 0; index < textLength; ++index) + { + char value = text[index]; + + if (NeedsEncoding(value)) + { + return index; + } + } + + return -1; + } + + public override unsafe bool TryEncodeUnicodeScalar(int unicodeScalar, char* buffer, int bufferLength, out int numberOfCharactersWritten) + { + bool encode = WillEncode(unicodeScalar); + + if (!encode) + { + Span<char> span = new Span<char>(buffer, bufferLength); + int spanWritten; + bool succeeded = new Rune(unicodeScalar).TryEncodeToUtf16(span, out spanWritten); + numberOfCharactersWritten = spanWritten; + return succeeded; + } + + if (!_preferHexEscape && unicodeScalar <= char.MaxValue && HasTwoCharacterEscape((char)unicodeScalar)) + { + if (bufferLength < 2) + { + numberOfCharactersWritten = 0; + return false; + } + + buffer[0] = '\\'; + buffer[1] = GetTwoCharacterEscapeSuffix((char)unicodeScalar); + numberOfCharactersWritten = 2; + return true; + } + else + { + if (bufferLength < 6) + { + numberOfCharactersWritten = 0; + return false; + } + + buffer[0] = '\\'; + buffer[1] = 'u'; + buffer[2] = '0'; + buffer[3] = '0'; + buffer[4] = ToHexDigit((unicodeScalar & 0xf0) >> 4, _preferUppercase); + buffer[5] = ToHexDigit(unicodeScalar & 0xf, _preferUppercase); + numberOfCharactersWritten = 6; + return true; + } + } + + public override bool WillEncode(int unicodeScalar) + { + if (unicodeScalar > char.MaxValue) + { + return false; + } + + return NeedsEncoding((char)unicodeScalar); + } + + // https://datatracker.ietf.org/doc/html/rfc8259#section-7 + private static bool NeedsEncoding(char value) + { + if (value == '"' || value == '\\') + { + return true; + } + + return value <= '\u001f'; + } + + private static bool HasTwoCharacterEscape(char value) + { + // RFC 8259, Section 7, "char = " BNF + switch (value) + { + case '"': + case '\\': + case '/': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + return true; + default: + return false; + } + } + + private static char GetTwoCharacterEscapeSuffix(char value) + { + // RFC 8259, Section 7, "char = " BNF + switch (value) + { + case '"': + return '"'; + case '\\': + return '\\'; + case '/': + return '/'; + case '\b': + return 'b'; + case '\f': + return 'f'; + case '\n': + return 'n'; + case '\r': + return 'r'; + case '\t': + return 't'; + default: + throw new ArgumentOutOfRangeException(nameof(value)); + } + } + + private static char ToHexDigit(int value, bool uppercase) + { + if (value > 0xf) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + if (value < 10) + { + return (char)(value + '0'); + } + else + { + return (char)(value - 0xa + (uppercase ? 'A' : 'a')); + } + } +} \ No newline at end of file diff --git a/LibMatrix/Helpers/HomeserverWeightEstimation.cs b/LibMatrix/Helpers/HomeserverWeightEstimation.cs deleted file mode 100644 index 5735af3..0000000 --- a/LibMatrix/Helpers/HomeserverWeightEstimation.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace LibMatrix.Helpers; - -public class HomeserverWeightEstimation { - public static Dictionary<string, int> EstimatedSize = new() { - { "matrix.org", 843870 }, - { "anontier.nl", 44809 }, - { "nixos.org", 8195 }, - { "the-apothecary.club", 6983 }, - { "waifuhunter.club", 3953 }, - { "neko.dev", 2666 }, - { "nerdsin.space", 2647 }, - { "feline.support", 2633 }, - { "gitter.im", 2584 }, - { "midov.pl", 2219 }, - { "no.lgbtqia.zone", 2083 }, - { "nheko.im", 1883 }, - { "fachschaften.org", 1849 }, - { "pixelthefox.net", 1478 }, - { "arcticfoxes.net", 981 }, - { "pixie.town", 817 }, - { "privacyguides.org", 809 }, - { "rory.gay", 653 }, - { "artemislena.eu", 599 }, - { "alchemi.dev", 445 }, - { "jameskitt616.one", 390 }, - { "hackint.org", 382 }, - { "pikaviestin.fi", 368 }, - { "matrix.nomagic.uk", 337 }, - { "thearcanebrony.net", 178 }, - { "fairydust.space", 176 }, - { "grin.hu", 176 }, - { "envs.net", 165 }, - { "tastytea.de", 143 }, - { "koneko.chat", 121 }, - { "vscape.tk", 115 }, - { "funklause.de", 112 }, - { "seirdy.one", 107 }, - { "pcg.life", 72 }, - { "draupnir.midnightthoughts.space", 22 }, - { "tchncs.de", 19 }, - { "catgirl.cloud", 16 }, - { "possum.city", 16 }, - { "tu-dresden.de", 9 }, - { "fosscord.com", 9 }, - { "nightshade.fun", 8 }, - { "matrix.eclipse.org", 8 }, - { "masfloss.net", 8 }, - { "e2e.zone", 8 }, - { "hyteck.de", 8 } - }; - - public static Dictionary<string, int> LargeRooms = new() { - { "!ehXvUhWNASUkSLvAGP:matrix.org", 21957 }, - { "!fRRqjOaQcUbKOfCjvc:anontier.nl", 19117 }, - { "!OGEhHVWSdvArJzumhm:matrix.org", 101457 }, - { "!YTvKGNlinIzlkMTVRl:matrix.org", 30164 } - }; -} \ No newline at end of file diff --git a/LibMatrix/Helpers/MessageBuilder.cs b/LibMatrix/Helpers/MessageBuilder.cs index d897078..b639e1f 100644 --- a/LibMatrix/Helpers/MessageBuilder.cs +++ b/LibMatrix/Helpers/MessageBuilder.cs @@ -91,6 +91,18 @@ public class MessageBuilder(string msgType = "m.text", string format = "org.matr return this; } + public MessageBuilder WithMention(string id, string? displayName = null) { + Content.Body += $"@{displayName ?? id}"; + Content.FormattedBody += $"<a href=\"https://matrix.to/#/{id}\">{displayName ?? id}</a>"; + return this; + } + + public MessageBuilder WithNewline() { + Content.Body += "\n"; + Content.FormattedBody += "<br>"; + return this; + } + public MessageBuilder WithTable(Action<TableBuilder> tableBuilder) { var tb = new TableBuilder(this); this.WithHtmlTag("table", msb => tableBuilder(tb)); diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs index 1833bd0..c9ca85d 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs @@ -4,6 +4,7 @@ using ArcaneLibs.Extensions; using LibMatrix.Filters; using LibMatrix.Homeservers; using LibMatrix.Responses; +using LibMatrix.Utilities; using Microsoft.Extensions.Logging; namespace LibMatrix.Helpers; @@ -42,6 +43,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg _filter = value; _filterIsDirty = true; _filterId = null; + _namedFilterName = null; } } @@ -81,16 +83,16 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg if (!string.IsNullOrWhiteSpace(Since)) url += $"&since={Since}"; if (_filterId is not null) url += $"&filter={_filterId}"; - logger?.LogInformation("SyncHelper: Calling: {}", url); + // logger?.LogInformation("SyncHelper: Calling: {}", url); try { 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.Content.Headers.ContentLength ?? -1, sw.Elapsed); + logger?.LogTrace("Got sync response: {} bytes, {} elapsed", httpResp.GetContentLength(), sw.Elapsed); var deserializeSw = Stopwatch.StartNew(); var resp = await httpResp.Content.ReadFromJsonAsync<SyncResponse>(cancellationToken: cancellationToken ?? CancellationToken.None, jsonTypeInfo: SyncResponseSerializerContext.Default.SyncResponse); - logger?.LogInformation("Deserialized sync response: {} bytes, {} elapsed, {} total", httpResp.Content.Headers.ContentLength ?? -1, deserializeSw.Elapsed, sw.Elapsed); + logger?.LogInformation("Deserialized sync response: {} bytes, {} elapsed, {} total", httpResp.GetContentLength(), deserializeSw.Elapsed, sw.Elapsed); var timeToWait = MinimumDelay.Subtract(sw.Elapsed); if (timeToWait.TotalMilliseconds > 0) await Task.Delay(timeToWait); diff --git a/LibMatrix/LibMatrix.csproj b/LibMatrix/LibMatrix.csproj index d0511ea..6158ff8 100644 --- a/LibMatrix/LibMatrix.csproj +++ b/LibMatrix/LibMatrix.csproj @@ -8,6 +8,7 @@ <Optimize>true</Optimize> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <!-- Required for UnicodeJsonEncoder... --> </PropertyGroup> <ItemGroup> @@ -22,12 +23,14 @@ Using the NuGet version in development is annoying due to delays between pushing and being able to consume. If you want to use a time-appropriate version of the library, recursively clone https://cgit.rory.gay/matrix/MatrixUtils.git instead, since this will be locked by the MatrixUtils project, which contains both LibMatrix and ArcaneLibs as a submodule. --> - <PackageReference Condition="!Exists('..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj')" Include="ArcaneLibs" Version="*-preview*"/> + <PackageReference Condition="!Exists('..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj')" Include="ArcaneLibs" Version="*-preview.202*"/> <ProjectReference Include="..\LibMatrix.EventTypes\LibMatrix.EventTypes.csproj"/> </ItemGroup> + <!-- <Target Name="ArcaneLibsNugetWarning" AfterTargets="AfterBuild"> <Warning Text="ArcaneLibs is being referenced from NuGet, which is dangerous. Please read the warning in LibMatrix.csproj!" Condition="!Exists('..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj')"/> </Target> + --> </Project> diff --git a/LibMatrix/Responses/SyncResponse.cs b/LibMatrix/Responses/SyncResponse.cs index e4addb6..b2308c5 100644 --- a/LibMatrix/Responses/SyncResponse.cs +++ b/LibMatrix/Responses/SyncResponse.cs @@ -39,7 +39,7 @@ public class SyncResponse { // supporting classes public class PresenceDataStructure { [JsonPropertyName("events")] - public List<StateEventResponse> Events { get; set; } = new(); + public List<StateEventResponse>? Events { get; set; } } public class RoomsDataStructure { diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs index 349ccb5..8398ab9 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs @@ -106,7 +106,7 @@ public class GenericRoom { Console.WriteLine("WARNING: Homeserver does not support getting event ID from state events, falling back to sync"); var sh = new SyncHelper(Homeserver); var emptyFilter = new SyncFilter.EventFilter(types: [], limit: 1, senders: [], notTypes: ["*"]); - var emptyStateFilter = new SyncFilter.RoomFilter.StateFilter(types: [], limit: 1, senders: [], notTypes: ["*"], rooms:[]); + var emptyStateFilter = new SyncFilter.RoomFilter.StateFilter(types: [], limit: 1, senders: [], notTypes: ["*"], rooms: []); sh.Filter = new() { Presence = emptyFilter, AccountData = emptyFilter, @@ -121,10 +121,11 @@ public class GenericRoom { var sync = await sh.SyncAsync(); var state = sync.Rooms.Join[RoomId].State.Events; var stateEvent = state.FirstOrDefault(x => x.Type == type && x.StateKey == stateKey); - if (stateEvent is null) throw new LibMatrixException() { - ErrorCode = LibMatrixException.ErrorCodes.M_NOT_FOUND, - Error = "State event not found in sync response" - }; + if (stateEvent is null) + throw new LibMatrixException() { + ErrorCode = LibMatrixException.ErrorCodes.M_NOT_FOUND, + Error = "State event not found in sync response" + }; return stateEvent.EventId; } @@ -232,7 +233,7 @@ public class GenericRoom { // var sw = Stopwatch.StartNew(); var res = await Homeserver.ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members"); // if (sw.ElapsedMilliseconds > 1000) - // Console.WriteLine($"Members call responded in {sw.GetElapsedAndRestart()}"); + // Console.WriteLine($"Members call responded in {sw.GetElapsedAndRestart()}"); // else sw.Restart(); // var resText = await res.Content.ReadAsStringAsync(); // Console.WriteLine($"Members call response read in {sw.GetElapsedAndRestart()}"); @@ -240,7 +241,7 @@ public class GenericRoom { TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default }); // if (sw.ElapsedMilliseconds > 100) - // Console.WriteLine($"Members call deserialised in {sw.GetElapsedAndRestart()}"); + // Console.WriteLine($"Members call deserialised in {sw.GetElapsedAndRestart()}"); // else sw.Restart(); foreach (var resp in result.Chunk) { if (resp?.Type != "m.room.member") continue; @@ -249,14 +250,14 @@ public class GenericRoom { } // if (sw.ElapsedMilliseconds > 100) - // Console.WriteLine($"Members call iterated in {sw.GetElapsedAndRestart()}"); + // Console.WriteLine($"Members call iterated in {sw.GetElapsedAndRestart()}"); } public async Task<FrozenSet<StateEventResponse>> GetMembersListAsync(bool joinedOnly = true) { // var sw = Stopwatch.StartNew(); var res = await Homeserver.ClientHttpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/members"); // if (sw.ElapsedMilliseconds > 1000) - // Console.WriteLine($"Members call responded in {sw.GetElapsedAndRestart()}"); + // Console.WriteLine($"Members call responded in {sw.GetElapsedAndRestart()}"); // else sw.Restart(); // var resText = await res.Content.ReadAsStringAsync(); // Console.WriteLine($"Members call response read in {sw.GetElapsedAndRestart()}"); @@ -264,7 +265,7 @@ public class GenericRoom { TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default }); // if (sw.ElapsedMilliseconds > 100) - // Console.WriteLine($"Members call deserialised in {sw.GetElapsedAndRestart()}"); + // Console.WriteLine($"Members call deserialised in {sw.GetElapsedAndRestart()}"); // else sw.Restart(); var members = new List<StateEventResponse>(); foreach (var resp in result.Chunk) { @@ -274,7 +275,7 @@ public class GenericRoom { } // if (sw.ElapsedMilliseconds > 100) - // Console.WriteLine($"Members call iterated in {sw.GetElapsedAndRestart()}"); + // Console.WriteLine($"Members call iterated in {sw.GetElapsedAndRestart()}"); return members.ToFrozenSet(); } @@ -320,7 +321,9 @@ public class GenericRoom { [Obsolete("This method will be merged into GetNameAsync() in the future.")] public async Task<string> GetNameOrFallbackAsync(int maxMemberNames = 2) { try { - return await GetNameAsync(); + var name = await GetNameAsync(); + if (!string.IsNullOrEmpty(name)) return name; + throw new(); } catch { try { @@ -374,9 +377,9 @@ public class GenericRoom { await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/ban", new UserIdAndReason { UserId = userId, Reason = reason }); - public async Task UnbanAsync(string userId) => + public async Task UnbanAsync(string userId, string? reason = null) => await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/unban", - new UserIdAndReason { UserId = userId }); + new UserIdAndReason { UserId = userId, Reason = reason }); public async Task InviteUserAsync(string userId, string? reason = null, bool skipExisting = true) { if (skipExisting && await GetStateOrNullAsync<RoomMemberEventContent>("m.room.member", userId) is not null) @@ -393,7 +396,7 @@ public class GenericRoom { .Content.ReadFromJsonAsync<EventIdResponse>(); public async Task<EventIdResponse?> SendStateEventAsync(string eventType, string stateKey, object content) => - await (await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}/{stateKey}", content)) + await (await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType.UrlEncode()}/{stateKey.UrlEncode()}", content)) .Content.ReadFromJsonAsync<EventIdResponse>(); public async Task<EventIdResponse> SendTimelineEventAsync(string eventType, TimelineEventContent content) { @@ -428,6 +431,16 @@ public class GenericRoom { return await res.Content.ReadFromJsonAsync<T>(); } + + public async Task<T?> GetRoomAccountDataOrNullAsync<T>(string key) { + try { + return await GetRoomAccountDataAsync<T>(key); + } + catch (MatrixException e) { + if (e.ErrorCode == "M_NOT_FOUND") return default; + throw; + } + } public async Task SetRoomAccountDataAsync(string key, object data) { var res = await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}", data); @@ -440,10 +453,17 @@ public class GenericRoom { public Task<StateEventResponse> GetEventAsync(string eventId) => Homeserver.ClientHttpClient.GetFromJsonAsync<StateEventResponse>($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}"); - public async Task<EventIdResponse> RedactEventAsync(string eventToRedact, string reason) { + public async Task<EventIdResponse> RedactEventAsync(string eventToRedact, string? reason = null) { var data = new { reason }; - return (await (await Homeserver.ClientHttpClient.PutAsJsonAsync( - $"/_matrix/client/v3/rooms/{RoomId}/redact/{eventToRedact}/{Guid.NewGuid()}", data)).Content.ReadFromJsonAsync<EventIdResponse>())!; + var url = $"/_matrix/client/v3/rooms/{RoomId}/redact/{eventToRedact}/{Guid.NewGuid().ToString()}"; + while (true) { + try { + return (await (await Homeserver.ClientHttpClient.PutAsJsonAsync(url, data)).Content.ReadFromJsonAsync<EventIdResponse>())!; + } catch (MatrixException e) { + if (e is { ErrorCode: MatrixException.ErrorCodes.M_FORBIDDEN }) throw; + throw; + } + } } #endregion @@ -510,7 +530,7 @@ public class GenericRoom { var uri = new Uri(path, UriKind.Relative); if (dir == "b" || dir == "f") uri = uri.AddQuery("dir", dir); - else if(!string.IsNullOrWhiteSpace(dir)) throw new ArgumentException("Invalid direction", nameof(dir)); + else if (!string.IsNullOrWhiteSpace(dir)) throw new ArgumentException("Invalid direction", nameof(dir)); if (!string.IsNullOrEmpty(from)) uri = uri.AddQuery("from", from); if (chunkLimit is not null) uri = uri.AddQuery("limit", chunkLimit.Value.ToString()); if (recurse is not null) uri = uri.AddQuery("recurse", recurse.Value.ToString()); diff --git a/LibMatrix/RoomTypes/SpaceRoom.cs b/LibMatrix/RoomTypes/SpaceRoom.cs index b40ccc6..4563ed3 100644 --- a/LibMatrix/RoomTypes/SpaceRoom.cs +++ b/LibMatrix/RoomTypes/SpaceRoom.cs @@ -4,6 +4,8 @@ using LibMatrix.Homeservers; namespace LibMatrix.RoomTypes; public class SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : GenericRoom(homeserver, roomId) { + public const string TypeName = "m.space"; + public async IAsyncEnumerable<GenericRoom> GetChildrenAsync(bool includeRemoved = false) { // var rooms = new List<GenericRoom>(); var state = GetFullStateAsync(); @@ -31,7 +33,7 @@ public class SpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) }); return resp; } - + public async Task<EventIdResponse> AddChildByIdAsync(string id) { return await AddChildAsync(Homeserver.GetRoom(id)); } diff --git a/LibMatrix/Services/ServiceInstaller.cs b/LibMatrix/Services/ServiceInstaller.cs index 06ea9de..8b7e54b 100644 --- a/LibMatrix/Services/ServiceInstaller.cs +++ b/LibMatrix/Services/ServiceInstaller.cs @@ -5,23 +5,13 @@ namespace LibMatrix.Services; public static class ServiceInstaller { public static IServiceCollection AddRoryLibMatrixServices(this IServiceCollection services, RoryLibMatrixConfiguration? config = null) { - //Check required services - // if (!services.Any(x => x.ServiceType == typeof(TieredStorageService))) - // throw new Exception("[RMUCore/DI] No TieredStorageService has been registered!"); //Add config services.AddSingleton(config ?? new RoryLibMatrixConfiguration()); //Add services services.AddSingleton<HomeserverResolverService>(sp => new HomeserverResolverService(sp.GetRequiredService<ILogger<HomeserverResolverService>>())); - - // if (services.First(x => x.ServiceType == typeof(TieredStorageService)).Lifetime == ServiceLifetime.Singleton) { services.AddSingleton<HomeserverProviderService>(); - // } - // else { - // services.AddScoped<HomeserverProviderService>(); - // } - // services.AddScoped<MatrixHttpClient>(); return services; } } diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs index 81ee3fe..cc870e4 100644 --- a/LibMatrix/StateEvent.cs +++ b/LibMatrix/StateEvent.cs @@ -13,7 +13,7 @@ using LibMatrix.Extensions; namespace LibMatrix; public class StateEvent { - public static FrozenSet<Type> KnownStateEventTypes { get; } = new ClassCollector<EventContent>().ResolveFromAllAccessibleAssemblies().ToFrozenSet(); + public static FrozenSet<Type> KnownStateEventTypes { get; } = ClassCollector<EventContent>.ResolveFromAllAccessibleAssemblies().ToFrozenSet(); public static FrozenDictionary<string, Type> KnownStateEventTypesByName { get; } = KnownStateEventTypes.Aggregate( new Dictionary<string, Type>(), @@ -44,6 +44,7 @@ public class StateEvent { public string FriendlyTypeNamePlural => MappedType.GetFriendlyNamePluralOrNull() ?? Type; private static readonly JsonSerializerOptions TypedContentSerializerOptions = new() { + // We need these, NumberHandling covers other number types that we don't want to convert Converters = { new JsonFloatStringConverter(), new JsonDoubleStringConverter(), @@ -55,9 +56,6 @@ public class StateEvent { [SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")] public EventContent? TypedContent { get { - // if (Type == "m.receipt") { - // return null; - // } try { var mappedType = GetStateEventType(Type); if (mappedType == typeof(UnknownEventContent)) @@ -81,6 +79,18 @@ public class StateEvent { } } + public T? ContentAs<T>() { + try { + return RawContent.Deserialize<T>(TypedContentSerializerOptions)!; + } + catch (JsonException e) { + Console.WriteLine(e); + Console.WriteLine("Content:\n" + (RawContent?.ToJson() ?? "null")); + } + + return default; + } + [JsonPropertyName("state_key")] public string? StateKey { get; set; } @@ -156,7 +166,7 @@ public class StateEventResponse : StateEvent { public string? Sender { get; set; } [JsonPropertyName("unsigned")] - public UnsignedData? Unsigned { get; set; } + public JsonObject? Unsigned { get; set; } [JsonPropertyName("event_id")] public string? EventId { get; set; } @@ -254,4 +264,23 @@ public class StateEventContentPolymorphicTypeInfoResolver : DefaultJsonTypeInfoR } */ -#endregion \ No newline at end of file +#endregion + +/* +public class ForgivingObjectConverter<T> : JsonConverter<T> where T : new() { + public override T? Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) { + try { + var text = JsonDocument.ParseValue(ref reader).RootElement.GetRawText(); + return JsonSerializer.Deserialize<T>(text, options); + } + catch (JsonException ex) { + Console.WriteLine(ex); + return null; + } + } + + public override bool CanConvert(Type typeToConvert) => true; + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + => JsonSerializer.Serialize<T>(writer, value, options); +}*/ \ No newline at end of file |