diff options
Diffstat (limited to '')
120 files changed, 3254 insertions, 1014 deletions
diff --git a/.gitignore b/.gitignore index 984ec54..67e71e8 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ MatrixRoomUtils.Bot/bot_data/ appsettings.Local*.json nixpkgs/ *.DotSettings.user +**/.DS_Store *.patch test.tsv diff --git a/.idea/.idea.MatrixRoomUtils/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.MatrixRoomUtils/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 55540ea..0000000 --- a/.idea/.idea.MatrixRoomUtils/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,22 +0,0 @@ -<component name="InspectionProjectProfileManager"> - <profile version="1.0"> - <option name="myName" value="Project Default" /> - <inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true"> - <option name="myValues"> - <value> - <list size="7"> - <item index="0" class="java.lang.String" itemvalue="nobr" /> - <item index="1" class="java.lang.String" itemvalue="noembed" /> - <item index="2" class="java.lang.String" itemvalue="comment" /> - <item index="3" class="java.lang.String" itemvalue="noscript" /> - <item index="4" class="java.lang.String" itemvalue="embed" /> - <item index="5" class="java.lang.String" itemvalue="script" /> - <item index="6" class="java.lang.String" itemvalue="width" /> - </list> - </value> - </option> - <option name="myCustomValuesEnabled" value="true" /> - </inspection_tool> - <inspection_tool class="JsonStandardCompliance" enabled="false" level="ERROR" enabled_by_default="false" /> - </profile> -</component> \ No newline at end of file diff --git a/.idea/.idea.MatrixRoomUtils/.idea/.gitignore b/.idea/.idea.MatrixUtils/.idea/.gitignore index f938d5a..f938d5a 100644 --- a/.idea/.idea.MatrixRoomUtils/.idea/.gitignore +++ b/.idea/.idea.MatrixUtils/.idea/.gitignore diff --git a/.idea/.idea.MatrixUtils/.idea/.name b/.idea/.idea.MatrixUtils/.idea/.name new file mode 100644 index 0000000..65d8b74 --- /dev/null +++ b/.idea/.idea.MatrixUtils/.idea/.name @@ -0,0 +1 @@ +MatrixUtils \ No newline at end of file diff --git a/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml b/.idea/.idea.MatrixUtils/.idea/avalonia.xml index 0aa65bb..0aa65bb 100644 --- a/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml +++ b/.idea/.idea.MatrixUtils/.idea/avalonia.xml diff --git a/.idea/.idea.MatrixRoomUtils/.idea/codeStyles/codeStyleConfig.xml b/.idea/.idea.MatrixUtils/.idea/codeStyles/codeStyleConfig.xml index a55e7a1..a55e7a1 100644 --- a/.idea/.idea.MatrixRoomUtils/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/.idea.MatrixUtils/.idea/codeStyles/codeStyleConfig.xml diff --git a/.idea/.idea.MatrixRoomUtils/.idea/encodings.xml b/.idea/.idea.MatrixUtils/.idea/encodings.xml index df87cf9..df87cf9 100644 --- a/.idea/.idea.MatrixRoomUtils/.idea/encodings.xml +++ b/.idea/.idea.MatrixUtils/.idea/encodings.xml diff --git a/.idea/.idea.MatrixRoomUtils/.idea/indexLayout.xml b/.idea/.idea.MatrixUtils/.idea/indexLayout.xml index 4520708..4520708 100644 --- a/.idea/.idea.MatrixRoomUtils/.idea/indexLayout.xml +++ b/.idea/.idea.MatrixUtils/.idea/indexLayout.xml diff --git a/.idea/.idea.MatrixUtils/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.MatrixUtils/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..0e61b0a --- /dev/null +++ b/.idea/.idea.MatrixUtils/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,53 @@ +<component name="InspectionProjectProfileManager"> + <profile version="1.0"> + <option name="myName" value="Project Default" /> + <inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="myValues"> + <value> + <list size="7"> + <item index="0" class="java.lang.String" itemvalue="nobr" /> + <item index="1" class="java.lang.String" itemvalue="noembed" /> + <item index="2" class="java.lang.String" itemvalue="comment" /> + <item index="3" class="java.lang.String" itemvalue="noscript" /> + <item index="4" class="java.lang.String" itemvalue="embed" /> + <item index="5" class="java.lang.String" itemvalue="script" /> + <item index="6" class="java.lang.String" itemvalue="width" /> + </list> + </value> + </option> + <option name="myCustomValuesEnabled" value="true" /> + </inspection_tool> + <inspection_tool class="HttpUrlsUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true"> + <option name="ignoredUrls"> + <list> + <option value="http://" /> + <option value="http://0.0.0.0" /> + <option value="http://127.0.0.1" /> + <option value="http://activemq.apache.org/schema/" /> + <option value="http://cxf.apache.org/schemas/" /> + <option value="http://java.sun.com/" /> + <option value="http://javafx.com/fxml" /> + <option value="http://javafx.com/javafx/" /> + <option value="http://json-schema.org/draft" /> + <option value="http://localhost" /> + <option value="http://maven.apache.org/POM/" /> + <option value="http://maven.apache.org/xsd/" /> + <option value="http://primefaces.org/ui" /> + <option value="http://schema.cloudfoundry.org/spring/" /> + <option value="http://schemas.xmlsoap.org/" /> + <option value="http://tiles.apache.org/" /> + <option value="http://www.ibm.com/webservices/xsd" /> + <option value="http://www.jboss.com/xml/ns/" /> + <option value="http://www.jboss.org/j2ee/schema/" /> + <option value="http://www.springframework.org/schema/" /> + <option value="http://www.springframework.org/security/tags" /> + <option value="http://www.springframework.org/tags" /> + <option value="http://www.thymeleaf.org" /> + <option value="http://www.w3.org/" /> + <option value="http://xmlns.jcp.org/" /> + </list> + </option> + </inspection_tool> + <inspection_tool class="JsonStandardCompliance" enabled="false" level="ERROR" enabled_by_default="false" /> + </profile> +</component> \ No newline at end of file diff --git a/.idea/.idea.MatrixRoomUtils/.idea/vcs.xml b/.idea/.idea.MatrixUtils/.idea/vcs.xml index 94a25f7..94a25f7 100644 --- a/.idea/.idea.MatrixRoomUtils/.idea/vcs.xml +++ b/.idea/.idea.MatrixUtils/.idea/vcs.xml diff --git a/Benchmarks/.gitignore b/Benchmarks/.gitignore new file mode 100644 index 0000000..a7d52bf --- /dev/null +++ b/Benchmarks/.gitignore @@ -0,0 +1,3 @@ + +BenchmarkDotNet.Artifacts +benchmark.log diff --git a/Benchmarks/Benchmarks.csproj b/Benchmarks/Benchmarks.csproj new file mode 100644 index 0000000..5d584c9 --- /dev/null +++ b/Benchmarks/Benchmarks.csproj @@ -0,0 +1,17 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net9.0</TargetFramework> + <LangVersion>preview</LangVersion> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> +<!-- <PublishAot>true</PublishAot>--> + <InvariantGlobalization>true</InvariantGlobalization> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="BenchmarkDotNet" Version="0.14.0" /> + </ItemGroup> + +</Project> diff --git a/Benchmarks/Program.cs b/Benchmarks/Program.cs new file mode 100644 index 0000000..b85fc24 --- /dev/null +++ b/Benchmarks/Program.cs @@ -0,0 +1,300 @@ +// See https://aka.ms/new-console-template for more information + +using System.Collections.Frozen; +using System.Collections.Immutable; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Running; + +[SimpleJob(RunStrategy.ColdStart, launchCount: 1, warmupCount: 5, iterationCount: 5, id: "FastAndDirtyJob")] +[InProcess] +[ProcessCount(4)] +public class Program { + public static void Main(string[] args) { + BenchmarkRunner.Run<Program>(args: args); + } + + [Params(true, false)] + public bool DoDisambiguate { get; set; } = true; + + [Params(true, false)] + public bool DisambiguateProfileUpdates { + get => field && DoDisambiguate; + set; + } = true; + + [Params(true, false)] + public bool DisambiguateKicks { + get => field && DoDisambiguate; + set; + } = true; + + [Params(true, false)] + public bool DisambiguateUnbans { + get => field && DoDisambiguate; + set; + } = true; + + [Params(true, false)] + public bool DisambiguateInviteAccepted { + get => field && DoDisambiguate && DisambiguateInviteActions; + set; + } = true; + + [Params(true, false)] + public bool DisambiguateInviteRejected { + get => field && DoDisambiguate && DisambiguateInviteActions; + set; + } = true; + + [Params(true, false)] + public bool DisambiguateInviteRetracted { + get => field && DoDisambiguate && DisambiguateInviteActions; + set; + } = true; + + [Params(true, false)] + public bool DisambiguateKnockAccepted { + get => field && DoDisambiguate && DisambiguateKnockActions; + set; + } = true; + + [Params(true, false)] + public bool DisambiguateKnockRejected { + get => field && DoDisambiguate && DisambiguateKnockActions; + set; + } = true; + + [Params(true, false)] + public bool DisambiguateKnockRetracted { + get => field && DoDisambiguate && DisambiguateKnockActions; + set; + } = true; + + [Params(true, false)] + public bool DisambiguateKnockActions { + get => field && DoDisambiguate; + set; + } = true; + + [Params(true, false)] + public bool DisambiguateInviteActions { + get => field && DoDisambiguate; + set; + } = true; + + public enum MembershipTransition : uint { + None, + Join = 0b0001, + Leave = 0b0010, + Knock = 0b0100, + Invite = 0b1000, + Ban = 0b1001, + + // disambiguated + ProfileUpdate = 0b0000_0001_0001, + Kick = 0b0000_0001_0010, + Unban = 0b0000_0010_0010, + InviteAccepted = 0b0000_0100_0001, + InviteRejected = 0b0000_1000_0010, + InviteRetracted = 0b0001_0000_0010, + KnockAccepted = 0b0010_0000_1000, + KnockRejected = 0b0100_0000_0010, + KnockRetracted = 0b1000_0000_0010 + } + + public readonly struct MembershipEntry { + public required MembershipTransition State { get; init; } + public string Aba { get; init; } + public string Abb { get; init; } + public string Abc { get; init; } + public string Abd { get; init; } + } + + [Params(100, 10_000, 1_000_000)] public int N; + + [GlobalSetup] + public void Setup() { + entries = Enumerable.Range(0, N).Select(_ => new MembershipEntry() { + State = (MembershipTransition)new Random().Next(1, 16), + Aba = Guid.NewGuid().ToString(), + Abb = Guid.NewGuid().ToString(), + Abc = Guid.NewGuid().ToString(), + Abd = Guid.NewGuid().ToString() + }).ToImmutableList(); + } + + public ImmutableList<MembershipEntry> entries = ImmutableList<MembershipEntry>.Empty; + + [Benchmark] + public void TestTruthyness() { + var @switch = AmbiguateMembershipsSwitch().GetEnumerator(); + var @switchpm = AmbiguateMembershipsSwitchPatternMatching().GetEnumerator(); + var @if = AmbiguateMembershipsIf().GetEnumerator(); + var @map = AmbiguateMembershipsStaticMap().GetEnumerator(); + var @binmask = AmbiguateMembershipsBinMask().GetEnumerator(); + + while (@switch.MoveNext() && @map.MoveNext() && @if.MoveNext() && @switchpm.MoveNext() && @binmask.MoveNext()) { + if (@switch.Current.State != @map.Current.State || @switch.Current.State != @if.Current.State || @switch.Current.State != @switchpm.Current.State || + @switch.Current.State != @binmask.Current.State) { + throw new InvalidOperationException("Results do not match!"); + } + } + + @switch.Dispose(); + @switchpm.Dispose(); + @if.Dispose(); + @map.Dispose(); + @binmask.Dispose(); + } + + [Benchmark] + public void TestAmbiguateMembershipsSwitchPatternMatching() => AmbiguateMembershipsSwitchPatternMatching().Consume(new Consumer()); + + public IEnumerable<MembershipEntry> AmbiguateMembershipsSwitchPatternMatching() { + foreach (var entry in entries) { + var newState = entry.State switch { + MembershipTransition.ProfileUpdate when !DoDisambiguate || !DisambiguateProfileUpdates => MembershipTransition.Join, + MembershipTransition.Kick when !DoDisambiguate || !DisambiguateKicks => MembershipTransition.Leave, + MembershipTransition.Unban when !DoDisambiguate || !DisambiguateUnbans => MembershipTransition.Leave, + MembershipTransition.InviteAccepted when !DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteAccepted => MembershipTransition.Join, + MembershipTransition.InviteRejected when !DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRejected => MembershipTransition.Leave, + MembershipTransition.InviteRetracted when !DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRetracted => MembershipTransition.Leave, + MembershipTransition.KnockAccepted when !DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockAccepted => MembershipTransition.Invite, + MembershipTransition.KnockRejected when !DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRejected => MembershipTransition.Leave, + MembershipTransition.KnockRetracted when !DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRetracted => MembershipTransition.Leave, + _ => entry.State + }; + yield return newState == entry.State ? entry : entry with { State = newState }; + } + } + + [Benchmark] + public void TestAmbiguateMembershipsSwitch() => AmbiguateMembershipsSwitch().Consume(new Consumer()); + + public IEnumerable<MembershipEntry> AmbiguateMembershipsSwitch() { + foreach (var entry in entries) { + if (!DoDisambiguate) { + yield return entry; + continue; + } + + MembershipTransition newState; + switch (entry.State) { + case MembershipTransition.ProfileUpdate: + newState = !DisambiguateProfileUpdates ? MembershipTransition.Join : entry.State; + break; + case MembershipTransition.Kick: + newState = !DisambiguateKicks ? MembershipTransition.Leave : entry.State; + break; + case MembershipTransition.Unban when !DisambiguateUnbans: + newState = MembershipTransition.Leave; + break; + case MembershipTransition.InviteAccepted when !DisambiguateInviteActions || !DisambiguateInviteAccepted: + newState = MembershipTransition.Join; + break; + case MembershipTransition.InviteRejected when !DisambiguateInviteActions || !DisambiguateInviteRejected: + + newState = MembershipTransition.Leave; + break; + case MembershipTransition.InviteRetracted when !DisambiguateInviteActions || !DisambiguateInviteRetracted: + newState = MembershipTransition.Leave; + break; + case MembershipTransition.KnockAccepted when !DisambiguateKnockActions || !DisambiguateKnockAccepted: + newState = MembershipTransition.Invite; + break; + case MembershipTransition.KnockRejected when !DisambiguateKnockActions || !DisambiguateKnockRejected: + newState = MembershipTransition.Leave; + break; + case MembershipTransition.KnockRetracted when !DisambiguateKnockActions || !DisambiguateKnockRetracted: + newState = MembershipTransition.Leave; + break; + default: + newState = entry.State; + break; + } + + yield return newState == entry.State ? entry : entry with { State = newState }; + } + } + + [Benchmark] + public void TestAmbiguateMembershipsIf() => AmbiguateMembershipsIf().Consume(new Consumer()); + + public IEnumerable<MembershipEntry> AmbiguateMembershipsIf() { + foreach (var entry in entries) { + MembershipTransition newState; + if (entry.State == MembershipTransition.ProfileUpdate && (!DoDisambiguate || !DisambiguateProfileUpdates)) + newState = MembershipTransition.Join; + else if ((entry.State == MembershipTransition.Kick && (!DoDisambiguate || !DisambiguateKicks)) || + (entry.State == MembershipTransition.Unban && (!DoDisambiguate || !DisambiguateUnbans))) + newState = MembershipTransition.Leave; + else if (entry.State == MembershipTransition.InviteAccepted && (!DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteAccepted)) + newState = MembershipTransition.Join; + else if ((entry.State == MembershipTransition.InviteRejected && (!DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRejected)) || + (entry.State == MembershipTransition.InviteRetracted && (!DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRetracted))) + newState = MembershipTransition.Leave; + else if (entry.State == MembershipTransition.KnockAccepted && (!DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockAccepted)) + newState = MembershipTransition.Invite; + else if ((entry.State == MembershipTransition.KnockRejected && (!DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRejected)) || + (entry.State == MembershipTransition.KnockRetracted && (!DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRetracted))) + newState = MembershipTransition.Leave; + else + newState = entry.State; + + yield return newState == entry.State ? entry : entry with { State = newState }; + } + } + + [Benchmark] + public void TestAmbiguateMembershipsStaticMap() => AmbiguateMembershipsStaticMap().Consume(new Consumer()); + + public IEnumerable<MembershipEntry> AmbiguateMembershipsStaticMap() { + Dictionary<MembershipTransition, MembershipTransition> _map = []; + if (!DoDisambiguate || !DisambiguateProfileUpdates) _map[MembershipTransition.ProfileUpdate] = MembershipTransition.Join; + if (!DoDisambiguate || !DisambiguateKicks) _map[MembershipTransition.Kick] = MembershipTransition.Leave; + if (!DoDisambiguate || !DisambiguateUnbans) _map[MembershipTransition.Unban] = MembershipTransition.Leave; + if (!DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteAccepted) _map[MembershipTransition.InviteAccepted] = MembershipTransition.Join; + if (!DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRejected) _map[MembershipTransition.InviteRejected] = MembershipTransition.Leave; + if (!DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRetracted) _map[MembershipTransition.InviteRetracted] = MembershipTransition.Leave; + if (!DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockAccepted) _map[MembershipTransition.KnockAccepted] = MembershipTransition.Invite; + if (!DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRejected) _map[MembershipTransition.KnockRejected] = MembershipTransition.Leave; + if (!DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRetracted) _map[MembershipTransition.KnockRetracted] = MembershipTransition.Leave; + FrozenDictionary<MembershipTransition, MembershipTransition> map = _map.ToFrozenDictionary(); + _map = null!; + foreach (var entry in entries) { + var newState = map.TryGetValue(entry.State, out var value) ? value : entry.State; + yield return newState == entry.State ? entry : entry with { State = newState }; + } + } + + [Benchmark] + public void TestAmbiguateMembershipsBinMask() => AmbiguateMembershipsBinMask().Consume(new Consumer()); + + public IEnumerable<MembershipEntry> AmbiguateMembershipsBinMask() { + uint mask = 0; + // dont mask last 4 bits + if (!DoDisambiguate || !DisambiguateProfileUpdates) mask |= (uint)MembershipTransition.ProfileUpdate >> 4; + if (!DoDisambiguate || !DisambiguateKicks) mask |= (uint)MembershipTransition.Kick >> 4; + if (!DoDisambiguate || !DisambiguateUnbans) mask |= (uint)MembershipTransition.Unban >> 4; + if (!DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteAccepted) mask |= (uint)MembershipTransition.InviteAccepted >> 4; + if (!DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRejected) mask |= (uint)MembershipTransition.InviteRejected >> 4; + if (!DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRetracted) mask |= (uint)MembershipTransition.InviteRetracted >> 4; + if (!DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockAccepted) mask |= (uint)MembershipTransition.KnockAccepted >> 4; + if (!DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRejected) mask |= (uint)MembershipTransition.KnockRejected >> 4; + if (!DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRetracted) mask |= (uint)MembershipTransition.KnockRetracted >> 4; + mask = (mask << 4) + 0b1111; + // Console.WriteLine(mask.ToString("b24")); + foreach (var entry in entries) { + if (((uint)entry.State & 0b1111_1111_0000) == 0) { + yield return entry; + continue; + } + + var newState = (MembershipTransition)((uint)entry.State & mask); + // Console.WriteLine(((uint)newState).ToString("b32")); + yield return newState == entry.State ? entry : entry with { State = newState }; + } + } +} \ No newline at end of file diff --git a/LibMatrix b/LibMatrix -Subproject 16e314ed714f8b3e298c0ecf2ebfe67b48e5f69 +Subproject ca3e6878422b7b55ae52b43f49f89a19546ea51 diff --git a/MatrixRoomUtils.sln b/MatrixRoomUtils.sln deleted file mode 100644 index 23d86b0..0000000 --- a/MatrixRoomUtils.sln +++ /dev/null @@ -1,232 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Web", "MatrixUtils.Web\MatrixUtils.Web.csproj", "{D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Web.Server", "MatrixUtils.Web.Server\MatrixUtils.Web.Server.csproj", "{F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Desktop", "MatrixUtils.Desktop\MatrixUtils.Desktop.csproj", "{27C08A4F-5AF0-4C2C-AFCB-050E3388C116}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LibMatrix", "LibMatrix", "{8F4F6BEC-0C66-486B-A21A-1C35B2EDAD33}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix", "LibMatrix\LibMatrix\LibMatrix.csproj", "{F4E241C3-0300-4B87-8707-BCBDEF1F0185}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MxApiExtensions", "MxApiExtensions", "{F1376F9A-FB65-4E60-BB9A-62A64F741FF4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MxApiExtensions", "MxApiExtensions\MxApiExtensions\MxApiExtensions.csproj", "{41200A7B-D2DB-4656-B66B-5206A63B367A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArcaneLibs", "LibMatrix\ArcaneLibs", "{B00C5CB6-6200-4B41-96BE-C6EAF1085A14}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs", "LibMatrix\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj", "{B00E29F5-1ED8-40A0-A70D-DE9F23FC572F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Blazor.Components", "LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj", "{2D6F31D7-3139-44EC-9D11-486282DD4ED1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MxApiExtensions.Classes", "MxApiExtensions\MxApiExtensions.Classes\MxApiExtensions.Classes.csproj", "{99C016AA-AFBA-4D32-A687-D1FABC0F5212}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MxApiExtensions.Classes.LibMatrix", "MxApiExtensions\MxApiExtensions.Classes.LibMatrix\MxApiExtensions.Classes.LibMatrix.csproj", "{C298E274-5D6C-47C8-9B71-A6B34D0195A3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ExampleBots", "ExampleBots", "{3E0FDE30-8DA8-4E65-A3C6-AA53B5BC70A2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.ExampleBot", "LibMatrix\ExampleBots\LibMatrix.ExampleBot\LibMatrix.ExampleBot.csproj", "{2CB12623-4918-4176-9B4A-88D846CCD3ED}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModerationBot", "LibMatrix\ExampleBots\ModerationBot\ModerationBot.csproj", "{48DBB05F-B007-4B24-89B3-3CC177C79007}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{A4BCBF5F-4936-44B9-BAB3-FAF240BDF40D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DebugDataValidationApi", "LibMatrix\Utilities\LibMatrix.DebugDataValidationApi\LibMatrix.DebugDataValidationApi.csproj", "{5DEF66C8-B931-435F-B4B5-E1858590D52E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Utilities.Bot", "LibMatrix\Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj", "{D8E5C678-3BE5-470C-A3A5-B5D525FC2012}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluralContactBotPoC", "LibMatrix\ExampleBots\PluralContactBotPoC\PluralContactBotPoC.csproj", "{95052EE6-7513-46FB-91BD-EE82026B42F1}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{7D2C9959-8309-4110-A67F-DEE64E97C1D8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Tests", "LibMatrix\Tests\LibMatrix.Tests\LibMatrix.Tests.csproj", "{E37B78F1-D7A5-4F79-ADBA-E12DF7D0F881}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestDataGenerator", "LibMatrix\Tests\TestDataGenerator\TestDataGenerator.csproj", "{F3312DE9-4335-4E85-A4CF-2616427A651E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.LibDMSpace", "MatrixUtils.LibDMSpace\MatrixUtils.LibDMSpace.csproj", "{EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix\LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{1CAA2B6D-0365-4C8B-96EE-26026514FEE2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Abstractions", "MatrixUtils.Abstractions\MatrixUtils.Abstractions.csproj", "{FE20ED20-0D55-4D74-822B-E2AC7A54C487}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.JsonSerializerContextGenerator", "LibMatrix\Utilities\LibMatrix.JsonSerializerContextGenerator\LibMatrix.JsonSerializerContextGenerator.csproj", "{CC836863-0EE8-44BD-BF39-45076F57C416}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "LibMatrix\Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{5CE239F8-C124-4A96-A0F8-B56B9AE27434}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "LibMatrix\Tests\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{6D93DA72-69D8-43BD-BC19-7FFF8A313971}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "LibMatrix\ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.DmSpaced", "MatrixUtils.DmSpaced\MatrixUtils.DmSpaced.csproj", "{B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Legacy", "LibMatrix\ArcaneLibs\ArcaneLibs.Legacy\ArcaneLibs.Legacy.csproj", "{748D215E-CA40-4D0F-BE1D-D2350D4AB8CA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Logging", "LibMatrix\ArcaneLibs\ArcaneLibs.Logging\ArcaneLibs.Logging.csproj", "{E33F7CB0-A03D-4ED0-9AE8-95B31A2D7ACC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.StringNormalisation", "LibMatrix\ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj", "{FDB12B6A-01AA-46C0-A55E-0F984496AB81}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "LibMatrix\ArcaneLibs\ArcaneLibs.Timings\ArcaneLibs.Timings.csproj", "{84EE78FF-E198-4090-BFE9-C47D266E115E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLib.Tests", "LibMatrix\ArcaneLibs\ArcaneLib.Tests\ArcaneLib.Tests.csproj", "{1607FCA9-7B5B-45B0-8D1F-205ABACB7173}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.MxApiExtensions", "LibMatrix\LibMatrix.MxApiExtensions\LibMatrix.MxApiExtensions.csproj", "{56A42391-4514-4352-B22B-622EE7A618AA}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Release|Any CPU.Build.0 = Release|Any CPU - {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Release|Any CPU.Build.0 = Release|Any CPU - {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|Any CPU.Build.0 = Debug|Any CPU - {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|Any CPU.ActiveCfg = Release|Any CPU - {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|Any CPU.Build.0 = Release|Any CPU - {F4E241C3-0300-4B87-8707-BCBDEF1F0185}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F4E241C3-0300-4B87-8707-BCBDEF1F0185}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F4E241C3-0300-4B87-8707-BCBDEF1F0185}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F4E241C3-0300-4B87-8707-BCBDEF1F0185}.Release|Any CPU.Build.0 = Release|Any CPU - {41200A7B-D2DB-4656-B66B-5206A63B367A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {41200A7B-D2DB-4656-B66B-5206A63B367A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {41200A7B-D2DB-4656-B66B-5206A63B367A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {41200A7B-D2DB-4656-B66B-5206A63B367A}.Release|Any CPU.Build.0 = Release|Any CPU - {B00E29F5-1ED8-40A0-A70D-DE9F23FC572F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B00E29F5-1ED8-40A0-A70D-DE9F23FC572F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B00E29F5-1ED8-40A0-A70D-DE9F23FC572F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B00E29F5-1ED8-40A0-A70D-DE9F23FC572F}.Release|Any CPU.Build.0 = Release|Any CPU - {2D6F31D7-3139-44EC-9D11-486282DD4ED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2D6F31D7-3139-44EC-9D11-486282DD4ED1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D6F31D7-3139-44EC-9D11-486282DD4ED1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2D6F31D7-3139-44EC-9D11-486282DD4ED1}.Release|Any CPU.Build.0 = Release|Any CPU - {99C016AA-AFBA-4D32-A687-D1FABC0F5212}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99C016AA-AFBA-4D32-A687-D1FABC0F5212}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99C016AA-AFBA-4D32-A687-D1FABC0F5212}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99C016AA-AFBA-4D32-A687-D1FABC0F5212}.Release|Any CPU.Build.0 = Release|Any CPU - {C298E274-5D6C-47C8-9B71-A6B34D0195A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C298E274-5D6C-47C8-9B71-A6B34D0195A3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C298E274-5D6C-47C8-9B71-A6B34D0195A3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C298E274-5D6C-47C8-9B71-A6B34D0195A3}.Release|Any CPU.Build.0 = Release|Any CPU - {2CB12623-4918-4176-9B4A-88D846CCD3ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2CB12623-4918-4176-9B4A-88D846CCD3ED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2CB12623-4918-4176-9B4A-88D846CCD3ED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2CB12623-4918-4176-9B4A-88D846CCD3ED}.Release|Any CPU.Build.0 = Release|Any CPU - {48DBB05F-B007-4B24-89B3-3CC177C79007}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {48DBB05F-B007-4B24-89B3-3CC177C79007}.Debug|Any CPU.Build.0 = Debug|Any CPU - {48DBB05F-B007-4B24-89B3-3CC177C79007}.Release|Any CPU.ActiveCfg = Release|Any CPU - {48DBB05F-B007-4B24-89B3-3CC177C79007}.Release|Any CPU.Build.0 = Release|Any CPU - {5DEF66C8-B931-435F-B4B5-E1858590D52E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5DEF66C8-B931-435F-B4B5-E1858590D52E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5DEF66C8-B931-435F-B4B5-E1858590D52E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5DEF66C8-B931-435F-B4B5-E1858590D52E}.Release|Any CPU.Build.0 = Release|Any CPU - {D8E5C678-3BE5-470C-A3A5-B5D525FC2012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D8E5C678-3BE5-470C-A3A5-B5D525FC2012}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D8E5C678-3BE5-470C-A3A5-B5D525FC2012}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D8E5C678-3BE5-470C-A3A5-B5D525FC2012}.Release|Any CPU.Build.0 = Release|Any CPU - {95052EE6-7513-46FB-91BD-EE82026B42F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {95052EE6-7513-46FB-91BD-EE82026B42F1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {95052EE6-7513-46FB-91BD-EE82026B42F1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {95052EE6-7513-46FB-91BD-EE82026B42F1}.Release|Any CPU.Build.0 = Release|Any CPU - {E37B78F1-D7A5-4F79-ADBA-E12DF7D0F881}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E37B78F1-D7A5-4F79-ADBA-E12DF7D0F881}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E37B78F1-D7A5-4F79-ADBA-E12DF7D0F881}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E37B78F1-D7A5-4F79-ADBA-E12DF7D0F881}.Release|Any CPU.Build.0 = Release|Any CPU - {F3312DE9-4335-4E85-A4CF-2616427A651E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3312DE9-4335-4E85-A4CF-2616427A651E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3312DE9-4335-4E85-A4CF-2616427A651E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F3312DE9-4335-4E85-A4CF-2616427A651E}.Release|Any CPU.Build.0 = Release|Any CPU - {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Release|Any CPU.Build.0 = Release|Any CPU - {1CAA2B6D-0365-4C8B-96EE-26026514FEE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1CAA2B6D-0365-4C8B-96EE-26026514FEE2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1CAA2B6D-0365-4C8B-96EE-26026514FEE2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1CAA2B6D-0365-4C8B-96EE-26026514FEE2}.Release|Any CPU.Build.0 = Release|Any CPU - {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|Any CPU.Build.0 = Release|Any CPU - {CC836863-0EE8-44BD-BF39-45076F57C416}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC836863-0EE8-44BD-BF39-45076F57C416}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC836863-0EE8-44BD-BF39-45076F57C416}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC836863-0EE8-44BD-BF39-45076F57C416}.Release|Any CPU.Build.0 = Release|Any CPU - {5CE239F8-C124-4A96-A0F8-B56B9AE27434}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5CE239F8-C124-4A96-A0F8-B56B9AE27434}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5CE239F8-C124-4A96-A0F8-B56B9AE27434}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5CE239F8-C124-4A96-A0F8-B56B9AE27434}.Release|Any CPU.Build.0 = Release|Any CPU - {6D93DA72-69D8-43BD-BC19-7FFF8A313971}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D93DA72-69D8-43BD-BC19-7FFF8A313971}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D93DA72-69D8-43BD-BC19-7FFF8A313971}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D93DA72-69D8-43BD-BC19-7FFF8A313971}.Release|Any CPU.Build.0 = Release|Any CPU - {EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0}.Release|Any CPU.Build.0 = Release|Any CPU - {B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}.Release|Any CPU.Build.0 = Release|Any CPU - {748D215E-CA40-4D0F-BE1D-D2350D4AB8CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {748D215E-CA40-4D0F-BE1D-D2350D4AB8CA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {748D215E-CA40-4D0F-BE1D-D2350D4AB8CA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {748D215E-CA40-4D0F-BE1D-D2350D4AB8CA}.Release|Any CPU.Build.0 = Release|Any CPU - {E33F7CB0-A03D-4ED0-9AE8-95B31A2D7ACC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E33F7CB0-A03D-4ED0-9AE8-95B31A2D7ACC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E33F7CB0-A03D-4ED0-9AE8-95B31A2D7ACC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E33F7CB0-A03D-4ED0-9AE8-95B31A2D7ACC}.Release|Any CPU.Build.0 = Release|Any CPU - {FDB12B6A-01AA-46C0-A55E-0F984496AB81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FDB12B6A-01AA-46C0-A55E-0F984496AB81}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FDB12B6A-01AA-46C0-A55E-0F984496AB81}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FDB12B6A-01AA-46C0-A55E-0F984496AB81}.Release|Any CPU.Build.0 = Release|Any CPU - {84EE78FF-E198-4090-BFE9-C47D266E115E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {84EE78FF-E198-4090-BFE9-C47D266E115E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {84EE78FF-E198-4090-BFE9-C47D266E115E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {84EE78FF-E198-4090-BFE9-C47D266E115E}.Release|Any CPU.Build.0 = Release|Any CPU - {1607FCA9-7B5B-45B0-8D1F-205ABACB7173}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1607FCA9-7B5B-45B0-8D1F-205ABACB7173}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1607FCA9-7B5B-45B0-8D1F-205ABACB7173}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1607FCA9-7B5B-45B0-8D1F-205ABACB7173}.Release|Any CPU.Build.0 = Release|Any CPU - {56A42391-4514-4352-B22B-622EE7A618AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56A42391-4514-4352-B22B-622EE7A618AA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56A42391-4514-4352-B22B-622EE7A618AA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56A42391-4514-4352-B22B-622EE7A618AA}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {F4E241C3-0300-4B87-8707-BCBDEF1F0185} = {8F4F6BEC-0C66-486B-A21A-1C35B2EDAD33} - {41200A7B-D2DB-4656-B66B-5206A63B367A} = {F1376F9A-FB65-4E60-BB9A-62A64F741FF4} - {B00E29F5-1ED8-40A0-A70D-DE9F23FC572F} = {B00C5CB6-6200-4B41-96BE-C6EAF1085A14} - {2D6F31D7-3139-44EC-9D11-486282DD4ED1} = {B00C5CB6-6200-4B41-96BE-C6EAF1085A14} - {99C016AA-AFBA-4D32-A687-D1FABC0F5212} = {F1376F9A-FB65-4E60-BB9A-62A64F741FF4} - {C298E274-5D6C-47C8-9B71-A6B34D0195A3} = {F1376F9A-FB65-4E60-BB9A-62A64F741FF4} - {3E0FDE30-8DA8-4E65-A3C6-AA53B5BC70A2} = {8F4F6BEC-0C66-486B-A21A-1C35B2EDAD33} - {2CB12623-4918-4176-9B4A-88D846CCD3ED} = {3E0FDE30-8DA8-4E65-A3C6-AA53B5BC70A2} - {48DBB05F-B007-4B24-89B3-3CC177C79007} = {3E0FDE30-8DA8-4E65-A3C6-AA53B5BC70A2} - {A4BCBF5F-4936-44B9-BAB3-FAF240BDF40D} = {8F4F6BEC-0C66-486B-A21A-1C35B2EDAD33} - {5DEF66C8-B931-435F-B4B5-E1858590D52E} = {A4BCBF5F-4936-44B9-BAB3-FAF240BDF40D} - {D8E5C678-3BE5-470C-A3A5-B5D525FC2012} = {A4BCBF5F-4936-44B9-BAB3-FAF240BDF40D} - {95052EE6-7513-46FB-91BD-EE82026B42F1} = {3E0FDE30-8DA8-4E65-A3C6-AA53B5BC70A2} - {7D2C9959-8309-4110-A67F-DEE64E97C1D8} = {8F4F6BEC-0C66-486B-A21A-1C35B2EDAD33} - {E37B78F1-D7A5-4F79-ADBA-E12DF7D0F881} = {7D2C9959-8309-4110-A67F-DEE64E97C1D8} - {F3312DE9-4335-4E85-A4CF-2616427A651E} = {7D2C9959-8309-4110-A67F-DEE64E97C1D8} - {1CAA2B6D-0365-4C8B-96EE-26026514FEE2} = {8F4F6BEC-0C66-486B-A21A-1C35B2EDAD33} - {CC836863-0EE8-44BD-BF39-45076F57C416} = {A4BCBF5F-4936-44B9-BAB3-FAF240BDF40D} - {5CE239F8-C124-4A96-A0F8-B56B9AE27434} = {A4BCBF5F-4936-44B9-BAB3-FAF240BDF40D} - {6D93DA72-69D8-43BD-BC19-7FFF8A313971} = {7D2C9959-8309-4110-A67F-DEE64E97C1D8} - {EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0} = {B00C5CB6-6200-4B41-96BE-C6EAF1085A14} - {748D215E-CA40-4D0F-BE1D-D2350D4AB8CA} = {B00C5CB6-6200-4B41-96BE-C6EAF1085A14} - {E33F7CB0-A03D-4ED0-9AE8-95B31A2D7ACC} = {B00C5CB6-6200-4B41-96BE-C6EAF1085A14} - {FDB12B6A-01AA-46C0-A55E-0F984496AB81} = {B00C5CB6-6200-4B41-96BE-C6EAF1085A14} - {84EE78FF-E198-4090-BFE9-C47D266E115E} = {B00C5CB6-6200-4B41-96BE-C6EAF1085A14} - {1607FCA9-7B5B-45B0-8D1F-205ABACB7173} = {B00C5CB6-6200-4B41-96BE-C6EAF1085A14} - {56A42391-4514-4352-B22B-622EE7A618AA} = {8F4F6BEC-0C66-486B-A21A-1C35B2EDAD33} - EndGlobalSection -EndGlobal diff --git a/MatrixUtils.Abstractions/FileStorageProvider.cs b/MatrixUtils.Abstractions/FileStorageProvider.cs index fbe068d..1083002 100644 --- a/MatrixUtils.Abstractions/FileStorageProvider.cs +++ b/MatrixUtils.Abstractions/FileStorageProvider.cs @@ -1,7 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using ArcaneLibs.Extensions; -using LibMatrix.Extensions; using LibMatrix.Interfaces.Services; using Microsoft.Extensions.Logging; diff --git a/MatrixUtils.Abstractions/MatrixUtils.Abstractions.csproj b/MatrixUtils.Abstractions/MatrixUtils.Abstractions.csproj index 1665ff0..96f9fcb 100644 --- a/MatrixUtils.Abstractions/MatrixUtils.Abstractions.csproj +++ b/MatrixUtils.Abstractions/MatrixUtils.Abstractions.csproj @@ -1,12 +1,12 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> - + <ItemGroup> - <ProjectReference Include="..\LibMatrix\LibMatrix\LibMatrix.csproj" /> + <ProjectReference Include="..\LibMatrix\LibMatrix\LibMatrix.csproj" /> </ItemGroup> </Project> diff --git a/MatrixUtils.Abstractions/RoomInfo.cs b/MatrixUtils.Abstractions/RoomInfo.cs index aff0e25..81ce388 100644 --- a/MatrixUtils.Abstractions/RoomInfo.cs +++ b/MatrixUtils.Abstractions/RoomInfo.cs @@ -3,7 +3,6 @@ using System.Collections.ObjectModel; using System.Text.Json.Nodes; using ArcaneLibs; using LibMatrix; -using LibMatrix.EventTypes.Spec.State; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Homeservers; using LibMatrix.RoomTypes; diff --git a/MatrixUtils.Desktop/Components/RoomListEntry.axaml.cs b/MatrixUtils.Desktop/Components/RoomListEntry.axaml.cs index 1e4a127..1e6b99f 100644 --- a/MatrixUtils.Desktop/Components/RoomListEntry.axaml.cs +++ b/MatrixUtils.Desktop/Components/RoomListEntry.axaml.cs @@ -44,7 +44,7 @@ public partial class RoomListEntry : UserControl { var avatarEvent = await Room.GetStateEvent("m.room.avatar"); if (avatarEvent?.TypedContent is RoomAvatarEventContent avatarData) { var mxcUrl = avatarData.Url; - var resolvedUrl = await Room.Room.GetResolvedRoomAvatarUrlAsync(); + var resolvedUrl = await Room.Room.GetAvatarUrlAsync(); // await using var svc = _serviceScopeFactory.CreateAsyncScope(); // var hs = await svc.ServiceProvider.GetService<RMUStorageWrapper>()?.GetCurrentSessionOrPrompt()!; @@ -54,10 +54,10 @@ public partial class RoomListEntry : UserControl { var storage = new FileStorageProvider("cache"); var storageKey = $"media/{mxcUrl.Replace("mxc://", "").Replace("/", ".")}"; try { - if (!await storage.ObjectExistsAsync(storageKey)) - await storage.SaveStreamAsync(storageKey, await hc.GetStreamAsync(resolvedUrl)); + // if (!await storage.ObjectExistsAsync(storageKey)) + // await storage.SaveStreamAsync(storageKey, await hc.GetStreamAsync(resolvedUrl)); - RoomIcon.Source = new Bitmap(await storage.LoadStreamAsync(storageKey) ?? throw new NullReferenceException()); + // RoomIcon.Source = new Bitmap(await storage.LoadStreamAsync(storageKey) ?? throw new NullReferenceException()); } catch (IOException) { } catch (MatrixException e) { diff --git a/MatrixUtils.Desktop/MatrixUtils.Desktop.csproj b/MatrixUtils.Desktop/MatrixUtils.Desktop.csproj index ce009d5..a7ff4b9 100644 --- a/MatrixUtils.Desktop/MatrixUtils.Desktop.csproj +++ b/MatrixUtils.Desktop/MatrixUtils.Desktop.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <Nullable>enable</Nullable> <BuiltInComInteropSupport>true</BuiltInComInteropSupport> <ApplicationManifest>app.manifest</ApplicationManifest> @@ -20,21 +20,21 @@ <ItemGroup> - <PackageReference Include="Avalonia" Version="11.0.10" /> - <PackageReference Include="Avalonia.Desktop" Version="11.0.10" /> - <PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.10" /> - <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.10" /> + <PackageReference Include="Avalonia" Version="11.2.1" /> + <PackageReference Include="Avalonia.Desktop" Version="11.2.1" /> + <PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.1" /> + <PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.1" /> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> - <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.10" /> - <PackageReference Include="Sentry" Version="4.7.0" /> + <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.1" /> + <PackageReference Include="Sentry" Version="4.13.0" /> </ItemGroup> <ItemGroup> - <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.10.9" /> - <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> + <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.2.0" /> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" /> </ItemGroup> <ItemGroup> <Content Include="appsettings*.json"> diff --git a/MatrixUtils.DmSpaced/MatrixUtils.DmSpaced.csproj b/MatrixUtils.DmSpaced/MatrixUtils.DmSpaced.csproj index 4b0f599..f8dd598 100644 --- a/MatrixUtils.DmSpaced/MatrixUtils.DmSpaced.csproj +++ b/MatrixUtils.DmSpaced/MatrixUtils.DmSpaced.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <LangVersion>preview</LangVersion> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> diff --git a/MatrixUtils.DmSpaced/Program.cs b/MatrixUtils.DmSpaced/Program.cs index ae352b7..6ed6cbc 100644 --- a/MatrixUtils.DmSpaced/Program.cs +++ b/MatrixUtils.DmSpaced/Program.cs @@ -22,17 +22,17 @@ if (Environment.GetEnvironmentVariable("MODERATIONBOT_APPSETTINGS_PATH") is stri builder.ConfigureAppConfiguration(x => x.AddJsonFile(path)); var host = builder.ConfigureServices((_, services) => { - services.AddScoped<TieredStorageService>(x => - new TieredStorageService( - cacheStorageProvider: new FileStorageProvider("bot_data/cache/"), - dataStorageProvider: new FileStorageProvider("bot_data/data/") - ) - ); + // services.AddScoped<TieredStorageService>(x => + // new TieredStorageService( + // cacheStorageProvider: new FileStorageProvider("bot_data/cache/"), + // dataStorageProvider: new FileStorageProvider("bot_data/data/") + // ) + // ); services.AddSingleton<ModerationBotConfiguration>(); services.AddRoryLibMatrixServices(); - services.AddSingleton<ModerationBotRoomProvider>(); + // services.AddSingleton<ModerationBotRoomProvider>(); services.AddHostedService<ModerationBot.ModerationBot>(); }).UseConsoleLifetime().Build(); diff --git a/MatrixUtils.LibDMSpace/DMSpaceRoom.cs b/MatrixUtils.LibDMSpace/DMSpaceRoom.cs index e2c8192..646a3f3 100644 --- a/MatrixUtils.LibDMSpace/DMSpaceRoom.cs +++ b/MatrixUtils.LibDMSpace/DMSpaceRoom.cs @@ -1,7 +1,5 @@ -using System.Net; using ArcaneLibs.Extensions; using LibMatrix; -using LibMatrix.EventTypes.Spec.State; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Homeservers; using LibMatrix.Responses; diff --git a/MatrixUtils.LibDMSpace/MatrixUtils.LibDMSpace.csproj b/MatrixUtils.LibDMSpace/MatrixUtils.LibDMSpace.csproj index 72c1666..e39440e 100644 --- a/MatrixUtils.LibDMSpace/MatrixUtils.LibDMSpace.csproj +++ b/MatrixUtils.LibDMSpace/MatrixUtils.LibDMSpace.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <LinkIncremental>true</LinkIncremental> @@ -10,6 +10,6 @@ </PropertyGroup> <ItemGroup> - <ProjectReference Include="..\LibMatrix\LibMatrix\LibMatrix.csproj"/> + <ProjectReference Include="..\LibMatrix\LibMatrix\LibMatrix.csproj" /> </ItemGroup> </Project> diff --git a/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs b/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs index bc595b5..f7e1e20 100644 --- a/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs +++ b/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs @@ -1,6 +1,5 @@ using System.Text.Json.Serialization; using LibMatrix.EventTypes; -using LibMatrix.Interfaces; namespace MatrixUtils.LibDMSpace.StateEvents; diff --git a/MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs index 16c7b70..886c34d 100644 --- a/MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs +++ b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs @@ -1,6 +1,5 @@ using System.Text.Json.Serialization; using LibMatrix.EventTypes; -using LibMatrix.Interfaces; namespace MatrixUtils.LibDMSpace.StateEvents; diff --git a/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs index f5daa74..170efc7 100644 --- a/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs +++ b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs @@ -1,6 +1,5 @@ using System.Text.Json.Serialization; using LibMatrix.EventTypes; -using LibMatrix.Interfaces; namespace MatrixUtils.LibDMSpace.StateEvents; diff --git a/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj b/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj index f2d47ea..24401ab 100644 --- a/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj +++ b/MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj @@ -1,14 +1,14 @@ <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <LangVersion>preview</LangVersion> </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.6" /> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.1" /> </ItemGroup> <ItemGroup> diff --git a/MatrixUtils.Web/App.razor b/MatrixUtils.Web/App.razor index 5e87bc3..a8cf817 100644 --- a/MatrixUtils.Web/App.razor +++ b/MatrixUtils.Web/App.razor @@ -1,5 +1,4 @@ -@using Microsoft.AspNetCore.Components.Routing -<Router AppAssembly="@typeof(App).Assembly"> +<Router AppAssembly="@typeof(App).Assembly"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/> <FocusOnNavigate RouteData="@routeData" Selector="h1"/> diff --git a/MatrixUtils.Web/Classes/Constants/RoomConstants.cs b/MatrixUtils.Web/Classes/Constants/RoomConstants.cs index 5df0d01..dc81d04 100644 --- a/MatrixUtils.Web/Classes/Constants/RoomConstants.cs +++ b/MatrixUtils.Web/Classes/Constants/RoomConstants.cs @@ -1,6 +1,7 @@ namespace MatrixUtils.Web.Classes.Constants; public class RoomConstants { - public static readonly string[] DangerousRoomVersions = { "1", "8" }; - public const string RecommendedRoomVersion = "10"; + public static readonly string[] DangerousRoomVersions = ["1", "8"]; + public static readonly string[] UnsupportedRoomVersions = ["1", "2", "3", "4", "5", "6"]; + public const string RecommendedRoomVersion = "11"; } diff --git a/MatrixUtils.Web/Classes/RMUStorageWrapper.cs b/MatrixUtils.Web/Classes/RMUStorageWrapper.cs index 45028ba..e63c28e 100644 --- a/MatrixUtils.Web/Classes/RMUStorageWrapper.cs +++ b/MatrixUtils.Web/Classes/RMUStorageWrapper.cs @@ -25,6 +25,10 @@ public class RMUStorageWrapper(ILogger<RMUStorageWrapper> logger, TieredStorageS await SetCurrentToken(currentToken = allTokens[0]); } + if (currentToken is null) { + await SetCurrentToken(currentToken = allTokens[0]); + } + if (!allTokens.Any(x => x.AccessToken == currentToken.AccessToken)) { await SetCurrentToken(currentToken = allTokens[0]); } @@ -47,12 +51,21 @@ public class RMUStorageWrapper(ILogger<RMUStorageWrapper> logger, TieredStorageS return null; } - return await homeserverProviderService.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy); + return await GetSession(token); } public async Task<AuthenticatedHomeserverGeneric?> GetSession(UserAuth userAuth) { logger.LogTrace("Getting session."); - return await homeserverProviderService.GetAuthenticatedWithToken(userAuth.Homeserver, userAuth.AccessToken, userAuth.Proxy); + AuthenticatedHomeserverGeneric hs; + try { + hs = await homeserverProviderService.GetAuthenticatedWithToken(userAuth.Homeserver, userAuth.AccessToken, userAuth.Proxy); + } + catch (Exception e) { + logger.LogError("Failed to get info for {0} via {1}: {2}", userAuth.UserId, userAuth.Homeserver, e); + logger.LogError("Continuing with server-less session"); + hs = await homeserverProviderService.GetAuthenticatedWithToken(userAuth.Homeserver, userAuth.AccessToken, userAuth.Proxy, useGeneric: true, enableServer: false); + } + return hs; } public async Task<AuthenticatedHomeserverGeneric?> GetCurrentSessionOrNavigate() { diff --git a/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs b/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs index a627a9c..7078308 100644 --- a/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs +++ b/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs @@ -1,6 +1,5 @@ using System.Text.Json.Nodes; using LibMatrix; -using LibMatrix.EventTypes.Spec.State; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Responses; @@ -34,7 +33,7 @@ public class DefaultRoomCreationTemplate : IRoomCreationTemplate { }, new() { Type = "m.room.server_acl", - TypedContent = new RoomServerACLEventContent() { + TypedContent = new RoomServerAclEventContent() { Allow = new List<string>() { "*" }, Deny = new List<string>(), AllowIpLiterals = false @@ -56,7 +55,7 @@ public class DefaultRoomCreationTemplate : IRoomCreationTemplate { Redact = 50, Kick = 50, Ban = 50, - NotificationsPl = new RoomPowerLevelEventContent.NotificationsPL { + NotificationsPl = new RoomPowerLevelEventContent.NotificationsPowerLevels { Room = 50 }, Events = new() { diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj index 8760e7a..b472b45 100644 --- a/MatrixUtils.Web/MatrixUtils.Web.csproj +++ b/MatrixUtils.Web/MatrixUtils.Web.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <LinkIncremental>true</LinkIncremental> @@ -12,62 +12,64 @@ <BlazorEnableCompression>false</BlazorEnableCompression> <ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest> <BlazorCacheBootResources>false</BlazorCacheBootResources> -<!-- <RunAOTCompilation>true</RunAOTCompilation>--> + <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport> </PropertyGroup> - <ItemGroup> - <PackageReference Include="Blazored.LocalStorage" Version="4.5.0" /> - <PackageReference Include="Blazored.SessionStorage" Version="2.4.0" /> - <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" /> - <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.6" PrivateAssets="all" /> - <PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="8.0.0" /> - </ItemGroup> + <!-- Explicitly disable all the unused runtime things trimming would have removed anyways --> + <!-- https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options --> + <PropertyGroup> + <AutoreleasePoolSupport>false</AutoreleasePoolSupport> <!-- Browser != MacOS --> + <MetadataUpdaterSupport>false</MetadataUpdaterSupport> <!-- Unreliable --> + <DebuggerSupport>false</DebuggerSupport> <!-- Unreliable --> + <InvariantGlobalization>true</InvariantGlobalization> <!-- invariant globalization is fine --> + <!-- unused features --> + <EventSourceSupport>false</EventSourceSupport> + <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization> + <HttpActivityPropagationSupport>false</HttpActivityPropagationSupport> + <EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding> + <MetricsSupport>false</MetricsSupport> + <UseNativeHttpHandler>false</UseNativeHttpHandler> + <XmlResolverIsNetworkingEnabledByDefault>false</XmlResolverIsNetworkingEnabledByDefault> + <BuiltInComInteropSupport>false</BuiltInComInteropSupport> + <CustomResourceTypesSupport>false</CustomResourceTypesSupport> + <EnableCppCLIHostActivation>false</EnableCppCLIHostActivation> + <StartupHookSupport>false</StartupHookSupport> + </PropertyGroup> <ItemGroup> - <ProjectReference Condition="Exists('..\LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj')" Include="..\LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj" /> - <PackageReference Condition="!Exists('..\LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj')" Include="ArcaneLibs.Blazor.Components" Version="*-preview*" /> - <ProjectReference Include="..\LibMatrix\LibMatrix\LibMatrix.csproj" /> - <ProjectReference Include="..\MatrixUtils.Abstractions\MatrixUtils.Abstractions.csproj" /> - <ProjectReference Include="..\MatrixUtils.LibDMSpace\MatrixUtils.LibDMSpace.csproj" /> + <PackageReference Include="Blazored.LocalStorage" Version="4.5.0"/> + <PackageReference Include="Blazored.SessionStorage" Version="2.4.0"/> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" /> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.1" PrivateAssets="all" /> + <PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="9.0.1" /> + <PackageReference Include="SpawnDev.BlazorJS.WebWorkers" Version="2.5.36" /> </ItemGroup> <ItemGroup> - <Content Update="appsettings.Development.json"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </Content> - <Content Update="appsettings.json"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </Content> - <Content Update="wwwroot\appsettings.json"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </Content> + <ProjectReference Include="..\MatrixUtils.Abstractions\MatrixUtils.Abstractions.csproj"/> + <ProjectReference Include="..\MatrixUtils.LibDMSpace\MatrixUtils.LibDMSpace.csproj"/> </ItemGroup> <ItemGroup> - <ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" /> +<!-- <PackageReference Include="ArcaneLibs.Blazor.Components" Version="1.0.0-preview.20241210-161342" Condition="'$(Configuration)' == 'Release'"/>--> +<!-- <ProjectReference Include="..\LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj" Condition="'$(Configuration)' == 'Debug'"/>--> + <ProjectReference Include="..\LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj"/> </ItemGroup> <ItemGroup> - <_ContentIncludedByDefault Remove="Pages\Client\ClientComponents\ClientRoomList.razor" /> - <_ContentIncludedByDefault Remove="Pages\Client\ClientComponents\ClientStatusList.razor" /> - <_ContentIncludedByDefault Remove="Pages\Client\ClientComponents\MatrixClient.razor" /> - <_ContentIncludedByDefault Remove="Pages\Client\Index.razor" /> + <Content Update="appsettings.Development.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + <Content Update="appsettings.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + <Content Update="wwwroot\appsettings.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> </ItemGroup> <ItemGroup> - <AdditionalFiles Include="Pages\Labs\Client\ClientComponents\ClientRoomList.razor" /> - <AdditionalFiles Include="Pages\Labs\Client\ClientComponents\ClientStatusList.razor" /> - <AdditionalFiles Include="Pages\Labs\Client\ClientComponents\MatrixClient.razor" /> - <AdditionalFiles Include="Pages\Labs\Client\Index.razor" /> - <AdditionalFiles Include="Pages\Labs\DMSpace\DMSpaceStages\DMSpaceStage0.razor" /> - <AdditionalFiles Include="Pages\Labs\DMSpace\DMSpaceStages\DMSpaceStage1.razor" /> - <AdditionalFiles Include="Pages\Labs\DMSpace\DMSpaceStages\DMSpaceStage2.razor" /> - <AdditionalFiles Include="Pages\Labs\DMSpace\DMSpaceStages\DMSpaceStage3.razor" /> - <AdditionalFiles Include="Pages\Labs\Rooms2\Index2Components\MainTabComponents\MainTabSpaceItem.razor" /> - <AdditionalFiles Include="Pages\Labs\Rooms2\Index2Components\RoomsIndex2ByRoomTypeTab.razor" /> - <AdditionalFiles Include="Pages\Labs\Rooms2\Index2Components\RoomsIndex2DMsTab.razor" /> - <AdditionalFiles Include="Pages\Labs\Rooms2\Index2Components\RoomsIndex2MainTab.razor" /> - <AdditionalFiles Include="Pages\Labs\Rooms2\Index2Components\RoomsIndex2SyncContainer.razor" /> + <ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js"/> </ItemGroup> - + </Project> diff --git a/MatrixUtils.Web/Pages/About.razor b/MatrixUtils.Web/Pages/About.razor index 18d7c3f..9f83991 100644 --- a/MatrixUtils.Web/Pages/About.razor +++ b/MatrixUtils.Web/Pages/About.razor @@ -7,6 +7,6 @@ <p>Rory&::MatrixUtils is a "small" collection of tools to do not-so-everyday things.</p> <p>These range from joining rooms on dead homeservers, to managing your accounts and rooms, and creating rooms based on templates.</p> -<br/><br/> -<p>You can find the source code on <a href="https://cgit.rory.gay/matrix/MatrixRoomUtils.git/">cgit.rory.gay</a>.<br/></p> +<br/> +<p>You can find the source code on <a href="https://cgit.rory.gay/matrix/tools/MatrixUtils.git/about/">cgit.rory.gay</a>.<br/></p> <p>You can also join the <a href="https://matrix.to/#/%23mru%3Arory.gay?via=rory.gay&via=matrix.org&via=feline.support">Matrix room</a> for this project.</p> diff --git a/MatrixUtils.Web/Pages/Dev/DevOptions.razor b/MatrixUtils.Web/Pages/Dev/DevOptions.razor index 7b646d1..e94cf76 100644 --- a/MatrixUtils.Web/Pages/Dev/DevOptions.razor +++ b/MatrixUtils.Web/Pages/Dev/DevOptions.razor @@ -2,7 +2,6 @@ @using ArcaneLibs.Extensions @using System.Text @using System.Text.Json -@using Microsoft.JSInterop @inject NavigationManager NavigationManager @inject IJSRuntime JSRuntime @inject TieredStorageService TieredStorage diff --git a/MatrixUtils.Web/Pages/Dev/DevUtilities.razor b/MatrixUtils.Web/Pages/Dev/DevUtilities.razor index bf5a396..d81a790 100644 --- a/MatrixUtils.Web/Pages/Dev/DevUtilities.razor +++ b/MatrixUtils.Web/Pages/Dev/DevUtilities.razor @@ -38,7 +38,7 @@ else { protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - hs = await RMUStorage.GetCurrentSessionOrNavigate(); + hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs == null) return; Rooms = (await hs.GetJoinedRooms()).Select(x => x.RoomId).ToList(); Console.WriteLine("Fetched joined rooms!"); diff --git a/MatrixUtils.Web/Pages/Dev/ModalTest.razor b/MatrixUtils.Web/Pages/Dev/ModalTest.razor new file mode 100644 index 0000000..665f548 --- /dev/null +++ b/MatrixUtils.Web/Pages/Dev/ModalTest.razor @@ -0,0 +1,12 @@ +@page "/Dev/ModalTest" + +<PageTitle>Modal test</PageTitle> + +<h3>Rory&::MatrixUtils - Modal test</h3> +<hr/> +@for (int i = 0; i < 10; i++) +{ + <ModalWindow X="i*75" Y="i*75"> + <h1>Hello, world!</h1> + </ModalWindow> +} diff --git a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor index 9c61431..409d582 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor @@ -24,7 +24,7 @@ else { public ServerVersionResponse? ServerVersionResponse { get; set; } protected override async Task OnInitializedAsync() { - Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + Homeserver = await RmuStorage.GetCurrentSessionOrNavigate(); if (Homeserver is null) return; ServerVersionResponse = await (Homeserver.FederationClient?.GetServerVersionAsync() ?? Task.FromResult<ServerVersionResponse?>(null)); await base.OnInitializedAsync(); diff --git a/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor b/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor index 11df261..1e63e16 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor @@ -167,7 +167,7 @@ private async Task Search() { Results.Clear(); - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is AuthenticatedHomeserverSynapse synapse) { var searchRooms = synapse.Admin.SearchRoomsAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", searchTerm: SearchTerm, localFilter: Filter).GetAsyncEnumerator(); while (await searchRooms.MoveNextAsync()) { diff --git a/MatrixUtils.Web/Pages/HSEInit.razor b/MatrixUtils.Web/Pages/HSEInit.razor index cabc671..1eb556a 100644 --- a/MatrixUtils.Web/Pages/HSEInit.razor +++ b/MatrixUtils.Web/Pages/HSEInit.razor @@ -19,7 +19,7 @@ async Task<UserAuth?> Login() { try { - var result = new UserAuth(await hsProvider.Login("http://localhost:5298", $"{Guid.NewGuid().ToString()}", "")); + var result = new UserAuth(await HsProvider.Login("http://localhost:5298", $"{Guid.NewGuid().ToString()}", "")); if (result == null) { Console.WriteLine($"Failed to login!"); return null; diff --git a/MatrixUtils.Web/Pages/Index.razor b/MatrixUtils.Web/Pages/Index.razor index a7619ae..8847467 100644 --- a/MatrixUtils.Web/Pages/Index.razor +++ b/MatrixUtils.Web/Pages/Index.razor @@ -22,20 +22,28 @@ Small collection of tools to do not-so-everyday things. <form> <table> @foreach (var session in _sessions.OrderByDescending(x => x.UserInfo.RoomCount)) { - var _auth = session.UserAuth; + var auth = session.UserAuth; <tr class="user-entry"> <td> - <img class="avatar" src="@session.UserInfo.AvatarUrl" crossorigin="anonymous"/> + @if (!string.IsNullOrWhiteSpace(@session.UserInfo.AvatarUrl)) { + <MxcAvatar Homeserver="session.Homeserver" MxcUri="@session.UserInfo.AvatarUrl" Circular="true" Size="4" SizeUnit="em"/> + } + else { + <img class="avatar" src="@_identiconGenerator.GenerateAsDataUri(session.Homeserver.WhoAmI.UserId)"/> + } + @* <img class="avatar" src="@session.UserInfo.AvatarUrl" crossorigin="anonymous"/> *@ </td> <td class="user-info"> <p> - <input type="radio" name="csa" checked="@(_currentSession.AccessToken == _auth.AccessToken)" @onclick="@(() => SwitchSession(_auth))" style="text-decoration-line: unset;"/> - <b>@session.UserInfo.DisplayName</b> on <b>@_auth.Homeserver</b><br/> + <input type="radio" name="csa" checked="@(_currentSession.AccessToken == auth.AccessToken)" @onclick="@(() => SwitchSession(auth))" + style="text-decoration-line: unset;"/> + <b>@session.UserInfo.DisplayName</b> on <b>@auth.Homeserver</b><br/> </p> <span style="display: inline-block; width: 128px;">@session.UserInfo.RoomCount rooms</span> - <a style="color: #888888" href="@("/ServerInfo/" + session.Homeserver?.ServerName + "/")">@session.ServerVersion?.Server.Name @session.ServerVersion?.Server.Version</a> - @if (_auth.Proxy != null) { - <span class="badge badge-info"> (proxied via @_auth.Proxy)</span> + <a style="color: #888888" + href="@("/ServerInfo/" + session.Homeserver?.ServerName + "/")">@session.ServerVersion?.Server.Name @session.ServerVersion?.Server.Version</a> + @if (auth.Proxy != null) { + <span class="badge badge-info"> (proxied via @auth.Proxy)</span> } else { <p>Not proxied</p> @@ -48,9 +56,9 @@ Small collection of tools to do not-so-everyday things. </td> <td> <p> - <LinkButton OnClick="@(() => ManageUser(_auth))">Manage</LinkButton> - <LinkButton OnClick="@(() => RemoveUser(_auth))">Remove</LinkButton> - <LinkButton OnClick="@(() => RemoveUser(_auth, true))">Log out</LinkButton> + <LinkButton OnClick="@(() => ManageUser(auth))">Manage</LinkButton> + <LinkButton OnClick="@(() => RemoveUser(auth))">Remove</LinkButton> + <LinkButton OnClick="@(() => RemoveUser(auth, true))">Log out</LinkButton> </p> </td> </tr> @@ -108,7 +116,7 @@ Small collection of tools to do not-so-everyday things. </p> </td> <td> - <LinkButton OnClick="@(() => Task.Run(()=>NavigationManager.NavigateTo($"/InvalidSession?ctx={session.AccessToken}")))">Re-login</LinkButton> + <LinkButton OnClick="@(() => Task.Run(() => NavigationManager.NavigateTo($"/InvalidSession?ctx={session.AccessToken}")))">Re-login</LinkButton> </td> <td> <LinkButton OnClick="@(() => RemoveUser(session))">Remove</LinkButton> @@ -138,23 +146,23 @@ Small collection of tools to do not-so-everyday things. private readonly List<UserAuth> _offlineSessions = []; private readonly List<UserAuth> _invalidSessions = []; private LoginResponse? _currentSession; - int scannedSessions = 0, totalSessions = 1; + int scannedSessions, totalSessions = 1; private SvgIdenticonGenerator _identiconGenerator = new(); protected override async Task OnInitializedAsync() { Console.WriteLine("Index.OnInitializedAsync"); logger.LogDebug("Initialising index page"); - _currentSession = await RMUStorage.GetCurrentToken(); + _currentSession = await RmuStorage.GetCurrentToken(); _sessions.Clear(); _offlineSessions.Clear(); - var tokens = await RMUStorage.GetAllTokens(); + var tokens = await RmuStorage.GetAllTokens(); scannedSessions = 0; totalSessions = tokens.Count; logger.LogDebug("Found {0} tokens", totalSessions); if (tokens is not { Count: > 0 }) { Console.WriteLine("No tokens found, trying migration from MRU..."); - await RMUStorage.MigrateFromMRU(); - tokens = await RMUStorage.GetAllTokens(); + await RmuStorage.MigrateFromMRU(); + tokens = await RmuStorage.GetAllTokens(); if (tokens is not { Count: > 0 }) { Console.WriteLine("No tokens found"); return; @@ -162,26 +170,34 @@ Small collection of tools to do not-so-everyday things. } List<string> offlineServers = []; - var sema = new SemaphoreSlim(64, 64); + var sema = new SemaphoreSlim(8, 8); var updateSw = Stopwatch.StartNew(); var tasks = tokens.Select(async token => { await sema.WaitAsync(); - if ((!string.IsNullOrWhiteSpace(token.Proxy) && offlineServers.Contains(token.Proxy)) || offlineServers.Contains(token.Homeserver)) { - _offlineSessions.Add(token); - sema.Release(); - scannedSessions++; - return; - } AuthenticatedHomeserverGeneric hs; try { - hs = await hsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy); + Task<ServerVersionResponse> serverVersionTask = Task.FromResult<ServerVersionResponse>(new() { + Server = new() { + Name = "Unknown", + Version = "0.0.0" + } + }); + try { + hs = await HsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy); + serverVersionTask = hs.FederationClient?.GetServerVersionAsync() ?? serverVersionTask!; + } + catch (Exception e) { + logger.LogError("Failed to get info for {0} via {1}: {2}", token.UserId, token.Homeserver, e); + logger.LogError("Continuing with server-less session"); + hs = await HsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy, useGeneric: true, enableServer: false); + } + var joinedRoomsTask = hs.GetJoinedRooms(); var profileTask = hs.GetProfileAsync(hs.WhoAmI.UserId); - var serverVersionTask = hs.FederationClient?.GetServerVersionAsync(); _sessions.Add(new() { UserInfo = new() { - AvatarUrl = string.IsNullOrWhiteSpace((await profileTask).AvatarUrl) ? _identiconGenerator.GenerateAsDataUri(hs.WhoAmI.UserId) : hs.ResolveMediaUri((await profileTask).AvatarUrl), + AvatarUrl = (await profileTask).AvatarUrl, RoomCount = (await joinedRoomsTask).Count, DisplayName = (await profileTask).DisplayName ?? hs.WhoAmI.UserId }, @@ -226,7 +242,7 @@ Small collection of tools to do not-so-everyday things. } private class UserInfo { - internal string AvatarUrl { get; set; } + internal string? AvatarUrl { get; set; } internal string DisplayName { get; set; } internal int RoomCount { get; set; } } @@ -234,7 +250,7 @@ Small collection of tools to do not-so-everyday things. private async Task RemoveUser(UserAuth auth, bool logout = false) { try { if (logout) { - await (await hsProvider.GetAuthenticatedWithToken(auth.Homeserver, auth.AccessToken, auth.Proxy)).Logout(); + await (await HsProvider.GetAuthenticatedWithToken(auth.Homeserver, auth.AccessToken, auth.Proxy)).Logout(); } } catch (Exception e) { @@ -246,15 +262,15 @@ Small collection of tools to do not-so-everyday things. Console.WriteLine(e); } - await RMUStorage.RemoveToken(auth); - if ((await RMUStorage.GetCurrentToken())?.AccessToken == auth.AccessToken) - await RMUStorage.SetCurrentToken((await RMUStorage.GetAllTokens() ?? throw new InvalidOperationException()).FirstOrDefault()); + await RmuStorage.RemoveToken(auth); + if ((await RmuStorage.GetCurrentToken())?.AccessToken == auth.AccessToken) + await RmuStorage.SetCurrentToken((await RmuStorage.GetAllTokens() ?? throw new InvalidOperationException()).FirstOrDefault()); StateHasChanged(); } private async Task SwitchSession(UserAuth auth) { Console.WriteLine($"Switching to {auth.Homeserver} {auth.UserId} via {auth.Proxy}"); - await RMUStorage.SetCurrentToken(auth); + await RmuStorage.SetCurrentToken(auth); _currentSession = auth; StateHasChanged(); } diff --git a/MatrixUtils.Web/Pages/InvalidSession.razor b/MatrixUtils.Web/Pages/InvalidSession.razor index e1a72ea..b63c14f 100644 --- a/MatrixUtils.Web/Pages/InvalidSession.razor +++ b/MatrixUtils.Web/Pages/InvalidSession.razor @@ -40,7 +40,7 @@ else { private MatrixException? _loginException { get; set; } protected override async Task OnInitializedAsync() { - var tokens = await RMUStorage.GetAllTokens(); + var tokens = await RmuStorage.GetAllTokens(); if (tokens is null || tokens.Count == 0) { NavigationManager.NavigateTo("/Login"); return; @@ -56,9 +56,9 @@ else { } private async Task RemoveUser() { - await RMUStorage.RemoveToken(_login!); - if ((await RMUStorage.GetCurrentToken())!.AccessToken == _login!.AccessToken) - await RMUStorage.SetCurrentToken((await RMUStorage.GetAllTokens())?.FirstOrDefault()); + await RmuStorage.RemoveToken(_login!); + if ((await RmuStorage.GetCurrentToken())!.AccessToken == _login!.AccessToken) + await RmuStorage.SetCurrentToken((await RmuStorage.GetAllTokens())?.FirstOrDefault()); await OnInitializedAsync(); } @@ -70,14 +70,14 @@ else { private async Task SwitchSession(UserAuth auth) { Console.WriteLine($"Switching to {auth.Homeserver} {auth.AccessToken} {auth.UserId}"); - await RMUStorage.SetCurrentToken(auth); + await RmuStorage.SetCurrentToken(auth); await OnInitializedAsync(); } private async Task TryLogin() { if(_login is null) throw new NullReferenceException("Login is null!"); try { - var result = new UserAuth(await hsProvider.Login(_login.Homeserver, _login.UserId, _password)); + var result = new UserAuth(await HsProvider.Login(_login.Homeserver, _login.UserId, _password)); if (result is null) { Console.WriteLine($"Failed to login to {_login.Homeserver} as {_login.UserId}!"); return; @@ -85,9 +85,9 @@ else { Console.WriteLine($"Obtained access token for {result.UserId}!"); await RemoveUser(); - await RMUStorage.AddToken(result); - if (result.UserId == (await RMUStorage.GetCurrentToken())?.UserId) - await RMUStorage.SetCurrentToken(result); + await RmuStorage.AddToken(result); + if (result.UserId == (await RmuStorage.GetCurrentToken())?.UserId) + await RmuStorage.SetCurrentToken(result); NavigationManager.NavigateTo("/"); } catch (MatrixException e) { diff --git a/MatrixUtils.Web/Pages/Labs/Client/Index.razor b/MatrixUtils.Web/Pages/Labs/Client/Index.razor index ef4a0b9..4656fcb 100644 --- a/MatrixUtils.Web/Pages/Labs/Client/Index.razor +++ b/MatrixUtils.Web/Pages/Labs/Client/Index.razor @@ -40,11 +40,11 @@ } protected override async Task OnInitializedAsync() { - var tokens = await RMUStorage.GetAllTokens(); + var tokens = await RmuStorage.GetAllTokens(); var tasks = tokens.Select(async token => { try { var cc = new ClientContext() { - Homeserver = await RMUStorage.GetSession(token) + Homeserver = await RmuStorage.GetSession(token) }; cc.SyncWrapper = new ClientSyncWrapper(cc.Homeserver); diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor index c0dc8a6..a382729 100644 --- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor @@ -52,7 +52,7 @@ NavigationManager.NavigateTo(NavigationManager.Uri.Replace("stage=", ""), true); //"/User/DMSpace/Setup" } DMSpaceRootPage = this; - SetupData.Homeserver ??= await RMUStorage.GetCurrentSessionOrNavigate(); + SetupData.Homeserver ??= await RmuStorage.GetCurrentSessionOrNavigate(); if (SetupData.Homeserver is null) return; try { SetupData.DmSpaceConfiguration = await SetupData.Homeserver.GetAccountDataAsync<DMSpaceConfiguration>("gay.rory.dm_space"); diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor index 55e17d6..a974a8f 100644 --- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor @@ -4,7 +4,8 @@ @using MatrixUtils.LibDMSpace @using MatrixUtils.LibDMSpace.StateEvents @using ArcaneLibs.Extensions -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo +@using LibMatrix.EventTypes.Spec.State.Space @using MatrixUtils.Abstractions <b> <u>DM Space setup tool - stage 1: Configure space</u> diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor index be6027a..b8eb257 100644 --- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor @@ -1,6 +1,6 @@ @using LibMatrix.RoomTypes -@using LibMatrix.EventTypes.Spec.State @using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using MatrixUtils.Abstractions <b> <u>DM Space setup tool - stage 2: Fix DM room attribution</u> diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor index 09de5d3..dac9c49 100644 --- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor @@ -1,8 +1,8 @@ @using LibMatrix.RoomTypes -@using LibMatrix.EventTypes.Spec.State @using LibMatrix.Responses @using MatrixUtils.LibDMSpace @using System.Text.Json.Serialization +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using MatrixUtils.Abstractions <b> diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor index 3392960..f9da6eb 100644 --- a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor @@ -55,7 +55,7 @@ public RoomListViewData Data { get; set; } = new RoomListViewData(); protected override async Task OnInitializedAsync() { - Data.Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + Data.Homeserver = await RmuStorage.GetCurrentSessionOrNavigate(); if (Data.Homeserver is null) return; var rooms = await Data.Homeserver.GetJoinedRooms(); Data.GlobalProfile = await Data.Homeserver.GetProfileAsync(Data.Homeserver.WhoAmI.UserId); diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor index 6483f01..596d63d 100644 --- a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor @@ -8,7 +8,7 @@ <span onclick="@ToggleSpace">▶ </span> } - <MxcImage Circular="true" Height="32" Width="32" Homeserver="Space.Room.Homeserver" MxcUri="@Space.RoomIcon"></MxcImage> + <MxcImage Circular="true" Height="32" Width="32" MxcUri="@Space.RoomIcon"></MxcImage> <span class="spaceNameEllipsis">@Space.RoomName</span> </div> @if (IsSpaceOpened()) { diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor index 7ccfae2..6bf542f 100644 --- a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor @@ -1,6 +1,6 @@ @using MatrixUtils.Abstractions @using System.ComponentModel -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.Space @using MatrixUtils.Web.Pages.Labs.Rooms2.Index2Components.MainTabComponents <h3>RoomsIndex2MainTab</h3> @@ -22,26 +22,27 @@ @* </div> *@ @* </div> *@ -<div> - <div class="row"> - <div class="col-3" style="background-color: #ffffff22;"> - <LinkButton>Uncategorised rooms</LinkButton> - @foreach (var space in GetTopLevelSpaces()) { - @* @RecursingSpaceChildren(space) *@ - <MainTabSpaceItem Space="space" OpenedSpaces="OpenedSpaces" @bind-SelectedSpace="SelectedSpace" /> - } - </div> - <div class="col-9" style="background-color: #ff00ff66;"> - <p>Placeholder for rooms list...</p> - @if (SelectedSpace != null) { - foreach (var room in GetSpaceChildRooms(SelectedSpace)) { - <p>@room.RoomName</p> +<CascadingValue Name="Homeserver" Value="@Data.Homeserver"> + <div> + <div class="row"> + <div class="col-3" style="background-color: #ffffff22;"> + <LinkButton>Uncategorised rooms</LinkButton> + @foreach (var space in GetTopLevelSpaces()) { + @* @RecursingSpaceChildren(space) *@ + <MainTabSpaceItem Space="space" OpenedSpaces="OpenedSpaces" @bind-SelectedSpace="SelectedSpace"/> + } + </div> + <div class="col-9" style="background-color: #ff00ff66;"> + <p>Placeholder for rooms list...</p> + @if (SelectedSpace != null) { + foreach (var room in GetSpaceChildRooms(SelectedSpace)) { + <p>@room.RoomName</p> + } } - } + </div> </div> </div> -</div> - +</CascadingValue> @code { @@ -118,7 +119,7 @@ var childSpaces = children.Where(x => x.RoomType == "m.space").ToList(); return childSpaces; } - + private List<RoomInfo> GetSpaceChildRooms(RoomInfo space) { var children = GetSpaceChildren(space); var childRooms = children.Where(x => x.RoomType != "m.space").ToList(); diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor index 91f228d..ae57521 100644 --- a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor @@ -2,11 +2,11 @@ @using LibMatrix.Responses @using MatrixUtils.Abstractions @using System.Diagnostics -@using LibMatrix.EventTypes.Spec.State @using LibMatrix.Extensions @using LibMatrix.Utilities @using System.Collections.ObjectModel @using ArcaneLibs +@using LibMatrix.EventTypes.Spec.State.Space @inject ILogger<RoomsIndex2SyncContainer> logger <pre>RoomsIndex2SyncContainer</pre> @foreach (var (name, value) in _statusList) { diff --git a/MatrixUtils.Web/Pages/LoginPage.razor b/MatrixUtils.Web/Pages/LoginPage.razor index 6c869ac..bc989c2 100644 --- a/MatrixUtils.Web/Pages/LoginPage.razor +++ b/MatrixUtils.Web/Pages/LoginPage.razor @@ -27,6 +27,27 @@ <br/> <br/> + +<h4>Add with access token</h4> +<hr/> + +<span style="display: block;"> + <label>Homeserver:</label> + <FancyTextBox @bind-Value="@newRecordInput.Homeserver"></FancyTextBox> +</span> +<span style="display: block;"> + <label>Access token:</label> + <FancyTextBox @bind-Value="@newRecordInput.Password" IsPassword="true"></FancyTextBox> +</span> +<span style="display: block"> + <label>Proxy (<a href="https://cgit.rory.gay/matrix/MxApiExtensions.git">MxApiExtensions</a> or similar):</label> + <FancyTextBox @bind-Value="@newRecordInput.Proxy"></FancyTextBox> +</span> +<br/> +<LinkButton OnClick="@(() => AddWithAccessToken(newRecordInput))">Add session</LinkButton> +<br/> +<br/> + <h4>Import from TSV</h4> <hr/> <span>Import credentials from a TSV (Tab Separated Values) file</span><br/> @@ -100,7 +121,7 @@ if (LoggedInSessions.Any(x => x.UserId == $"@{record.Username}:{record.Homeserver}" && x.Proxy == record.Proxy)) return; StateHasChanged(); try { - var result = new UserAuth(await hsProvider.Login(record.Homeserver, record.Username, record.Password, record.Proxy)) { + var result = new UserAuth(await HsProvider.Login(record.Homeserver, record.Username, record.Password, record.Proxy)) { Proxy = record.Proxy }; if (result == null) { @@ -110,8 +131,8 @@ Console.WriteLine($"Obtained access token for {result.UserId}!"); - await RMUStorage.AddToken(result); - LoggedInSessions = await RMUStorage.GetAllTokens(); + await RmuStorage.AddToken(result); + LoggedInSessions = await RmuStorage.GetAllTokens(); } catch (Exception e) { Console.WriteLine($"Failed to login to {record.Homeserver} as {record.Username}!"); @@ -123,7 +144,7 @@ } private async Task FileChanged(InputFileChangeEventArgs obj) { - LoggedInSessions = await RMUStorage.GetAllTokens(); + LoggedInSessions = await RmuStorage.GetAllTokens(); Console.WriteLine(JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true })); @@ -141,7 +162,7 @@ } private async Task AddRecord() { - LoggedInSessions = await RMUStorage.GetAllTokens(); + LoggedInSessions = await RmuStorage.GetAllTokens(); records.Add(newRecordInput); newRecordInput = new(); } @@ -156,4 +177,27 @@ internal Exception? Exception { get; set; } } + private async Task AddWithAccessToken(LoginStruct record) { + try { + var session = await HsProvider.GetAuthenticatedWithToken(record.Homeserver, record.Password, record.Proxy); + if (session == null) { + Console.WriteLine($"Failed to login to {record.Homeserver} as {record.Username}!"); + return; + } + + await RmuStorage.AddToken(new UserAuth() { + UserId = session.WhoAmI.UserId, + AccessToken = session.AccessToken, + Proxy = record.Proxy, + DeviceId = session.WhoAmI.DeviceId + }); + LoggedInSessions = await RmuStorage.GetAllTokens(); + } + catch (Exception e) { + Console.WriteLine($"Failed to login to {record.Homeserver} as {record.Username}!"); + Console.WriteLine(e); + record.Exception = e; + } + } + } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor b/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor index 9218c8c..9bb20f0 100644 --- a/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor +++ b/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor @@ -1,7 +1,7 @@ @page "/Moderation/UserRoomHistory/{UserId}" -@using LibMatrix.EventTypes.Spec.State @using LibMatrix.RoomTypes @using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using MatrixUtils.Abstractions <h3>UserRoomHistory</h3> @@ -44,11 +44,11 @@ else { private AuthenticatedHomeserverGeneric? currentHs { get; set; } protected override async Task OnInitializedAsync() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - var sessions = await RMUStorage.GetAllTokens(); + var sessions = await RmuStorage.GetAllTokens(); foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); + var session = await RmuStorage.GetSession(userAuth); if (session is not null) { hss.Add(session); StateHasChanged(); diff --git a/MatrixUtils.Web/Pages/Rooms/Create.razor b/MatrixUtils.Web/Pages/Rooms/Create.razor index f2dfb01..a36ccf8 100644 --- a/MatrixUtils.Web/Pages/Rooms/Create.razor +++ b/MatrixUtils.Web/Pages/Rooms/Create.razor @@ -3,11 +3,9 @@ @using System.Reflection @using ArcaneLibs.Extensions @using LibMatrix -@using LibMatrix.EventTypes.Spec.State @using LibMatrix.EventTypes.Spec.State.RoomInfo @using LibMatrix.Responses @using MatrixUtils.Web.Classes.RoomCreationTemplates -@using Microsoft.AspNetCore.Components.Forms @* @* ReSharper disable once RedundantUsingDirective - Must not remove this, Rider marks this as "unused" when it's not */ *@ <h3>Room Manager - Create Room</h3> @@ -89,7 +87,7 @@ <tr> <td>Room icon:</td> <td> - <img src="@Homeserver.ResolveMediaUri(roomAvatarEvent.Url)" style="width: 128px; height: 128px; border-radius: 50%;"/> + @* <img src="@Homeserver.ResolveMediaUri(roomAvatarEvent.Url)" style="width: 128px; height: 128px; border-radius: 50%;"/> *@ <div style="display: inline-block; vertical-align: middle;"> <FancyTextBox @bind-Value="@roomAvatarEvent.Url"></FancyTextBox><br/> <InputFile OnChange="RoomIconFilePicked"></InputFile> @@ -134,7 +132,7 @@ } else { <details> - <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerACLEventContent).Allow.Count) allow rules</summary> + <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerAclEventContent).Allow.Count) allow rules</summary> @* <StringListEditor @bind-Items="@serverAcl.Allow"></StringListEditor> *@ </details> } @@ -144,7 +142,7 @@ } else { <details> - <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerACLEventContent).Deny.Count) deny rules</summary> + <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerAclEventContent).Deny.Count) deny rules</summary> @* <StringListEditor @bind-Items="@serverAcl.Allow"></StringListEditor> *@ </details> } @@ -256,11 +254,11 @@ private RoomHistoryVisibilityEventContent? historyVisibility => creationEvent?["m.room.history_visibility"].TypedContent as RoomHistoryVisibilityEventContent; private RoomGuestAccessEventContent? guestAccessEvent => creationEvent?["m.room.guest_access"].TypedContent as RoomGuestAccessEventContent; - private RoomServerACLEventContent? serverAcl => creationEvent?["m.room.server_acls"].TypedContent as RoomServerACLEventContent; + private RoomServerAclEventContent? serverAcl => creationEvent?["m.room.server_acls"].TypedContent as RoomServerAclEventContent; private RoomAvatarEventContent? roomAvatarEvent => creationEvent?["m.room.avatar"].TypedContent as RoomAvatarEventContent; protected override async Task OnInitializedAsync() { - Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + Homeserver = await RmuStorage.GetCurrentSessionOrNavigate(); if (Homeserver is null) return; foreach (var x in Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsClass && !x.IsAbstract && x.GetInterfaces().Contains(typeof(IRoomCreationTemplate))).ToList()) { diff --git a/MatrixUtils.Web/Pages/Rooms/Index.razor b/MatrixUtils.Web/Pages/Rooms/Index.razor index 28c4de2..5dd8189 100644 --- a/MatrixUtils.Web/Pages/Rooms/Index.razor +++ b/MatrixUtils.Web/Pages/Rooms/Index.razor @@ -68,7 +68,7 @@ // SyncHelper profileSyncHelper; protected override async Task OnInitializedAsync() { - Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + Homeserver = await RmuStorage.GetCurrentSessionOrNavigate(); if (Homeserver is null) return; // var rooms = await Homeserver.GetJoinedRooms(); // SemaphoreSlim _semaphore = new(160, 160); @@ -170,7 +170,7 @@ } } - private bool RenderContents { get; set; } = false; + private bool RenderContents { get; set; } private string _status; diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor index b7ebae2..94113dd 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor @@ -1,7 +1,6 @@ @page "/Rooms/{RoomId}/Policies" @using LibMatrix @using ArcaneLibs.Extensions -@using LibMatrix.EventTypes.Spec.State @using LibMatrix.EventTypes.Spec.State.Policy @using System.Diagnostics @using LibMatrix.RoomTypes @@ -9,13 +8,25 @@ @using System.Reflection @using ArcaneLibs.Attributes @using LibMatrix.EventTypes +@using LibMatrix.EventTypes.Common +@using LibMatrix.EventTypes.Interop.Draupnir +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using MatrixUtils.Web.Shared.PolicyEditorComponents +@using SpawnDev.BlazorJS.WebWorkers +@inject WebWorkerService WebWorkerService -<h3>Policy list editor - Editing @RoomId</h3> +<h3>Policy list editor - Editing @(RoomName ?? RoomId)</h3> +@if (!string.IsNullOrWhiteSpace(DraupnirShortcode)) { + <span style="margin-right: 2em;">Shortcode: @DraupnirShortcode</span> +} +@if (!string.IsNullOrWhiteSpace(RoomAlias)) { + <span>Alias: @RoomAlias</span> +} <hr/> @* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@ <LinkButton OnClick="@(() => { CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; return Task.CompletedTask; })">Create new policy</LinkButton> +<LinkButton OnClick="@(() => { MassCreatePolicies = true; return Task.CompletedTask; })">Create many new policies</LinkButton> @if (Loading) { <p>Loading...</p> @@ -24,6 +35,8 @@ else if (PolicyEventsByType is not { Count: > 0 }) { <p>No policies yet</p> } else { + var renderSw = Stopwatch.StartNew(); + var renderTotalSw = Stopwatch.StartNew(); @foreach (var (type, value) in PolicyEventsByType) { <p> @(GetValidPolicyEventsByType(type).Count) active, @@ -33,6 +46,8 @@ else { </p> } + Console.WriteLine($"Rendered hearder in {renderSw.GetElapsedAndRestart()}"); + @foreach (var type in KnownPolicyTypes.OrderByDescending(t => GetPolicyEventsByType(t).Count)) { <details> <summary> @@ -41,7 +56,7 @@ else { </span> <hr style="margin: revert;"/> </summary> - <table class="table table-striped table-hover" style="width: fit-content; border-width: 1px; vertical-align: middle;"> + <table class="table table-striped table-hover"> @{ var policies = GetValidPolicyEventsByType(type); var invalidPolicies = GetInvalidPolicyEventsByType(type); @@ -51,13 +66,18 @@ else { .Where(x => x.GetCustomAttribute<TableHideAttribute>() is null) .ToFrozenSet(); var propNames = props.Select(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName()!).ToFrozenSet(); + + var proxySafeProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => props.Any(y => y.Name == x.Name)) + .ToFrozenSet(); + Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}"); } <thead> <tr> @foreach (var name in propNames) { - <th style="border-width: 1px">@name</th> + <th>@name</th> } - <th style="border-width: 1px">Actions</th> + <th>Actions</th> </tr> </thead> <tbody style="border-width: 1px;"> @@ -65,10 +85,6 @@ else { <tr> @{ var typedContent = policy.TypedContent!; - var proxySafeProps = typedContent.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(x => props.Any(y => y.Name == x.Name)) - .ToFrozenSet(); - Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}"); } @foreach (var prop in proxySafeProps ?? Enumerable.Empty<PropertyInfo>()) { <td>@prop.GetGetMethod()?.Invoke(typedContent, null)</td> @@ -81,6 +97,16 @@ else { @if (policy.IsLegacyType) { <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton> } + + @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.Type)) { + <LinkButton OnClick="@(() => { ServerPolicyToMakePermanent = policy; return Task.CompletedTask; })">Make permanent</LinkButton> + @if (CurrentUserIsDraupnir) { + <LinkButton Color="@(ActiveKicks.ContainsKey(policy) ? "#FF0000" : null)" OnClick="@(() => DraupnirKickMatching(policy))">Kick users @(ActiveKicks.ContainsKey(policy) ? $"({ActiveKicks[policy]})" : null)</LinkButton> + } + } + } + else { + <p>No permission to modify</p> } </div> </td> @@ -94,11 +120,11 @@ else { @("Invalid " + GetPolicyTypeName(type).ToLower()) </u> </summary> - <table class="table table-striped table-hover" style="width: fit-content; border-width: 1px; vertical-align: middle;"> + <table class="table table-striped table-hover"> <thead> <tr> - <th style="border-width: 1px">State key</th> - <th style="border-width: 1px">Json contents</th> + <th>State key</th> + <th>Json contents</th> </tr> </thead> <tbody> @@ -115,12 +141,25 @@ else { </details> </details> } + + Console.WriteLine($"Rendered policies in {renderSw.GetElapsedAndRestart()}"); + Console.WriteLine($"Rendered in {renderTotalSw.Elapsed}"); } @if (CurrentlyEditingEvent is not null) { <PolicyEditorModal PolicyEvent="@CurrentlyEditingEvent" OnClose="@(() => CurrentlyEditingEvent = null)" OnSave="@(e => UpdatePolicyAsync(e))"></PolicyEditorModal> } +@if (ServerPolicyToMakePermanent is not null) { + <ModalWindow Title="Make policy permanent"> + + </ModalWindow> +} + +@if (MassCreatePolicies) { + <MassPolicyEditorModal Room="@Room" OnClose="@(() => MassCreatePolicies = false)" OnSaved="@(() => { MassCreatePolicies = false; LoadStatesAsync(); })"></MassPolicyEditorModal> +} + @code { #if DEBUG @@ -130,21 +169,15 @@ else { #endif private bool Loading { get; set; } = true; - //get room list - // - sync withroom list filter - // Type = support.feline.msc3784 - //support.feline.policy.lists.msc.v1 [Parameter] public string RoomId { get; set; } = null!; private bool _enableAvatars; private StateEventResponse? _currentlyEditingEvent; + private bool _massCreatePolicies; + private StateEventResponse? _serverPolicyToMakePermanent; - // static readonly Dictionary<string, string?> Avatars = new(); - // static readonly Dictionary<string, RemoteHomeserver> Servers = new(); - - // private static List<StateEventResponse> PolicyEvents { get; set; } = new(); private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new(); private StateEventResponse? CurrentlyEditingEvent { @@ -155,25 +188,44 @@ else { } } - // public bool EnableAvatars { - // get => _enableAvatars; - // set { - // _enableAvatars = value; - // if (value) GetAllAvatars(); - // } - // } + public StateEventResponse? ServerPolicyToMakePermanent { + get => _serverPolicyToMakePermanent; + set { + _serverPolicyToMakePermanent = value; + StateHasChanged(); + } + } private AuthenticatedHomeserverGeneric Homeserver { get; set; } private GenericRoom Room { get; set; } private RoomPowerLevelEventContent PowerLevels { get; set; } + public bool CurrentUserIsDraupnir { get; set; } + public string? RoomName { get; set; } + public string? RoomAlias { get; set; } + public string? DraupnirShortcode { get; set; } + public Dictionary<StateEventResponse, int> ActiveKicks { get; set; } = []; + + public bool MassCreatePolicies { + get => _massCreatePolicies; + set { + _massCreatePolicies = value; + StateHasChanged(); + } + } protected override async Task OnInitializedAsync() { var sw = Stopwatch.StartNew(); await base.OnInitializedAsync(); - Homeserver = (await RMUStorage.GetCurrentSessionOrNavigate())!; + Homeserver = (await RmuStorage.GetCurrentSessionOrNavigate())!; if (Homeserver is null) return; Room = Homeserver.GetRoom(RoomId!); - PowerLevels = (await Room.GetPowerLevelsAsync())!; + await Task.WhenAll([ + Task.Run(async () => { PowerLevels = (await Room.GetPowerLevelsAsync())!; }), + Task.Run(async () => { DraupnirShortcode = (await Room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode; }), + Task.Run(async () => { RoomAlias = (await Room.GetCanonicalAliasAsync())?.Alias; }), + Task.Run(async () => { RoomName = await Room.GetNameOrFallbackAsync(); }), + Task.Run(async () => { CurrentUserIsDraupnir = (await Homeserver.GetAccountDataOrNullAsync<object>("org.matrix.mjolnir.protected_rooms")) is not null; }), + ]); await LoadStatesAsync(); Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!"); } @@ -193,48 +245,13 @@ else { StateHasChanged(); } - // private async Task GetAllAvatars() { - // // if (!_enableAvatars) return; - // Console.WriteLine("Getting avatars..."); - // var users = GetValidPolicyEventsByType(typeof(UserPolicyRuleEventContent)).Select(x => x.RawContent!["entity"]!.GetValue<string>()).Where(x => x.Contains(':') && !x.Contains("*")).ToList(); - // Console.WriteLine($"Got {users.Count} users!"); - // var usersByHomeServer = users.GroupBy(x => x!.Split(':')[1]).ToDictionary(x => x.Key!, x => x.ToList()); - // Console.WriteLine($"Got {usersByHomeServer.Count} homeservers!"); - // var homeserverTasks = usersByHomeServer.Keys.Select(x => RemoteHomeserver.TryCreate(x)).ToAsyncEnumerable(); - // await foreach (var server in homeserverTasks) { - // if (server is null) continue; - // var profileTasks = usersByHomeServer[server.BaseUrl].Select(x => TryGetProfile(server, x)).ToList(); - // await Task.WhenAll(profileTasks); - // profileTasks.RemoveAll(x => x.Result is not { Value: { AvatarUrl: not null } }); - // foreach (var profile in profileTasks.Select(x => x.Result!.Value)) { - // // if (profile is null) continue; - // if (!string.IsNullOrWhiteSpace(profile.Value.AvatarUrl)) { - // var url = await hsResolver.ResolveMediaUri(server.BaseUrl, profile.Value.AvatarUrl); - // Avatars.TryAdd(profile.Key, url); - // } - // else Avatars.TryAdd(profile.Key, null); - // } - // - // StateHasChanged(); - // } - // } - // - // private async Task<KeyValuePair<string, UserProfileResponse>?> TryGetProfile(RemoteHomeserver server, string mxid) { - // try { - // return new KeyValuePair<string, UserProfileResponse>(mxid, await server.GetProfileAsync(mxid)); - // } - // catch { - // return null; - // } - // } - private List<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) - .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList(); + .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList(); private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) - .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList(); + .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList(); private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull() ?? type.GetCustomAttributes<MatrixEventAttribute>() @@ -265,4 +282,139 @@ else { private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); + private static Dictionary<Type, string[]> PolicyTypeIds = KnownPolicyTypes + .ToDictionary(x => x, x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName).ToArray()); + +#region Draupnir interop + + private SemaphoreSlim ss = new(16, 16); + + private async Task DraupnirKickMatching(StateEventResponse policy) { + try { + var content = policy.TypedContent! as PolicyRuleEventContent; + if (content is null) return; + if (string.IsNullOrWhiteSpace(content.Entity)) return; + + var data = await Homeserver.GetAccountDataAsync<DraupnirProtectedRoomsData>(DraupnirProtectedRoomsData.EventId); + var rooms = data.Rooms.Select(Homeserver.GetRoom).ToList(); + + ActiveKicks.Add(policy, rooms.Count); + StateHasChanged(); + await Task.Delay(500); + + // for (int i = 0; i < 12; i++) { + // _ = WebWorkerService.TaskPool.Invoke(WasteCpu); + // } + + // static async Task runKicks(string roomId, PolicyRuleEventContent content) { + // Console.WriteLine($"Checking {roomId}..."); + // // Console.WriteLine($"Checking {room.RoomId}..."); + // // + // // try { + // // var members = await room.GetMembersListAsync(); + // // foreach (var member in members) { + // // var membership = member.ContentAs<RoomMemberEventContent>(); + // // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue; + // // if (membership?.Membership is "leave" or "ban") continue; + // // + // // if (content.EntityMatches(member.StateKey!)) + // // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given"); + // // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)"); + // // } + // // } + // // finally { + // // Console.WriteLine($"Finished checking {room.RoomId}..."); + // // } + // } + // + // try { + // var tasks = rooms.Select(room => WebWorkerService.TaskPool.Invoke(runKicks, room.RoomId, content)).ToList(); + // + // await Task.WhenAll(tasks); + // } + // catch (Exception e) { + // Console.WriteLine(e); + // } + + await NastyInternalsPleaseIgnore.ExecuteKickWithWasmWorkers(WebWorkerService, Homeserver, policy, data.Rooms); + // await Task.Run(async () => { + // foreach (var room in rooms) { + // try { + // Console.WriteLine($"Checking {room.RoomId}..."); + // var members = await room.GetMembersListAsync(); + // foreach (var member in members) { + // var membership = member.ContentAs<RoomMemberEventContent>(); + // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue; + // if (membership?.Membership is "leave" or "ban") continue; + // + // if (content.EntityMatches(member.StateKey!)) + // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given"); + // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)"); + // } + // ActiveKicks[policy]--; + // StateHasChanged(); + // } + // finally { + // Console.WriteLine($"Finished checking {room.RoomId}..."); + // } + // } + // }); + } + finally { + ActiveKicks.Remove(policy); + StateHasChanged(); + await Task.Delay(500); + } + } + +#region Nasty, nasty internals, please ignore! + + private static class NastyInternalsPleaseIgnore { + public static async Task ExecuteKickWithWasmWorkers(WebWorkerService workerService, AuthenticatedHomeserverGeneric hs, StateEventResponse evt, List<string> roomIds) { + try { + // var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal, hs.WellKnownUris.Client, hs.AccessToken, roomId, content.Entity)).ToList(); + var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal2, hs.WellKnownUris, hs.AccessToken, roomId, evt)).ToList(); + // workerService.TaskPool.Invoke(ExecuteKickInternal, hs.BaseUrl, hs.AccessToken, roomIds, content.Entity); + await Task.WhenAll(tasks); + } + catch (Exception e) { + Console.WriteLine(e); + } + } + + private static async Task ExecuteKickInternal(string homeserverBaseUrl, string accessToken, string roomId, string entity) { + try { + Console.WriteLine("args: " + string.Join(", ", homeserverBaseUrl, accessToken, roomId, entity)); + Console.WriteLine($"Checking {roomId}..."); + var hs = new AuthenticatedHomeserverGeneric(homeserverBaseUrl, new() { Client = homeserverBaseUrl }, null, accessToken); + Console.WriteLine($"Got HS..."); + var room = hs.GetRoom(roomId); + Console.WriteLine($"Got room..."); + var members = await room.GetMembersListAsync(); + Console.WriteLine($"Got members..."); + // foreach (var member in members) { + // var membership = member.ContentAs<RoomMemberEventContent>(); + // if (member.StateKey == hs.WhoAmI.UserId) continue; + // if (membership?.Membership is "leave" or "ban") continue; + // + // if (entity == member.StateKey) + // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given"); + // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)"); + // } + } + catch (Exception e) { + Console.WriteLine(e); + } + } + + private async static Task ExecuteKickInternal2(HomeserverResolverService.WellKnownUris wellKnownUris, string accessToken, string roomId, StateEventResponse policy) { + Console.WriteLine($"Checking {roomId}..."); + Console.WriteLine(policy.EventId); + } + } + +#endregion + +#endregion + } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css new file mode 100644 index 0000000..afe9fb0 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css @@ -0,0 +1,9 @@ +th { + border-width: 1px; +} + +table { + width: fit-content; + border-width: 1px; + vertical-align: middle; +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor new file mode 100644 index 0000000..50f304a --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor @@ -0,0 +1,240 @@ +@page "/Rooms/{RoomId}/Policies2" +@using LibMatrix +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State.Policy +@using System.Diagnostics +@using LibMatrix.RoomTypes +@using System.Collections.Frozen +@using System.Reflection +@using ArcaneLibs.Attributes +@using LibMatrix.EventTypes +@using LibMatrix.EventTypes.Spec.State.RoomInfo + +@using MatrixUtils.Web.Shared.PolicyEditorComponents + +<h3>Policy list editor - Editing @RoomId</h3> +<hr/> +@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@ +<LinkButton OnClick="@(() => { CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; return Task.CompletedTask; })">Create new policy</LinkButton> + +@if (Loading) { + <p>Loading...</p> +} +else if (PolicyEventsByType is not { Count: > 0 }) { + <p>No policies yet</p> +} +else { + var renderSw = Stopwatch.StartNew(); + var renderTotalSw = Stopwatch.StartNew(); + @foreach (var (type, value) in PolicyEventsByType) { + <p> + @(GetValidPolicyEventsByType(type).Count) active, + @(GetInvalidPolicyEventsByType(type).Count) invalid + (@value.Count total) + @(GetPolicyTypeName(type).ToLower()) + </p> + } + + Console.WriteLine($"Rendered hearder in {renderSw.GetElapsedAndRestart()}"); + + @foreach (var type in KnownPolicyTypes.OrderByDescending(t => GetPolicyEventsByType(t).Count)) { + <details> + <summary> + <span> + @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies") + </span> + <hr style="margin: revert;"/> + </summary> + <div class="flex-grid"> + @{ + var policies = GetValidPolicyEventsByType(type); + var invalidPolicies = GetInvalidPolicyEventsByType(type); + // enumerate all properties with friendly name + var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => (x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyNameOrNull()) is not null) + .Where(x => x.GetCustomAttribute<TableHideAttribute>() is null) + .ToFrozenSet(); + var propNames = props.Select(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName()!).ToFrozenSet(); + + var proxySafeProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => props.Any(y => y.Name == x.Name)) + .ToFrozenSet(); + Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}"); + } + @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue<string>())) { + <div class="flex-item"> + @{ + var typedContent = policy.TypedContent!; + } + @foreach (var prop in proxySafeProps ?? Enumerable.Empty<PropertyInfo>()) { + <td>@prop.GetGetMethod()?.Invoke(typedContent, null)</td> + } + <div style="display: ruby;"> + @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) { + <LinkButton OnClick="@(() => { CurrentlyEditingEvent = policy; return Task.CompletedTask; })">Edit</LinkButton> + <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Remove</LinkButton> + @if (policy.IsLegacyType) { + <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton> + } + + @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.EventId)) { + <LinkButton OnClick="@(() => { ServerPolicyToMakePermanent = policy; return Task.CompletedTask; })">Make permanent (wildcard)</LinkButton> + @if (CurrentUserIsDraupnir) { + <LinkButton OnClick="@(() => UpgradePolicyAsync(policy))">Kick matching users</LinkButton> + } + } + else { + <p>meow</p> + } + } + else { + <p>No permission to modify</p> + } + </div> + </div> + } + </div> + <details> + <summary> + <u> + @("Invalid " + GetPolicyTypeName(type).ToLower()) + </u> + </summary> + <table class="table table-striped table-hover"> + <thead> + <tr> + <th>State key</th> + <th>Json contents</th> + </tr> + </thead> + <tbody> + @foreach (var policy in invalidPolicies) { + <tr> + <td>@policy.StateKey</td> + <td> + <pre>@policy.RawContent.ToJson(true, false)</pre> + </td> + </tr> + } + </tbody> + </table> + </details> + </details> + } + + Console.WriteLine($"Rendered policies in {renderSw.GetElapsedAndRestart()}"); + Console.WriteLine($"Rendered in {renderTotalSw.Elapsed}"); +} + +@if (CurrentlyEditingEvent is not null) { + <PolicyEditorModal PolicyEvent="@CurrentlyEditingEvent" OnClose="@(() => CurrentlyEditingEvent = null)" OnSave="@(e => UpdatePolicyAsync(e))"></PolicyEditorModal> +} + +@code { + +#if DEBUG + private const bool Debug = true; +#else + private const bool Debug = false; +#endif + + private bool Loading { get; set; } = true; + + [Parameter] + public string RoomId { get; set; } = null!; + + private bool _enableAvatars; + private StateEventResponse? _currentlyEditingEvent; + private StateEventResponse? _serverPolicyToMakePermanent; + + private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new(); + + private StateEventResponse? CurrentlyEditingEvent { + get => _currentlyEditingEvent; + set { + _currentlyEditingEvent = value; + StateHasChanged(); + } + } + + private StateEventResponse? ServerPolicyToMakePermanent { + get => _serverPolicyToMakePermanent; + set { + _serverPolicyToMakePermanent = value; + StateHasChanged(); + } + } + + private AuthenticatedHomeserverGeneric Homeserver { get; set; } + private GenericRoom Room { get; set; } + private RoomPowerLevelEventContent PowerLevels { get; set; } + private bool CurrentUserIsDraupnir { get; set; } + + protected override async Task OnInitializedAsync() { + var sw = Stopwatch.StartNew(); + await base.OnInitializedAsync(); + Homeserver = (await RmuStorage.GetCurrentSessionOrNavigate())!; + if (Homeserver is null) return; + Room = Homeserver.GetRoom(RoomId!); + PowerLevels = (await Room.GetPowerLevelsAsync())!; + CurrentUserIsDraupnir = (await Homeserver.GetAccountDataOrNullAsync<object>("org.matrix.mjolnir.protected_rooms")) is not null; + await LoadStatesAsync(); + Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!"); + } + + private async Task LoadStatesAsync() { + Loading = true; + var states = Room.GetFullStateAsync(); + PolicyEventsByType.Clear(); + await foreach (var state in states) { + if (state is null) continue; + if (!state.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; + if (!PolicyEventsByType.ContainsKey(state.MappedType)) PolicyEventsByType.Add(state.MappedType, new()); + PolicyEventsByType[state.MappedType].Add(state); + } + + Loading = false; + StateHasChanged(); + } + + private List<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; + + private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) + .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList(); + + private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) + .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList(); + + private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull() + ?? type.GetCustomAttributes<MatrixEventAttribute>() + .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.EventName))?.EventName; + + private string GetPolicyTypeName(Type type) => GetPolicyTypeNameOrNull(type) ?? type.Name; + + private async Task RemovePolicyAsync(StateEventResponse policyEvent) { + await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey.UrlEncode(), new { }); + PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent); + await LoadStatesAsync(); + } + + private async Task UpdatePolicyAsync(StateEventResponse policyEvent) { + await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey.UrlEncode(), policyEvent.RawContent); + CurrentlyEditingEvent = null; + await LoadStatesAsync(); + } + + private async Task UpgradePolicyAsync(StateEventResponse policyEvent) { + policyEvent.RawContent["upgraded_from_type"] = policyEvent.Type; + await LoadStatesAsync(); + } + + private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); + + // event types, unnamed + private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes + .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); + + private static Dictionary<Type, string[]> PolicyTypeIds = KnownPolicyTypes + .ToDictionary(x => x, x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName).ToArray()); + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor.css b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor.css new file mode 100644 index 0000000..d224737 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor.css @@ -0,0 +1,32 @@ +th { + border-width: 1px; +} + +table { + width: fit-content; + border-width: 1px; + vertical-align: middle; +} + +.flex-grid { + display: grid; + /*grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));*/ + /*// fit based on content max width*/ + grid-template-columns: repeat(auto-fill, minmax(min-content, 1fr)); + + gap: 10px; +} + +.flex-item { + /*flex: 1 1 30%;*/ + /*margin: 0.25rem;*/ + /*position: relative;*/ + /*display: flex;*/ + /*flex-direction: column;*/ + min-width: 0; + word-wrap: break-word; + background-color: #fff1; + background-clip: border-box; + border: 1px solid rgba(0, 0, 0, .125); + border-radius: .5rem +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor new file mode 100644 index 0000000..4f06822 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor @@ -0,0 +1,176 @@ +@page "/PolicyLists" +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.EventTypes +@using LibMatrix.EventTypes.Common +@using LibMatrix.EventTypes.Spec.State.Policy +@using LibMatrix.RoomTypes +@inject ILogger<Index> logger +<h3>Policy lists </h3> @* <LinkButton href="/Rooms/Create">Create new policy list</LinkButton> *@ + +@if (!string.IsNullOrWhiteSpace(Status)) { + <p>@Status</p> +} +@if (!string.IsNullOrWhiteSpace(Status2)) { + <p>@Status2</p> +} +<hr/> + +<table> + <thead> + <tr> + <th/> + <th>Room name</th> + <th>Policies</th> + </tr> + </thead> + <tbody> + @foreach (var room in Rooms.OrderByDescending(x => x.PolicyCounts.Sum(y => y.Value))) { + <tr> + <td> + <LinkButton href="@($"/Rooms/{room.Room.RoomId}/Policies")"> + <span class="oi oi-pencil" aria-hidden="true"></span> + </LinkButton> + </td> + <td style="padding-right: 24px;"> + <span>@room.RoomName</span> + @if (room.IsLegacy) { + <span style="color: red;"> (legacy)</span> + } + <br/> + @if (!string.IsNullOrWhiteSpace(room.Shortcode)) { + <span style="font-size: 0.8em;">@room.Shortcode</span> + } + else { + <span style="color: red;">(no shortcode)</span> + } + </td> + <td> + <span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.User) ?? 0) user policies</span><br/> + <span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Server) ?? 0) server policies</span><br/> + <span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Room) ?? 0) room policies</span><br/> + </td> + </tr> + } + </tbody> +</table> + +@code { + + private List<RoomInfo> Rooms { get; } = []; + + private AuthenticatedHomeserverGeneric? Homeserver { get; set; } + + protected override async Task OnInitializedAsync() { + Homeserver = await RmuStorage.GetCurrentSessionOrNavigate(); + if (Homeserver is null) return; + + Status = "Fetching rooms..."; + + var userEventTypes = EventContent.GetMatchingEventTypes<UserPolicyRuleEventContent>(); + var serverEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>(); + var roomEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>(); + var knownPolicyTypes = (List<string>) [..userEventTypes, ..serverEventTypes, ..roomEventTypes]; + + List<GenericRoom> roomsByType = []; + await foreach (var room in Homeserver.GetJoinedRoomsByType("support.feline.policy.lists.msc.v1")) { + roomsByType.Add(room); + Status2 = $"Found {room.RoomId} (MSC3784)..."; + } + + List<Task<RoomInfo>> tasks = roomsByType.Select(async room => { + Status2 = $"Fetching room {room.RoomId}..."; + return await RoomInfo.FromRoom(room); + }).ToList(); + + var results = tasks.ToAsyncEnumerable(); + await foreach (var result in results) { + Rooms.Add(result); + StateHasChanged(); + } + + Status = "Searching for legacy lists..."; + + var rooms = (await Homeserver.GetJoinedRooms()) + .Where(x => !Rooms.Any(y => y.Room.RoomId == x.RoomId)) + .Select(async room => { + var state = await room.GetFullStateAsListAsync(); + var policies = state + .Where(x => knownPolicyTypes.Contains(x.Type)) + .ToList(); + if (policies.Count == 0) return null; + Status2 = $"Found legacy list {room.RoomId}..."; + return await RoomInfo.FromRoom(room, state, true); + }) + .ToAsyncEnumerable(); + + await foreach (var room in rooms) { + if (room is not null) { + Rooms.Add(room); + StateHasChanged(); + } + } + + Status = ""; + Status2 = ""; + await base.OnInitializedAsync(); + } + + private string _status; + + public string Status { + get => _status; + set { + _status = value; + StateHasChanged(); + } + } + + private string _status2; + + public string Status2 { + get => _status2; + set { + _status2 = value; + StateHasChanged(); + } + } + + private class RoomInfo { + public GenericRoom Room { get; set; } + public string RoomName { get; set; } + public string? Shortcode { get; set; } + public Dictionary<PolicyType, int?> PolicyCounts { get; set; } + public bool IsLegacy { get; set; } + + public enum PolicyType { + User, + Room, + Server + } + + private static readonly List<string> userEventTypes = EventContent.GetMatchingEventTypes<UserPolicyRuleEventContent>(); + private static readonly List<string> serverEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>(); + private static readonly List<string> roomEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>(); + private static readonly List<string> allKnownPolicyTypes = [..userEventTypes, ..serverEventTypes, ..roomEventTypes]; + + public static async Task<RoomInfo> FromRoom(GenericRoom room, List<StateEventResponse>? state = null, bool legacy = false) { + state ??= await room.GetFullStateAsListAsync(); + return new RoomInfo() { + Room = room, + IsLegacy = legacy, + RoomName = await room.GetNameAsync() + ?? (await room.GetCanonicalAliasAsync())?.Alias + ?? (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode + ?? room.RoomId, + Shortcode = (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode, + PolicyCounts = new() { + { PolicyType.User, state.Count(x => userEventTypes.Contains(x.Type)) }, + { PolicyType.Server, state.Count(x => serverEventTypes.Contains(x.Type)) }, + { PolicyType.Room, state.Count(x => roomEventTypes.Contains(x.Type)) } + } + }; + } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css new file mode 100644 index 0000000..f9b5b3f --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css @@ -0,0 +1,6 @@ +table, th, td { + border-width: 1px; +} +td { + padding: 8px; +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Space.razor b/MatrixUtils.Web/Pages/Rooms/Space.razor index 01ab1c4..e3bb4e4 100644 --- a/MatrixUtils.Web/Pages/Rooms/Space.razor +++ b/MatrixUtils.Web/Pages/Rooms/Space.razor @@ -2,11 +2,15 @@ @using LibMatrix.RoomTypes @using ArcaneLibs.Extensions @using LibMatrix +@using MatrixUtils.Abstractions <h3>Room manager - Viewing Space</h3> +<span>Add new room to space: </span> +<FancyTextBox @bind-Value="@NewRoomId"></FancyTextBox> +<button onclick="@AddNewRoom">Add</button> <button onclick="@JoinAllRooms">Join all rooms</button> @foreach (var room in Rooms) { - <RoomListItem Room="room" ShowOwnProfile="true"></RoomListItem> + <RoomListItem RoomInfo="room" ShowOwnProfile="true"></RoomListItem> } @@ -27,11 +31,12 @@ private GenericRoom? Room { get; set; } private StateEventResponse[] States { get; set; } = Array.Empty<StateEventResponse>(); - private List<GenericRoom> Rooms { get; } = new(); + private List<RoomInfo> Rooms { get; } = new(); private List<string> ServersInSpace { get; } = new(); + private string? NewRoomId { get; set; } protected override async Task OnInitializedAsync() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; Room = hs.GetRoom(RoomId.Replace('~', '.')); @@ -43,7 +48,18 @@ var roomId = stateEvent.StateKey; var room = hs.GetRoom(roomId); if (room is not null) { - Rooms.Add(room); + Task.Run(async () => { + try { + Rooms.Add(new(Room, await room.GetFullStateAsListAsync())); + } + catch (MatrixException e) { + if (e is { ErrorCode: MatrixException.ErrorCodes.M_FORBIDDEN }) { + Rooms.Add(new(Room) { + RoomName = "M_FORBIDDEN" + }); + } + } + }); } break; } @@ -96,8 +112,37 @@ // List<Task<RoomIdResponse>> tasks = Rooms.Select(room => room.JoinAsync(ServersInSpace.ToArray())).ToList(); // await Task.WhenAll(tasks); foreach (var room in Rooms) { + await JoinRecursive(room.Room.RoomId); + } + } + + private async Task JoinRecursive(string roomId) { + var room = Room!.Homeserver.GetRoom(roomId); + if (room is null) return; + try { await room.JoinAsync(ServersInSpace.ToArray()); + var joined = false; + while (!joined) { + var ce = await room.GetCreateEventAsync(); + if(ce is null) continue; + if (ce.Type == "m.space") { + var children = room.AsSpace.GetChildrenAsync(false); + await foreach (var child in children) { + JoinRecursive(child.RoomId); + } + } + joined = true; + } } + catch (Exception e) { + Console.WriteLine(e); + } + + } + + private async Task AddNewRoom() { + if (string.IsNullOrWhiteSpace(NewRoomId)) return; + await Room.AsSpace.AddChildByIdAsync(NewRoomId); } } diff --git a/MatrixUtils.Web/Pages/Rooms/StateEditor.razor b/MatrixUtils.Web/Pages/Rooms/StateEditor.razor index 6110b83..4d24d47 100644 --- a/MatrixUtils.Web/Pages/Rooms/StateEditor.razor +++ b/MatrixUtils.Web/Pages/Rooms/StateEditor.razor @@ -43,7 +43,7 @@ protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; RoomId = RoomId.Replace('~', '.'); await LoadStatesAsync(); @@ -53,7 +53,7 @@ private DateTime _lastUpdate = DateTime.Now; private async Task LoadStatesAsync() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); var StateLoaded = 0; var response = (hs.GetRoom(RoomId)).GetFullStateAsync(); diff --git a/MatrixUtils.Web/Pages/Rooms/StateViewer.razor b/MatrixUtils.Web/Pages/Rooms/StateViewer.razor index 7c31136..565d97f 100644 --- a/MatrixUtils.Web/Pages/Rooms/StateViewer.razor +++ b/MatrixUtils.Web/Pages/Rooms/StateViewer.razor @@ -70,7 +70,7 @@ protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; await LoadStatesAsync(); Console.WriteLine("Policy list editor initialized!"); @@ -80,7 +80,7 @@ private async Task LoadStatesAsync() { var StateLoaded = 0; - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; var response = (hs.GetRoom(RoomId)).GetFullStateAsync(); await foreach (var _ev in response) { diff --git a/MatrixUtils.Web/Pages/Rooms/Timeline.razor b/MatrixUtils.Web/Pages/Rooms/Timeline.razor index e6b1248..a064956 100644 --- a/MatrixUtils.Web/Pages/Rooms/Timeline.razor +++ b/MatrixUtils.Web/Pages/Rooms/Timeline.razor @@ -2,7 +2,7 @@ @using MatrixUtils.Web.Shared.TimelineComponents @using LibMatrix @using LibMatrix.EventTypes.Spec -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo <h3>RoomManagerTimeline</h3> <hr/> <p>Loaded @Events.Count events...</p> @@ -27,7 +27,7 @@ protected override async Task OnInitializedAsync() { Console.WriteLine("RoomId: " + RoomId); - Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + Homeserver = await RmuStorage.GetCurrentSessionOrNavigate(); if (Homeserver is null) return; var room = Homeserver.GetRoom(RoomId); MessagesResponse? msgs = null; diff --git a/MatrixUtils.Web/Pages/ServerInfo.razor b/MatrixUtils.Web/Pages/ServerInfo.razor index e6f1f16..8dd7907 100644 --- a/MatrixUtils.Web/Pages/ServerInfo.razor +++ b/MatrixUtils.Web/Pages/ServerInfo.razor @@ -78,7 +78,7 @@ protected override async Task OnParametersSetAsync() { if (Homeserver is not null) { - var rhs = await hsProvider.GetRemoteHomeserver(Homeserver); + var rhs = await HsProvider.GetRemoteHomeserver(Homeserver); ServerVersionResponse = await (rhs.FederationClient?.GetServerVersionAsync() ?? Task.FromResult<ServerVersionResponse?>(null)); ClientVersionsResponse = await rhs.GetClientVersionsAsync(); } diff --git a/MatrixUtils.Web/Pages/StreamTest.razor b/MatrixUtils.Web/Pages/StreamTest.razor new file mode 100644 index 0000000..4cec354 --- /dev/null +++ b/MatrixUtils.Web/Pages/StreamTest.razor @@ -0,0 +1,119 @@ +@page "/StreamTest" +@inject ILogger<Index> logger +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State.RoomInfo + +<PageTitle>StreamText</PageTitle> +@if (Homeserver is not null) { + <p>Got homeserver @Homeserver.BaseUrl</p> + + @* <img src="@ResolvedUri" @ref="imgElement"/> *@ + @* <StreamedImage Stream="@Stream"/> *@ + + <br/> + @foreach (var stream in Streams.OrderBy(x => x.GetHashCode())) { + <StreamedImage Stream="@stream" style="width: 12em; height: 12em; object-fit: cover;"/> + } +} + +@code +{ + private string? _resolvedUri; + + private AuthenticatedHomeserverGeneric? Homeserver { get; set; } + + private string? ResolvedUri { + get => _resolvedUri; + set { + _resolvedUri = value; + StateHasChanged(); + } + } + + ElementReference imgElement { get; set; } + public Stream? Stream { get; set; } + public List<Stream> Streams { get; set; } = new(); + + protected override async Task OnInitializedAsync() { + Homeserver = await RmuStorage.GetCurrentSessionOrNavigate(); + + //await InitOld(); + await Init2(); + + await base.OnInitializedAsync(); + } + + private async Task Init2() { + var roomState = await Homeserver.GetRoom("!dSMpkVKGgQHlgBDSpo:matrix.org").GetFullStateAsListAsync(); + var members = roomState.Where(x => x.Type == RoomMemberEventContent.EventId).ToList(); + Console.WriteLine($"Got {members.Count()} members"); + var ss = new SemaphoreSlim(1, 1); + foreach (var stateEventResponse in members) { + // Console.WriteLine(stateEventResponse.ToJson()); + var mc = stateEventResponse.TypedContent as RoomMemberEventContent; + if (!string.IsNullOrWhiteSpace(mc?.AvatarUrl)) { + var uri = mc.AvatarUrl[6..].Split('/'); + var url = $"/_matrix/media/v3/download/{uri[0]}/{uri[1]}"; + // Homeserver.GetMediaStreamAsync(mc?.AvatarUrl).ContinueWith(async x => { + // await ss.WaitAsync(); + // var stream = x.Result; + // Streams.Add(stream); + // StateHasChanged(); + await Task.Delay(100); + // ss.Release(); + // }); + try { + Homeserver.ClientHttpClient.GetStreamAsync(url).ContinueWith(async x => { + // await ss.WaitAsync(); + var stream = x.Result; + Streams.Add(stream); + StateHasChanged(); + // await Task.Delay(100); + // ss.Release(); + }); + } + catch (Exception e) { + Console.WriteLine(e); + } + } + } + } + + private async Task InitOld() { + // var value = "mxc://rory.gay/AcFYcSpVXhEwbejrPVQrRUqt"; + // var value = "mxc://rory.gay/oqfCjIUVTAObSQbnMFekQvYR"; + var value = "mxc://feline.support/LUslNRVIYfeyCdRElqkkumKP"; + var uri = value[6..].Split('/'); + var url = $"/_matrix/media/v3/download/{uri[0]}/{uri[1]}"; + // var res = Homeserver.ClientHttpClient.GetAsync(url); + // var res2 = Homeserver.ClientHttpClient.GetAsync(url); + // var tasks = Enumerable.Range(1, 128) + // .Select(x => Homeserver.ClientHttpClient.GetStreamAsync(url+$"?width={x*128}&height={x*128}")) + // .ToAsyncEnumerable(); + await foreach (var result in GetStreamsDelayed(url)) { + Streams.Add(result); + // await Task.Delay(100); + StateHasChanged(); + } + + // var stream = await (await res).Content.ReadAsStreamAsync(); + // Stream = await (await res2).Content.ReadAsStreamAsync(); + StateHasChanged(); + + // await JSRuntime.streamImage(stream, imgElement); + } + + private async IAsyncEnumerable<Stream> GetStreamsDelayed(string url) { + for (int i = 0; i < 32; i++) { + var tasks = Enumerable.Range(1, 4) + .Select(x => Homeserver.ClientHttpClient.GetStreamAsync(url + $"?width={x * 128}&height={x * 128}&r={Random.Shared.Next(100000)}")) + .ToAsyncEnumerable(); + await foreach (var result in tasks) { + yield return result; + } + // var resp = await Homeserver.ClientHttpClient.GetAsync(url + $"?width={i * 128}&height={i * 128}"); + // yield return await resp.Content.ReadAsStreamAsync(); + // await Task.Delay(250); + } + } +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor index 841552e..8782dbe 100644 --- a/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor +++ b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor @@ -17,7 +17,7 @@ public string? RoomId { get; set; } protected override async Task OnInitializedAsync() { - hs = await RMUStorage.GetCurrentSessionOrNavigate(); + hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; Log.CollectionChanged += (sender, args) => StateHasChanged(); diff --git a/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor b/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor index 6e87926..dd8a801 100644 --- a/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor +++ b/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor @@ -92,7 +92,7 @@ lines.ToList().ForEach(async line => { await sem.WaitAsync(); try { - homeservers.Add((await hsResolver.ResolveHomeserverFromWellKnown(line)).Client); + homeservers.Add((await HsResolver.ResolveHomeserverFromWellKnown(line)).Client); StateHasChanged(); } catch (Exception e) { diff --git a/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor index 11d35f1..f21bac2 100644 --- a/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor +++ b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor @@ -39,7 +39,7 @@ private string newRoomId { get; set; } protected override async Task OnInitializedAsync() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; StateHasChanged(); @@ -48,7 +48,7 @@ } private async Task Execute() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; var oldRoom = hs.GetRoom(roomId); var newRoom = hs.GetRoom(newRoomId); @@ -90,7 +90,7 @@ private async Task TryFetchUsers() { try { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; var room = hs.GetRoom(roomId); var members = await room.GetMembersListAsync(); diff --git a/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor b/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor index 263879b..70ae27d 100644 --- a/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor +++ b/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor @@ -45,7 +45,7 @@ protected override async Task OnInitializedAsync() { Status = "Getting homeserver..."; - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; var syncHelper = new SyncHelper(hs) { diff --git a/MatrixUtils.Web/Pages/Tools/Index.razor b/MatrixUtils.Web/Pages/Tools/Index.razor index e68bb9a..f99e932 100644 --- a/MatrixUtils.Web/Pages/Tools/Index.razor +++ b/MatrixUtils.Web/Pages/Tools/Index.razor @@ -24,7 +24,7 @@ <a href="/Tools/Moderation/UserTrace">Trace user across rooms</a><br/> <a href="/tools/Moderation/MassCMEBan">Mass write policies to Community Moderation Effort</a><br/> <a href="/tools/Moderation/RoomIntersections">Find rooms with common users</a><br/> -<a href="/tools/Moderation/DraupnirProtectedRoomsEditor">Edit Draupnir protected rooms set</a><br/> +<a href="/tools/Moderation/Draupnir/ProtectedRoomsEditor">Draupnir: edit protected rooms set</a><br/> <h4 class="tool-category">Debugging tools</h4> diff --git a/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor b/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor index ddd7b15..296852a 100644 --- a/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor +++ b/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor @@ -1,45 +1,73 @@ -@page "/Tools/KnownHomeserverList" +@page "/Tools/Info/KnownHomeserverList" @using ArcaneLibs.Extensions +@using LibMatrix.RoomTypes +@using SpawnDev.BlazorJS.WebWorkers +@inject WebWorkerService workerService <h3>Known Homeserver List</h3> <hr/> @if (!IsFinished) { <p> - <b>Loading...</b> + <b>Loading... @RoomCount rooms remaining to process...</b> </p> } -@foreach (var (homeserver, members) in counts.OrderByDescending(x => x.Value)) { - <p>@homeserver - @members</p> +@{ + var shownCounts = counts.OrderByDescending(x => x.Value).AsEnumerable(); + if (!IsFinished && counts.Count > 500) { + shownCounts = shownCounts.Where(x => x.Value > 5); + } +} +@foreach (var (homeserver, members) in shownCounts.ToList()) { + <p>@homeserver - @members users</p> } <hr/> @code { Dictionary<string, List<string>> homeservers { get; set; } = new(); + Dictionary<string, int> counts { get; set; } = new(); + // List<HomeserverInfo> Homeservers = new(); bool IsFinished { get; set; } + // HomeserverInfoQueryProgress QueryProgress { get; set; } = new(); AuthenticatedHomeserverGeneric? hs { get; set; } + int RoomCount { get; set; } = 0; protected override async Task OnInitializedAsync() { - hs = await RMUStorage.GetCurrentSessionOrNavigate(); + hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - var fetchTasks = (await hs.GetJoinedRooms()).Select(x=>x.GetMembersByHomeserverAsync()).ToAsyncEnumerable(); + var ss = new SemaphoreSlim(32, 32); + var rooms = await hs.GetJoinedRooms(); + RoomCount = rooms.Count; + var fetchTasks = rooms.Select(roomId => workerService.TaskPool.Invoke(() => InternalGetMembersByHomeserver(hs.WellKnownUris.Client, hs.AccessToken, roomId.RoomId))).ToList().ToAsyncEnumerable(); + // var fetchTasks = rooms.Select(async x => { + // await ss.WaitAsync(); + // var res = await x.GetMembersByHomeserverAsync(); + // ss.Release(); + // return res; + // }).ToAsyncEnumerable(); await foreach (var result in fetchTasks) { foreach (var (resHomeserver, resMembers) in result) { if (!homeservers.TryAdd(resHomeserver, resMembers)) { homeservers[resHomeserver].AddRange(resMembers); } + counts[resHomeserver] = homeservers[resHomeserver].Count; } - // StateHasChanged(); + + RoomCount--; + StateHasChanged(); // await Task.Delay(250); + await Task.Yield(); } foreach (var resHomeserver in homeservers.Keys) { homeservers[resHomeserver] = homeservers[resHomeserver].Distinct().ToList(); counts[resHomeserver] = homeservers[resHomeserver].Count; + StateHasChanged(); + await Task.Yield(); } IsFinished = true; @@ -48,4 +76,10 @@ await base.OnInitializedAsync(); } + private static async Task<Dictionary<string, List<string>>> InternalGetMembersByHomeserver(string homeserverBaseUrl, string accessToken, string roomId) { + var hs = new AuthenticatedHomeserverGeneric(homeserverBaseUrl, new() { Client = homeserverBaseUrl }, null, accessToken); + var room = hs.GetRoom(roomId); + return await room.GetMembersByHomeserverAsync(); + } + } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor index de0bfe7..8f9f043 100644 --- a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor +++ b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor @@ -5,6 +5,8 @@ @using LibMatrix.EventTypes.Common + +@* <ActivityGraph Data="TestData"/> *@ @if (RoomData.Count == 0) { <p>Loading...</p> @@ -15,10 +17,8 @@ else <h3>@room.Key</h3> @foreach (var year in room.Value.OrderBy(x => x.Key)) { - <h5>@year.Key</h5> - <ActivityGraph Data="@year.Value" GlobalMax="MaxValue" - RLabel="removed" GLabel="new" BLabel="updated policies"> - </ActivityGraph> + <span>@year.Key</span> + <ActivityGraph Data="@year.Value" GlobalMax="MaxValue" RLabel="removed" GLabel="new" BLabel="updated policies"/> } } @@ -40,17 +40,24 @@ else { var sw = Stopwatch.StartNew(); await base.OnInitializedAsync(); - Homeserver = (await RMUStorage.GetCurrentSessionOrNavigate())!; + Homeserver = (await RmuStorage.GetCurrentSessionOrNavigate())!; if (Homeserver is null) return; - + // //random test data - for (DateOnly i = new DateOnly(2020, 1, 1); i < new DateOnly(2020, 12, 30); i = i.AddDays(Random.Shared.Next(5))) + for (DateOnly i = new DateOnly(2020, 1, 1); i < new DateOnly(2020, 12, 30); i = i.AddDays(2)) { + // TestData[i] = new() + // { + // R = (int)(Random.Shared.NextSingle() * 255), + // G = (int)(Random.Shared.NextSingle() * 255), + // B = (int)(Random.Shared.NextSingle() * 255) + // }; + // rgb based on number of week TestData[i] = new() { - R = (int)(Random.Shared.NextSingle() * 255), - G = (int)(Random.Shared.NextSingle() * 255), - B = (int)(Random.Shared.NextSingle() * 255) + R = i.DayOfYear % 255, + G = i.DayOfYear + 96 % 255, + B = i.DayOfYear + 192 % 255 }; } @@ -109,7 +116,7 @@ else } //use timeline - var timeline = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 5000); + var timeline = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 2000); await foreach (var response in timeline) { Console.WriteLine($"Got {response.State.Count} state, {response.Chunk.Count} timeline"); @@ -133,7 +140,7 @@ else var rgb = RoomData[roomName][date.Year][date]; if (message.RawContent?.Count == 0) rgb.R++; - else if (string.IsNullOrWhiteSpace(message.Unsigned?.ReplacesState)) rgb.G++; + else if (message.Unsigned?.ContainsKey("replaces_state") ?? false) rgb.G++; else rgb.B++; RoomData[roomName][date.Year][date] = rgb; } diff --git a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor index 3b68bfa..1adb440 100644 --- a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor +++ b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor @@ -4,7 +4,7 @@ @using System.Collections.ObjectModel @using LibMatrix @using System.Collections.Frozen -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo <h3>User Trace</h3> <hr/> @@ -73,12 +73,12 @@ protected override async Task OnInitializedAsync() { log.CollectionChanged += (sender, args) => StateHasChanged(); - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; rooms.CollectionChanged += (sender, args) => StateHasChanged(); - var sessions = await RMUStorage.GetAllTokens(); + var sessions = await RmuStorage.GetAllTokens(); foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); + var session = await RmuStorage.GetSession(userAuth); if (session is not null) { var sessionRooms = await session.GetJoinedRooms(); foreach (var room in sessionRooms) { diff --git a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/InviteCounter.razor index 8f4b4dd..fa94f18 100644 --- a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor +++ b/MatrixUtils.Web/Pages/Tools/InviteCounter.razor @@ -1,11 +1,8 @@ @page "/Tools/InviteCounter" -@using ArcaneLibs.Extensions -@using LibMatrix.RoomTypes @using System.Collections.ObjectModel -@using LibMatrix -@using System.Collections.Frozen -@using LibMatrix.EventTypes.Spec.State -@using MatrixUtils.Abstractions +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State.RoomInfo +@using LibMatrix.Filters <h3>User Trace</h3> <hr/> @@ -18,7 +15,7 @@ <details> <summary>Results</summary> - @foreach (var (userId, events) in invites.OrderByDescending(x=>x.Value).ToList()) { + @foreach (var (userId, events) in invites.OrderByDescending(x => x.Value).ToList()) { <p>@userId: @events</p> } </details> @@ -32,16 +29,15 @@ private ObservableCollection<string> log { get; set; } = new(); private Dictionary<string, int> invites { get; set; } = new(); private AuthenticatedHomeserverGeneric hs { get; set; } - + [Parameter, SupplyParameterFromQuery(Name = "room")] public string roomId { get; set; } - protected override async Task OnInitializedAsync() { log.CollectionChanged += (sender, args) => StateHasChanged(); - hs = await RMUStorage.GetCurrentSessionOrNavigate(); + hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - + StateHasChanged(); Console.WriteLine("Rerendered!"); await base.OnInitializedAsync(); @@ -49,22 +45,21 @@ private async Task<string> Execute() { var room = hs.GetRoom(roomId); - var events = room.GetManyMessagesAsync(limit: int.MaxValue); + var filter = new SyncFilter.EventFilter(types: ["m.room.member"]); + var events = room.GetManyMessagesAsync(limit: int.MaxValue, filter: filter.ToJson(indent: false, ignoreNull: true)); await foreach (var resp in events) { var all = resp.State.Concat(resp.Chunk); foreach (var evt in all) { - if(evt.Type != RoomMemberEventContent.EventId) continue; + if (evt.Type != RoomMemberEventContent.EventId) continue; var content = evt.TypedContent as RoomMemberEventContent; - if(content.Membership != "invite") continue; - if(!invites.ContainsKey(evt.Sender)) invites[evt.Sender] = 0; + if (content.Membership != "invite") continue; + if (!invites.ContainsKey(evt.Sender)) invites[evt.Sender] = 0; invites[evt.Sender]++; } log.Add($"{resp.State.Count} state, {resp.Chunk.Count} timeline"); } - - - + StateHasChanged(); return ""; diff --git a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor index cbbca9e..547c586 100644 --- a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor +++ b/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor @@ -1,12 +1,6 @@ @page "/Tools/MassCMEBan" -@using ArcaneLibs.Extensions -@using LibMatrix.RoomTypes @using System.Collections.ObjectModel -@using LibMatrix -@using System.Collections.Frozen -@using LibMatrix.EventTypes.Spec.State @using LibMatrix.EventTypes.Spec.State.Policy -@using MatrixUtils.Abstractions <h3>User Trace</h3> <hr/> @@ -33,7 +27,7 @@ protected override async Task OnInitializedAsync() { log.CollectionChanged += (sender, args) => StateHasChanged(); - hs = await RMUStorage.GetCurrentSessionOrNavigate(); + hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; StateHasChanged(); diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor new file mode 100644 index 0000000..953a1e6 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor @@ -0,0 +1,138 @@ +@page "/Moderation/DraupnirProtectedRoomsEditor" +@page "/Tools/Moderation/DraupnirProtectedRoomsEditor" +@page "/Tools/Moderation/Draupnir/ProtectedRoomsEditor" +@using LibMatrix +@using LibMatrix.EventTypes.Interop.Draupnir +@using LibMatrix.EventTypes.Spec.State.RoomInfo +@using LibMatrix.RoomTypes +<h3>Edit Draupnir protected rooms</h3> +<hr/> +<p><b>Note:</b> You will need to restart Draupnir after applying changes!</p> +<p>Minor note: This <i>should</i> also work with Mjolnir, but this hasn't been tested, and as such functionality cannot be guaranteed.</p> + +@if (data is not null) { + <div class="row"> + <div class="col-12"> + <details> + <summary>Currently protected room IDs</summary> + <ul> + @foreach (var room in data.Rooms) { + <li>@room</li> + } + </ul> + </details> + <hr/> + <h4>Tickyboxes</h4> + <table class="table"> + <thead> + <tr> + <th></th> @* Checkbox column *@ + <th>Kick?</th> @* PL > kick *@ + <th>Ban?</th> @* PL > ban *@ + <th>ACL?</th> @* PL > m.room.server_acls event *@ + <th>Room ID</th> + <th>Room name</th> + </tr> + </thead> + <tbody> + @foreach (var room in Rooms.OrderBy(x => x.RoomName)) { + <tr> + <td> + <input type="checkbox" @bind="room.IsProtected"/> + </td> + <td>@(room.PowerLevels.Kick <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td> + <td>@(room.PowerLevels.Ban <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td> + <td>@(room.PowerLevels.UserHasStatePermission(hs.UserId, RoomServerAclEventContent.EventId) ? "X" : "")</td> + <td>@room.Room.RoomId</td> + <td>@room.RoomName</td> + </tr> + } + </tbody> + </table> + </div> + </div> +} +<br/> +<LinkButton OnClick="@Apply">Apply</LinkButton> + + +@code { + private DraupnirProtectedRoomsData data { get; set; } = new(); + private List<EditorRoomInfo> Rooms { get; set; } = new(); + private AuthenticatedHomeserverGeneric hs { get; set; } + + protected override async Task OnInitializedAsync() { + hs = await RmuStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + data = await hs.GetAccountDataAsync<DraupnirProtectedRoomsData>(DraupnirProtectedRoomsData.EventId); + StateHasChanged(); + var tasks = (await hs.GetJoinedRooms()).Select(async room => { + var plTask = room.GetPowerLevelsAsync(); + var roomNameTask = room.GetNameOrFallbackAsync(); + var EditorRoomInfo = new EditorRoomInfo { + Room = room, + IsProtected = data.Rooms.Contains(room.RoomId), + RoomName = await roomNameTask, + PowerLevels = await plTask + }; + + Rooms.Add(EditorRoomInfo); + StateHasChanged(); + return Task.CompletedTask; + }).ToList(); + await Task.WhenAll(tasks); + await Task.Delay(500); + + foreach (var protectedRoomId in data.Rooms) { + if (Rooms.Any(x => x.Room.RoomId == protectedRoomId)) continue; + var room = hs.GetRoom(protectedRoomId); + var editorRoomInfo = new EditorRoomInfo { + Room = room, + IsProtected = true + }; + + try { + var pl = await room.GetPowerLevelsAsync(); + editorRoomInfo.PowerLevels = pl; + } + catch (MatrixException e) { + Console.WriteLine($"Failed to get power levels for {room.RoomId}: {e}"); + } + + try { + editorRoomInfo.RoomName = await room.GetNameOrFallbackAsync(); + } + catch (MatrixException e) { + Console.WriteLine($"Failed to get name for {room.RoomId}: {e}"); + } + + try { + var membership = await room.GetStateEventOrNullAsync(hs.UserId); + if (membership is not null) { + editorRoomInfo.RoomName = $"(!! {membership.ContentAs<RoomMemberEventContent>()?.Membership ?? "null"} !!) {editorRoomInfo.RoomName}"; + } + } + catch (MatrixException e) { + Console.WriteLine($"Failed to get membership for {room.RoomId}: {e}"); + } + + Rooms.Add(editorRoomInfo); + } + + StateHasChanged(); + } + + private class EditorRoomInfo { + public GenericRoom Room { get; set; } + public bool IsProtected { get; set; } + public string RoomName { get; set; } + public RoomPowerLevelEventContent PowerLevels { get; set; } + } + + private async Task Apply() { + Console.WriteLine(string.Join('\n', Rooms.Where(x => x.IsProtected).Select(x => x.Room.RoomId))); + data.Rooms = Rooms.Where(x => x.IsProtected).Select(x => x.Room.RoomId).ToList(); + await hs.SetAccountDataAsync("org.matrix.mjolnir.protected_rooms", data); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor index 805bd40..b4a9d35 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor @@ -1,7 +1,7 @@ -@page "/Moderation/DraupnirProtectedRoomsEditor" -@page "/Tools/Moderation/DraupnirProtectedRoomsEditor" +@page "/Tools/Moderation/Draupnir/ProtectionsEditor" @using System.Text.Json.Serialization -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using LibMatrix.RoomTypes <h3>Edit Draupnir protected rooms</h3> <hr/> @@ -38,7 +38,7 @@ </td> <td>@(room.PowerLevels.Kick <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td> <td>@(room.PowerLevels.Ban <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td> - <td>@(room.PowerLevels.UserHasStatePermission(hs.UserId, RoomServerACLEventContent.EventId) ? "X" : "")</td> + <td>@(room.PowerLevels.UserHasStatePermission(hs.UserId, RoomServerAclEventContent.EventId) ? "X" : "")</td> <td>@room.Room.RoomId</td> <td>@room.RoomName</td> </tr> @@ -58,7 +58,7 @@ private AuthenticatedHomeserverGeneric hs { get; set; } protected override async Task OnInitializedAsync() { - hs = await RMUStorage.GetCurrentSessionOrNavigate(); + hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; data = await hs.GetAccountDataAsync<DraupnirProtectedRoomsData>("org.matrix.mjolnir.protected_rooms"); StateHasChanged(); @@ -78,6 +78,43 @@ }).ToList(); await Task.WhenAll(tasks); await Task.Delay(500); + + foreach (var protectedRoomId in data.Rooms) { + if (Rooms.Any(x => x.Room.RoomId == protectedRoomId)) continue; + var room = hs.GetRoom(protectedRoomId); + var editorRoomInfo = new EditorRoomInfo { + Room = room, + IsProtected = true + }; + + try { + var pl = await room.GetPowerLevelsAsync(); + editorRoomInfo.PowerLevels = pl; + } + catch (MatrixException e) { + Console.WriteLine($"Failed to get power levels for {room.RoomId}: {e}"); + } + + try { + editorRoomInfo.RoomName = await room.GetNameOrFallbackAsync(); + } + catch (MatrixException e) { + Console.WriteLine($"Failed to get name for {room.RoomId}: {e}"); + } + + try { + var membership = await room.GetStateEventOrNullAsync(hs.UserId); + if (membership is not null) { + editorRoomInfo.RoomName = $"(!! {membership.ContentAs<RoomMemberEventContent>()?.Membership ?? "null"} !!) {editorRoomInfo.RoomName}"; + } + } + catch (MatrixException e) { + Console.WriteLine($"Failed to get membership for {room.RoomId}: {e}"); + } + + Rooms.Add(editorRoomInfo); + } + StateHasChanged(); } diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor new file mode 100644 index 0000000..1384a2a --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor @@ -0,0 +1,139 @@ +@page "/Tools/Moderation/Draupnir/WatchedListsEditor" +@using System.Text.Json.Serialization +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State.RoomInfo +@using LibMatrix.RoomTypes +<h3>Edit Draupnir protected rooms</h3> +<hr/> +<p><b>Note:</b> You will need to restart Draupnir after applying changes!</p> +<p>Minor note: This <i>should</i> also work with Mjolnir, but this hasn't been tested, and as such functionality cannot be guaranteed.</p> + +@if (data is not null) { + <div class="row"> + <div class="col-12"> + <h4>Current rooms</h4> + <ul> + @foreach (var room in data.Rooms) { + <li>@room</li> + } + </ul> + <hr/> + <h4>Tickyboxes</h4> + <table class="table"> + <thead> + <tr> + <th></th> @* Checkbox column *@ + <th>Kick?</th> @* PL > kick *@ + <th>Ban?</th> @* PL > ban *@ + <th>ACL?</th> @* PL > m.room.server_acls event *@ + <th>Room ID</th> + <th>Room name</th> + </tr> + </thead> + <tbody> + @foreach (var room in Rooms.OrderBy(x => x.RoomName)) { + <tr> + <td> + <input type="checkbox" @bind="room.IsProtected"/> + </td> + <td>@(room.PowerLevels.Kick <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td> + <td>@(room.PowerLevels.Ban <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td> + <td>@(room.PowerLevels.UserHasStatePermission(hs.UserId, RoomServerAclEventContent.EventId) ? "X" : "")</td> + <td>@room.Room.RoomId</td> + <td>@room.RoomName</td> + </tr> + } + </tbody> + </table> + </div> + </div> +} +<br/> +<LinkButton OnClick="@Apply">Apply</LinkButton> + + +@code { + private DraupnirProtectedRoomsData data { get; set; } = new(); + private List<EditorRoomInfo> Rooms { get; set; } = new(); + private AuthenticatedHomeserverGeneric hs { get; set; } + + protected override async Task OnInitializedAsync() { + hs = await RmuStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + data = await hs.GetAccountDataAsync<DraupnirProtectedRoomsData>("org.matrix.mjolnir.protected_rooms"); + StateHasChanged(); + var tasks = (await hs.GetJoinedRooms()).Select(async room => { + var plTask = room.GetPowerLevelsAsync(); + var roomNameTask = room.GetNameOrFallbackAsync(); + var EditorRoomInfo = new EditorRoomInfo { + Room = room, + IsProtected = data.Rooms.Contains(room.RoomId), + RoomName = await roomNameTask, + PowerLevels = await plTask + }; + + Rooms.Add(EditorRoomInfo); + StateHasChanged(); + return Task.CompletedTask; + }).ToList(); + await Task.WhenAll(tasks); + await Task.Delay(500); + + foreach (var protectedRoomId in data.Rooms) { + if (Rooms.Any(x => x.Room.RoomId == protectedRoomId)) continue; + var room = hs.GetRoom(protectedRoomId); + var editorRoomInfo = new EditorRoomInfo { + Room = room, + IsProtected = true + }; + + try { + var pl = await room.GetPowerLevelsAsync(); + editorRoomInfo.PowerLevels = pl; + } + catch (MatrixException e) { + Console.WriteLine($"Failed to get power levels for {room.RoomId}: {e}"); + } + + try { + editorRoomInfo.RoomName = await room.GetNameOrFallbackAsync(); + } + catch (MatrixException e) { + Console.WriteLine($"Failed to get name for {room.RoomId}: {e}"); + } + + try { + var membership = await room.GetStateEventOrNullAsync(hs.UserId); + if (membership is not null) { + editorRoomInfo.RoomName = $"(!! {membership.ContentAs<RoomMemberEventContent>()?.Membership ?? "null"} !!) {editorRoomInfo.RoomName}"; + } + } + catch (MatrixException e) { + Console.WriteLine($"Failed to get membership for {room.RoomId}: {e}"); + } + + Rooms.Add(editorRoomInfo); + } + + StateHasChanged(); + } + + private class DraupnirProtectedRoomsData { + [JsonPropertyName("rooms")] + public List<string> Rooms { get; set; } = new(); + } + + private class EditorRoomInfo { + public GenericRoom Room { get; set; } + public bool IsProtected { get; set; } + public string RoomName { get; set; } + public RoomPowerLevelEventContent PowerLevels { get; set; } + } + + private async Task Apply() { + Console.WriteLine(string.Join('\n', Rooms.Where(x => x.IsProtected).Select(x => x.Room.RoomId))); + data.Rooms = Rooms.Where(x => x.IsProtected).Select(x => x.Room.RoomId).ToList(); + await hs.SetAccountDataAsync("org.matrix.mjolnir.protected_rooms", data); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor new file mode 100644 index 0000000..4b2af68 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor @@ -0,0 +1,192 @@ +@page "/Tools/Moderation/FindUsersByRegex" +@using System.Collections.Frozen +@using ArcaneLibs.Extensions +@using LibMatrix.RoomTypes +@using System.Collections.ObjectModel +@using System.Text.RegularExpressions +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State.RoomInfo +@using LibMatrix.Filters +@using LibMatrix.Helpers +<h3>Find users by regex</h3> +<hr/> + +<p>Users (regex): </p> +<InputTextArea @bind-Value="@UserIdString"></InputTextArea> + +<LinkButton OnClick="Execute">Execute</LinkButton> +<br/> +<LinkButton OnClick="RemoveKicks">Remove kicks</LinkButton> +<LinkButton OnClick="RemoveBans">Remove bans</LinkButton> +<br/> + + +<details> + <summary>Results</summary> + @foreach (var (userId, events) in matches) { + <h4>@userId</h4> + <ul> + @foreach (var match in events) { + <li> + <ul> + <li>@match.RoomName (<span>@match.Room.RoomId</span>)</li> + <li>Membership: @(match.Event.RawContent.ToJson(indent: false)) (sent by @match.Event.Sender)</li> + </ul> + </li> + } + </ul> + } +</details> + +<br/> +@foreach (var line in log.Reverse()) { + <pre>@line</pre> +} + +@code { + + private ObservableCollection<string> log { get; set; } = new(); + + // List<RoomInfo> rooms { get; set; } = new(); + List<GenericRoom> rooms { get; set; } = []; + Dictionary<string, List<Match>> matches = new(); + + private string UserIdString { + get => string.Join("\n", UserIDs); + set => UserIDs = value.Split("\n").Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); + } + + private List<string> UserIDs { get; set; } = new(); + + private AuthenticatedHomeserverGeneric hs { get; set; } + + protected override async Task OnInitializedAsync() { + log.CollectionChanged += (sender, args) => StateHasChanged(); + log.Add("Authenticating"); + hs = await RmuStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task<string> Execute() { + log.Add("Constructing sync helper..."); + var sh = new SyncHelper(hs) { + Filter = new SyncFilter() { + AccountData = new(types: []), + Presence = new(types: []), + Room = new() { + AccountData = new(types: []), + Ephemeral = new(types: []), + State = new(types: [RoomMemberEventContent.EventId]), + Timeline = new(types: []), + IncludeLeave = false + }, + } + }; + + log.Add("Starting sync..."); + var res = await sh.SyncAsync(); + + log.Add("Got sync response, parsing..."); + + var roomNames = (await Task.WhenAll((await hs.GetJoinedRooms()).Select(async room => { return (room.RoomId, await room.GetNameOrFallbackAsync()); }).ToList())).ToFrozenDictionary(x => x.Item1, x => x.Item2); + + foreach (var userIdRegex in UserIDs) { + var regex = new Regex(userIdRegex, RegexOptions.Compiled); + log.Add($"Searching for {regex}:"); + foreach (var (roomId, joinedRoom) in res.Rooms.Join) { + log.Add($"- Checking room {roomId}..."); + foreach (var evt in joinedRoom.State.Events) { + if (evt.StateKey is null) continue; + if (evt.Type is not RoomMemberEventContent.EventId) continue; + + if (regex.IsMatch(evt.StateKey)) { + log.Add($" - Found match in {roomId} for {evt.StateKey}"); + if (!matches.ContainsKey(evt.StateKey)) { + matches[evt.StateKey] = new(); + } + + var room = hs.GetRoom(roomId); + matches[evt.StateKey].Add(new Match { + Room = room, + Event = evt, + RoomName = roomNames[roomId] + }); + } + } + } + } + + log.Add("Done!"); + + StateHasChanged(); + + return ""; + } + + public string? ImportFromRoomId { get; set; } + + private async Task DoImportFromRoomId() { + try { + if (ImportFromRoomId is null) return; + var room = rooms.FirstOrDefault(x => x.RoomId == ImportFromRoomId); + UserIdString = string.Join("\n", (await room.GetMembersListAsync()).Select(x => x.StateKey)); + } + catch (Exception e) { + Console.WriteLine(e); + log.Add("Could not fetch members list!\n" + e.ToString()); + } + + StateHasChanged(); + } + + private class Match { + public GenericRoom Room; + public StateEventResponse Event; + public string RoomName { get; set; } + } + + private async IAsyncEnumerable<Match> GetMatches(string userId) { + var results = rooms.Select(async room => { + var state = await room.GetStateEventOrNullAsync(room.RoomId, userId); + if (state is not null) { + return new Match { + Room = room, + Event = state, + RoomName = await room.GetNameOrFallbackAsync() + }; + } + + return null; + }).ToAsyncEnumerable(); + await foreach (var result in results) { + if (result is not null) { + yield return result; + } + } + } + + private Task RemoveKicks() { + foreach (var (userId, matches) in matches) { + matches.RemoveAll(x => x.Event.ContentAs<RoomMemberEventContent>()!.Membership == "leave" && x.Event.Sender != x.Event.StateKey); + } + + matches.RemoveAll((x, y) => y.Count == 0); + StateHasChanged(); + return Task.CompletedTask; + } + + private Task RemoveBans() { + foreach (var (userId, matches) in matches) { + matches.RemoveAll(x => x.Event.ContentAs<RoomMemberEventContent>()!.Membership == "ban" && x.Event.Sender != x.Event.StateKey); + } + + matches.RemoveAll((x, y) => y.Count == 0); + StateHasChanged(); + return Task.CompletedTask; + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor index 2123d4d..ea47237 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor @@ -1,6 +1,6 @@ @page "/Tools/Moderation/InviteCounter" @using System.Collections.ObjectModel -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo <h3>Invite counter</h3> <hr/> @@ -34,7 +34,7 @@ protected override async Task OnInitializedAsync() { log.CollectionChanged += (sender, args) => StateHasChanged(); - hs = await RMUStorage.GetCurrentSessionOrNavigate(); + hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; StateHasChanged(); diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor index ea1e5f6..b5e5edb 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor @@ -1,6 +1,7 @@ @page "/Tools/Moderation/MassCMEBan" @using System.Collections.ObjectModel @using LibMatrix.EventTypes.Spec.State.Policy +@using LibMatrix.RoomTypes <h3>User Trace</h3> <hr/> @@ -17,19 +18,19 @@ } @code { + // TODO: Properly implement page to be more useful private ObservableCollection<string> log { get; set; } = new(); private AuthenticatedHomeserverGeneric hs { get; set; } - + [Parameter, SupplyParameterFromQuery(Name = "room")] public string roomId { get; set; } - protected override async Task OnInitializedAsync() { log.CollectionChanged += (sender, args) => StateHasChanged(); - hs = await RMUStorage.GetCurrentSessionOrNavigate(); + hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - + StateHasChanged(); Console.WriteLine("Rerendered!"); await base.OnInitializedAsync(); @@ -37,33 +38,41 @@ private async Task<string> Execute() { var room = hs.GetRoom("!fTjMjIzNKEsFlUIiru:neko.dev"); - // var room = hs.GetRoom("!yf7OpOiRDXx6zUGpT6:conduit.rory.gay"); - var users = roomId.Split("\n").Select(x => x.Trim()).Where(x=>x.StartsWith('@')).ToList(); - foreach (var user in users) { - var exists = false; - try { - exists = !string.IsNullOrWhiteSpace((await room.GetStateAsync<UserPolicyRuleEventContent>(UserPolicyRuleEventContent.EventId, user.Replace('@', '_'))).Entity); - } catch (Exception e) { - log.Add($"Failed to get {user}"); - } + // var room = hs.GetRoom("!IVSjKMsVbjXsmUTuRR:rory.gay"); + var users = roomId.Split("\n").Select(x => x.Trim()).Where(x => x.StartsWith('@')).ToList(); + var tasks = users.Select(x => ExecuteBan(room, x)).ToList(); + await Task.WhenAll(tasks); + + StateHasChanged(); + + return ""; + } - if (!exists) { + private async Task ExecuteBan(GenericRoom room, string user) { + var exists = false; + try { + exists = !string.IsNullOrWhiteSpace((await room.GetStateAsync<UserPolicyRuleEventContent>(UserPolicyRuleEventContent.EventId, user.Replace('@', '_'))).Entity); + } + catch (Exception e) { + log.Add($"Failed to get {user}"); + } + + if (!exists) { + try { var evt = await room.SendStateEventAsync(UserPolicyRuleEventContent.EventId, user.Replace('@', '_'), new UserPolicyRuleEventContent() { Entity = user, - Reason = "spam (invite)", + Reason = "spam", Recommendation = "m.ban" }); log.Add($"Sent {evt.EventId} to ban {user}"); } - else { - log.Add($"User {user} already exists"); + catch (Exception e) { + log.Add($"Failed to ban {user}: {e}"); } } - - - StateHasChanged(); - - return ""; + else { + log.Add($"User {user} already exists"); + } } } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor index e5ba004..6b5b5e4 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor @@ -1,30 +1,158 @@ @page "/Tools/Moderation/MembershipHistory" +@using System.Collections.Frozen @using System.Collections.ObjectModel +@using System.Diagnostics @using LibMatrix -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo +@{ + var sw = Stopwatch.StartNew(); + Console.WriteLine("Start render"); +} <h3>Membership history viewer</h3> <hr/> - <br/> <span>Room ID: </span> -<InputText @bind-Value="@roomId"></InputText> +<InputText @bind-Value="@RoomId"></InputText> <LinkButton OnClick="@Execute">Execute</LinkButton> -<p><InputCheckbox @bind-Value="ChronologicalOrder"/> Chronological order</p> +<p> + <span><InputCheckbox @bind-Value="ChronologicalOrder"/>Chronological order</span> + <span><InputCheckbox @bind-Value="DoDisambiguate"/>Enable extended filters</span> +</p> <p> <span>Show </span> - <InputCheckbox @bind-Value="ShowJoins"/> joins - <InputCheckbox @bind-Value="ShowLeaves"/> leaves - <InputCheckbox @bind-Value="ShowUpdates"/> profile updates - <InputCheckbox @bind-Value="ShowKnocks"/> knocks - <InputCheckbox @bind-Value="ShowInvites"/> invites - <InputCheckbox @bind-Value="ShowKicks"/> kicks - <InputCheckbox @bind-Value="ShowBans"/> bans + <span><InputCheckbox @bind-Value="ShowJoins"/> joins</span> + <span><InputCheckbox @bind-Value="ShowLeaves"/> leaves</span> + <span><InputCheckbox @bind-Value="ShowKnocks"/> knocks</span> + <span><InputCheckbox @bind-Value="ShowInvites"/> invites</span> + <span><InputCheckbox @bind-Value="ShowBans"/> bans</span> +</p> +<p> + <LinkButton OnClick="@(async () => { + ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = false; + StateHasChanged(); + })">Hide all + </LinkButton> + <LinkButton OnClick="@(async () => { + ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = true; + StateHasChanged(); + })">Show all + </LinkButton> + <LinkButton OnClick="@(async () => { + ShowJoins ^= true; + ShowLeaves ^= true; + ShowKnocks ^= true; + ShowInvites ^= true; + ShowBans ^= true; + StateHasChanged(); + })">Toggle all + </LinkButton> </p> <p> - <LinkButton OnClick="@(async () => { ShowJoins = ShowLeaves = ShowUpdates = ShowKnocks = ShowInvites = ShowKicks = ShowBans = false; })">Hide all</LinkButton> - <LinkButton OnClick="@(async () => { ShowJoins = ShowLeaves = ShowUpdates = ShowKnocks = ShowInvites = ShowKicks = ShowBans = true; })">Show all</LinkButton> - <LinkButton OnClick="@(async () => { ShowJoins ^= true; ShowLeaves ^= true; ShowUpdates ^= true; ShowKnocks ^= true; ShowInvites ^= true; ShowKicks ^= true; ShowBans ^= true; })">Toggle all</LinkButton> + <span><InputCheckbox @bind-Value="DoDisambiguate"/> Disambiguate </span> + @if (DoDisambiguate) { + <span><InputCheckbox @bind-Value="DisambiguateKicks"/> kicks</span> + <span><InputCheckbox @bind-Value="DisambiguateUnbans"/> unbans</span> + <span><InputCheckbox @bind-Value="DisambiguateProfileUpdates"/> profile updates</span> + <details style="display: inline-block; vertical-align: top;"> + <summary> + <InputCheckbox @bind-Value="DisambiguateInviteActions"/> + invite actions + </summary> + <span><InputCheckbox @bind-Value="DisambiguateInviteAccepted"/> accepted</span> + <span><InputCheckbox @bind-Value="DisambiguateInviteRejected"/> rejected</span> + <span><InputCheckbox @bind-Value="DisambiguateInviteRetracted"/> retracted</span> + </details> + <details style="display: inline-block; vertical-align: top;"> + <summary> + <InputCheckbox @bind-Value="DisambiguateKnockActions"/> + knock actions + </summary> + <span><InputCheckbox @bind-Value="DisambiguateKnockAccepted"/> accepted</span> + <span><InputCheckbox @bind-Value="DisambiguateKnockRejected"/> rejected</span> + <span><InputCheckbox @bind-Value="DisambiguateKnockRetracted"/> retracted</span> + </details> + } </p> +@if (DoDisambiguate) { + <p> + <span>Show </span> + @if (DisambiguateKicks) { + <span><InputCheckbox @bind-Value="ShowKicks"/> kicks</span> + } + @if (DisambiguateUnbans) { + <span><InputCheckbox @bind-Value="ShowUnbans"/> unbans</span> + } + @if (DisambiguateProfileUpdates) { + <span><InputCheckbox @bind-Value="ShowProfileUpdates"/> profile updates</span> + } + @if (DisambiguateInviteActions) { + <details style="display: inline-block; vertical-align: top;"> + <summary> + <InputCheckbox @bind-Value="ShowInviteActions"/> + invite actions + </summary> + @if (DisambiguateInviteAccepted) { + <span><InputCheckbox @bind-Value="ShowInviteAccepted"/> accepted</span> + } + + @if (DisambiguateInviteRejected) { + <span><InputCheckbox @bind-Value="ShowInviteRejected"/> rejected</span> + } + + @if (DisambiguateInviteRetracted) { + <span><InputCheckbox @bind-Value="ShowInviteRetracted"/> retracted</span> + } + </details> + } + @if (DisambiguateKnockActions) { + <details style="display: inline-block; vertical-align: top;"> + <summary> + <InputCheckbox @bind-Value="ShowKnockActions"/> + knock actions + </summary> + @if (DisambiguateKnockAccepted) { + <span><InputCheckbox @bind-Value="ShowKnockAccepted"/> accepted</span> + } + + @if (DisambiguateKnockRejected) { + <span><InputCheckbox @bind-Value="ShowKnockRejected"/> rejected</span> + } + + @if (DisambiguateKnockRetracted) { + <span><InputCheckbox @bind-Value="ShowKnockRetracted"/> retracted</span> + } + </details> + } + </p> + + <p> + <LinkButton OnClick="@(async () => { + DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = false; + StateHasChanged(); + })">Un-disambiguate all + </LinkButton> + <LinkButton OnClick="@(async () => { + DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = true; + StateHasChanged(); + })">Disambiguate all + </LinkButton> + <LinkButton OnClick="@(async () => { + DisambiguateProfileUpdates ^= true; + DisambiguateKicks ^= true; + DisambiguateUnbans ^= true; + DisambiguateInviteAccepted ^= true; + DisambiguateInviteRejected ^= true; + DisambiguateInviteRetracted ^= true; + DisambiguateKnockAccepted ^= true; + DisambiguateKnockRejected ^= true; + DisambiguateKnockRetracted ^= true; + DisambiguateKnockActions ^= true; + DisambiguateInviteActions ^= true; + StateHasChanged(); + })">Toggle all + </LinkButton> + </p> +} <p> <span>Sender: </span> <InputSelect @bind-Value="Sender"> @@ -44,92 +172,121 @@ </InputSelect> </p> - +@{ Console.WriteLine($"Rendering took {sw.Elapsed} for {Memberships.Count} items"); } <br/> -<details> +<details open> <summary>Results</summary> @{ - Dictionary<string, StateEventResponse> previousMemberships = []; - var filteredMemberships = Memberships.AsEnumerable(); - if (ChronologicalOrder) { - filteredMemberships = filteredMemberships.Reverse(); - } - if(!string.IsNullOrWhiteSpace(Sender)) { - filteredMemberships = filteredMemberships.Where(x => x.Sender == Sender); - } - if(!string.IsNullOrWhiteSpace(User)) { - filteredMemberships = filteredMemberships.Where(x => x.StateKey == User); - } - - @foreach (var membership in filteredMemberships) { - RoomMemberEventContent content = membership.TypedContent as RoomMemberEventContent; - @switch (content.Membership) { - case RoomMemberEventContent.MembershipTypes.Invite: { - if (_showInvites) { - <p style="color: green;">@membership.Sender invited @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p> - } - - break; - } - case RoomMemberEventContent.MembershipTypes.Ban: { - if (_showBans) { - <p style="color: red;">@membership.Sender banned @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p> - } - - break; - } - case RoomMemberEventContent.MembershipTypes.Leave: { - if (membership.Sender == membership.StateKey) { - if (_showLeaves) { - <p style="color: #C66;">@membership.Sender left the room</p> - } - } - else { - if (_showKicks) { - <p style="color: darkorange;">@membership.Sender kicked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p> - } - } - - break; - } - case RoomMemberEventContent.MembershipTypes.Knock: { - if (_showKnocks) { - <p>@membership.Sender knocked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p> - } - - break; - } - case RoomMemberEventContent.MembershipTypes.Join: { - if (previousMemberships.TryGetValue(membership.StateKey, out var previous) - && (previous.TypedContent as RoomMemberEventContent).Membership == RoomMemberEventContent.MembershipTypes.Join) { - if (_showUpdates) { - <p style="color: #777;">@membership.Sender changed their profile</p> - } - } - else { - if (_showJoins) { - <p style="color: #6C6;">@membership.Sender joined the room @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p> - } + var filteredMemberships = GetFilteredMemberships(); + } + <table> + @foreach (var membershipEntry in filteredMemberships) { + var (transition, membership, previousMembership) = membershipEntry; + RoomMemberEventContent content = membership.TypedContent as RoomMemberEventContent ?? throw new InvalidOperationException("Event is not a RoomMemberEventContent!"); + RoomMemberEventContent? previousContent = previousMembership?.TypedContent as RoomMemberEventContent; + + <tr> + <td>@DateTimeOffset.FromUnixTimeMilliseconds(membership.OriginServerTs ?? 0).ToString("g")</td> + <td> + @switch (transition) { + case MembershipTransition.None: + <b>Unknown membership! Got None</b> + break; + case MembershipTransition.Join: + <p style="color: #6C6;"> + @membership.StateKey joined the room @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")<br/> + Display name: @content.DisplayName<br/> + Avatar URL: @content.AvatarUrl + </p> + break; + case MembershipTransition.Leave: + <p style="color: #C66;"> + @membership.StateKey left the room + </p> + break; + case MembershipTransition.Knock: + <p style="color: #426"> + @membership.StateKey knocked @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})") + </p> + break; + case MembershipTransition.Invite: + <p style="color: #262;"> + @membership.Sender invited @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})") + </p> + break; + case MembershipTransition.Ban: + <p style="color: red;"> + @membership.Sender banned @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})") + </p> + break; + @* disambiguated *@ + case MembershipTransition.Kick: + <p style="color: darkorange;"> + @membership.Sender kicked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})") + </p> + break; + case MembershipTransition.ProfileUpdate: + <p style="color: #777;"> + @membership.Sender changed their profile<br/> + Display name: @previousContent!.DisplayName -> @content.DisplayName<br/> + Avatar URL: @previousContent.AvatarUrl -> @content.AvatarUrl + </p> + break; + case MembershipTransition.InviteAccepted: + <p style="color: #084;"> + @membership.StateKey accepted the invite + from @previousMembership!.Sender @(string.IsNullOrWhiteSpace(previousContent?.Reason) ? "" : $"(invite reason: {previousContent.Reason})") @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(accept reason: {content.Reason})") + </p> + break; + case MembershipTransition.KnockAccepted: + <p style="color: #288;"> + @membership.StateKey's knock was accepted + by @previousMembership!.Sender @(string.IsNullOrWhiteSpace(previousContent?.Reason) ? "" : $"(knock reason: {previousContent.Reason})") @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(accept reason: {content.Reason})") + </p> + break; + case MembershipTransition.KnockRejected: + <p style="color: #828;"> + @membership.StateKey's knock was rejected + by @previousMembership!.Sender @(string.IsNullOrWhiteSpace(previousContent?.Reason) ? "" : $"(knock reason: {previousContent.Reason})") @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reject reason: {content.Reason})") + </p> + break; + case MembershipTransition.Unban: + <p style="color: #0C0;"> + @membership.Sender unbanned @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})") + </p> + break; + case MembershipTransition.InviteRejected: + <p style="color: #733;"> + @membership.StateKey rejected the invite + from @previousMembership!.Sender @(string.IsNullOrWhiteSpace(previousContent?.Reason) ? "" : $"(invite reason: {previousContent.Reason})") @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reject reason: {content.Reason})") + </p> + break; + case MembershipTransition.InviteRetracted: + <p style="color: #844;"> + @membership.Sender retracted the invite + for @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})") + </p> + break; + case MembershipTransition.KnockRetracted: + <p style="color: #b55;"> + @membership.Sender retracted the knock + for @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})") + </p> + break; + default: + throw new ArgumentOutOfRangeException(); } - - break; - } - default: { - <b>Unknown membership @content.Membership!</b> - break; - } - } - - previousMemberships[membership.StateKey] = membership; + </td> + </tr> } - } + </table> </details> <br/> <details open> <summary>Log</summary> - @foreach (var line in log.Reverse()) { + @foreach (var line in Log.Reverse()) { <pre>@line</pre> } </details> @@ -138,139 +295,280 @@ #region Filter bindings - private bool _chronologicalOrder = false; - - private bool ChronologicalOrder { - get => _chronologicalOrder; - set { - _chronologicalOrder = value; - StateHasChanged(); - } - } - - private bool _showJoins = true; - - private bool ShowJoins { - get => _showJoins; - set { - _showJoins = value; - StateHasChanged(); - } - } - - private bool _showLeaves = true; - - private bool ShowLeaves { - get => _showLeaves; - set { - _showLeaves = value; - StateHasChanged(); - } - } - - private bool _showUpdates = true; - - private bool ShowUpdates { - get => _showUpdates; - set { - _showUpdates = value; - StateHasChanged(); - } - } - - private bool _showKnocks = true; - - private bool ShowKnocks { - get => _showKnocks; - set { - _showKnocks = value; - StateHasChanged(); - } - } - - private bool _showInvites = true; - - private bool ShowInvites { - get => _showInvites; - set { - _showInvites = value; - StateHasChanged(); - } - } + private bool ChronologicalOrder { get; set; } + private bool ShowJoins { get; set; } = true; + private bool ShowLeaves { get; set; } = true; + private bool ShowKnocks { get; set; } = true; + private bool ShowInvites { get; set; } = true; + private bool ShowBans { get; set; } = true; + + private bool DoDisambiguate { get; set; } = true; + private bool DisambiguateProfileUpdates { get => field && DoDisambiguate; set; } = true; + private bool DisambiguateKicks { get => field && DoDisambiguate; set; } = true; + private bool DisambiguateUnbans { get => field && DoDisambiguate; set; } = true; + private bool DisambiguateInviteAccepted { get => field && DoDisambiguate && DisambiguateInviteActions; set; } = true; + private bool DisambiguateInviteRejected { get => field && DoDisambiguate && DisambiguateInviteActions; set; } = true; + private bool DisambiguateInviteRetracted { get => field && DoDisambiguate && DisambiguateInviteActions; set; } = true; + private bool DisambiguateKnockAccepted { get => field && DoDisambiguate && DisambiguateKnockActions; set; } = true; + private bool DisambiguateKnockRejected { get => field && DoDisambiguate && DisambiguateKnockActions; set; } = true; + private bool DisambiguateKnockRetracted { get => field && DoDisambiguate && DisambiguateKnockActions; set; } = true; + + private bool DisambiguateKnockActions { get => field && DoDisambiguate; set; } = true; + private bool DisambiguateInviteActions { get => field && DoDisambiguate; set; } = true; - private bool _showKicks = true; + private bool ShowProfileUpdates { + get => field && DisambiguateProfileUpdates; + set; + } = true; private bool ShowKicks { - get => _showKicks; - set { - _showKicks = value; - StateHasChanged(); - } - } - - private bool _showBans = true; - - private bool ShowBans { - get => _showBans; - set { - _showBans = value; - StateHasChanged(); - } - } - - private string sender = ""; - - private string Sender { - get => sender; - set { - sender = value; - StateHasChanged(); - } - } - - private string user = ""; - - private string User { - get => user; + get => field && DisambiguateKicks; + set; + } = true; + + private bool ShowUnbans { + get => field && DisambiguateUnbans; + set; + } = true; + + private bool ShowInviteAccepted { + get => field && DisambiguateInviteAccepted; + set; + } = true; + + private bool ShowInviteRejected { + get => field && DisambiguateInviteRejected; + set; + } = true; + + private bool ShowInviteRetracted { + get => field && DisambiguateInviteRetracted; + set; + } = true; + + private bool ShowKnockAccepted { + get => field && DisambiguateKnockAccepted; + set; + } = true; + + private bool ShowKnockRejected { + get => field && DisambiguateKnockRejected; + set; + } = true; + + private bool ShowKnockRetracted { + get => field && DisambiguateKnockRetracted; + set; + } = true; + + private bool ShowKnockActions { + get => field && DisambiguateKnockActions; + set; + } = true; + + private bool ShowInviteActions { + get => field && DisambiguateInviteActions; + set; + } = true; + + [Parameter, SupplyParameterFromQuery(Name = "sender")] + public string Sender { get; set; } = ""; + + [Parameter, SupplyParameterFromQuery(Name = "user")] + public string User { get; set; } = ""; + + [Parameter, SupplyParameterFromQuery(Name = "filter")] + public string Filter { + get; set { - user = value; + field = value; + if (string.IsNullOrWhiteSpace(value)) return; + var parts = value.Split(','); + ShowJoins = parts.Contains("join"); + ShowLeaves = parts.Contains("leave"); + ShowKnocks = parts.Contains("knock"); + ShowInvites = parts.Contains("invite"); + ShowBans = parts.Contains("ban"); StateHasChanged(); } - } + } = ""; #endregion - private ObservableCollection<string> log { get; set; } = new(); + private ObservableCollection<string> Log { get; set; } = new(); private List<StateEventResponse> Memberships { get; set; } = []; - private AuthenticatedHomeserverGeneric hs { get; set; } + private AuthenticatedHomeserverGeneric Homeserver { get; set; } = null!; [Parameter, SupplyParameterFromQuery(Name = "room")] - public string roomId { get; set; } + public string RoomId { get; set; } = ""; protected override async Task OnInitializedAsync() { - log.CollectionChanged += (sender, args) => StateHasChanged(); - hs = await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; + Log.CollectionChanged += (sender, args) => StateHasChanged(); + Homeserver = await RmuStorage.GetCurrentSessionOrNavigate(); + if (Homeserver is null) return; StateHasChanged(); Console.WriteLine("Rerendered!"); await base.OnInitializedAsync(); - if (!string.IsNullOrWhiteSpace(roomId)) + if (!string.IsNullOrWhiteSpace(RoomId)) await Execute(); } private async Task Execute() { Memberships.Clear(); - var room = hs.GetRoom(roomId); + var room = Homeserver.GetRoom(RoomId); var events = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 5000); await foreach (var resp in events) { var all = resp.State.Concat(resp.Chunk); Memberships.AddRange(all.Where(x => x.Type == RoomMemberEventContent.EventId)); - log.Add($"{resp.State.Count} state, {resp.Chunk.Count} timeline"); + Log.Add($"Got {resp.State.Count} state and {resp.Chunk.Count} timeline events."); } + Log.Add("Reached end of timeline!"); + StateHasChanged(); } + private readonly struct MembershipEntry { + public required MembershipTransition State { get; init; } + public required StateEventResponse Event { get; init; } + public required StateEventResponse? Previous { get; init; } + + public void Deconstruct(out MembershipTransition transition, out StateEventResponse evt, out StateEventResponse? prev) { + transition = State; + evt = Event; + prev = Previous; + } + } + + private enum MembershipTransition : byte { + None, + Join, + Leave, + Knock, + Invite, + Ban, + + // disambiguated + ProfileUpdate, + Kick, + Unban, + InviteAccepted, + InviteRejected, + InviteRetracted, + KnockAccepted, + KnockRejected, + KnockRetracted + } + + private static IEnumerable<MembershipEntry> GetTransitions(List<StateEventResponse> evts) { + Dictionary<string, MembershipEntry> transitions = new(); + foreach (var evt in evts.OrderBy(x => x.OriginServerTs)) { + var content = evt.TypedContent as RoomMemberEventContent ?? throw new InvalidOperationException("Event is not a RoomMemberEventContent!"); + var prev = transitions.GetValueOrDefault(evt.StateKey!) as MembershipEntry?; + transitions[evt.StateKey ?? throw new Exception("Member event has no state key??")] = new MembershipEntry { + Event = evt, + Previous = prev?.Event, + State = content.Membership switch { + RoomMemberEventContent.MembershipTypes.Join => + prev?.State switch { + MembershipTransition.Join or MembershipTransition.InviteAccepted => MembershipTransition.ProfileUpdate, + MembershipTransition.Invite => MembershipTransition.InviteAccepted, + _ => MembershipTransition.Join + }, + RoomMemberEventContent.MembershipTypes.Leave => + evt.Sender == evt.StateKey + ? prev?.State switch { + MembershipTransition.Knock => MembershipTransition.KnockRetracted, + MembershipTransition.Invite => MembershipTransition.InviteRejected, + _ => MembershipTransition.Leave + } + : prev?.State switch { + // not self + MembershipTransition.Knock => MembershipTransition.KnockRejected, + MembershipTransition.Invite => MembershipTransition.InviteRetracted, + _ => MembershipTransition.Kick, + }, + RoomMemberEventContent.MembershipTypes.Invite => + prev?.State switch { + MembershipTransition.Knock => MembershipTransition.KnockAccepted, + _ => MembershipTransition.Invite + }, + RoomMemberEventContent.MembershipTypes.Knock => MembershipTransition.Knock, + RoomMemberEventContent.MembershipTypes.Ban => MembershipTransition.Ban, + _ => MembershipTransition.None + } + }; + yield return transitions[evt.StateKey]; + } + } + + private IEnumerable<MembershipEntry> Disambiguated(IEnumerable<MembershipEntry> entries) { + FrozenDictionary<MembershipTransition, MembershipTransition> disambiguated = new Dictionary<MembershipTransition, MembershipTransition>() { + { MembershipTransition.ProfileUpdate, MembershipTransition.Join }, + { MembershipTransition.Kick, MembershipTransition.Leave }, + { MembershipTransition.Unban, MembershipTransition.Leave }, + { MembershipTransition.InviteAccepted, MembershipTransition.Join }, + { MembershipTransition.InviteRejected, MembershipTransition.Leave }, + { MembershipTransition.InviteRetracted, MembershipTransition.Leave }, + { MembershipTransition.KnockAccepted, MembershipTransition.Invite }, + { MembershipTransition.KnockRejected, MembershipTransition.Leave }, + { MembershipTransition.KnockRetracted, MembershipTransition.Leave } + }.ToFrozenDictionary(); + + foreach (var entry in entries) { + if (!DoDisambiguate) { + yield return entry; + continue; + } + + var newState = entry.State switch { + MembershipTransition.ProfileUpdate when !DoDisambiguate || !DisambiguateProfileUpdates => MembershipTransition.Join, + MembershipTransition.Kick when !DoDisambiguate || !DisambiguateKicks => MembershipTransition.Leave, + MembershipTransition.Unban when !DoDisambiguate || !DisambiguateUnbans => MembershipTransition.Leave, + MembershipTransition.InviteAccepted when !DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteAccepted => MembershipTransition.Join, + MembershipTransition.InviteRejected when !DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRejected => MembershipTransition.Leave, + MembershipTransition.InviteRetracted when !DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRetracted => MembershipTransition.Leave, + MembershipTransition.KnockAccepted when !DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockAccepted => MembershipTransition.Invite, + MembershipTransition.KnockRejected when !DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRejected => MembershipTransition.Leave, + MembershipTransition.KnockRetracted when !DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRetracted => MembershipTransition.Leave, + _ => entry.State + }; + if (newState != entry.State) { + yield return entry with { State = newState }; + } + else yield return entry; + } + } + + private IEnumerable<MembershipEntry> GetFilteredMemberships() { + var filteredMemberships = GetTransitions(Memberships); + if (!string.IsNullOrWhiteSpace(Sender)) filteredMemberships = filteredMemberships.Where(x => x.Event.Sender == Sender); + if (!string.IsNullOrWhiteSpace(User)) filteredMemberships = filteredMemberships.Where(x => x.Event.StateKey == User); + filteredMemberships = Disambiguated(filteredMemberships); + + if (!ShowJoins) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Join); + if (!ShowLeaves) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Leave); + if (!ShowKnocks) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Knock); + if (!ShowInvites) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Invite); + if (!ShowBans) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Ban); + // extended filters + if (DoDisambiguate) { + if (!DisambiguateProfileUpdates || !ShowProfileUpdates) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.ProfileUpdate); + if (!DisambiguateKicks || !ShowKicks) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Kick); + if (!DisambiguateUnbans || !ShowUnbans) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Unban); + if (!DisambiguateInviteActions || !ShowInviteActions || !DisambiguateInviteAccepted || !ShowInviteAccepted) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.InviteAccepted); + if (!DisambiguateInviteActions || !ShowInviteActions || !DisambiguateInviteRejected || !ShowInviteRejected) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.InviteRejected); + if (!DisambiguateInviteActions || !ShowInviteActions || !DisambiguateInviteRetracted || !ShowInviteRetracted) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.InviteRetracted); + if (!DisambiguateKnockActions || !ShowKnockActions || !DisambiguateKnockAccepted || !ShowKnockAccepted) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.KnockAccepted); + if (!DisambiguateKnockActions || !ShowKnockActions || !DisambiguateKnockRejected || !ShowKnockRejected) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.KnockRejected); + if (!DisambiguateKnockActions || !ShowKnockActions || !DisambiguateKnockRetracted || !ShowKnockRetracted) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.KnockRetracted); + } + + if (!ChronologicalOrder) filteredMemberships = filteredMemberships.Reverse(); + + return filteredMemberships; + } + } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor index b8baeb8..15eea15 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor @@ -2,7 +2,7 @@ @using LibMatrix.RoomTypes @using System.Collections.ObjectModel @using LibMatrix -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo <h3>Room intersections</h3> <hr/> @@ -113,7 +113,7 @@ protected override async Task OnInitializedAsync() { Log.CollectionChanged += (sender, args) => StateHasChanged(); - hs = await RMUStorage.GetCurrentSessionOrNavigate(); + hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; StateHasChanged(); diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor index 915f8dc..0d622cc 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor @@ -3,13 +3,15 @@ @using LibMatrix.RoomTypes @using System.Collections.ObjectModel @using LibMatrix +@using LibMatrix.EventTypes.Spec.State.RoomInfo <h3>User Trace</h3> <hr/> <p>Users: </p> <InputTextArea @bind-Value="@UserIdString"></InputTextArea> <br/> -<InputText @bind-Value="@ImportFromRoomId"></InputText><LinkButton OnClick="@DoImportFromRoomId">Import from room (ID)</LinkButton> +<InputText @bind-Value="@ImportFromRoomId"></InputText> +<LinkButton OnClick="@DoImportFromRoomId">Import from room (ID)</LinkButton> <details> <summary>Rooms to be searched (@rooms.Count)</summary> @@ -24,18 +26,21 @@ <details> <summary>Results</summary> - @foreach (var (userId, events) in matches) { + @foreach (var (userId, events) in matches.OrderBy(x=>x.Key)) { <h4>@userId</h4> - <ul> - @foreach (var match in events) { - <li> - <ul> - <li>@match.RoomName (<span>@match.Room.RoomId</span>)</li> - <li>Membership: @(match.Event.RawContent.ToJson(indent: false))</li> - </ul> - </li> + <table> + @foreach (var match in events.OrderBy(x=>x.RoomName)) { + <tr> + <td>@match.RoomName (<span>@match.Room.RoomId</span>)</td> + <td> + <details> + <summary>@SummarizeMembership(match.Event)</summary> + <pre>@match.Event.RawContent.ToJson(indent: true)</pre> + </details> + </td> + </tr> } - </ul> + </table> } </details> @@ -61,56 +66,34 @@ protected override async Task OnInitializedAsync() { log.CollectionChanged += (sender, args) => StateHasChanged(); - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - // var sessions = await RMUStorage.GetAllTokens(); - // var baseRooms = new List<GenericRoom>(); - // foreach (var userAuth in sessions) { - // var session = await RMUStorage.GetSession(userAuth); - // if (session is not null) { - // baseRooms.AddRange(await session.GetJoinedRooms()); - // var sessionRooms = (await session.GetJoinedRooms()).Where(x => !rooms.Any(y => y.Room.RoomId == x.RoomId)).ToList(); - // StateHasChanged(); - // log.Add($"Got {sessionRooms.Count} rooms for {userAuth.UserId}"); - // } - // } - // - // log.Add("Done fetching rooms!"); - // - // baseRooms = baseRooms.DistinctBy(x => x.RoomId).ToList(); - // - // // rooms.CollectionChanged += (sender, args) => StateHasChanged(); - // var tasks = baseRooms.Select(async newRoom => { - // bool success = false; - // while (!success) - // try { - // var state = await newRoom.GetFullStateAsListAsync(); - // var newRoomInfo = new RoomInfo(newRoom, state); - // rooms.Add(newRoomInfo); - // log.Add($"Got {newRoomInfo.StateEvents.Count} events for {newRoomInfo.RoomName}"); - // success = true; - // } - // catch (MatrixException e) { - // log.Add($"Failed to fetch room {newRoom.RoomId}! {e}"); - // throw; - // } - // catch (HttpRequestException e) { - // log.Add($"Failed to fetch room {newRoom.RoomId}! {e}"); - // } - // }); - // await Task.WhenAll(tasks); - // - // log.Add($"Done fetching members!"); - // - // UserIDs.RemoveAll(x => sessions.Any(y => y.UserId == x)); - - foreach (var session in await RMUStorage.GetAllTokens()) { - var _hs = await RMUStorage.GetSession(session); - if (_hs is not null) { - rooms.AddRange(await _hs.GetJoinedRooms()); - log.Add($"Got {rooms.Count} rooms after adding {_hs.UserId}"); + + var sessions = await RmuStorage.GetAllTokens(); + var tasks = sessions.Select(async session => { + try { + var _hs = await RmuStorage.GetSession(session); + if (_hs is not null) { + try { + var _rooms = await _hs.GetJoinedRooms(); + if (!_rooms.Any()) return; + // Check if homeserver supports `?format=event`: + await _rooms.First().GetStateEventAsync(RoomMemberEventContent.EventId, session.UserId); + rooms.AddRange(_rooms); + log.Add($"Got {_rooms.Count} rooms for {_hs.UserId}, total {rooms.Count}"); + } + catch (Exception e) { + if (e is LibMatrixException { ErrorCode: LibMatrixException.ErrorCodes.M_UNSUPPORTED }) + log.Add($"Homeserver {_hs.UserId} does not support `?format=event`! Skipping..."); + else log.Add($"Failed to fetch rooms for {_hs.UserId}! {e}"); + } + } } - } + catch (Exception e) { + log.Add($"Failed to fetch rooms for {session.UserId}! {e}"); + } + }); + await Task.WhenAll(tasks); //get distinct rooms evenly distributed per session, accounting for count per session rooms = rooms.OrderBy(x => rooms.Count(y => y.Homeserver == x.Homeserver)).DistinctBy(x => x.RoomId).ToList(); @@ -125,17 +108,6 @@ foreach (var userId in UserIDs) { matches.Add(userId, new List<Match>()); - // foreach (var room in rooms) { - // var state = room.StateEvents.Where(x => x!.Type == RoomMemberEventContent.EventId).ToList(); - // if (state!.Any(x => x.StateKey == userId)) { - // matches[userId].Add(new() { - // Event = state.First(x => x.StateKey == userId), - // Room = room.Room, - // RoomName = room.RoomName ?? "No name" - // }); - // } - // } - log.Add($"Searching for {userId}..."); await foreach (var match in GetMatches(userId)) { matches[userId].Add(match); @@ -173,13 +145,19 @@ private async IAsyncEnumerable<Match> GetMatches(string userId) { var results = rooms.Select(async room => { - var state = await room.GetStateEventOrNullAsync(room.RoomId, userId); - if (state is not null) { - return new Match { - Room = room, - Event = state, - RoomName = await room.GetNameOrFallbackAsync() - }; + try { + var state = await room.GetStateEventOrNullAsync(RoomMemberEventContent.EventId, userId); + if (state is not null) { + log.Add($"Found {userId} in {room.RoomId} with membership {state.RawContent.ToJson(indent: false)}"); + return new Match { + Room = room, + Event = state, + RoomName = await room.GetNameOrFallbackAsync() + }; + } + } + catch (Exception e) { + log.Add($"Failed to fetch state for {userId} in {room.RoomId}! {e}"); } return null; @@ -191,4 +169,24 @@ } } + public string SummarizeMembership(StateEventResponse state) { + var membership = state.ContentAs<RoomMemberEventContent>(); + var time = DateTimeOffset.FromUnixTimeMilliseconds(state.OriginServerTs!.Value); + return membership switch { + { Membership: "invite", Reason: null } => $"Invited by {state.Sender} at {time}", + { Membership: "invite", Reason: not null } => $"Invited by {state.Sender} at {time} for {membership.Reason}", + { Membership: "join", Reason: null } => $"Joined at {time}", + { Membership: "join", Reason: not null } => $"Joined at {time} for {membership.Reason}", + { Membership: "leave", Reason: null } => state.Sender == state.StateKey ? $"Left at {time}" : $"Kicked by {state.Sender} at {time}", + { Membership: "leave", Reason: not null } => state.Sender == state.StateKey ? $"Left at {time} with reason {membership.Reason}" : $"Kicked by {state.Sender} at {time} for {membership.Reason}", + { Membership: "ban", Reason: null } => $"Banned by {state.Sender} at {time}", + { Membership: "ban", Reason: not null } => $"Banned by {state.Sender} at {time} for {membership.Reason}", + }; + } + + private async Task ExportJson() { + var json = matches.ToJson(); + + } + } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor b/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor index 80a03f2..b57810a 100644 --- a/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor +++ b/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor @@ -1,6 +1,6 @@ @page "/Tools/Room/SpaceRestrictedJoins" @using System.Collections.ObjectModel -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo <h3>Allow space to restricted join children</h3> <hr/> @@ -31,7 +31,7 @@ protected override async Task OnInitializedAsync() { log.CollectionChanged += (sender, args) => StateHasChanged(); - hs = await RMUStorage.GetCurrentSessionOrNavigate(); + hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; StateHasChanged(); diff --git a/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor index 667b518..64dbfcf 100644 --- a/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor +++ b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor @@ -1,7 +1,7 @@ @page "/Tools/CopyPowerlevel" @using ArcaneLibs.Extensions @using LibMatrix -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using LibMatrix.RoomTypes <h3>Copy powerlevel</h3> <hr/> @@ -23,11 +23,11 @@ List<AuthenticatedHomeserverGeneric> hss { get; set; } = new(); protected override async Task OnInitializedAsync() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - var sessions = await RMUStorage.GetAllTokens(); + var sessions = await RmuStorage.GetAllTokens(); foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); + var session = await RmuStorage.GetSession(userAuth); if (session is not null) { hss.Add(session); StateHasChanged(); @@ -42,7 +42,7 @@ private async Task Execute() { foreach (var hs in hss) { var rooms = await hs.GetJoinedRooms(); - var tasks = rooms.Select(x=>Execute(hs, x)).ToAsyncEnumerable(); + var tasks = rooms.Select(x=>ApplyPowerlevelsInRoom(hs, x)).ToAsyncEnumerable(); await foreach (var a in tasks) { if (!string.IsNullOrWhiteSpace(a)) { log.Add(a); @@ -52,7 +52,7 @@ } } - private async Task<string> Execute(AuthenticatedHomeserverGeneric hs, GenericRoom room) { + private async Task<string> ApplyPowerlevelsInRoom(AuthenticatedHomeserverGeneric hs, GenericRoom room) { try { var pls = await room.GetPowerLevelsAsync(); // if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) == pls.UsersDefault) return "I am default PL in " + room.RoomId; diff --git a/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor index a2ad388..e352c91 100644 --- a/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor +++ b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor @@ -1,7 +1,7 @@ @page "/Tools/MassRoomJoin" @using ArcaneLibs.Extensions @using LibMatrix -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo <h3>Mass join room</h3> <hr/> <p>Room: </p> @@ -25,11 +25,11 @@ string roomId { get; set; } protected override async Task OnInitializedAsync() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - var sessions = await RMUStorage.GetAllTokens(); + var sessions = await RmuStorage.GetAllTokens(); foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); + var session = await RmuStorage.GetSession(userAuth); if (session is not null) { hss.Add(session); StateHasChanged(); diff --git a/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor b/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor index d8b02bb..b73b5ac 100644 --- a/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor +++ b/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor @@ -1,4 +1,4 @@ -@page "/Tools/ViewAccountData" +@page "/Tools/User/ViewAccountData" @using ArcaneLibs.Extensions @using LibMatrix <h3>View account data</h3> @@ -16,7 +16,7 @@ Dictionary<string, EventList?> perRoomAccountData = new(); protected override async Task OnInitializedAsync() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; perRoomAccountData = await hs.EnumerateAccountDataPerRoom(); globalAccountData = await hs.EnumerateAccountData(); diff --git a/MatrixUtils.Web/Pages/User/DMManager.razor b/MatrixUtils.Web/Pages/User/DMManager.razor index 80bf3b2..fe45eb8 100644 --- a/MatrixUtils.Web/Pages/User/DMManager.razor +++ b/MatrixUtils.Web/Pages/User/DMManager.razor @@ -1,8 +1,8 @@ @page "/User/DirectMessages" -@using LibMatrix.EventTypes.Spec.State @using LibMatrix.Responses @using MatrixUtils.Abstractions @using LibMatrix +@using LibMatrix.EventTypes.Spec.State.RoomInfo <h3>Direct Messages</h3> <hr/> @@ -29,7 +29,7 @@ } protected override async Task OnInitializedAsync() { - Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + Homeserver = await RmuStorage.GetCurrentSessionOrNavigate(); if (Homeserver is null) return; Status = "Loading global profile..."; if (Homeserver.WhoAmI?.UserId is null) return; diff --git a/MatrixUtils.Web/Pages/User/Profile.razor b/MatrixUtils.Web/Pages/User/Profile.razor index 49af22f..d0af2c8 100644 --- a/MatrixUtils.Web/Pages/User/Profile.razor +++ b/MatrixUtils.Web/Pages/User/Profile.razor @@ -1,10 +1,8 @@ @page "/User/Profile" -@using LibMatrix.EventTypes.Spec.State -@using ArcaneLibs.Extensions @using LibMatrix +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using LibMatrix.Responses @using MatrixUtils.Abstractions -@using Microsoft.AspNetCore.Components.Forms <h3>Manage Profile - @Homeserver?.WhoAmI?.UserId</h3> <hr/> @@ -12,7 +10,7 @@ <h4>Profile</h4> <hr/> <div> - <img src="@Homeserver.ResolveMediaUri(NewProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> + <MxcAvatar MxcUri="@NewProfile.AvatarUrl" Circular="true" Size="96"/> <div style="display: inline-block; vertical-align: middle;"> <span>Display name: </span><FancyTextBox @bind-Value="@NewProfile.DisplayName"></FancyTextBox><br/> <span>Avatar URL: </span><FancyTextBox @bind-Value="@NewProfile.AvatarUrl"></FancyTextBox> @@ -35,12 +33,13 @@ <summary style="@(room.OwnMembership?.DisplayName == OldProfile.DisplayName && room.OwnMembership?.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")"> <div style="display: inline-block; width: calc(100% - 50px); vertical-align: middle; margin-top: -8px; margin-bottom: -8px;"> <CascadingValue Value="OldProfile"> - <RoomListItem ShowOwnProfile="true" RoomInfo="@room" OwnMemberState="@room.OwnMembership"></RoomListItem> + <RoomListItem Homeserver="Homeserver" ShowOwnProfile="true" RoomInfo="@room" OwnMemberState="@room.OwnMembership"></RoomListItem> </CascadingValue> </div> </summary> @if (room.OwnMembership is not null) { - <img src="@Homeserver.ResolveMediaUri(room.OwnMembership.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> + @* <img src="@Homeserver.ResolveMediaUri(room.OwnMembership.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> *@ + <MxcAvatar MxcUri="@room.OwnMembership.AvatarUrl" Circular="true" Size="96"/> <div style="display: inline-block; vertical-align: middle;"> <span>Display name: </span><FancyTextBox BackgroundColor="@(room.OwnMembership.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@room.OwnMembership.DisplayName"></FancyTextBox><br/> <span>Avatar URL: </span><FancyTextBox BackgroundColor="@(room.OwnMembership.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@room.OwnMembership.AvatarUrl"></FancyTextBox> @@ -58,29 +57,11 @@ </details> <br/> } - - @foreach (var (roomId, roomProfile) in RoomProfiles.OrderBy(x => RoomNames.TryGetValue(x.Key, out var _name) ? _name : x.Key)) { - <details class="details-compact"> - <summary style="@(roomProfile.DisplayName == OldProfile.DisplayName && roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")">@(RoomNames.TryGetValue(roomId, out var name) ? name : roomId)</summary> - <img src="@Homeserver.ResolveMediaUri(roomProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> - <div style="display: inline-block; vertical-align: middle;"> - <span>Display name: </span><FancyTextBox BackgroundColor="@(roomProfile.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@roomProfile.DisplayName"></FancyTextBox><br/> - <span>Avatar URL: </span><FancyTextBox BackgroundColor="@(roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@roomProfile.AvatarUrl"></FancyTextBox> - <InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, roomId))"></InputFile><br/> - <LinkButton OnClick="@(() => UpdateRoomProfile(roomId))">Update profile</LinkButton> - </div> - <br/> - @if (!string.IsNullOrWhiteSpace(Status)) { - <p>@Status</p> - } - </details> - <br/> - } // </details> } @code { - private string? _status = null; + private string? _status; private AuthenticatedHomeserverGeneric? Homeserver { get; set; } private UserProfileResponse? NewProfile { get; set; } @@ -99,7 +80,7 @@ private Dictionary<string, string> RoomNames { get; set; } = new(); protected override async Task OnInitializedAsync() { - Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + Homeserver = await RmuStorage.GetCurrentSessionOrNavigate(); if (Homeserver is null) return; Status = "Loading global profile..."; if (Homeserver.WhoAmI?.UserId is null) return; @@ -107,44 +88,50 @@ OldProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)); //.DeepClone(); Status = "Loading room profiles..."; var roomProfiles = Homeserver.GetRoomProfilesAsync(); + List<Task> roomInfoTasks = []; await foreach (var (roomId, roomProfile) in roomProfiles) { - var room = Homeserver.GetRoom(roomId); - var roomNameTask = room.GetNameOrFallbackAsync(); - var roomIconTask = room.GetAvatarUrlAsync(); - var roomInfo = new RoomInfo(room) { - OwnMembership = roomProfile - }; - try { - roomInfo.RoomIcon = (await roomIconTask).Url; - } - catch (MatrixException e) { - if (e is not { ErrorCode: "M_NOT_FOUND" }) throw; - } + var task = Task.Run(async () => { + var room = Homeserver.GetRoom(roomId); + var roomNameTask = room.GetNameOrFallbackAsync(); + var roomIconTask = room.GetAvatarUrlAsync(); + var roomInfo = new RoomInfo(room) { + OwnMembership = roomProfile + }; + try { + roomInfo.RoomIcon = (await roomIconTask).Url; + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_NOT_FOUND" }) throw; + } - try { - roomInfo.RoomName = await roomNameTask; - } - catch (MatrixException e) { - if (e is not { ErrorCode: "M_NOT_FOUND" }) throw; - } + try { + RoomNames[roomId] = roomInfo.RoomName = await roomNameTask; + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_NOT_FOUND" }) throw; + } - Rooms.Add(roomInfo); - // Status = $"Got profile for {roomId}..."; - RoomProfiles[roomId] = roomProfile; //.DeepClone(); + Rooms.Add(roomInfo); + // Status = $"Got profile for {roomId}..."; + RoomProfiles[roomId] = roomProfile; //.DeepClone(); + }); + roomInfoTasks.Add(task); } + + await Task.WhenAll(roomInfoTasks); StateHasChanged(); Status = "Room profiles loaded, loading room names..."; - var roomNameTasks = RoomProfiles.Keys.Select(x => Homeserver.GetRoom(x)).Select(async x => { - var name = await x.GetNameOrFallbackAsync(); - return new KeyValuePair<string, string?>(x.RoomId, name); - }).ToAsyncEnumerable(); + // var roomNameTasks = RoomProfiles.Keys.Select(x => Homeserver.GetRoom(x)).Select(async x => { + // var name = await x.GetNameOrFallbackAsync(); + // return new KeyValuePair<string, string?>(x.RoomId, name); + // }).ToAsyncEnumerable(); - await foreach (var (roomId, roomName) in roomNameTasks) { - // Status = $"Got room name for {roomId}: {roomName}"; - RoomNames[roomId] = roomName; - } + // await foreach (var (roomId, roomName) in roomNameTasks) { + // Status = $"Got room name for {roomId}: {roomName}"; + // RoomNames[roomId] = roomName; + // } StateHasChanged(); Status = null; diff --git a/MatrixUtils.Web/Program.cs b/MatrixUtils.Web/Program.cs index 1b8960c..8bc2c8f 100644 --- a/MatrixUtils.Web/Program.cs +++ b/MatrixUtils.Web/Program.cs @@ -8,6 +8,8 @@ using MatrixUtils.Web; using MatrixUtils.Web.Classes; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using SpawnDev.BlazorJS; +using SpawnDev.BlazorJS.WebWorkers; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app"); @@ -16,6 +18,18 @@ builder.RootComponents.Add<HeadOutlet>("head::after"); // builder.Logging.SetMinimumLevel(LogLevel.Trace); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +builder.Services.AddBlazorJSRuntime(); +builder.Services.AddWebWorkerService(webWorkerService => +{ + // Optionally configure the WebWorkerService service before it is used + // Default WebWorkerService.TaskPool settings: PoolSize = 0, MaxPoolSize = 1, AutoGrow = true + // Below sets TaskPool max size to 2. By default the TaskPool size will grow as needed up to the max pool size. + // Setting max pool size to -1 will set it to the value of navigator.hardwareConcurrency + webWorkerService.TaskPool.MaxPoolSize = 2; + // Below is telling the WebWorkerService TaskPool to set the initial size to 2 if running in a Window scope and 0 otherwise + // This starts up 2 WebWorkers to handle TaskPool tasks as needed + webWorkerService.TaskPool.PoolSize = webWorkerService.GlobalScope == GlobalScope.Window ? 2 : 0; +}); try { builder.Configuration.AddJsonStream(await new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }.GetStreamAsync("/appsettings.json")); @@ -65,4 +79,5 @@ builder.Services.AddScoped<TieredStorageService>(x => builder.Services.AddRoryLibMatrixServices(); builder.Services.AddScoped<RMUStorageWrapper>(); -await builder.Build().RunAsync(); \ No newline at end of file +// await builder.Build().RunAsync(); +await builder.Build().BlazorJSRunAsync(); \ No newline at end of file diff --git a/MatrixUtils.Web/Properties/launchSettings.json b/MatrixUtils.Web/Properties/launchSettings.json index aa41dc8..660211d 100644 --- a/MatrixUtils.Web/Properties/launchSettings.json +++ b/MatrixUtils.Web/Properties/launchSettings.json @@ -13,7 +13,7 @@ "dotnetRunMessages": true, "launchBrowser": false, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5117", + "applicationUrl": "http://*:5117", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/MatrixUtils.Web/Shared/InlineUserItem.razor b/MatrixUtils.Web/Shared/InlineUserItem.razor index 9c6608a..50fa9e1 100644 --- a/MatrixUtils.Web/Shared/InlineUserItem.razor +++ b/MatrixUtils.Web/Shared/InlineUserItem.razor @@ -1,4 +1,4 @@ -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using LibMatrix.Responses <div style="background-color: #ffffff11; border-radius: 0.5em; height: 1em; display: inline-block; vertical-align: middle;" alt="@UserId"> <img style="@(ChildContent is not null ? "vertical-align: baseline;" : "vertical-align: top;") width: 1em; height: 1em; border-radius: 50%;" src="@ProfileAvatar"/> @@ -39,7 +39,7 @@ protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - Homeserver ??= await RMUStorage.GetCurrentSessionOrNavigate(); + Homeserver ??= await RmuStorage.GetCurrentSessionOrNavigate(); if(Homeserver is null) return; await _semaphoreSlim.WaitAsync(); @@ -59,7 +59,7 @@ } - ProfileAvatar ??= Homeserver.ResolveMediaUri(User.AvatarUrl); + // ProfileAvatar ??= Homeserver.ResolveMediaUri(User.AvatarUrl); ProfileName ??= User.DisplayName; _semaphoreSlim.Release(); diff --git a/MatrixUtils.Web/Shared/MainLayout.razor b/MatrixUtils.Web/Shared/MainLayout.razor index c67f73c..0392d9a 100644 --- a/MatrixUtils.Web/Shared/MainLayout.razor +++ b/MatrixUtils.Web/Shared/MainLayout.razor @@ -1,5 +1,4 @@ -@using ArcaneLibs -@inherits LayoutComponentBase +@inherits LayoutComponentBase <div class="page"> <div class="sidebar"> @@ -10,7 +9,7 @@ <div class="top-row px-4"> @* <PortableDevTools/> *@ @* <ResourceUsage/> *@ - <a style="color: #ccc; text-decoration: underline" href="https://cgit.rory.gay/matrix/MatrixRoomUtils.git/" target="_blank">Git</a> + <a style="color: #ccc; text-decoration: underline" href="https://cgit.rory.gay/matrix/tools/MatrixUtils.git/" target="_blank">Git</a> <a style="color: #ccc; text-decoration: underline" href="https://matrix.to/#/%23mru%3Arory.gay?via=rory.gay&via=matrix.org&via=feline.support" target="_blank">Matrix</a> </div> diff --git a/MatrixUtils.Web/Shared/MainLayout.razor.css b/MatrixUtils.Web/Shared/MainLayout.razor.css index 01a5066..924393b 100644 --- a/MatrixUtils.Web/Shared/MainLayout.razor.css +++ b/MatrixUtils.Web/Shared/MainLayout.razor.css @@ -57,6 +57,7 @@ main { .sidebar { width: 250px; + min-width: 250px; height: 100vh; position: sticky; top: 0; diff --git a/MatrixUtils.Web/Shared/MxcAvatar.razor b/MatrixUtils.Web/Shared/MxcAvatar.razor new file mode 100644 index 0000000..02aff72 --- /dev/null +++ b/MatrixUtils.Web/Shared/MxcAvatar.razor @@ -0,0 +1,55 @@ +<StreamedImage Stream="@_stream" style="@StyleString"/> + +@code { + private string _mxcUri; + private string _style; + private Stream _stream; + + [Parameter] + public string MxcUri { + get => _mxcUri ?? ""; + set { + if(_mxcUri == value) return; + _mxcUri = value; + UriHasChanged(value); + } + } + + [Parameter] + public bool Circular { get; set; } + + [Parameter] + public int Size { get; set; } = 48; + + [Parameter] + public string SizeUnit { get; set; } = "px"; + + [Parameter] + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + + private string StyleString => $"{(Circular ? "border-radius: 50%;" : "")} width: {Size}{SizeUnit}; height: {Size}{SizeUnit}; object-fit: cover;"; + + private static readonly string Prefix = "mxc://"; + private static readonly int PrefixLength = Prefix.Length; + + private async Task UriHasChanged(string value) { + if (!value.StartsWith(Prefix)) { + // Console.WriteLine($"UriHasChanged: {value} does not start with {Prefix}, passing as resolved URI!!!"); + // ResolvedUri = value; + return; + } + + if (Homeserver is null) { + Console.WriteLine("Homeserver is required for MxcAvatar"); + return; + } + + var uri = value[PrefixLength..].Split('/'); + // Console.WriteLine($"UriHasChanged: {value} {uri[0]}"); + var url = $"/_matrix/media/v3/download/{uri[0]}/{uri[1]}"; + Console.WriteLine($"ResolvedUri: {url}"); + _stream = await Homeserver.ClientHttpClient.GetStreamAsync(url); + StateHasChanged(); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/MxcImage.razor b/MatrixUtils.Web/Shared/MxcImage.razor index e651c3f..e7cb2e0 100644 --- a/MatrixUtils.Web/Shared/MxcImage.razor +++ b/MatrixUtils.Web/Shared/MxcImage.razor @@ -31,7 +31,7 @@ } } - [Parameter] + [CascadingParameter, Parameter] public RemoteHomeserver? Homeserver { get; set; } private string ResolvedUri { @@ -48,19 +48,19 @@ private static readonly int PrefixLength = Prefix.Length; private async Task UriHasChanged(string value) { - if (!value.StartsWith(Prefix)) { - Console.WriteLine($"UriHasChanged: {value} does not start with {Prefix}, passing as resolved URI!!!"); - ResolvedUri = value; - return; - } - var uri = value[PrefixLength..].Split('/'); - Console.WriteLine($"UriHasChanged: {value} {uri[0]}"); - if (Homeserver is null) { - Console.WriteLine($"Homeserver is null, creating new remotehomeserver for {uri[0]}"); - Homeserver = await hsProvider.GetRemoteHomeserver(uri[0]); - } - ResolvedUri = Homeserver.ResolveMediaUri(value); - Console.WriteLine($"ResolvedUri: {ResolvedUri}"); + // if (!value.StartsWith(Prefix)) { + // Console.WriteLine($"UriHasChanged: {value} does not start with {Prefix}, passing as resolved URI!!!"); + // ResolvedUri = value; + // return; + // } + // var uri = value[PrefixLength..].Split('/'); + // Console.WriteLine($"UriHasChanged: {value} {uri[0]}"); + // if (Homeserver is null) { + // Console.WriteLine($"Homeserver is null, creating new remotehomeserver for {uri[0]}"); + // Homeserver = await hsProvider.GetRemoteHomeserver(uri[0]); + // } + // ResolvedUri = Homeserver.ResolveMediaUri(value); + // Console.WriteLine($"ResolvedUri: {ResolvedUri}"); } // [Parameter] diff --git a/MatrixUtils.Web/Shared/NavMenu.razor b/MatrixUtils.Web/Shared/NavMenu.razor index 770a246..7371e66 100644 --- a/MatrixUtils.Web/Shared/NavMenu.razor +++ b/MatrixUtils.Web/Shared/NavMenu.razor @@ -37,6 +37,12 @@ </div> <div class="nav-item px-3"> + <NavLink class="nav-link" href="PolicyLists"> + <span class="oi oi-ban" aria-hidden="true"></span> Manage policy lists + </NavLink> + </div> + + <div class="nav-item px-3"> <NavLink class="nav-link" href="User/Profile"> <span class="oi oi-person" aria-hidden="true"></span> Manage profile </NavLink> diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor new file mode 100644 index 0000000..11ba18a --- /dev/null +++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor @@ -0,0 +1,102 @@ +@using LibMatrix.EventTypes.Spec.State.Policy +@using System.Reflection +@using ArcaneLibs.Attributes +@using LibMatrix +@using System.Collections.Frozen +@using LibMatrix.EventTypes +@using LibMatrix.RoomTypes +<ModalWindow Title="@("Creating many new " + (PolicyTypes.ContainsKey(MappedType??"") ? PolicyTypes[MappedType!].GetFriendlyNamePluralOrNull()?.ToLower() ?? PolicyTypes[MappedType!].Name : "event"))" + OnCloseClicked="@OnClose" X="60" Y="60" MinWidth="600"> + <span>Policy type:</span> + <select @bind="@MappedType"> + <option>Select a value</option> + @foreach (var (type, mappedType) in PolicyTypes) { + <option value="@type">@mappedType.GetFriendlyName().ToLower()</option> + } + </select><br/> + + <span>Reason:</span> + <FancyTextBox @bind-Value="@Reason"></FancyTextBox><br/> + + <span>Recommendation:</span> + <FancyTextBox @bind-Value="@Recommendation"></FancyTextBox><br/> + + <span>Entities:</span><br/> + <InputTextArea @bind-Value="@Users" style="width: 500px;"></InputTextArea><br/> + + + @* <details> *@ + @* <summary>JSON data</summary> *@ + @* <pre> *@ + @* $1$ @PolicyEvent.ToJson(true, true) #1# *@ + @* </pre> *@ + @* </details> *@ + <LinkButton OnClick="@(() => { OnClose.Invoke(); return Task.CompletedTask; })"> Cancel </LinkButton> + <LinkButton OnClick="@(() => { _ = Save(); return Task.CompletedTask; })"> Save </LinkButton> + +</ModalWindow> + +@code { + + [Parameter] + public required Action OnClose { get; set; } + + [Parameter] + public required Action OnSaved { get; set; } + + [Parameter] + public required GenericRoom Room { get; set; } + + public string Recommendation { get; set; } = "m.ban"; + public string Reason { get; set; } = "spam"; + public string Users { get; set; } = ""; + + private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); + + private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes + .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); + + private string? MappedType { get; set; } + + private async Task Save() { + try { + await DoActualSave(); + } + catch (Exception e) { + Console.WriteLine($"Failed to save: {e}"); + } + } + + private async Task DoActualSave() { + Console.WriteLine($"Saving ---"); + Console.WriteLine($"Users = {Users}"); + var users = Users.Split("\n").Select(x => x.Trim()).Where(x => x.StartsWith('@')).ToList(); + var tasks = users.Select(x => ExecuteBan(Room, x)).ToList(); + await Task.WhenAll(tasks); + + OnSaved.Invoke(); + } + + private async Task ExecuteBan(GenericRoom room, string entity) { + bool success = false; + while (!success) { + try { + var content = Activator.CreateInstance(PolicyTypes[MappedType!]) as PolicyRuleEventContent; + content.Recommendation = Recommendation; + content.Reason = Reason; + content.Entity = entity; + await room.SendStateEventAsync(MappedType!, content.GetDraupnir2StateKey(), content); + success = true; + } + catch (MatrixException e) { + if (e is not { ErrorCode: MatrixException.ErrorCodes.M_FORBIDDEN }) throw; + Console.WriteLine(e); + } + catch (Exception e) { + //ignored + Console.WriteLine(e); + } + } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor index 1bd00d1..a1d870c 100644 --- a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor +++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor @@ -35,39 +35,61 @@ </thead> <tbody> @foreach (var prop in props) { + var isNullable = Nullable.GetUnderlyingType(prop.PropertyType) is not null; <tr> <td style="padding-right: 8px;"> <span>@prop.GetFriendlyName()</span> - @if (Nullable.GetUnderlyingType(prop.PropertyType) is not null) { + @if (Nullable.GetUnderlyingType(prop.PropertyType) is null) { <span style="color: red;">*</span> } </td> @{ var getter = prop.GetGetMethod(); var setter = prop.GetSetMethod(); - } - @switch (Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType) { - case Type t when t == typeof(string): - <FancyTextBox Value="@(getter?.Invoke(PolicyData, null) as string)" ValueChanged="@(e => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"></FancyTextBox> - break; - default: - <p style="color: red;">Unsupported type: @prop.PropertyType</p> - break; + if (getter is null) { + <p style="color: red;">Missing property getter: @prop.Name</p> + } + else { + switch (Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType) { + case Type t when t == typeof(string): + <FancyTextBox Value="@(getter?.Invoke(PolicyData, null) as string)" ValueChanged="@((string e) => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"></FancyTextBox> + break; + case Type t when t == typeof(DateTime): + if (!isNullable) { + <InputDate TValue="DateTime" Value="@(getter?.Invoke(PolicyData, null) as DateTime? ?? new DateTime())" ValueChanged="@(e => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"></InputDate> + } + else { + var value = getter?.Invoke(PolicyData, null) as DateTime?; + if (value is null) { + <button @onclick="() => { setter?.Invoke(PolicyData, [DateTime.Now]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); }">Add value</button> + } + else { + var notNullValue = Nullable.GetValueRefOrDefaultRef(ref value); + Console.WriteLine($"Value: {value?.ToString() ?? "null"}"); + <InputDate TValue="DateTime" ValueExpression="@(() => notNullValue)" ValueChanged="@(e => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"></InputDate> + <button @onclick="() => { setter?.Invoke(PolicyData, [null]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); }">Remove value</button> + } + } + + break; + default: + <p style="color: red;">Unsupported type: @prop.PropertyType</p> + break; + } + } } </tr> } </tbody> </table> - <br/> - <pre> - @PolicyEvent.ToJson(true, false) - </pre> + <details> + <summary>JSON data</summary> + <pre> + @PolicyEvent.ToJson(true, true) + </pre> + </details> <LinkButton OnClick="@(() => { OnClose.Invoke(); return Task.CompletedTask; })"> Cancel </LinkButton> <LinkButton OnClick="@(() => { OnSave.Invoke(PolicyEvent); return Task.CompletedTask; })"> Save </LinkButton> - @* <span>Target entity: </span> *@ - @* <FancyTextBox @bind-Value="@policyData.Entity"></FancyTextBox><br/> *@ - @* <span>Reason: </span> *@ - @* <FancyTextBox @bind-Value="@policyData.Reason"></FancyTextBox> *@ } else { <p>Policy data is null</p> @@ -102,7 +124,7 @@ .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); private StateEventResponse? _policyEvent; - + private string? MappedType { get => _policyEvent?.Type; set { @@ -110,9 +132,9 @@ PolicyEvent.Type = value; PolicyEvent.TypedContent ??= Activator.CreateInstance(PolicyTypes[value]) as PolicyRuleEventContent; PolicyData = PolicyEvent.TypedContent as PolicyRuleEventContent; + PolicyData.Recommendation ??= "m.ban"; } } } - } \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor b/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor index 1f5ce89..555b1f1 100644 --- a/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor +++ b/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor @@ -1,5 +1,5 @@ +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using MatrixUtils.Web.Classes.Constants -@using LibMatrix.EventTypes.Spec.State @using LibMatrix.Responses @using MatrixUtils.Abstractions <details open> diff --git a/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor b/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor index 6954990..11f9040 100644 --- a/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor +++ b/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor @@ -42,7 +42,7 @@ if (Breadcrumbs == null) throw new ArgumentNullException(nameof(Breadcrumbs)); await Task.Delay(Random.Shared.Next(1000, 10000)); var rooms = Space.Room.AsSpace.GetChildrenAsync(); - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await RmuStorage.GetCurrentSessionOrNavigate(); var joinedRooms = await hs.GetJoinedRooms(); await foreach (var room in rooms) { if (Breadcrumbs.Contains(room.RoomId)) continue; diff --git a/MatrixUtils.Web/Shared/RoomListItem.razor b/MatrixUtils.Web/Shared/RoomListItem.razor index bfaa900..d75d159 100644 --- a/MatrixUtils.Web/Shared/RoomListItem.razor +++ b/MatrixUtils.Web/Shared/RoomListItem.razor @@ -1,5 +1,5 @@ @using LibMatrix -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using LibMatrix.Responses @using MatrixUtils.Abstractions @using MatrixUtils.Web.Classes.Constants @@ -7,13 +7,15 @@ <div class="roomListItem @(HasDangerousRoomVersion ? "dangerousRoomVersion" : HasOldRoomVersion ? "oldRoomVersion" : "")" id="@RoomInfo.Room.RoomId"> @if (OwnMemberState != null) { @* Class="@("avatar32" + (OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? " highlightChange" : "") + (ChildContent is not null ? " vcenter" : ""))" *@ - <MxcImage Homeserver="hs" Circular="true" Height="32" Width="32" MxcUri="@(OwnMemberState.AvatarUrl ?? GlobalProfile.AvatarUrl)"/> + @* <MxcImage Homeserver="hs" Circular="true" Height="32" Width="32" MxcUri="@(OwnMemberState.AvatarUrl ?? GlobalProfile.AvatarUrl)"/> *@ + <MxcAvatar Homeserver="Homeserver" Circular="true" Size="32" MxcUri="@(OwnMemberState.AvatarUrl ?? GlobalProfile.AvatarUrl)"/> <span class="centerVertical border75 @(OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? "highlightChange" : "")"> @(OwnMemberState?.DisplayName ?? GlobalProfile?.DisplayName ?? "Loading...") </span> <span class="centerVertical noLeftPadding">-></span> } - <MxcImage Circular="true" Height="32" Width="32" MxcUri="@RoomInfo.RoomIcon" Style="@(ChildContent is not null ? "vertical-align: middle;" : "")"/> + @* <MxcImage Circular="true" Height="32" Width="32" MxcUri="@RoomInfo.RoomIcon" Style="@(ChildContent is not null ? "vertical-align: middle;" : "")"/> *@ + <MxcAvatar Homeserver="Homeserver" Circular="true" Size="32" MxcUri="@RoomInfo.RoomIcon"/> <div class="inlineBlock"> <span class="centerVertical">@RoomInfo.RoomName</span> @if (ChildContent is not null) { @@ -42,8 +44,6 @@ else { } } - - [Parameter] public bool ShowOwnProfile { get; set; } = false; @@ -61,6 +61,9 @@ else { OnParametersSetAsync(); } } + + [Parameter] + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } private bool HasOldRoomVersion { get; set; } = false; private bool HasDangerousRoomVersion { get; set; } = false; @@ -68,20 +71,19 @@ else { private static SemaphoreSlim _semaphoreSlim = new(8); private RoomInfo? _roomInfo; private bool _loadData = false; - private static AuthenticatedHomeserverGeneric? hs { get; set; } private bool _hooked; - + private async Task RoomInfoChanged() { RoomInfo.PropertyChanged += async (_, a) => { if (a.PropertyName == nameof(RoomInfo.CreationEventContent)) { await CheckRoomVersion(); } - + StateHasChanged(); }; } - + // protected override async Task OnParametersSetAsync() { // if (RoomInfo != null) { // if (!_hooked) { @@ -127,21 +129,24 @@ else { protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - hs ??= await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; + // hs ??= await RmuStorage.GetCurrentSessionOrNavigate(); + // if (hs is null) return; + if (Homeserver is null) { + Console.WriteLine($"RoomListItem called without homeserver"); + } await CheckRoomVersion(); } private async Task LoadOwnProfile() { if (!ShowOwnProfile) return; try { - // OwnMemberState ??= (await RoomInfo.GetStateEvent("m.room.member", hs.UserId)).TypedContent as RoomMemberEventContent; - GlobalProfile ??= await hs.GetProfileAsync(hs.UserId); + // OwnMemberState ??= (await RoomInfo.GetStateEvent("m.room.member", hs.UserId)).TypedContent as RoomMemberEventContent; + GlobalProfile ??= await Homeserver.GetProfileAsync(Homeserver.UserId); } catch (MatrixException e) { if (e is { ErrorCode: "M_FORBIDDEN" }) { - Console.WriteLine($"Failed to get profile for {hs.UserId}: {e.Message}"); + Console.WriteLine($"Failed to get profile for {Homeserver.UserId}: {e.Message}"); ShowOwnProfile = false; } else { @@ -151,8 +156,8 @@ else { } private async Task CheckRoomVersion() { - if (RoomInfo?.CreationEventContent is null) return; - + if (RoomInfo?.CreationEventContent is null) return; + var ce = RoomInfo.CreationEventContent; if (int.TryParse(ce.RoomVersion, out var rv)) { if (rv < 10) @@ -163,7 +168,7 @@ else { if (RoomConstants.DangerousRoomVersions.Contains(ce.RoomVersion)) { HasDangerousRoomVersion = true; - // RoomName = "Dangerous room: " + RoomName; + // RoomName = "Dangerous room: " + RoomName; } } diff --git a/MatrixUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor b/MatrixUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor index 08aeffe..f107eb3 100644 --- a/MatrixUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor +++ b/MatrixUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor @@ -1,5 +1,5 @@ @using LibMatrix -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using LibMatrix.Responses <h3>BaseTimelineItem</h3> diff --git a/MatrixUtils.Web/Shared/TimelineComponents/TimelineCanonicalAliasItem.razor b/MatrixUtils.Web/Shared/TimelineComponents/TimelineCanonicalAliasItem.razor index 0488e36..d1984dd 100644 --- a/MatrixUtils.Web/Shared/TimelineComponents/TimelineCanonicalAliasItem.razor +++ b/MatrixUtils.Web/Shared/TimelineComponents/TimelineCanonicalAliasItem.razor @@ -1,5 +1,5 @@ @using ArcaneLibs.Extensions -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo @inherits BaseTimelineItem @if (currentEventContent is not null) { diff --git a/MatrixUtils.Web/Shared/TimelineComponents/TimelineHistoryVisibilityItem.razor b/MatrixUtils.Web/Shared/TimelineComponents/TimelineHistoryVisibilityItem.razor index bdd6104..5d09603 100644 --- a/MatrixUtils.Web/Shared/TimelineComponents/TimelineHistoryVisibilityItem.razor +++ b/MatrixUtils.Web/Shared/TimelineComponents/TimelineHistoryVisibilityItem.razor @@ -1,5 +1,5 @@ @using ArcaneLibs.Extensions -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo @inherits BaseTimelineItem @if (currentEventContent is not null) { diff --git a/MatrixUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor b/MatrixUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor index 3b18b95..e5a5650 100644 --- a/MatrixUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor +++ b/MatrixUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor @@ -1,5 +1,5 @@ @using ArcaneLibs.Extensions -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using LibMatrix.Responses @inherits BaseTimelineItem diff --git a/MatrixUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor b/MatrixUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor index 81956b0..98b5a6d 100644 --- a/MatrixUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor +++ b/MatrixUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor @@ -15,7 +15,7 @@ } case "m.image": { <i>@currentEventContent.Body</i><br/> - <img src="@Homeserver.ResolveMediaUri(currentEventContent.Url)"> + @* <img src="@Homeserver.ResolveMediaUri(currentEventContent.Url)"> *@ break; } default: { diff --git a/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor b/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor index f3e6c7e..aeb987a 100644 --- a/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor +++ b/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor @@ -1,5 +1,5 @@ @using ArcaneLibs.Extensions -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo @inherits BaseTimelineItem <i> diff --git a/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomNameItem.razor b/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomNameItem.razor index 63594a9..c342c83 100644 --- a/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomNameItem.razor +++ b/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomNameItem.razor @@ -1,5 +1,5 @@ @using ArcaneLibs.Extensions -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo @inherits BaseTimelineItem @if (currentEventContent is not null) { diff --git a/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomTopicItem.razor b/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomTopicItem.razor index f70d563..467c644 100644 --- a/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomTopicItem.razor +++ b/MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomTopicItem.razor @@ -1,5 +1,5 @@ @using ArcaneLibs.Extensions -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo @inherits BaseTimelineItem @if (currentEventContent is not null) { diff --git a/MatrixUtils.Web/Shared/UserListItem.razor b/MatrixUtils.Web/Shared/UserListItem.razor index d4652b2..cf7f24d 100644 --- a/MatrixUtils.Web/Shared/UserListItem.razor +++ b/MatrixUtils.Web/Shared/UserListItem.razor @@ -28,7 +28,7 @@ private SvgIdenticonGenerator _identiconGenerator = new(); protected override async Task OnInitializedAsync() { - _homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + _homeserver = await RmuStorage.GetCurrentSessionOrNavigate(); if (_homeserver is null) return; if (User == null) { diff --git a/MatrixUtils.Web/_Imports.razor b/MatrixUtils.Web/_Imports.razor index 81c7874..47c8b36 100644 --- a/MatrixUtils.Web/_Imports.razor +++ b/MatrixUtils.Web/_Imports.razor @@ -1,13 +1,10 @@ @using System.Net.Http @using System.Net.Http.Json -@* @using Blazored.LocalStorage *@ @using LibMatrix.Services @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web -@* @using Microsoft.AspNetCore.Components.Web.Virtualization *@ @using Microsoft.AspNetCore.Components.WebAssembly.Http -@* @using Microsoft.JSInterop *@ @using MatrixUtils.Web @using MatrixUtils.Web.Classes @using MatrixUtils.Web.Shared @@ -16,8 +13,8 @@ @using Microsoft.JSInterop @inject NavigationManager NavigationManager -@inject RMUStorageWrapper RMUStorage -@inject HomeserverProviderService hsProvider +@inject RMUStorageWrapper RmuStorage +@inject HomeserverProviderService HsProvider @inject TieredStorageService TieredStorage -@inject HomeserverResolverService hsResolver -@inject IJSRuntime JSRuntime +@inject HomeserverResolverService HsResolver +@inject IJSRuntime JsRuntime diff --git a/MatrixUtils.Web/wwwroot/index.html b/MatrixUtils.Web/wwwroot/index.html index 5182193..7425de2 100644 --- a/MatrixUtils.Web/wwwroot/index.html +++ b/MatrixUtils.Web/wwwroot/index.html @@ -57,6 +57,22 @@ height: window.innerHeight }; } + + setImageStream = async (element, imageStream) => { + if(!(element instanceof HTMLElement)) { + console.error("Element is not an HTMLElement", element); + return; + } + + const arrayBuffer = await imageStream.arrayBuffer(); + const blob = new Blob([arrayBuffer]); + const url = URL.createObjectURL(blob); + const image = document.getElementById(imageElementId); + image.onload = () => { + URL.revokeObjectURL(url); + } + image.src = url; + } </script> <script src="_framework/blazor.webassembly.js"></script> <!-- <script>navigator.serviceWorker.register('service-worker.js');</script>--> diff --git a/MatrixUtils.sln b/MatrixUtils.sln new file mode 100644 index 0000000..5fb0c1f --- /dev/null +++ b/MatrixUtils.sln @@ -0,0 +1,215 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Web", "MatrixUtils.Web\MatrixUtils.Web.csproj", "{D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Web.Server", "MatrixUtils.Web.Server\MatrixUtils.Web.Server.csproj", "{F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Desktop", "MatrixUtils.Desktop\MatrixUtils.Desktop.csproj", "{27C08A4F-5AF0-4C2C-AFCB-050E3388C116}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.LibDMSpace", "MatrixUtils.LibDMSpace\MatrixUtils.LibDMSpace.csproj", "{EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Abstractions", "MatrixUtils.Abstractions\MatrixUtils.Abstractions.csproj", "{FE20ED20-0D55-4D74-822B-E2AC7A54C487}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LibMatrix", "LibMatrix", "{933DC8A6-8B1F-46BF-9046-4B636AA46469}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArcaneLibs", "ArcaneLibs", "{84BE90C4-2FDE-4A48-B154-58926EF24846}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Tests", "LibMatrix\ArcaneLibs\ArcaneLibs.Tests\ArcaneLibs.Tests.csproj", "{EC5536AB-0613-4CB5-B22B-822A3DBB112A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Blazor.Components", "LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj", "{CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Legacy", "LibMatrix\ArcaneLibs\ArcaneLibs.Legacy\ArcaneLibs.Legacy.csproj", "{0C542A8E-54B6-4A20-B7A9-8C7190A0C232}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Logging", "LibMatrix\ArcaneLibs\ArcaneLibs.Logging\ArcaneLibs.Logging.csproj", "{48AF8AC7-5F59-4401-B173-523D37FDD7A8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.StringNormalisation", "LibMatrix\ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj", "{CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "LibMatrix\ArcaneLibs\ArcaneLibs.Timings\ArcaneLibs.Timings.csproj", "{ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "LibMatrix\ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{D6315791-949B-4501-AA95-50516DE899C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs", "LibMatrix\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj", "{03466515-77CC-49E4-90E5-9A21EDD0A644}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix\LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{0336306C-285A-4810-9253-5C5F0373992E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix", "LibMatrix\LibMatrix\LibMatrix.csproj", "{D7E5B226-114C-4747-9277-A4D6341A16FE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B37F87A8-B5E2-4724-800C-F5D9A91F35C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Tests", "LibMatrix\Tests\LibMatrix.Tests\LibMatrix.Tests.csproj", "{D293AFEC-8322-4FEC-8425-143B5FE10D0F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{80828C75-9C5B-442F-86A4-8CE9D85E811C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DebugDataValidationApi", "LibMatrix\Utilities\LibMatrix.DebugDataValidationApi\LibMatrix.DebugDataValidationApi.csproj", "{FA6A9923-419A-40E1-8A32-30DD906E5025}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "LibMatrix\Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.E2eeTestKit", "LibMatrix\Utilities\LibMatrix.E2eeTestKit\LibMatrix.E2eeTestKit.csproj", "{CC87DFFB-EE19-4147-9212-4FAF16D79AD5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "LibMatrix\Utilities\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{DBCE6260-052E-46F9-ACCD-059AA51B8A48}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.JsonSerializerContextGenerator", "LibMatrix\Utilities\LibMatrix.JsonSerializerContextGenerator\LibMatrix.JsonSerializerContextGenerator.csproj", "{7AA3CDF9-D1F6-4A12-BA47-EB721F353701}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.TestDataGenerator", "LibMatrix\Utilities\LibMatrix.TestDataGenerator\LibMatrix.TestDataGenerator.csproj", "{D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Utilities.Bot", "LibMatrix\Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj", "{72D44C6C-1BC7-4310-B1A9-1169C0812E33}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.DmSpaced", "MatrixUtils.DmSpaced\MatrixUtils.DmSpaced.csproj", "{CDBE012E-B48B-4F9D-8CA4-99F6328E9630}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MxApiExtensions", "MxApiExtensions", "{0641F1C8-8518-4C67-B385-832745C063FD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MxApiExtensions.Classes.LibMatrix", "MxApiExtensions\MxApiExtensions.Classes.LibMatrix\MxApiExtensions.Classes.LibMatrix.csproj", "{3BD05B05-86DE-4680-A7A0-5A326E41E776}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MxApiExtensions.Classes", "MxApiExtensions\MxApiExtensions.Classes\MxApiExtensions.Classes.csproj", "{98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MxApiExtensions", "MxApiExtensions\MxApiExtensions\MxApiExtensions.csproj", "{44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{CEECE820-1BA9-4E29-8668-25967B3E712B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D38DA95D-DD83-4340-96A4-6F59FC6AE3D9}.Release|Any CPU.Build.0 = Release|Any CPU + {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F997F26F-2EC1-4D18-B3DD-C46FB2AD65C0}.Release|Any CPU.Build.0 = Release|Any CPU + {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27C08A4F-5AF0-4C2C-AFCB-050E3388C116}.Release|Any CPU.Build.0 = Release|Any CPU + {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDD2FBAB-2DEC-4527-AE9C-20E21D0D6B14}.Release|Any CPU.Build.0 = Release|Any CPU + {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|Any CPU.Build.0 = Release|Any CPU + {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC5536AB-0613-4CB5-B22B-822A3DBB112A}.Release|Any CPU.Build.0 = Release|Any CPU + {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE}.Release|Any CPU.Build.0 = Release|Any CPU + {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C542A8E-54B6-4A20-B7A9-8C7190A0C232}.Release|Any CPU.Build.0 = Release|Any CPU + {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48AF8AC7-5F59-4401-B173-523D37FDD7A8}.Release|Any CPU.Build.0 = Release|Any CPU + {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D}.Release|Any CPU.Build.0 = Release|Any CPU + {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72}.Release|Any CPU.Build.0 = Release|Any CPU + {D6315791-949B-4501-AA95-50516DE899C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6315791-949B-4501-AA95-50516DE899C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6315791-949B-4501-AA95-50516DE899C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6315791-949B-4501-AA95-50516DE899C1}.Release|Any CPU.Build.0 = Release|Any CPU + {03466515-77CC-49E4-90E5-9A21EDD0A644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03466515-77CC-49E4-90E5-9A21EDD0A644}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03466515-77CC-49E4-90E5-9A21EDD0A644}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03466515-77CC-49E4-90E5-9A21EDD0A644}.Release|Any CPU.Build.0 = Release|Any CPU + {0336306C-285A-4810-9253-5C5F0373992E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0336306C-285A-4810-9253-5C5F0373992E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0336306C-285A-4810-9253-5C5F0373992E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0336306C-285A-4810-9253-5C5F0373992E}.Release|Any CPU.Build.0 = Release|Any CPU + {D7E5B226-114C-4747-9277-A4D6341A16FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7E5B226-114C-4747-9277-A4D6341A16FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7E5B226-114C-4747-9277-A4D6341A16FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7E5B226-114C-4747-9277-A4D6341A16FE}.Release|Any CPU.Build.0 = Release|Any CPU + {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D293AFEC-8322-4FEC-8425-143B5FE10D0F}.Release|Any CPU.Build.0 = Release|Any CPU + {FA6A9923-419A-40E1-8A32-30DD906E5025}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA6A9923-419A-40E1-8A32-30DD906E5025}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA6A9923-419A-40E1-8A32-30DD906E5025}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA6A9923-419A-40E1-8A32-30DD906E5025}.Release|Any CPU.Build.0 = Release|Any CPU + {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC87DFFB-EE19-4147-9212-4FAF16D79AD5}.Release|Any CPU.Build.0 = Release|Any CPU + {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DBCE6260-052E-46F9-ACCD-059AA51B8A48}.Release|Any CPU.Build.0 = Release|Any CPU + {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AA3CDF9-D1F6-4A12-BA47-EB721F353701}.Release|Any CPU.Build.0 = Release|Any CPU + {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9}.Release|Any CPU.Build.0 = Release|Any CPU + {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72D44C6C-1BC7-4310-B1A9-1169C0812E33}.Release|Any CPU.Build.0 = Release|Any CPU + {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDBE012E-B48B-4F9D-8CA4-99F6328E9630}.Release|Any CPU.Build.0 = Release|Any CPU + {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3BD05B05-86DE-4680-A7A0-5A326E41E776}.Release|Any CPU.Build.0 = Release|Any CPU + {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53}.Release|Any CPU.Build.0 = Release|Any CPU + {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684}.Release|Any CPU.Build.0 = Release|Any CPU + {CEECE820-1BA9-4E29-8668-25967B3E712B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEECE820-1BA9-4E29-8668-25967B3E712B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CEECE820-1BA9-4E29-8668-25967B3E712B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEECE820-1BA9-4E29-8668-25967B3E712B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {84BE90C4-2FDE-4A48-B154-58926EF24846} = {933DC8A6-8B1F-46BF-9046-4B636AA46469} + {EC5536AB-0613-4CB5-B22B-822A3DBB112A} = {84BE90C4-2FDE-4A48-B154-58926EF24846} + {CF252EDF-C5A1-4030-8666-C78AA0A3B7DE} = {84BE90C4-2FDE-4A48-B154-58926EF24846} + {0C542A8E-54B6-4A20-B7A9-8C7190A0C232} = {84BE90C4-2FDE-4A48-B154-58926EF24846} + {48AF8AC7-5F59-4401-B173-523D37FDD7A8} = {84BE90C4-2FDE-4A48-B154-58926EF24846} + {CFAFFBF1-8C85-4FB2-AB32-B5C17AC7BB5D} = {84BE90C4-2FDE-4A48-B154-58926EF24846} + {ADFBDF2D-0CEC-43C1-8896-75DCE439CF72} = {84BE90C4-2FDE-4A48-B154-58926EF24846} + {D6315791-949B-4501-AA95-50516DE899C1} = {84BE90C4-2FDE-4A48-B154-58926EF24846} + {03466515-77CC-49E4-90E5-9A21EDD0A644} = {84BE90C4-2FDE-4A48-B154-58926EF24846} + {0336306C-285A-4810-9253-5C5F0373992E} = {933DC8A6-8B1F-46BF-9046-4B636AA46469} + {D7E5B226-114C-4747-9277-A4D6341A16FE} = {933DC8A6-8B1F-46BF-9046-4B636AA46469} + {B37F87A8-B5E2-4724-800C-F5D9A91F35C7} = {933DC8A6-8B1F-46BF-9046-4B636AA46469} + {D293AFEC-8322-4FEC-8425-143B5FE10D0F} = {B37F87A8-B5E2-4724-800C-F5D9A91F35C7} + {80828C75-9C5B-442F-86A4-8CE9D85E811C} = {933DC8A6-8B1F-46BF-9046-4B636AA46469} + {FA6A9923-419A-40E1-8A32-30DD906E5025} = {80828C75-9C5B-442F-86A4-8CE9D85E811C} + {43ECF2DB-CBA6-4A31-BD6A-B059CEA03CA0} = {80828C75-9C5B-442F-86A4-8CE9D85E811C} + {CC87DFFB-EE19-4147-9212-4FAF16D79AD5} = {80828C75-9C5B-442F-86A4-8CE9D85E811C} + {DBCE6260-052E-46F9-ACCD-059AA51B8A48} = {80828C75-9C5B-442F-86A4-8CE9D85E811C} + {7AA3CDF9-D1F6-4A12-BA47-EB721F353701} = {80828C75-9C5B-442F-86A4-8CE9D85E811C} + {D7F9BDF7-35B7-4C84-A34E-B940C1763CC9} = {80828C75-9C5B-442F-86A4-8CE9D85E811C} + {72D44C6C-1BC7-4310-B1A9-1169C0812E33} = {80828C75-9C5B-442F-86A4-8CE9D85E811C} + {3BD05B05-86DE-4680-A7A0-5A326E41E776} = {0641F1C8-8518-4C67-B385-832745C063FD} + {98BB2D9F-BFB9-4E70-93D5-7C4C1205BD53} = {0641F1C8-8518-4C67-B385-832745C063FD} + {44BFB1AD-62FB-4B5B-A5A8-E7D04D731684} = {0641F1C8-8518-4C67-B385-832745C063FD} + EndGlobalSection +EndGlobal diff --git a/MxApiExtensions b/MxApiExtensions -Subproject 86e41aa749d961c5731ea52e570cf0f9e8f8d3a +Subproject b4ef05afcfac87ae197ae69bdbae93c3ca4d46b diff --git a/global.json b/global.json index ecc6db8..6d77f62 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.0", + "version": "9.0.0", "rollForward": "latestMajor", "allowPrerelease": true } diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 1abe9e7..7c5086f 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -11,4 +11,5 @@ BASE_DIR=`pwd` rm -rf **/bin/Release cd MatrixUtils.Web dotnet publish -c Release -rsync --delete -raP bin/Release/net8.0/publish/wwwroot/ rory.gay:/data/nginx/html_mru/ +dotnet restore # restore debug deps +rsync --delete -raP bin/Release/net9.0/publish/wwwroot/ rory.gay:/data/nginx/html_mru/ |