about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.gitmodules2
-rw-r--r--.idea/.idea.MatrixRoomUtils/.idea/inspectionProfiles/Project_Default.xml22
-rw-r--r--.idea/.idea.MatrixUtils/.idea/.gitignore (renamed from .idea/.idea.MatrixRoomUtils/.idea/.gitignore)0
-rw-r--r--.idea/.idea.MatrixUtils/.idea/.name1
-rw-r--r--.idea/.idea.MatrixUtils/.idea/avalonia.xml (renamed from .idea/.idea.MatrixRoomUtils/.idea/avalonia.xml)0
-rw-r--r--.idea/.idea.MatrixUtils/.idea/codeStyles/codeStyleConfig.xml (renamed from .idea/.idea.MatrixRoomUtils/.idea/codeStyles/codeStyleConfig.xml)0
-rw-r--r--.idea/.idea.MatrixUtils/.idea/encodings.xml (renamed from .idea/.idea.MatrixRoomUtils/.idea/encodings.xml)0
-rw-r--r--.idea/.idea.MatrixUtils/.idea/indexLayout.xml (renamed from .idea/.idea.MatrixRoomUtils/.idea/indexLayout.xml)1
-rw-r--r--.idea/.idea.MatrixUtils/.idea/inspectionProfiles/Project_Default.xml53
-rw-r--r--.idea/.idea.MatrixUtils/.idea/vcs.xml (renamed from .idea/.idea.MatrixRoomUtils/.idea/vcs.xml)0
-rw-r--r--Benchmarks/.gitignore3
-rw-r--r--Benchmarks/Benchmarks.csproj17
-rw-r--r--Benchmarks/Program.cs303
m---------LibMatrix0
-rw-r--r--MatrixRoomUtils.sln232
-rw-r--r--MatrixUtils.Abstractions/FileStorageProvider.cs1
-rw-r--r--MatrixUtils.Abstractions/MatrixUtils.Abstractions.csproj6
-rw-r--r--MatrixUtils.Abstractions/RoomInfo.cs9
-rw-r--r--MatrixUtils.Desktop/Components/RoomListEntry.axaml.cs8
-rw-r--r--MatrixUtils.Desktop/MatrixUtils.Desktop.csproj18
-rw-r--r--MatrixUtils.DmSpaced/MatrixUtils.DmSpaced.csproj2
-rw-r--r--MatrixUtils.DmSpaced/Program.cs14
-rw-r--r--MatrixUtils.LibDMSpace/DMSpaceRoom.cs2
-rw-r--r--MatrixUtils.LibDMSpace/MatrixUtils.LibDMSpace.csproj4
-rw-r--r--MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs1
-rw-r--r--MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs1
-rw-r--r--MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs1
-rw-r--r--MatrixUtils.Web.Server/MatrixUtils.Web.Server.csproj4
-rw-r--r--MatrixUtils.Web/App.razor3
-rw-r--r--MatrixUtils.Web/Classes/Constants/RoomConstants.cs5
-rw-r--r--MatrixUtils.Web/Classes/LocalStorageProviderService.cs2
-rw-r--r--MatrixUtils.Web/Classes/RMUStorageWrapper.cs138
-rw-r--r--MatrixUtils.Web/Classes/RmuSessionStore.cs260
-rw-r--r--MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs5
-rw-r--r--MatrixUtils.Web/Classes/SessionStorageProviderService.cs2
-rw-r--r--MatrixUtils.Web/MatrixUtils.Web.csproj87
-rw-r--r--MatrixUtils.Web/Pages/About.razor4
-rw-r--r--MatrixUtils.Web/Pages/Dev/DevOptions.razor9
-rw-r--r--MatrixUtils.Web/Pages/Dev/DevUtilities.razor3
-rw-r--r--MatrixUtils.Web/Pages/Dev/ModalTest.razor12
-rw-r--r--MatrixUtils.Web/Pages/Dev/WellKnownRes.razor123
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor10
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor43
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor201
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/Synapse/BackgroundJobs.razor29
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor191
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor.css (renamed from MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor.css)0
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindow.razor5
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor113
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor420
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor.css0
-rw-r--r--MatrixUtils.Web/Pages/HSEInit.razor2
-rw-r--r--MatrixUtils.Web/Pages/Index.razor136
-rw-r--r--MatrixUtils.Web/Pages/InvalidSession.razor50
-rw-r--r--MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor2
-rw-r--r--MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientStatusList.razor2
-rw-r--r--MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor2
-rw-r--r--MatrixUtils.Web/Pages/Labs/Client/Index.razor6
-rw-r--r--MatrixUtils.Web/Pages/Labs/DMSpace/DMSpace.razor2
-rw-r--r--MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor3
-rw-r--r--MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor2
-rw-r--r--MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor2
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2.razor2
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor11
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor2
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor2
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor41
-rw-r--r--MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2SyncContainer.razor6
-rw-r--r--MatrixUtils.Web/Pages/LoginPage.razor60
-rw-r--r--MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor10
-rw-r--r--MatrixUtils.Web/Pages/Rooms/Create.razor12
-rw-r--r--MatrixUtils.Web/Pages/Rooms/Index.razor26
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyList.razor286
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css9
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyList2.razor240
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyList2.razor.css32
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyLists.razor176
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css6
-rw-r--r--MatrixUtils.Web/Pages/Rooms/Space.razor53
-rw-r--r--MatrixUtils.Web/Pages/Rooms/StateEditor.razor4
-rw-r--r--MatrixUtils.Web/Pages/Rooms/StateViewer.razor4
-rw-r--r--MatrixUtils.Web/Pages/Rooms/Timeline.razor4
-rw-r--r--MatrixUtils.Web/Pages/ServerInfo.razor2
-rw-r--r--MatrixUtils.Web/Pages/StreamTest.razor119
-rw-r--r--MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor2
-rw-r--r--MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor2
-rw-r--r--MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor6
-rw-r--r--MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor2
-rw-r--r--MatrixUtils.Web/Pages/Tools/Index.razor2
-rw-r--r--MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor48
-rw-r--r--MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor178
-rw-r--r--MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor16
-rw-r--r--MatrixUtils.Web/Pages/Tools/InviteCounter.razor31
-rw-r--r--MatrixUtils.Web/Pages/Tools/MassCMEBan.razor8
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor138
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor (renamed from MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor)47
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor139
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor192
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor28
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor53
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor694
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor4
-rw-r--r--MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor155
-rw-r--r--MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor4
-rw-r--r--MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor14
-rw-r--r--MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor10
-rw-r--r--MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor4
-rw-r--r--MatrixUtils.Web/Pages/User/DMManager.razor4
-rw-r--r--MatrixUtils.Web/Pages/User/Profile.razor97
-rw-r--r--MatrixUtils.Web/Program.cs19
-rw-r--r--MatrixUtils.Web/Properties/launchSettings.json2
-rw-r--r--MatrixUtils.Web/Shared/InlineUserItem.razor5
-rw-r--r--MatrixUtils.Web/Shared/MainLayout.razor12
-rw-r--r--MatrixUtils.Web/Shared/MainLayout.razor.css1
-rw-r--r--MatrixUtils.Web/Shared/MxcAvatar.razor49
-rw-r--r--MatrixUtils.Web/Shared/MxcImage.razor78
-rw-r--r--MatrixUtils.Web/Shared/NavMenu.razor6
-rw-r--r--MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor102
-rw-r--r--MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor60
-rw-r--r--MatrixUtils.Web/Shared/RoomList.razor5
-rw-r--r--MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor13
-rw-r--r--MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor11
-rw-r--r--MatrixUtils.Web/Shared/RoomListItem.razor53
-rw-r--r--MatrixUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor2
-rw-r--r--MatrixUtils.Web/Shared/TimelineComponents/TimelineCanonicalAliasItem.razor2
-rw-r--r--MatrixUtils.Web/Shared/TimelineComponents/TimelineHistoryVisibilityItem.razor2
-rw-r--r--MatrixUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor2
-rw-r--r--MatrixUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor2
-rw-r--r--MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor2
-rw-r--r--MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomNameItem.razor2
-rw-r--r--MatrixUtils.Web/Shared/TimelineComponents/TimelineRoomTopicItem.razor2
-rw-r--r--MatrixUtils.Web/Shared/UserListItem.razor7
-rw-r--r--MatrixUtils.Web/_Imports.razor11
-rw-r--r--MatrixUtils.Web/wwwroot/index.html16
-rw-r--r--MatrixUtils.sln215
m---------MxApiExtensions0
-rw-r--r--global.json2
-rwxr-xr-xscripts/deploy.sh3
139 files changed, 4674 insertions, 1577 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/.gitmodules b/.gitmodules
index 8cbedc0..487c63b 100644 --- a/.gitmodules +++ b/.gitmodules
@@ -1,6 +1,6 @@ [submodule "LibMatrix"] path = LibMatrix - url = https://git.rory.gay/matrix/LibMatrix.git + url = https://cgit.rory.gay/matrix/LibMatrix.git [submodule "MxApiExtensions"] path = MxApiExtensions url = https://cgit.rory.gay/matrix/tools/MxApiExtensions.git 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..d166ec4 100644 --- a/.idea/.idea.MatrixRoomUtils/.idea/indexLayout.xml +++ b/.idea/.idea.MatrixUtils/.idea/indexLayout.xml
@@ -2,6 +2,7 @@ <project version="4"> <component name="UserContentModel"> <attachedFolders> + <Path>LibMatrix/LibMatrix/Homeservers</Path> <Path>MatrixRoomUtils.Bot/bot_data</Path> <Path>MatrixRoomUtils.Desktop/bin/Debug/net7.0/mru-desktop</Path> </attachedFolders> 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..90d004a --- /dev/null +++ b/Benchmarks/Program.cs
@@ -0,0 +1,303 @@ +// See https://aka.ms/new-console-template for more information + +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +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 + 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 cacabe2b1a15bb7492e23d477ec653513e84d26 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..4b2a53c 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; @@ -13,13 +12,13 @@ namespace MatrixUtils.Abstractions; public class RoomInfo : NotifyPropertyChanged { public RoomInfo(GenericRoom room) { Room = room; - _fallbackIcon = identiconGenerator.GenerateAsDataUri(room.RoomId); + // _fallbackIcon = identiconGenerator.GenerateAsDataUri(room.RoomId); RegisterEventListener(); } public RoomInfo(GenericRoom room, List<StateEventResponse>? stateEvents) { Room = room; - _fallbackIcon = identiconGenerator.GenerateAsDataUri(room.RoomId); + // _fallbackIcon = identiconGenerator.GenerateAsDataUri(room.RoomId); if (stateEvents is { Count: > 0 }) StateEvents = new(stateEvents!); RegisterEventListener(); ProcessNewItems(stateEvents!); @@ -30,7 +29,7 @@ public class RoomInfo : NotifyPropertyChanged { public ObservableCollection<StateEventResponse?> Timeline { get; private set; } = new(); private static ConcurrentBag<AuthenticatedHomeserverGeneric> homeserversWithoutEventFormatSupport = new(); - private static SvgIdenticonGenerator identiconGenerator = new(); + // private static SvgIdenticonGenerator identiconGenerator = new(); public async Task<StateEventResponse?> GetStateEvent(string type, string stateKey = "") { if (homeserversWithoutEventFormatSupport.Contains(Room.Homeserver)) return await GetStateEventForged(type, stateKey); @@ -96,7 +95,7 @@ public class RoomInfo : NotifyPropertyChanged { } public string? RoomIcon { - get => _roomIcon ?? _fallbackIcon; + get => _roomIcon; set => SetField(ref _roomIcon, value); } 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/LocalStorageProviderService.cs b/MatrixUtils.Web/Classes/LocalStorageProviderService.cs
index 3803a17..0e99cd4 100644 --- a/MatrixUtils.Web/Classes/LocalStorageProviderService.cs +++ b/MatrixUtils.Web/Classes/LocalStorageProviderService.cs
@@ -22,7 +22,7 @@ public class LocalStorageProviderService : IStorageProvider { async Task<bool> IStorageProvider.ObjectExistsAsync(string key) => await _localStorageService.ContainKeyAsync(key); - async Task<List<string>> IStorageProvider.GetAllKeysAsync() => (await _localStorageService.KeysAsync()).ToList(); + async Task<IEnumerable<string>> IStorageProvider.GetAllKeysAsync() => (await _localStorageService.KeysAsync()).ToList(); async Task IStorageProvider.DeleteObjectAsync(string key) => await _localStorageService.RemoveItemAsync(key); } diff --git a/MatrixUtils.Web/Classes/RMUStorageWrapper.cs b/MatrixUtils.Web/Classes/RMUStorageWrapper.cs deleted file mode 100644
index 45028ba..0000000 --- a/MatrixUtils.Web/Classes/RMUStorageWrapper.cs +++ /dev/null
@@ -1,138 +0,0 @@ -using LibMatrix; -using LibMatrix.Homeservers; -using LibMatrix.Services; -using Microsoft.AspNetCore.Components; - -namespace MatrixUtils.Web.Classes; - -public class RMUStorageWrapper(ILogger<RMUStorageWrapper> logger, TieredStorageService storageService, HomeserverProviderService homeserverProviderService, NavigationManager navigationManager) { - public async Task<List<UserAuth>?> GetAllTokens() { - logger.LogTrace("Getting all tokens."); - return await storageService.DataStorageProvider.LoadObjectAsync<List<UserAuth>>("rmu.tokens") ?? - new List<UserAuth>(); - } - - public async Task<UserAuth?> GetCurrentToken() { - logger.LogTrace("Getting current token."); - var currentToken = await storageService.DataStorageProvider.LoadObjectAsync<UserAuth>("rmu.token"); - var allTokens = await GetAllTokens(); - if (allTokens is null or { Count: 0 }) { - await SetCurrentToken(null); - return null; - } - - if (currentToken is null) { - await SetCurrentToken(currentToken = allTokens[0]); - } - - if (!allTokens.Any(x => x.AccessToken == currentToken.AccessToken)) { - await SetCurrentToken(currentToken = allTokens[0]); - } - - return currentToken; - } - - public async Task AddToken(UserAuth UserAuth) { - logger.LogTrace("Adding token."); - var tokens = await GetAllTokens() ?? new List<UserAuth>(); - - tokens.Add(UserAuth); - await storageService.DataStorageProvider.SaveObjectAsync("rmu.tokens", tokens); - } - - private async Task<AuthenticatedHomeserverGeneric?> GetCurrentSession() { - logger.LogTrace("Getting current session."); - var token = await GetCurrentToken(); - if (token == null) { - return null; - } - - return await homeserverProviderService.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy); - } - - public async Task<AuthenticatedHomeserverGeneric?> GetSession(UserAuth userAuth) { - logger.LogTrace("Getting session."); - return await homeserverProviderService.GetAuthenticatedWithToken(userAuth.Homeserver, userAuth.AccessToken, userAuth.Proxy); - } - - public async Task<AuthenticatedHomeserverGeneric?> GetCurrentSessionOrNavigate() { - logger.LogTrace("Getting current session or navigating."); - AuthenticatedHomeserverGeneric? session = null; - - try { - //catch if the token is invalid - session = await GetCurrentSession(); - } - catch (MatrixException e) { - if (e.ErrorCode == "M_UNKNOWN_TOKEN") { - var token = await GetCurrentToken(); - logger.LogWarning("Encountered invalid token for {user} on {homeserver}", token.UserId, token.Homeserver); - navigationManager.NavigateTo("/InvalidSession?ctx=" + token.AccessToken); - return null; - } - - throw; - } - - if (session is null) { - logger.LogInformation("No session found. Navigating to login."); - navigationManager.NavigateTo("/Login"); - } - - return session; - } - - public class Settings { - public DeveloperSettings DeveloperSettings { get; set; } = new(); - } - - public class DeveloperSettings { - public bool EnableLogViewers { get; set; } - public bool EnableConsoleLogging { get; set; } = true; - public bool EnablePortableDevtools { get; set; } - } - - public async Task RemoveToken(UserAuth auth) { - logger.LogTrace("Removing token."); - var tokens = await GetAllTokens(); - if (tokens == null) { - return; - } - - tokens.RemoveAll(x => x.AccessToken == auth.AccessToken); - await storageService.DataStorageProvider.SaveObjectAsync("rmu.tokens", tokens); - } - - public async Task SetCurrentToken(UserAuth? auth) { - logger.LogTrace("Setting current token."); - await storageService.DataStorageProvider.SaveObjectAsync("rmu.token", auth); - } - - public async Task MigrateFromMRU() { - logger.LogInformation("Migrating from MRU token namespace!"); - var dsp = storageService.DataStorageProvider!; - if(await dsp.ObjectExistsAsync("token")) { - var oldToken = await dsp.LoadObjectAsync<UserAuth>("token"); - if (oldToken != null) { - await dsp.SaveObjectAsync("rmu.token", oldToken); - await dsp.DeleteObjectAsync("tokens"); - } - } - - if(await dsp.ObjectExistsAsync("tokens")) { - var oldTokens = await dsp.LoadObjectAsync<List<UserAuth>>("tokens"); - if (oldTokens != null) { - await dsp.SaveObjectAsync("rmu.tokens", oldTokens); - await dsp.DeleteObjectAsync("tokens"); - } - } - - if(await dsp.ObjectExistsAsync("mru.tokens")) { - var oldTokens = await dsp.LoadObjectAsync<List<UserAuth>>("mru.tokens"); - if (oldTokens != null) { - await dsp.SaveObjectAsync("rmu.tokens", oldTokens); - await dsp.DeleteObjectAsync("mru.tokens"); - } - } - } -} diff --git a/MatrixUtils.Web/Classes/RmuSessionStore.cs b/MatrixUtils.Web/Classes/RmuSessionStore.cs new file mode 100644
index 0000000..746f68a --- /dev/null +++ b/MatrixUtils.Web/Classes/RmuSessionStore.cs
@@ -0,0 +1,260 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using LibMatrix; +using LibMatrix.Homeservers; +using LibMatrix.Services; +using Microsoft.AspNetCore.Components; + +namespace MatrixUtils.Web.Classes; + +public class RmuSessionStore( + ILogger<RmuSessionStore> logger, + TieredStorageService storageService, + HomeserverProviderService homeserverProviderService, + NavigationManager navigationManager) { + private SessionInfo? CurrentSession { get; set; } + private Dictionary<string, SessionInfo> SessionCache { get; set; } = []; + + private bool _isInitialized; + private static readonly SemaphoreSlim InitSemaphore = new(1, 1); + +#region Sessions + + public async Task<Dictionary<string, SessionInfo>> GetAllSessions() { + await LoadStorage(); + logger.LogTrace("Getting all tokens."); + return SessionCache; + } + + public async Task<SessionInfo?> GetSession(string sessionId) { + await LoadStorage(); + if (SessionCache.TryGetValue(sessionId, out var cachedSession)) + return cachedSession; + + logger.LogWarning("Session {sessionId} not found in all tokens.", sessionId); + return null; + } + + public async Task<SessionInfo?> GetCurrentSession(bool log = true) { + await LoadStorage(); + if (log) logger.LogTrace("Getting current token."); + if (CurrentSession is not null) return CurrentSession; + + var currentSessionId = await storageService.DataStorageProvider!.LoadObjectAsync<string>("rmu.session"); + return await GetSession(currentSessionId); + } + + public async Task<string> AddSession(UserAuth auth) { + await LoadStorage(); + logger.LogTrace("Adding token."); + + var sessionId = auth.GetHashCode().ToString(); + SessionCache[sessionId] = new() { + Auth = auth, + SessionId = sessionId + }; + + if (CurrentSession == null) await SetCurrentSession(sessionId); + else await SaveStorage(); + + return sessionId; + } + + public async Task RemoveSession(string sessionId) { + await LoadStorage(); + logger.LogTrace("Removing session {sessionId}.", sessionId); + var tokens = await GetAllSessions(); + if (tokens == null) { + return; + } + + if ((await GetCurrentSession())?.SessionId == sessionId) + await SetCurrentSession(tokens.First(x => x.Key != sessionId).Key); + + if (tokens.Remove(sessionId)) + await SaveStorage(); + } + + public async Task SetCurrentSession(string? sessionId) { + await LoadStorage(); + logger.LogTrace("Setting current session to {sessionId}.", sessionId); + CurrentSession = await GetSession(sessionId); + await SaveStorage(); + } + +#endregion + +#region Homeservers + + public async Task<AuthenticatedHomeserverGeneric?> GetHomeserver(string session, bool log = true) { + await LoadStorage(); + if (log) logger.LogTrace("Getting session."); + if (!SessionCache.TryGetValue(session, out var cachedSession)) return null; + if (cachedSession.Homeserver is not null) return cachedSession.Homeserver; + + try { + cachedSession.Homeserver = + await homeserverProviderService.GetAuthenticatedWithToken(cachedSession.Auth.Homeserver, cachedSession.Auth.AccessToken, cachedSession.Auth.Proxy); + } + catch (Exception e) { + logger.LogError("Failed to get info for {0} via {1}: {2}", cachedSession.Auth.UserId, cachedSession.Auth.Homeserver, e); + logger.LogError("Continuing with server-less session"); + cachedSession.Homeserver = await homeserverProviderService.GetAuthenticatedWithToken(cachedSession.Auth.Homeserver, cachedSession.Auth.AccessToken, + cachedSession.Auth.Proxy, useGeneric: true, enableServer: false); + } + + return cachedSession.Homeserver; + } + + public async Task<AuthenticatedHomeserverGeneric?> GetCurrentHomeserver(bool log = true, bool navigateOnFailure = false) { + await LoadStorage(); + if (log) logger.LogTrace("Getting current session."); + if (CurrentSession?.Homeserver is not null) return CurrentSession.Homeserver; + + var currentSession = CurrentSession ??= await GetCurrentSession(log: false); + if (currentSession == null) { + if (navigateOnFailure) { + logger.LogInformation("No session found. Navigating to login."); + navigationManager.NavigateTo("/Login"); + } + + return null; + } + + try { + return currentSession.Homeserver ??= await GetHomeserver(currentSession.SessionId); + } + catch (MatrixException e) { + if (e.ErrorCode == "M_UNKNOWN_TOKEN" && navigateOnFailure) { + logger.LogWarning("Encountered invalid token for {user} on {homeserver}", currentSession.Auth.UserId, currentSession.Auth.Homeserver); + if (navigateOnFailure) { + navigationManager.NavigateTo("/InvalidSession?ctx=" + currentSession.SessionId); + } + } + + throw; + } + } + +#endregion + +#region Storage + + private async Task LoadStorage(bool hasMigrated = false) { + if (!await storageService.DataStorageProvider!.ObjectExistsAsync("rmu.sessions") || !await storageService.DataStorageProvider.ObjectExistsAsync("rmu.session")) { + if (!hasMigrated) { + await RunMigrations(); + await LoadStorage(true); + } + else + logger.LogWarning("No sessions found in storage."); + + return; + } + + SessionCache = (await storageService.DataStorageProvider.LoadObjectAsync<Dictionary<string, UserAuth>>("rmu.sessions") ?? throw new Exception("Failed to load sessions")) + .ToDictionary(x => x.Key, x => new SessionInfo { + SessionId = x.Key, + Auth = x.Value + }); + + var currentSessionId = await storageService.DataStorageProvider.LoadObjectAsync<string>("rmu.session"); + if (currentSessionId == null) { + logger.LogWarning("No current session found in storage."); + return; + } + + if (!SessionCache.TryGetValue(currentSessionId, out var currentSession)) { + logger.LogWarning("Current session {currentSessionId} not found in storage.", currentSessionId); + return; + } + + CurrentSession = currentSession; + } + + private async Task SaveStorage() { + await storageService.DataStorageProvider!.SaveObjectAsync("rmu.sessions", + SessionCache.ToDictionary( + x => x.Key, + x => x.Value.Auth + ) + ); + await storageService.DataStorageProvider.SaveObjectAsync("rmu.session", CurrentSession?.SessionId); + } + +#endregion + +#region Migrations + + public async Task RunMigrations() { + await MigrateFromMru(); + await MigrateAccountsToKeyedStorage(); + } + + private async Task MigrateFromMru() { + logger.LogInformation("Migrating from MRU token namespace!"); + var dsp = storageService.DataStorageProvider!; + if (await dsp.ObjectExistsAsync("token")) { + var oldToken = await dsp.LoadObjectAsync<UserAuth>("token"); + if (oldToken != null) { + await dsp.SaveObjectAsync("rmu.token", oldToken); + await dsp.DeleteObjectAsync("token"); + } + } + + if (await dsp.ObjectExistsAsync("tokens")) { + var oldTokens = await dsp.LoadObjectAsync<List<UserAuth>>("tokens"); + if (oldTokens != null) { + await dsp.SaveObjectAsync("rmu.tokens", oldTokens); + await dsp.DeleteObjectAsync("tokens"); + } + } + + if (await dsp.ObjectExistsAsync("mru.tokens")) { + var oldTokens = await dsp.LoadObjectAsync<List<UserAuth>>("mru.tokens"); + if (oldTokens != null) { + await dsp.SaveObjectAsync("rmu.tokens", oldTokens); + await dsp.DeleteObjectAsync("mru.tokens"); + } + } + } + + private async Task MigrateAccountsToKeyedStorage() { + var dsp = storageService.DataStorageProvider!; + if (!await dsp.ObjectExistsAsync("rmu.tokens")) return; + logger.LogInformation("Migrating accounts to keyed storage!"); + var tokens = await dsp.LoadObjectAsync<UserAuth[]>("rmu.tokens") ?? throw new Exception("Failed to load tokens"); + Dictionary<string, UserAuth> keyedTokens = tokens.ToDictionary(x => x.GetHashCode().ToString(), x => x); + + if (await dsp.ObjectExistsAsync("rmu.token")) { + var token = await dsp.LoadObjectAsync<UserAuth>("rmu.token") ?? throw new Exception("Failed to load token"); + var sessionId = keyedTokens.FirstOrDefault(x => x.Value.Equals(token)).Key; + + if (sessionId is null) keyedTokens.Add(sessionId = token.GetHashCode().ToString(), token); + await dsp.SaveObjectAsync("rmu.session", sessionId); + + await dsp.DeleteObjectAsync("rmu.token"); + } + + await dsp.SaveObjectAsync("rmu.sessions", keyedTokens); + await dsp.DeleteObjectAsync("rmu.tokens"); + } + +#endregion + + public class Settings { + public DeveloperSettings DeveloperSettings { get; set; } = new(); + } + + public class DeveloperSettings { + public bool EnableLogViewers { get; set; } + public bool EnableConsoleLogging { get; set; } = true; + public bool EnablePortableDevtools { get; set; } + } + + public class SessionInfo { + public required string SessionId { get; set; } + public required UserAuth Auth { get; set; } + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + } +} \ No newline at end of file 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/Classes/SessionStorageProviderService.cs b/MatrixUtils.Web/Classes/SessionStorageProviderService.cs
index ae0bb79..da169de 100644 --- a/MatrixUtils.Web/Classes/SessionStorageProviderService.cs +++ b/MatrixUtils.Web/Classes/SessionStorageProviderService.cs
@@ -22,7 +22,7 @@ public class SessionStorageProviderService : IStorageProvider { async Task<bool> IStorageProvider.ObjectExistsAsync(string key) => await _sessionStorageService.ContainKeyAsync(key); - async Task<List<string>> IStorageProvider.GetAllKeysAsync() => (await _sessionStorageService.KeysAsync()).ToList(); + async Task<IEnumerable<string>> IStorageProvider.GetAllKeysAsync() => (await _sessionStorageService.KeysAsync()).ToList(); async Task IStorageProvider.DeleteObjectAsync(string key) => await _sessionStorageService.RemoveItemAsync(key); } diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj
index 8760e7a..aa9f37f 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,65 @@ <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> &lt;!&ndash; Browser != MacOS &ndash;&gt;--> +<!-- <MetadataUpdaterSupport>false</MetadataUpdaterSupport> &lt;!&ndash; Unreliable &ndash;&gt;--> +<!-- <DebuggerSupport>false</DebuggerSupport> &lt;!&ndash; Unreliable &ndash;&gt;--> +<!-- <InvariantGlobalization>true</InvariantGlobalization> &lt;!&ndash; invariant globalization is fine &ndash;&gt;--> +<!-- &lt;!&ndash; unused features &ndash;&gt;--> +<!-- <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.AspNetCore.WebUtilities" Version="9.0.2" /> + <PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="9.0.1" /> + <PackageReference Include="SpawnDev.BlazorJS.WebWorkers" Version="2.5.39" /> </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..33e577f 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 @@ -20,6 +19,10 @@ <span>Export local storage: </span> <button @onclick="@ExportLocalStorage">Export</button> </p> +<details> + <summary>Manage local sessions</summary> + +</details> @if (userSettings is not null) { <InputCheckbox @bind-Value="@userSettings.DeveloperSettings.EnableLogViewers" @oninput="@LogStuff"></InputCheckbox> @@ -36,9 +39,9 @@ @code { - private RMUStorageWrapper.Settings? userSettings { get; set; } + private RmuSessionStore.Settings? userSettings { get; set; } protected override async Task OnInitializedAsync() { - // userSettings = await TieredStorage.DataStorageProvider.LoadObjectAsync<RMUStorageWrapper.Settings>("rmu.settings"); + // userSettings = await TieredStorage.DataStorageProvider.LoadObjectAsync<RmuSessionStore.Settings>("rmu.settings"); await base.OnInitializedAsync(); } diff --git a/MatrixUtils.Web/Pages/Dev/DevUtilities.razor b/MatrixUtils.Web/Pages/Dev/DevUtilities.razor
index bf5a396..3b2d533 100644 --- a/MatrixUtils.Web/Pages/Dev/DevUtilities.razor +++ b/MatrixUtils.Web/Pages/Dev/DevUtilities.razor
@@ -4,6 +4,7 @@ <h3>Debug Tools</h3> <hr/> +<LinkButton href="/Dev/WellKnownRes">Well known res tests</LinkButton> @if (Rooms.Count == 0) { <p>You are not in any rooms!</p> @* <p>Loading progress: @checkedRoomCount/@totalRoomCount</p> *@ @@ -38,7 +39,7 @@ else { protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - hs = await RMUStorage.GetCurrentSessionOrNavigate(); + hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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/Dev/WellKnownRes.razor b/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor new file mode 100644
index 0000000..1906dd8 --- /dev/null +++ b/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor
@@ -0,0 +1,123 @@ +@page "/Dev/WellKnownRes" +@using ArcaneLibs.Extensions +@using LibMatrix.Services.WellKnownResolver +@using LibMatrix.Services.WellKnownResolver.WellKnownResolvers +@inject HomeserverResolverService legacyResolver +@inject WellKnownResolverService rewriteResolver +@inject ClientWellKnownResolver rewriteClientResolver +<h3>Known Homeserver List</h3> +<hr/> + +<span>Room ID: <FancyTextBox @bind-Value="@RoomId"/><LinkButton OnClick="@Execute">Execute</LinkButton></span> + +<span>Stats:</span><br/> +<span>Server count: @entries.Count</span><br/> +<span>Client server resolution rate (N/O/T): @entries.Count(x => x.HasClientWellKnown)/@entries.Count(x => !string.IsNullOrWhiteSpace(x.LegacyResolutionResult?.Client))/@entries.Count</span> +<br/> +<span>Server server resolution rate (N/T): @entries.Count(x => x.HasServerWellKnown)/@entries.Count</span><br/> +<span>Support resolution rate (N/T): @entries.Count(x => x.HasSupportWellKnown)/@entries.Count</span><br/> + +<table class="table-bordered"> + <thead> + <td>Homeserver</td> + <td>Client API</td> + <td>Server API</td> + <td>Has support record</td> + </thead> + @foreach (var entry in entries) { + <tr> + <td>@entry.Homeserver</td> + <td style="background-color: @GetClientColor(entry)"> + <span>L: @entry.LegacyResolutionResult?.Client</span><br/> + <span>R: @entry.WellKnownResolutionResult?.ClientWellKnown?.Content?.Homeserver.BaseUrl</span> + </td> + <td style="background-color: @GetServerColor(entry)"> + <span>L: @entry.LegacyResolutionResult?.Server</span><br/> + <span>R: @entry.WellKnownResolutionResult?.ServerWellKnown?.Content?.Homeserver</span> + </td> + <td>@(entry.HasSupportWellKnown ? "Y" : "X")</td> + </tr> + <tr> + <td colspan="6"> + <details> + <pre>@(entry.WellKnownResolutionResult?.ToJson() ?? "null")</pre> + </details> + </td> + </tr> + } +</table> + +@code { + private List<TableEntry> entries = new(); + + [SupplyParameterFromQuery] + public string? RoomId { get; set; } + + AuthenticatedHomeserverGeneric? hs { get; set; } + + protected override async Task OnInitializedAsync() { + hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + if (hs is null) return; + + if (RoomId is not null) { + await Execute(); + } + } + + private class TableEntry { + public required string Homeserver { get; set; } + public HomeserverResolverService.WellKnownUris? LegacyResolutionResult { get; set; } + public WellKnownResolverService.WellKnownRecords? WellKnownResolutionResult { get; set; } + + public bool HasClientWellKnown => WellKnownResolutionResult?.ClientWellKnown is { Content.Homeserver.BaseUrl: { Length: > 0 } }; + public bool HasServerWellKnown => WellKnownResolutionResult?.ServerWellKnown is { Content.Homeserver.Length: > 0 }; + public bool HasSupportWellKnown => WellKnownResolutionResult?.SupportWellKnown?.Content is not null and not { SupportPage: null, Contacts: null or { Count: 0 } }; + } + + private async Task Execute() { + var members = await hs!.GetRoom(RoomId!).GetMembersListAsync(); + var homeservers = members.Select(x => x.StateKey!.Split(':', 2)[1]).Distinct().ToList(); + var entries = new List<TableEntry>(); + foreach (var homeserver in homeservers) { + var e = new TableEntry() { Homeserver = homeserver }; + _ = TryResolveLegacy(e); + _ = TryFullResolveRewrite(e); + entries.Add(e); + } + + this.entries = entries; + StateHasChanged(); + } + + private async Task TryResolveLegacy(TableEntry entry) { + try { + var cTask = legacyResolver.ResolveHomeserverFromWellKnown(entry.Homeserver, enableServer: false); + var sTask = legacyResolver.ResolveHomeserverFromWellKnown(entry.Homeserver, enableClient: false); + entry.LegacyResolutionResult = (await cTask); + entry.LegacyResolutionResult.Server = (await sTask).Server; + StateHasChanged(); + } + catch { } + } + + private async Task TryFullResolveRewrite(TableEntry entry) { + try { + entry.WellKnownResolutionResult = await rewriteResolver.TryResolveWellKnownRecords(entry.Homeserver); + StateHasChanged(); + } + catch { } + } + + private string GetClientColor(TableEntry entry) { + if (entry.LegacyResolutionResult?.Client == entry.WellKnownResolutionResult?.ClientWellKnown?.Content?.Homeserver?.BaseUrl && entry.WellKnownResolutionResult?.ClientWellKnown?.Content?.Homeserver?.BaseUrl == null) return "#333333"; + if (entry.LegacyResolutionResult?.Client == entry.WellKnownResolutionResult?.ClientWellKnown?.Content?.Homeserver?.BaseUrl?.TrimEnd('/')) return "#008800"; + return "#ff0000"; + } + + private string GetServerColor(TableEntry entry) { + if (entry.LegacyResolutionResult?.Server == entry.WellKnownResolutionResult?.ServerWellKnown?.Content?.Homeserver && entry.WellKnownResolutionResult?.ServerWellKnown?.Content?.Homeserver == null) return "#333333"; + if (entry.LegacyResolutionResult?.Server == entry.WellKnownResolutionResult?.ServerWellKnown?.Content?.Homeserver.TrimEnd('/')) return "#008800"; + return "#ff0000"; + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor
index 9c61431..e1b46e2 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor
@@ -10,7 +10,13 @@ else { @if (Homeserver is AuthenticatedHomeserverSynapse) { <h4>Synapse tools</h4> <hr/> - <a href="/HSAdmin/RoomQuery">Query rooms</a> + <a href="/HSAdmin/Synapse/RoomQuery">Query rooms</a><br/> + <a href="/HSAdmin/Synapse/BlockMedia">Block media</a> + } + else if (Homeserver is AuthenticatedHomeserverHSE) { + <h4>Rory&amp;::LibMatrix.HomeserverEmulator tools</h4> + <hr/> + <a href="/HSAdmin/HSE/ManageExternalProfiles">Manage external profiles</a> } else { <p>Homeserver type @Homeserver.GetType().Name does not have any administration tools in RMU.</p> @@ -24,7 +30,7 @@ else { public ServerVersionResponse? ServerVersionResponse { get; set; } protected override async Task OnInitializedAsync() { - Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); if (Homeserver is null) return; ServerVersionResponse = await (Homeserver.FederationClient?.GetServerVersionAsync() ?? Task.FromResult<ServerVersionResponse?>(null)); await base.OnInitializedAsync(); diff --git a/MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor b/MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor new file mode 100644
index 0000000..87600c6 --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor
@@ -0,0 +1,43 @@ +@page "/HSAdmin/HSE/ManageExternalProfiles" +@using ArcaneLibs.Extensions +@using LibMatrix.Responses +<h3>Manage external profiles</h3> + +<LinkButton OnClick="AddAllLocalProfiles">Add local sessions</LinkButton> + +@foreach(var p in ExternalProfiles) +{ + <h4>@p.Key</h4> + <pre>@p.Value.ToJson(indent: true)</pre> +} + +@code { + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + private Dictionary<string, LoginResponse> ExternalProfiles = new(); + + protected override async Task OnInitializedAsync() + { + Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + if (Homeserver is null) return; + await LoadProfiles(); + await base.OnInitializedAsync(); + } + + private async Task LoadProfiles() { + if(Homeserver is AuthenticatedHomeserverHSE hse) + { + ExternalProfiles = await hse.GetExternalProfilesAsync(); + } + StateHasChanged(); + } + + private async Task AddAllLocalProfiles() { + if(Homeserver is AuthenticatedHomeserverHSE hse) { + var sessions = await sessionStore.GetAllSessions(); + foreach(var session in sessions) { + await hse.SetExternalProfile(session.Value.Auth.UserId, session.Value.Auth); + } + await LoadProfiles(); + } + } +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor b/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor deleted file mode 100644
index 11df261..0000000 --- a/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor +++ /dev/null
@@ -1,201 +0,0 @@ -@page "/HSAdmin/RoomQuery" -@using LibMatrix.Responses.Admin -@using LibMatrix.Filters -@using ArcaneLibs.Extensions - -<h3>Homeserver Administration - Room Query</h3> - -<label>Search name: </label> -<InputText @bind-Value="SearchTerm"/><br/> -<label>Order by: </label> -<select @bind="OrderBy"> - @foreach (var item in validOrderBy) { - <option value="@item.Key">@item.Value</option> - } -</select><br/> -<label>Ascending: </label> -<InputCheckbox @bind-Value="Ascending"/><br/> -<details> - <summary> - <span>Local filtering (slow)</span> - - </summary> - <div style="margin-left: 8px; margin-bottom: 8px;"> - <u style="display: block;">String contains</u> - <span class="tile tile280">Room ID: <FancyTextBox @bind-Value="@Filter.RoomIdContains"></FancyTextBox></span> - <span class="tile tile280">Room name: <FancyTextBox @bind-Value="@Filter.NameContains"></FancyTextBox></span> - <span class="tile tile280">Canonical alias: <FancyTextBox @bind-Value="@Filter.CanonicalAliasContains"></FancyTextBox></span> - <span class="tile tile280">Creator: <FancyTextBox @bind-Value="@Filter.CreatorContains"></FancyTextBox></span> - <span class="tile tile280">Room version: <FancyTextBox @bind-Value="@Filter.VersionContains"></FancyTextBox></span> - <span class="tile tile280">Encryption algorithm: <FancyTextBox @bind-Value="@Filter.EncryptionContains"></FancyTextBox></span> - <span class="tile tile280">Join rules: <FancyTextBox @bind-Value="@Filter.JoinRulesContains"></FancyTextBox></span> - <span class="tile tile280">Guest access: <FancyTextBox @bind-Value="@Filter.GuestAccessContains"></FancyTextBox></span> - <span class="tile tile280">History visibility: <FancyTextBox @bind-Value="@Filter.HistoryVisibilityContains"></FancyTextBox></span> - - <u style="display: block;">Optional checks</u> - <span class="tile tile150"> - <InputCheckbox @bind-Value="@Filter.CheckFederation"></InputCheckbox> Is federated: - @if (Filter.CheckFederation) { - <InputCheckbox @bind-Value="@Filter.Federatable"></InputCheckbox> - } - </span> - <span class="tile tile150"> - <InputCheckbox @bind-Value="@Filter.CheckPublic"></InputCheckbox> Is public: - @if (Filter.CheckPublic) { - <InputCheckbox @bind-Value="@Filter.Public"></InputCheckbox> - } - </span> - - <u style="display: block;">Ranges</u> - <span class="tile center-children"> - <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEventsGreaterThan"></InputNumber><span class="range-sep">state events</span><InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEventsLessThan"></InputNumber> - </span> - <span class="tile center-children"> - <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembersGreaterThan"></InputNumber><span class="range-sep">members</span><InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembersLessThan"></InputNumber> - </span> - <span class="tile center-children"> - <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedLocalMembersGreaterThan"></InputNumber><span class="range-sep">local members</span><InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedLocalMembersLessThan"></InputNumber> - </span> - </div> -</details> -<button class="btn btn-primary" @onclick="Search">Search</button> -<br/> - -@if (Results.Count > 0) { - <p>Found @Results.Count rooms</p> - <details> - <summary>TSV data (copy/paste)</summary> - <pre style="font-size: 0.6em;"> - <table> - @foreach (var res in Results) { - <tr> - <td style="padding: 8px;">@res.RoomId@("\t")</td> - <td style="padding: 8px;">@res.CanonicalAlias@("\t")</td> - <td style="padding: 8px;">@res.Creator@("\t")</td> - <td style="padding: 8px;">@res.Name</td> - </tr> - } - </table> - </pre> - </details> -} - -@foreach (var res in Results) { - <div style="background-color: #ffffff11; border-radius: 0.5em; display: block; margin-top: 4px; padding: 4px;"> - @* <RoomListItem RoomName="@res.Name" RoomId="@res.RoomId"></RoomListItem> *@ - <p> - @if (!string.IsNullOrWhiteSpace(res.CanonicalAlias)) { - <span>@res.CanonicalAlias - @res.RoomId (@res.Name)</span> - <br/> - } - else { - <span>@res.RoomId (@res.Name)</span> - <br/> - } - @if (!string.IsNullOrWhiteSpace(res.Creator)) { - @* <span>Created by <InlineUserItem UserId="@res.Creator"></InlineUserItem></span> *@ - <span>Created by @res.Creator</span> - <br/> - } - </p> - <span>@res.StateEvents state events</span><br/> - <span>@res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server</span> - <details> - <summary>Full result data</summary> - <pre>@res.ToJson(ignoreNull: true)</pre> - </details> - </div> -} - -<style> - .int-input { - width: 128px; - } - .tile { - display: inline-block; - padding: 4px; - border: 1px solid #ffffff22; - } - .tile280 { - min-width: 280px; - } - .tile150 { - min-width: 150px; - } - .range-sep { - display: inline-block; - padding: 4px; - width: 150px; - } - .range-sep::before { - content: "@("<") "; - } - .range-sep::after { - content: " @("<")"; - } - .center-children { - text-align: center; - } -</style> - -@code { - - [Parameter] - [SupplyParameterFromQuery(Name = "order_by")] - public string? OrderBy { get; set; } - - [Parameter] - [SupplyParameterFromQuery(Name = "name_search")] - public string SearchTerm { get; set; } - - [Parameter] - [SupplyParameterFromQuery(Name = "ascending")] - public bool Ascending { get; set; } - - public List<AdminRoomListingResult.AdminRoomListingResultRoom> Results { get; set; } = new(); - - private string Status { get; set; } - - public LocalRoomQueryFilter Filter { get; set; } = new(); - - protected override Task OnParametersSetAsync() { - if (Ascending == null) - Ascending = true; - OrderBy ??= "name"; - return Task.CompletedTask; - } - - private async Task Search() { - Results.Clear(); - 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()) { - var room = searchRooms.Current; - Console.WriteLine("Hit: " + room.ToJson(false)); - Results.Add(room); - if (Results.Count % 10 == 0) - StateHasChanged(); - } - } - - StateHasChanged(); - } - - private readonly Dictionary<string, string> validOrderBy = new() { - { "name", "Room name" }, - { "canonical_alias", "Main alias address" }, - { "joined_members", "Number of members (reversed)" }, - { "joined_local_members", "Number of local members (reversed)" }, - { "version", "Room version" }, - { "creator", "Creator of the room" }, - { "encryption", "End-to-end encryption algorithm" }, - { "federatable", "Is room federated" }, - { "public", "Visibility in room list" }, - { "join_rules", "Join rules" }, - { "guest_access", "Guest access" }, - { "history_visibility", "Visibility of history" }, - { "state_events", "Number of state events" } - }; - -} diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BackgroundJobs.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BackgroundJobs.razor new file mode 100644
index 0000000..d855cba --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BackgroundJobs.razor
@@ -0,0 +1,29 @@ +@page "/HSAdmin/Synapse/BackgroundJobs" +@using ArcaneLibs.Extensions +@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses + +<h3>Homeserver Administration - Background jobs</h3> +<pre>@BackgroundJobStatus?.ToJson(ignoreNull: true)</pre> + +@code { + private AuthenticatedHomeserverSynapse? Homeserver { get; set; } + private SynapseAdminBackgroundUpdateStatusResponse? BackgroundJobStatus { get; set; } + + protected override async Task OnInitializedAsync() { + var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true) as AuthenticatedHomeserverSynapse; + if (hs is null) return; + Homeserver = hs; + + while (true) { + try { + BackgroundJobStatus = await hs.Admin.GetBackgroundUpdatesStatusAsync(); + StateHasChanged(); + await Task.Delay(1000); + } + catch (Exception ex) { + Console.WriteLine(ex); + } + } + } + +} diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor new file mode 100644
index 0000000..d07ff08 --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor
@@ -0,0 +1,191 @@ +@page "/HSAdmin/Synapse/BlockMedia" +@using System.Text.Json +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.EventTypes.Spec + +<h3>Homeserver Administration - Block media</h3> +@if (Homeserver is not null) { + <label>Event URL: </label> + <FancyTextBox @bind-Value="EventUrl"/> + <br/> + <label>Event JSON: </label> + <details> + <summary>@(string.IsNullOrEmpty(EventJson) ? "" : "{ ... }")</summary> + <FancyTextBox Multiline="true" @bind-Value="EventJson"/> + </details> + <br/> + <label>MXC URI: </label> + <FancyTextBox @bind-Value="MxcUrl"/> + <br/> + <label>Room ID: </label> + <FancyTextBox @bind-Value="RoomId"/> + <br/> + <pre>@MxcUri?.ToJson(ignoreNull: true)</pre> + + @if (Event is not null) { + <LinkButton OnClick="@RedactAllEvents">Redact all messages</LinkButton> + } + + @if (Event?.Sender?.Split(':', 2)[1] == Homeserver?.ServerName) { + <p>User is a local user!</p> + <LinkButton OnClick="@DeactivateUser">Deactivate User</LinkButton> + <LinkButton OnClick="@QuarantineMediaByUser">Quarantine all media</LinkButton> + } +} + +<style> + .int-input { + width: 128px; + } + + .tile { + display: inline-block; + padding: 4px; + border: 1px solid #ffffff22; + } + + .tile280 { + min-width: 280px; + } + + .tile150 { + min-width: 150px; + } + + .range-sep { + display: inline-block; + padding: 4px; + width: 150px; + } + + .range-sep::before { + content: "@("<") "; + } + + .range-sep::after { + content: " @("<")"; + } + + .center-children { + text-align: center; + } +</style> + +@code { + + private AuthenticatedHomeserverSynapse? Homeserver { get; set; } + + protected override async Task OnInitializedAsync() { + var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true) as AuthenticatedHomeserverSynapse; + if (hs is null) return; + Homeserver = hs; + + if (!string.IsNullOrWhiteSpace(EventUrl)) { + _ = ExpandEventUrl(); + } + } + + [SupplyParameterFromQuery] + public string? EventUrl { + get; + set { + field = value?.Split('?')[0]; + _ = ExpandEventUrl(); + } + } + + private StateEventResponse? Event { get; set; } + + private string? EventJson { + get; + set { + field = value; + _ = ExpandEventJson(); + } + } + + private string? MxcUrl { + get; + set { + field = value; + _ = ExpandMxcUri(); + } + } + + private MxcUri? MxcUri { get; set; } + + private string? RoomId { + get => Event?.RoomId ?? field; + set; + } + + private async Task ExpandEventUrl() { + Console.WriteLine("Expanding event URL..."); + if (!string.IsNullOrWhiteSpace(EventUrl)) { + Console.WriteLine("Parsing event URL..."); + var data = ParseEventUrl(EventUrl); + Console.WriteLine($"Room: {data.room}, Event: {data.eventId}"); + RoomId = data.room; + var room = Homeserver.GetRoom(data.room); + var eventResponse = await room.GetEventAsync(data.eventId); + eventResponse.RoomId ??= data.room; + EventJson = eventResponse?.ToJson() ?? "null"; + } + + StateHasChanged(); + } + + private async Task ExpandEventJson() { + Console.WriteLine("Expanding event JSON..."); + if (!string.IsNullOrWhiteSpace(EventJson)) { + Event = JsonSerializer.Deserialize<StateEventResponse>(EventJson); + MxcUrl = Event?.ContentAs<RoomMessageEventContent>()?.Url; + Console.WriteLine($"MXC URL: {MxcUrl}"); + + var possiblyRelated = await Homeserver.Admin.GetRoomMediaAsync(Event!.RoomId!); + } + + StateHasChanged(); + } + + private async Task ExpandMxcUri() { + Console.WriteLine("Expanding MXC URI..."); + if (!string.IsNullOrWhiteSpace(MxcUrl)) { + MxcUri = MxcUrl; + } + + StateHasChanged(); + } + + private (string room, string eventId) ParseEventUrl(string url) { + var parts = url.Split('/'); + Console.WriteLine($"Parts: {string.Join(", ", parts)}"); + return (parts[4].UrlDecode(), parts[5].Split('?')[0].UrlDecode()); + } + +#region Local user + + private async Task DeactivateUser() { + await Homeserver.Admin.DeactivateUserAsync(Event.Sender, true); + } + + private async Task QuarantineMediaByUser() { + if (Event is null) return; + var media = Homeserver.Admin.GetUserMediaEnumerableAsync(Event?.Sender!); + await foreach (var m in media) { + if (m is not null) { + // await Homeserver.Admin.QuarantineMedia(m); + // await Homeserver.Admin.DeleteMedia(m); + } + } + } + +#endregion + + private async Task RedactAllEvents() { + if (Event is null) return; + await Homeserver!.Admin.DeleteAllMessages(Event.Sender!); + } + +} diff --git a/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor.css b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor.css
index e69de29..e69de29 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/RoomQuery.razor.css +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor.css
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindow.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindow.razor new file mode 100644
index 0000000..d598994 --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindow.razor
@@ -0,0 +1,5 @@ +<h3>SynapseRoomShutdownWindow</h3> + +@code { + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor new file mode 100644
index 0000000..d5daf75 --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor
@@ -0,0 +1,113 @@ +@using LibMatrix.Homeservers.Extensions.NamedCaches +@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests + +@if (string.IsNullOrWhiteSpace(Context.DeleteId)) { + <b>Media options</b> + <br/> + <hr/> + <span>Quarantine local media: </span> + <InputCheckbox @bind-Value="@Context.ExtraOptions.QuarantineLocalMedia"/> + <br/> + <span>Quarantine remote media: </span> + <InputCheckbox @bind-Value="@Context.ExtraOptions.QuarantineRemoteMedia"/> + <br/> + <span>Delete remote media: </span> + <InputCheckbox @bind-Value="@Context.ExtraOptions.DeleteRemoteMedia"/> + <br/> + + <b>User options</b> + <br/> + <hr/> + <span>Suspend local users: </span> + <InputCheckbox @bind-Value="@Context.ExtraOptions.SuspendLocalUsers"></InputCheckbox> + <br/> + <span>Quarantine <b>ALL</b> local user media: </span> + <InputCheckbox @bind-Value="@Context.ExtraOptions.QuarantineLocalUserMedia"></InputCheckbox> + <br/> + <span>Delete <b>ALL</b> local user media: </span> + <InputCheckbox @bind-Value="@Context.ExtraOptions.DeleteLocalUserMedia"></InputCheckbox> + <br/> + + <b>Room deletion options</b> + <br/> + <hr/> + <span>Block room: </span> + <InputCheckbox @bind-Value="@Context.DeleteRequest.Block"/> + <br/> + <span>Purge room: </span> + <InputCheckbox @bind-Value="@Context.DeleteRequest.Purge"/> + <br/> + <span>Force purge room (unsafe): </span> + <InputCheckbox @bind-Value="@Context.DeleteRequest.ForcePurge"></InputCheckbox> + <br/> + <span>Warning room User ID (optional): </span> + <FancyTextBox @bind-Value="@Context.DeleteRequest.NewRoomUserId"/> + <br/> + @if (!string.IsNullOrWhiteSpace(Context.DeleteRequest.NewRoomUserId)) { + <span>Warning room name: </span> + <FancyTextBox @bind-Value="@Context.DeleteRequest.RoomName"/> + <br/> + <span>Warning room message (plaintext): </span> + <FancyTextBox Multiline="true" @bind-Value="@Context.DeleteRequest.Message"/> + <br/> + } + + <LinkButton OnClick="@DeleteRoom">Execute</LinkButton> +} + +@code { + + [Parameter] + public required RoomShutdownContext Context { get; set; } + + [Parameter] + public required AuthenticatedHomeserverSynapse Homeserver { get; set; } + + private NamedCache<RoomShutdownContext> TaskMap { get; set; } = null!; + + protected override async Task OnInitializedAsync() { + TaskMap = new NamedCache<RoomShutdownContext>(Homeserver, "gay.rory.matrixutils.synapse_room_shutdown_tasks"); + } + + public class RoomShutdownContext { + public required string RoomId { get; set; } + public string? DeleteId { get; set; } + public ExtraDeleteOptions ExtraOptions { get; set; } = new(); + + public SynapseAdminRoomDeleteRequest DeleteRequest { get; set; } = new() { + Block = true, + Purge = true, + ForcePurge = false + }; + + public class ExtraDeleteOptions { + // room options + public bool QuarantineLocalMedia { get; set; } + public bool QuarantineRemoteMedia { get; set; } + + public bool DeleteRemoteMedia { get; set; } + + // user options + public bool SuspendLocalUsers { get; set; } + public bool QuarantineLocalUserMedia { get; set; } + public bool DeleteLocalUserMedia { get; set; } + } + } + + public async Task OnComplete() { + await OnCompleteLock.WaitAsync(); + try { + await TaskMap.RemoveValueAsync(Context.DeleteId!); + } + finally { + OnCompleteLock.Release(); + } + } + + public async Task DeleteRoom() { + await TaskMap.SetValueAsync(Context.RoomId, Context); + } + + private static readonly SemaphoreSlim OnCompleteLock = new(1, 1); + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor new file mode 100644
index 0000000..79e7357 --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor
@@ -0,0 +1,420 @@ +@page "/HSAdmin/Synapse/RoomQuery" +@using Microsoft.AspNetCore.WebUtilities +@using ArcaneLibs.Extensions +@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters +@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests +@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses +@using MatrixUtils.Web.Pages.HSAdmin.Synapse.Components + +<h3>Homeserver Administration - Room Query</h3> + +<label>Search name: </label> +<InputText @bind-Value="SearchTerm"/><br/> +<label>Order by: </label> +<select @bind="OrderBy"> + @foreach (var item in validOrderBy) { + <option value="@item.Key">@item.Value</option> + } +</select><br/> +<label>Ascending: </label> +<InputCheckbox @bind-Value="Ascending"/><br/> +<details> + <summary> + <span>Local filtering (slow)</span> + + </summary> + <div style="margin-left: 8px; margin-bottom: 8px;"> + <u style="display: block;">String contains</u> + <span class="tile tile280">Room ID: <FancyTextBox @bind-Value="@Filter.RoomIdContains"></FancyTextBox></span> + <span class="tile tile280">Room name: <FancyTextBox @bind-Value="@Filter.NameContains"></FancyTextBox></span> + <span class="tile tile280">Canonical alias: <FancyTextBox @bind-Value="@Filter.CanonicalAliasContains"></FancyTextBox></span> + <span class="tile tile280">Creator: <FancyTextBox @bind-Value="@Filter.CreatorContains"></FancyTextBox></span> + <span class="tile tile280">Room version: <FancyTextBox @bind-Value="@Filter.VersionContains"></FancyTextBox></span> + <span class="tile tile280">Encryption algorithm: <FancyTextBox @bind-Value="@Filter.EncryptionContains"></FancyTextBox></span> + <span class="tile tile280">Join rules: <FancyTextBox @bind-Value="@Filter.JoinRulesContains"></FancyTextBox></span> + <span class="tile tile280">Guest access: <FancyTextBox @bind-Value="@Filter.GuestAccessContains"></FancyTextBox></span> + <span class="tile tile280">History visibility: <FancyTextBox @bind-Value="@Filter.HistoryVisibilityContains"></FancyTextBox></span> + + <u style="display: block;">Optional checks</u> + <span class="tile tile150"> + <InputCheckbox @bind-Value="@Filter.CheckFederation"></InputCheckbox> Is federated: + @if (Filter.CheckFederation) { + <InputCheckbox @bind-Value="@Filter.Federatable"></InputCheckbox> + } + </span> + <span class="tile tile150"> + <InputCheckbox @bind-Value="@Filter.CheckPublic"></InputCheckbox> Is public: + @if (Filter.CheckPublic) { + <InputCheckbox @bind-Value="@Filter.Public"></InputCheckbox> + } + </span> + + <u style="display: block;">Ranges</u> + <span class="tile center-children"> + <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEventsGreaterThan"></InputNumber><span class="range-sep">state events</span><InputNumber + max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.StateEventsLessThan"></InputNumber> + </span> + <span class="tile center-children"> + <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembersGreaterThan"></InputNumber><span class="range-sep">members</span><InputNumber + max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedMembersLessThan"></InputNumber> + </span> + <span class="tile center-children"> + <InputNumber max="@int.MaxValue" class="int-input" TValue="int" @bind-Value="@Filter.JoinedLocalMembersGreaterThan"></InputNumber><span + class="range-sep">local members</span><InputNumber max="@int.MaxValue" class="int-input" TValue="int" + @bind-Value="@Filter.JoinedLocalMembersLessThan"></InputNumber> + </span> + </div> +</details> +<button class="btn btn-primary" @onclick="Search">Search</button> +<br/> + +@if (Results.Count > 0) { + <p>Found @Results.Count rooms</p> + <details> + <summary>TSV data (copy/paste)</summary> + <pre style="font-size: 0.6em;"> + <table> + @foreach (var res in Results) { + <tr> + <td style="padding: 8px;">@res.RoomId@("\t")</td> + <td style="padding: 8px;">@res.CanonicalAlias@("\t")</td> + <td style="padding: 8px;">@res.Creator@("\t")</td> + <td style="padding: 8px;">@res.Name</td> + </tr> + } + </table> + </pre> + </details> +} + +@foreach (var res in Results) { + <div style="background-color: #ffffff11; border-radius: 0.5em; display: block; margin-top: 4px; padding: 4px;"> + @* <RoomListItem RoomName="@res.Name" RoomId="@res.RoomId"></RoomListItem> *@ + <p> + @if (!string.IsNullOrWhiteSpace(res.CanonicalAlias)) { + <span>@res.CanonicalAlias - @res.RoomId (@res.Name)</span> + <br/> + } + else { + <span>@res.RoomId (@res.Name)</span> + <br/> + } + @if (!string.IsNullOrWhiteSpace(res.Creator)) { + @* <span>Created by <InlineUserItem UserId="@res.Creator"></InlineUserItem></span> *@ + <span>Created by @res.Creator</span> + <br/> + } + </p> + <p> + <LinkButton OnClick="@(() => { + DeleteRequests.Add(res.RoomId, new() { + RoomId = res.RoomId, + DeleteRequest = new() { + Block = true, + Purge = true, + ForcePurge = false + } + }); + + return Task.CompletedTask; + })">Delete room + </LinkButton> + </p> + <span>@res.StateEvents state events</span><br/> + @if (res.LocalMembers is null) { + <span>@res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server</span> + } + else { + <span>@res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server: @(string.Join(", ", res.LocalMembers))</span> + } + <details> + <summary>Full result data</summary> + <pre>@res.ToJson(ignoreNull: true)</pre> + </details> + </div> +} +@* *@ +@* @if (DeleteRequest.HasValue) { *@ +@* <ModalWindow MinWidth="600" Title="@("Delete " + DeleteRequest.Value.RoomId)" OnCloseClicked="@(() => { DeleteRequest = null; })"> *@ +@* *@ +@* </ModalWindow> *@ +@* } *@ + +@* @foreach (var (roomId, status) in DeleteStatuses) { *@ +@* <ModalWindow Title="@("Delete status for " + roomId)" MinWidth="600"> *@ +@* <pre>@status.ToJson()</pre> *@ +@* </ModalWindow> *@ +@* } *@ + +@foreach(var (roomId, deleteRequest) in DeleteRequests) { + <SynapseRoomShutdownWindowContent Context="deleteRequest" Homeserver="Homeserver"/> +} + +<style> + .int-input { + width: 128px; + } + + .tile { + display: inline-block; + padding: 4px; + border: 1px solid #ffffff22; + } + + .tile280 { + min-width: 280px; + } + + .tile150 { + min-width: 150px; + } + + .range-sep { + display: inline-block; + padding: 4px; + width: 150px; + } + + .range-sep::before { + content: "@("<") "; + } + + .range-sep::after { + content: " @("<")"; + } + + .center-children { + text-align: center; + } +</style> + +@code { + + [Parameter] + [SupplyParameterFromQuery(Name = "order_by")] + public string? OrderBy { get; set; } + + [Parameter] + [SupplyParameterFromQuery(Name = "name_search")] + public string SearchTerm { get; set; } + + [Parameter] + [SupplyParameterFromQuery(Name = "ascending")] + public bool Ascending { get; set; } + + private List<RoomInfo> Results { get; set; } = new(); + + private AuthenticatedHomeserverSynapse Homeserver { get; set; } = null!; + + private string Status { get; set; } + + public SynapseAdminLocalRoomQueryFilter Filter { get; set; } = new(); + + private Dictionary<string, SynapseRoomShutdownWindowContent.RoomShutdownContext> DeleteRequests { get; set; } = []; + + // private Dictionary<string, SynapseAdminRoomDeleteStatus> DeleteStatuses { get; set; } = new(); + + protected override Task OnParametersSetAsync() { + if (Ascending == null) + Ascending = true; + OrderBy ??= "name"; + + var execute = false; + + foreach (var (key, value) in QueryHelpers.ParseQuery(new Uri(NavigationManager.Uri).Query)) { + switch (key) { + case "RoomIdContains": + Filter.RoomIdContains = value[0]!; + break; + case "NameContains": + Filter.NameContains = value[0]!; + break; + case "CanonicalAliasContains": + Filter.CanonicalAliasContains = value[0]!; + break; + case "VersionContains": + Filter.VersionContains = value[0]!; + break; + case "CreatorContains": + Filter.CreatorContains = value[0]!; + break; + case "EncryptionContains": + Filter.EncryptionContains = value[0]!; + break; + case "JoinRulesContains": + Filter.JoinRulesContains = value[0]!; + break; + case "GuestAccessContains": + Filter.GuestAccessContains = value[0]!; + break; + case "HistoryVisibilityContains": + Filter.HistoryVisibilityContains = value[0]!; + break; + case "Federatable": + Filter.Federatable = bool.Parse(value[0]!); + Filter.CheckFederation = true; + break; + case "Public": + Filter.Public = value[0] == "true"; + Filter.CheckPublic = true; + break; + case "JoinedMembersGreaterThan": + Filter.JoinedMembersGreaterThan = int.Parse(value[0]!); + break; + case "JoinedMembersLessThan": + Filter.JoinedMembersLessThan = int.Parse(value[0]!); + break; + case "JoinedLocalMembersGreaterThan": + Filter.JoinedLocalMembersGreaterThan = int.Parse(value[0]!); + break; + case "JoinedLocalMembersLessThan": + Filter.JoinedLocalMembersLessThan = int.Parse(value[0]!); + break; + case "StateEventsGreaterThan": + Filter.StateEventsGreaterThan = int.Parse(value[0]!); + break; + case "StateEventsLessThan": + Filter.StateEventsLessThan = int.Parse(value[0]!); + break; + case "Execute": + execute = true; + break; + default: + Console.WriteLine($"Unknown query parameter: {key}"); + break; + } + } + + if (execute) + _ = Search(); + + return Task.CompletedTask; + } + + private async Task Search() { + Results.Clear(); + var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + if (hs is AuthenticatedHomeserverSynapse synapse) { + Homeserver = synapse; + var searchRooms = synapse.Admin.SearchRoomsAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", searchTerm: SearchTerm, localFilter: Filter).GetAsyncEnumerator(); + while (await searchRooms.MoveNextAsync()) { + var room = searchRooms.Current; + + if (Results.Count < 100) + Console.WriteLine("Hit: " + room.ToJson(false)); + + var roomInfo = new RoomInfo { + RoomId = room.RoomId, + Name = room.Name, + CanonicalAlias = room.CanonicalAlias, + Creator = room.Creator, + Version = room.Version, + Encryption = room.Encryption, + Federatable = room.Federatable, + Public = room.Public, + JoinRules = room.JoinRules, + GuestAccess = room.GuestAccess, + HistoryVisibility = room.HistoryVisibility, + StateEvents = room.StateEvents, + JoinedMembers = room.JoinedMembers, + JoinedLocalMembers = room.JoinedLocalMembers + }; + + Results.Add(roomInfo); + + if (room.JoinedLocalMembers is > 0 and < 100) + roomInfo.LocalMembers = (await synapse.Admin.GetRoomMembersAsync(room.RoomId)).Members.Where(x => x.EndsWith(":" + synapse.ServerName)).ToList(); + + if (Results.Count < 200 || Results.Count % 1000 == 0) { + StateHasChanged(); + await Task.Yield(); + } + } + } + + StateHasChanged(); + } + // + // private async Task DeleteRoom() { + // if (DeleteRequest is { } deleteRequest) { + // var media = await Homeserver.Admin.GetRoomMediaAsync(deleteRequest.RoomId); + // if (deleteRequest.DeleteRequest.QuarantineRemoteMedia) { + // foreach (var remoteMedia in media.Remote) { + // await Homeserver.Admin.QuarantineMediaById(remoteMedia); + // } + // } + // + // if (deleteRequest.DeleteRequest.DeleteRemoteMedia) { + // foreach (var remoteMedia in media.Remote) { + // await Homeserver.Admin.DeleteMediaById(remoteMedia); + // } + // } + // else if (deleteRequest.DeleteRequest.QuarantineLocalMedia) { + // foreach (var localMedia in media.Local) { + // await Homeserver.Admin.QuarantineMediaById(localMedia); + // } + // } + // + // var deleteId = await Homeserver.Admin.DeleteRoom(deleteRequest.RoomId, deleteRequest.DeleteRequest, waitForCompletion: false); + // DeleteRequest = null; + // List<string> alreadyCleanedUsers = []; + // while (true) { + // var status = await Homeserver.Admin.GetRoomDeleteStatus(deleteId.DeleteId); + // DeleteStatuses[deleteRequest.RoomId] = status; + // StateHasChanged(); + // await Task.Delay(5000); + // if (status.Status == "complete") { + // DeleteStatuses.Remove(deleteRequest.RoomId); + // StateHasChanged(); + // break; + // } + // + // if (status.Status == "failed") { + // deleteId = await Homeserver.Admin.DeleteRoom(deleteRequest.RoomId, deleteRequest.DeleteRequest, waitForCompletion: false); + // } + // + // var newCleanedUsers = status.ShutdownRoom?.KickedUsers?.Except(alreadyCleanedUsers).ToList(); + // if (newCleanedUsers is not null) { + // alreadyCleanedUsers.AddRange(newCleanedUsers); + // foreach (var user in newCleanedUsers) { + // if (deleteRequest.DeleteRequest.SuspendLocalUsers) { + // // await Homeserver.Admin.(user); + // } + // + // if (deleteRequest.DeleteRequest.QuarantineLocalUserMedia) { + // await Homeserver.Admin.QuarantineMediaByUserId(user); + // } + // + // if (deleteRequest.DeleteRequest.DeleteLocalUserMedia) { + // var userMedia = Homeserver.Admin.GetUserMediaEnumerableAsync(user); + // await foreach (var mediaEntry in userMedia) { + // await Homeserver.Admin.DeleteMediaById(mediaEntry.MediaId); + // } + // } + // } + // } + // } + // } + // } + + private readonly Dictionary<string, string> validOrderBy = new() { + { "name", "Room name" }, + { "canonical_alias", "Main alias address" }, + { "joined_members", "Number of members (reversed)" }, + { "joined_local_members", "Number of local members (reversed)" }, + { "version", "Room version" }, + { "creator", "Creator of the room" }, + { "encryption", "End-to-end encryption algorithm" }, + { "federatable", "Is room federated" }, + { "public", "Visibility in room list" }, + { "join_rules", "Join rules" }, + { "guest_access", "Guest access" }, + { "history_visibility", "Visibility of history" }, + { "state_events", "Number of state events" } + }; + + private class RoomInfo : SynapseAdminRoomListResult.SynapseAdminRoomListResultRoom { + public List<string>? LocalMembers { get; set; } + } + +} diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor.css b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor.css new file mode 100644
index 0000000..e69de29 --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor.css
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..cefd7d5 100644 --- a/MatrixUtils.Web/Pages/Index.razor +++ b/MatrixUtils.Web/Pages/Index.razor
@@ -22,20 +22,29 @@ 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.Auth; <tr class="user-entry"> <td> - <img class="avatar" src="@session.UserInfo.AvatarUrl" crossorigin="anonymous"/> + @if (!string.IsNullOrWhiteSpace(@session.UserInfo?.AvatarUrl)) { + // Console.WriteLine($"Rendering {session.UserInfo.AvatarUrl} with homeserver {session.Homeserver}"); + <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.Auth.AccessToken == auth.AccessToken)" @onclick="@(() => SwitchSession(session.SessionId))" + 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> @@ -44,13 +53,14 @@ Small collection of tools to do not-so-everyday things. <p>T=@session.Homeserver.GetType().FullName</p> <p>D=@session.Homeserver.WhoAmI.DeviceId</p> <p>U=@session.Homeserver.WhoAmI.UserId</p> + <p>S=@session.Homeserver.WhoAmI.UserId</p> } </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(session.SessionId))">Manage</LinkButton> + <LinkButton OnClick="@(() => RemoveUser(session.SessionId))">Remove</LinkButton> + <LinkButton OnClick="@(() => RemoveUser(session.SessionId, true))">Log out</LinkButton> </p> </td> </tr> @@ -70,16 +80,16 @@ Small collection of tools to do not-so-everyday things. <td> <p> @{ - string[] parts = session.UserId.Split(':'); + string[] parts = session.Auth.UserId.Split(':'); } <span>@parts[0][1..]</span> on <span>@parts[1]</span> - @if (!string.IsNullOrWhiteSpace(session.Proxy)) { - <span class="badge badge-info"> (proxied via @session.Proxy)</span> + @if (!string.IsNullOrWhiteSpace(session.Auth.Proxy)) { + <span class="badge badge-info"> (proxied via @session.Auth.Proxy)</span> } </p> </td> <td> - <LinkButton OnClick="@(() => RemoveUser(session))">Remove</LinkButton> + <LinkButton OnClick="@(() => RemoveUser(session.SessionId))">Remove</LinkButton> </td> </tr> } @@ -99,19 +109,19 @@ Small collection of tools to do not-so-everyday things. <td> <p> @{ - string[] parts = session.UserId.Split(':'); + string[] parts = session.Auth.UserId.Split(':'); } <span>@parts[0][1..]</span> on <span>@parts[1]</span> - @if (!string.IsNullOrWhiteSpace(session.Proxy)) { - <span class="badge badge-info"> (proxied via @session.Proxy)</span> + @if (!string.IsNullOrWhiteSpace(session.Auth.Proxy)) { + <span class="badge badge-info"> (proxied via @session.Auth.Proxy)</span> } </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.SessionId}")))">Re-login</LinkButton> </td> <td> - <LinkButton OnClick="@(() => RemoveUser(session))">Remove</LinkButton> + <LinkButton OnClick="@(() => RemoveUser(session.SessionId))">Remove</LinkButton> </td> </tr> } @@ -127,67 +137,76 @@ Small collection of tools to do not-so-everyday things. private const bool _debug = false; #endif - private class AuthInfo { - public UserAuth? UserAuth { get; set; } + private class HomepageSessionInfo : RmuSessionStore.SessionInfo { public UserInfo? UserInfo { get; set; } public ServerVersionResponse? ServerVersion { get; set; } public AuthenticatedHomeserverGeneric? Homeserver { get; set; } } - private readonly List<AuthInfo> _sessions = []; - private readonly List<UserAuth> _offlineSessions = []; - private readonly List<UserAuth> _invalidSessions = []; - private LoginResponse? _currentSession; - int scannedSessions = 0, totalSessions = 1; + private readonly List<HomepageSessionInfo> _sessions = []; + private readonly List<RmuSessionStore.SessionInfo> _offlineSessions = []; + private readonly List<RmuSessionStore.SessionInfo> _invalidSessions = []; + private RmuSessionStore.SessionInfo? _currentSession; + 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 sessionStore.GetCurrentSession(); _sessions.Clear(); _offlineSessions.Clear(); - var tokens = await RMUStorage.GetAllTokens(); + var sessions = await sessionStore.GetAllSessions(); scannedSessions = 0; - totalSessions = tokens.Count; + totalSessions = sessions.Count; logger.LogDebug("Found {0} tokens", totalSessions); - if (tokens is not { Count: > 0 }) { + if (sessions is not { Count: > 0 }) { Console.WriteLine("No tokens found, trying migration from MRU..."); - await RMUStorage.MigrateFromMRU(); - tokens = await RMUStorage.GetAllTokens(); - if (tokens is not { Count: > 0 }) { + sessions = await sessionStore.GetAllSessions(); + if (sessions is not { Count: > 0 }) { Console.WriteLine("No tokens found"); return; } } 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 => { + var tasks = sessions.Select(async session => { await sema.WaitAsync(); - if ((!string.IsNullOrWhiteSpace(token.Proxy) && offlineServers.Contains(token.Proxy)) || offlineServers.Contains(token.Homeserver)) { - _offlineSessions.Add(token); - sema.Release(); - scannedSessions++; - return; - } + var token = session.Value.Auth; 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() { + Auth = token, + SessionId = session.Value.SessionId, + Homeserver = hs, 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 }, - UserAuth = token, ServerVersion = await (serverVersionTask ?? Task.FromResult<ServerVersionResponse?>(null)!), - Homeserver = hs }); if (updateSw.ElapsedMilliseconds > 25) { updateSw.Restart(); @@ -197,7 +216,7 @@ Small collection of tools to do not-so-everyday things. catch (MatrixException e) { if (e is { ErrorCode: "M_UNKNOWN_TOKEN" }) { logger.LogWarning("Got unknown token error for {0} via {1}", token.UserId, token.Homeserver); - _invalidSessions.Add(token); + _invalidSessions.Add(session.Value); } else { logger.LogError("Failed to get info for {0} via {1}: {2}", token.UserId, token.Homeserver, e); @@ -226,15 +245,16 @@ 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; } } - private async Task RemoveUser(UserAuth auth, bool logout = false) { + private async Task RemoveUser(string sessionId, bool logout = false) { try { if (logout) { - await (await hsProvider.GetAuthenticatedWithToken(auth.Homeserver, auth.AccessToken, auth.Proxy)).Logout(); + var auth = (await sessionStore.GetSession(sessionId))?.Auth; + await (await HsProvider.GetAuthenticatedWithToken(auth.Homeserver, auth.AccessToken, auth.Proxy)).Logout(); } } catch (Exception e) { @@ -246,21 +266,19 @@ 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 sessionStore.RemoveSession(sessionId); StateHasChanged(); } - private async Task SwitchSession(UserAuth auth) { - Console.WriteLine($"Switching to {auth.Homeserver} {auth.UserId} via {auth.Proxy}"); - await RMUStorage.SetCurrentToken(auth); - _currentSession = auth; + private async Task SwitchSession(string sessionId) { + Console.WriteLine($"Switching to {sessionId}"); + await sessionStore.SetCurrentSession(sessionId); + _currentSession = await sessionStore.GetCurrentSession(); StateHasChanged(); } - private async Task ManageUser(UserAuth auth) { - await SwitchSession(auth); + private async Task ManageUser(string sessionId) { + await sessionStore.SetCurrentSession(sessionId); NavigationManager.NavigateTo("/User/Profile"); } } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/InvalidSession.razor b/MatrixUtils.Web/Pages/InvalidSession.razor
index e1a72ea..1ec99f6 100644 --- a/MatrixUtils.Web/Pages/InvalidSession.razor +++ b/MatrixUtils.Web/Pages/InvalidSession.razor
@@ -6,14 +6,15 @@ <h3>Rory&::MatrixUtils - Invalid session encountered</h3> <p>A session was encountered that is no longer valid. This can happen if you have logged out of the account on another device, or if the access token has expired.</p> -@if (_login is not null) { - <p>It appears that the affected user is @_login.UserId (@_login.DeviceId) on @_login.Homeserver!</p> +@if (_auth is not null) { + <p>It appears that the affected user is @_auth.UserId (@_auth.DeviceId) on @_auth.Homeserver!</p> <LinkButton OnClick="@(OpenRefreshDialog)">Refresh token</LinkButton> <LinkButton OnClick="@(RemoveUser)">Remove</LinkButton> @if (_showRefreshDialog) { - <ModalWindow MinWidth="300" X="275" Y="300" Title="@($"Password for {_login.UserId}")"> - <FancyTextBox IsPassword="true" @bind-Value="@_password"></FancyTextBox><br/> + <ModalWindow MinWidth="300" X="275" Y="300" Title="@($"Password for {_auth.UserId}")"> + <FancyTextBox IsPassword="true" @bind-Value="@_password"></FancyTextBox> + <br/> <LinkButton OnClick="TryLogin">Log in</LinkButton> @if (_loginException is not null) { <pre style="color: red;">@_loginException.RawContent</pre> @@ -29,9 +30,9 @@ else { { [Parameter] [SupplyParameterFromQuery(Name = "ctx")] - public string Context { get; set; } + public string SessionId { get; set; } - private UserAuth? _login { get; set; } + private UserAuth? _auth { get; set; } private bool _showRefreshDialog { get; set; } @@ -40,25 +41,21 @@ else { private MatrixException? _loginException { get; set; } protected override async Task OnInitializedAsync() { - var tokens = await RMUStorage.GetAllTokens(); - if (tokens is null || tokens.Count == 0) { + var tokens = await sessionStore.GetAllSessions(); + if (tokens.Count == 0) { NavigationManager.NavigateTo("/Login"); return; } - _login = tokens.FirstOrDefault(x => x.AccessToken == Context); - - if (_login is null) { - Console.WriteLine($"Could not find {_login} in stored tokens!"); - } + if (tokens.TryGetValue(SessionId, out var session)) + _auth = session.Auth; + else Console.WriteLine($"Could not find {SessionId} in stored sessions!"); await base.OnInitializedAsync(); } private async Task RemoveUser() { - await RMUStorage.RemoveToken(_login!); - if ((await RMUStorage.GetCurrentToken())!.AccessToken == _login!.AccessToken) - await RMUStorage.SetCurrentToken((await RMUStorage.GetAllTokens())?.FirstOrDefault()); + await sessionStore.RemoveSession(SessionId); await OnInitializedAsync(); } @@ -68,30 +65,29 @@ else { await Task.CompletedTask; } - private async Task SwitchSession(UserAuth auth) { - Console.WriteLine($"Switching to {auth.Homeserver} {auth.AccessToken} {auth.UserId}"); - await RMUStorage.SetCurrentToken(auth); + private async Task SwitchSession(string sessionId) { + Console.WriteLine($"Switching to session {sessionId}"); + await sessionStore.SetCurrentSession(sessionId); await OnInitializedAsync(); } private async Task TryLogin() { - if(_login is null) throw new NullReferenceException("Login is null!"); + if (_auth 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(_auth.Homeserver, _auth.UserId, _password)); if (result is null) { - Console.WriteLine($"Failed to login to {_login.Homeserver} as {_login.UserId}!"); + Console.WriteLine($"Failed to login to {_auth.Homeserver} as {_auth.UserId}!"); return; } + 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 sessionStore.RemoveSession(SessionId); + await sessionStore.AddSession(result); NavigationManager.NavigateTo("/"); } catch (MatrixException e) { - Console.WriteLine($"Failed to login to {_login.Homeserver} as {_login.UserId}!"); + Console.WriteLine($"Failed to login to {_auth.Homeserver} as {_auth.UserId}!"); Console.WriteLine(e); _loginException = e; StateHasChanged(); diff --git a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor
index b370080..8831dd1 100644 --- a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor +++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor
@@ -10,6 +10,6 @@ @code { [Parameter] - public ClientContext Data { get; set; } = null!; + public ClientContext Data { get; set; } } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientStatusList.razor b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientStatusList.razor
index c680c13..60f850d 100644 --- a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientStatusList.razor +++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientStatusList.razor
@@ -10,7 +10,7 @@ @code { [Parameter] - public ObservableCollection<ClientContext> Data { get; set; } = null!; + public ObservableCollection<ClientContext> Data { get; set; } protected override void OnInitialized() { Data.CollectionChanged += (_, e) => { diff --git a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor
index 67dcae5..6a930b1 100644 --- a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor +++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/MatrixClient.razor
@@ -25,6 +25,6 @@ @code { [Parameter] - public Index.ClientContext Data { get; set; } = null!; + public Index.ClientContext Data { get; set; } } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Labs/Client/Index.razor b/MatrixUtils.Web/Pages/Labs/Client/Index.razor
index ef4a0b9..c6e7d1a 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 tasks = tokens.Select(async token => { + var tokens = await sessionStore.GetAllSessions(); + var tasks = tokens.Keys.Select(async token => { try { var cc = new ClientContext() { - Homeserver = await RMUStorage.GetSession(token) + Homeserver = await sessionStore.GetHomeserver(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..f81afe5 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..441752b 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..ba994d1 100644 --- a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor
@@ -1,14 +1,14 @@ @using MatrixUtils.Abstractions -<div class="spaceListItem" style="@(SelectedSpace == Space ? "background-color: #FFFFFF33;" : "")" onclick="@SelectSpace"> +<div class="spaceListItem" style="@(SelectedSpace == Space ? "background-color: #FFFFFF33;" : "")" @onclick="@SelectSpace"> <div class="spaceListItemContainer"> @if (IsSpaceOpened()) { - <span onclick="@ToggleSpace">▼ </span> + <span @onclick="@ToggleSpace">▼ </span> } else { - <span onclick="@ToggleSpace">▶ </span> + <span @onclick="@ToggleSpace">▶ </span> } - <MxcImage Circular="true" Height="32" Width="32" Homeserver="Space.Room.Homeserver" MxcUri="@Space.RoomIcon"></MxcImage> + <MxcImage Homeserver="@Homeserver" Circular="true" Height="32" Width="32" Uri="@Space.RoomIcon"></MxcImage> <span class="spaceNameEllipsis">@Space.RoomName</span> </div> @if (IsSpaceOpened()) { @@ -30,6 +30,9 @@ [Parameter] public List<RoomInfo> OpenedSpaces { get; set; } + [Parameter] + public AuthenticatedHomeserverGeneric Homeserver { get; set; } + protected override Task OnInitializedAsync() { Space.PropertyChanged += (sender, args) => { StateHasChanged(); }; return base.OnInitializedAsync(); diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor
index f4cf849..79f931b 100644 --- a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2ByRoomTypeTab.razor
@@ -22,7 +22,7 @@ @code { [CascadingParameter] - public Index2.RoomListViewData Data { get; set; } = null!; + public Index2.RoomListViewData Data { get; set; } protected override async Task OnInitializedAsync() { Data.Rooms.CollectionChanged += (sender, args) => { diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor
index f4cf849..79f931b 100644 --- a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2DMsTab.razor
@@ -22,7 +22,7 @@ @code { [CascadingParameter] - public Index2.RoomListViewData Data { get; set; } = null!; + public Index2.RoomListViewData Data { get; set; } protected override async Task OnInitializedAsync() { Data.Rooms.CollectionChanged += (sender, args) => { diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor
index 7ccfae2..99b031a 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,31 +22,32 @@ @* </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 { [CascadingParameter] - public Index2.RoomListViewData Data { get; set; } = null!; + public Index2.RoomListViewData Data { get; set; } protected override async Task OnInitializedAsync() { Data.Rooms.CollectionChanged += (sender, args) => { @@ -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..33c310a 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) { @@ -16,7 +16,7 @@ @code { [Parameter] - public Index2.RoomListViewData Data { get; set; } = null!; + public Index2.RoomListViewData Data { get; set; } private SyncHelper syncHelper; @@ -113,7 +113,7 @@ statusd.Status = $"{roomId} already known with {room.StateEvents?.Count ?? 0} state events"; } else { - statusd.Status = $"Eencountered new room {roomId}!"; + statusd.Status = $"Encountered new room {roomId}!"; room = new RoomInfo(Data.Homeserver!.GetRoom(roomId), roomData.State?.Events); Data.Rooms.Add(room); } diff --git a/MatrixUtils.Web/Pages/LoginPage.razor b/MatrixUtils.Web/Pages/LoginPage.razor
index 6c869ac..88577a2 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/> @@ -47,7 +68,7 @@ </thead> @foreach (var record in records) { var r = record; - <tr style="background-color: @(LoggedInSessions.Any(x => x.UserId == $"@{r.Username}:{r.Homeserver}" && x.Proxy == r.Proxy) ? "green" : "unset")"> + <tr style="background-color: @(LoggedInSessions.Any(x => x.Value.Auth.UserId == $"@{r.Username}:{r.Homeserver}" && x.Value.Auth.Proxy == r.Proxy) ? "green" : "unset")"> <td style="border-width: 1px;"> <FancyTextBox @bind-Value="@r.Username"></FancyTextBox> </td> @@ -87,7 +108,7 @@ readonly List<LoginStruct> records = new(); private LoginStruct newRecordInput = new(); - List<UserAuth>? LoggedInSessions { get; set; } = new(); + Dictionary<string, RmuSessionStore.SessionInfo> LoggedInSessions { get; set; } = new(); async Task LoginAll() { var loginTasks = records.Select(Login); @@ -97,10 +118,10 @@ async Task Login(LoginStruct record) { if (!records.Contains(record)) records.Add(record); - if (LoggedInSessions.Any(x => x.UserId == $"@{record.Username}:{record.Homeserver}" && x.Proxy == record.Proxy)) return; + if (LoggedInSessions.Any(x => x.Value.Auth.UserId == $"@{record.Username}:{record.Homeserver}" && x.Value.Auth.UserId == 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 sessionStore.AddSession(result); + LoggedInSessions = await sessionStore.GetAllSessions(); } 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 sessionStore.GetAllSessions(); Console.WriteLine(JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true })); @@ -141,7 +162,7 @@ } private async Task AddRecord() { - LoggedInSessions = await RMUStorage.GetAllTokens(); + LoggedInSessions = await sessionStore.GetAllSessions(); 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 sessionStore.AddSession(new UserAuth() { + UserId = session.WhoAmI.UserId, + AccessToken = session.AccessToken, + Proxy = record.Proxy, + DeviceId = session.WhoAmI.DeviceId + }); + LoggedInSessions = await sessionStore.GetAllSessions(); + } + 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..e30adf6 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); if (hs is null) return; - var sessions = await RMUStorage.GetAllTokens(); - foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); + var sessions = await sessionStore.GetAllSessions(); + foreach (var userAuth in sessions.Keys) { + var session = await sessionStore.GetHomeserver(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..021ad18 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..0373a46 100644 --- a/MatrixUtils.Web/Pages/Rooms/Index.razor +++ b/MatrixUtils.Web/Pages/Rooms/Index.razor
@@ -13,9 +13,7 @@ <p>@Status2</p> <LinkButton href="/Rooms/Create">Create new room</LinkButton> -<CascadingValue TValue="AuthenticatedHomeserverGeneric" Value="Homeserver"> - <RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" @bind-StillFetching="RenderContents"></RoomList> -</CascadingValue> +<RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" @bind-StillFetching="RenderContents" Homeserver="@Homeserver"></RoomList> @code { @@ -68,7 +66,7 @@ // SyncHelper profileSyncHelper; protected override async Task OnInitializedAsync() { - Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); if (Homeserver is null) return; // var rooms = await Homeserver.GetJoinedRooms(); // SemaphoreSlim _semaphore = new(160, 160); @@ -122,7 +120,7 @@ try { while (queue.Count == 0) { Console.WriteLine("Queue is empty, waiting..."); - await Task.Delay(isInitialSync ? 100 : 2500); + await Task.Delay(isInitialSync ? 1000 : 2500); } Console.WriteLine($"Queue no longer empty after {renderTimeSw.Elapsed}!"); @@ -131,15 +129,15 @@ isInitialSync = false; while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) { var (roomId, roomData) = queueEntry; - Console.WriteLine($"Dequeued room {roomId}"); + // Console.WriteLine($"Dequeued room {roomId}"); RoomInfo room; if (Rooms.Any(x => x.Room.RoomId == roomId)) { room = Rooms.First(x => x.Room.RoomId == roomId); - Console.WriteLine($"QueueWorker: {roomId} already known with {room.StateEvents?.Count ?? 0} state events"); + // Console.WriteLine($"QueueWorker: {roomId} already known with {room.StateEvents?.Count ?? 0} state events"); } else { - Console.WriteLine($"QueueWorker: encountered new room {roomId}!"); + // Console.WriteLine($"QueueWorker: encountered new room {roomId}!"); room = new RoomInfo(Homeserver.GetRoom(roomId), roomData.State?.Events); Rooms.Add(room); } @@ -155,6 +153,11 @@ Console.WriteLine($"QueueWorker: could not merge state for {room.Room.RoomId} as new data contains no state events!"); } + if (maxUpdates % 100 == 0) { + Console.WriteLine($"QueueWorker: {queue.Count} entries left in queue, {maxUpdates} maxUpdates left, RenderContents: {RenderContents}"); + StateHasChanged(); + await Task.Yield(); + } // await Task.Delay(100); } @@ -170,7 +173,7 @@ } } - private bool RenderContents { get; set; } = false; + private bool RenderContents { get; set; } private string _status; @@ -225,9 +228,10 @@ Rooms.Remove(Rooms.First(x => x.Room.RoomId == leftRoom.Key)); Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue... " + - $"{sync?.Rooms?.Join?.Count ?? 0} new updates!"; + $"{sync.Rooms?.Join?.Count ?? 0} new updates!"; - Status2 = $"Next batch: {sync.NextBatch}"; + Status2 = $"Next batch: {sync?.NextBatch}"; + await Task.Yield(); } } diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
index b7ebae2..9c35673 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!; + public string RoomId { get; set; } 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true))!; 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..982fc5a --- /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; } + + 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true))!; + 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..2903ab8 --- /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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + 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..46e39ed 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..51cb265 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..c8b87d3 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..108581c 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..8b9735e --- /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.ServerName</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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + + //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..9a56fc0 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..0943216 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); if (hs is null) return; StateHasChanged(); @@ -48,7 +48,7 @@ } private async Task Execute() { - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..7abb3d2 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..acad827 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..f8d1d31 100644 --- a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor +++ b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
@@ -1,24 +1,24 @@ @page "/Tools/Info/PolicyListActivity" @using LibMatrix.EventTypes.Spec.State.Policy @using System.Diagnostics +@using System.Reflection +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.EventTypes @using LibMatrix.RoomTypes @using LibMatrix.EventTypes.Common +@using LibMatrix.Filters - -@if (RoomData.Count == 0) -{ +@* <ActivityGraph Data="TestData"/> *@ +@if (RoomData.Count == 0) { <p>Loading...</p> } else - foreach (var room in RoomData) - { + foreach (var room in RoomData) { <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> + @foreach (var year in room.Value.OrderBy(x => x.Key)) { + <span>@year.Key</span> + <ActivityGraph Data="@year.Value" GlobalMax="MaxValue" RLabel="removed" GLabel="new" BLabel="updated policies"/> } } @@ -29,59 +29,26 @@ else public Dictionary<DateOnly, ActivityGraph.RGB> TestData { get; set; } = new(); - public ActivityGraph.RGB MaxValue { get; set; } = new() - { + public ActivityGraph.RGB MaxValue { get; set; } = new() { R = 255, G = 255, B = 255 }; public Dictionary<string, Dictionary<int, Dictionary<DateOnly, ActivityGraph.RGB>>> RoomData { get; set; } = new(); - protected override async Task OnInitializedAsync() - { + protected override async Task OnInitializedAsync() { var sw = Stopwatch.StartNew(); await base.OnInitializedAsync(); - Homeserver = (await RMUStorage.GetCurrentSessionOrNavigate())!; + Homeserver = (await sessionStore.GetCurrentHomeserver(navigateOnFailure: true))!; 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))) - { - TestData[i] = new() - { - R = (int)(Random.Shared.NextSingle() * 255), - G = (int)(Random.Shared.NextSingle() * 255), - B = (int)(Random.Shared.NextSingle() * 255) - }; - } - - StateHasChanged(); - // return; - var rooms = await Homeserver.GetJoinedRooms(); - // foreach (var room in rooms) - // { - // var type = await room.GetRoomType(); - // if (type == "support.feline.policy.lists.msc.v1") - // { - // Console.WriteLine($"{room.RoomId} is policy list by type"); - // FilteredRooms.Add(room); - // } - // else if(await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId) is not null) - // { - // Console.WriteLine($"{room.RoomId} is policy list by shortcode"); - // FilteredRooms.Add(room); - // } - // } - var roomFilterTasks = rooms.Select(async room => - { + var roomFilterTasks = rooms.Select(async room => { var type = await room.GetRoomType(); - if (type == "support.feline.policy.lists.msc.v1") - { + if (type == "support.feline.policy.lists.msc.v1") { Console.WriteLine($"{room.RoomId} is policy list by type"); return room; } - else if (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId) is not null) - { + else if (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId) is not null) { Console.WriteLine($"{room.RoomId} is policy list by shortcode"); return room; } @@ -99,60 +66,75 @@ else Console.WriteLine($"Filtered {FilteredRooms.Count} rooms in {sw.ElapsedMilliseconds}ms"); } - public async Task FetchRoomHistory(GenericRoom room) - { + public async Task FetchRoomHistory(GenericRoom room) { var roomName = await room.GetNameOrFallbackAsync(); - if (string.IsNullOrWhiteSpace(roomName)) roomName = room.RoomId; - if (!RoomData.ContainsKey(roomName)) - { - RoomData[roomName] = new(); - } + if (string.IsNullOrWhiteSpace(roomName)) roomName = room.RoomId; + if (!RoomData.ContainsKey(roomName)) { + RoomData[roomName] = new(); + } + + //use timeline + var types = StateEventResponse.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))); + var filter = new SyncFilter.EventFilter(types: types.SelectMany(x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName)).ToList()); + var timeline = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 2500, filter: filter.ToJson(indent: false, ignoreNull: true)); + await foreach (var response in timeline) { + Console.WriteLine($"Got {response.State.Count} state, {response.Chunk.Count} timeline"); + if (response.State.Count != 0) throw new Exception("Why the hell did we receive state events?"); + foreach (var message in response.Chunk) { + if (!message.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; + //OriginServerTs to datetime + var dt = DateTimeOffset.FromUnixTimeMilliseconds((long)message.OriginServerTs!.Value).DateTime; + var date = new DateOnly(dt.Year, dt.Month, dt.Day); + if (!RoomData[roomName].ContainsKey(date.Year)) { + RoomData[roomName][date.Year] = new(); + } - //use timeline - var timeline = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 5000); - await foreach (var response in timeline) - { - Console.WriteLine($"Got {response.State.Count} state, {response.Chunk.Count} timeline"); - if (response.State.Count != 0) throw new Exception("Why the hell did we receive state events?"); - foreach (var message in response.Chunk) - { - if (!message.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; - //OriginServerTs to datetime - var dt = DateTimeOffset.FromUnixTimeMilliseconds((long)message.OriginServerTs!.Value).DateTime; - var date = new DateOnly(dt.Year, dt.Month, dt.Day); - if (!RoomData[roomName].ContainsKey(date.Year)) - { - RoomData[roomName][date.Year] = new(); - } - - if (!RoomData[roomName][date.Year].ContainsKey(date)) - { - // Console.WriteLine($"Adding {date} to {roomName}"); - RoomData[roomName][date.Year][date] = new(); - } - - var rgb = RoomData[roomName][date.Year][date]; - if (message.RawContent?.Count == 0) rgb.R++; - else if (string.IsNullOrWhiteSpace(message.Unsigned?.ReplacesState)) rgb.G++; - else rgb.B++; - RoomData[roomName][date.Year][date] = rgb; + if (!RoomData[roomName][date.Year].ContainsKey(date)) { + // Console.WriteLine($"Adding {date} to {roomName}"); + RoomData[roomName][date.Year][date] = new(); } - var max = RoomData.SelectMany(x => x.Value.Values).Aggregate(new ActivityGraph.RGB(), (current, next) => new() - { - R = Math.Max(current.R, next.Average(x => x.Value.R)), - G = Math.Max(current.G, next.Average(x => x.Value.G)), - B = Math.Max(current.B, next.Average(x => x.Value.B)) - }); - MaxValue = new ActivityGraph.RGB( - r: Math.Max(max.R, Math.Max(max.G, max.B)), - g: Math.Max(max.R, Math.Max(max.G, max.B)), - b: Math.Max(max.R, Math.Max(max.G, max.B))); - Console.WriteLine($"Max value is {MaxValue.R} {MaxValue.G} {MaxValue.B}"); - StateHasChanged(); - await Task.Delay(100); + var rgb = RoomData[roomName][date.Year][date]; + if (message.RawContent is { Count: 0 } or null) rgb.R++; + else if (!message.Unsigned?.ContainsKey("replaces_state") ?? true) rgb.G++; + else rgb.B++; + RoomData[roomName][date.Year][date] = rgb; } + + + } + var max = RoomData.SelectMany(x => x.Value.Values).Aggregate(new ActivityGraph.RGB(), (current, next) => new() { + R = Math.Max(current.R, next.Average(x => x.Value.R)), + G = Math.Max(current.G, next.Average(x => x.Value.G)), + B = Math.Max(current.B, next.Average(x => x.Value.B)) + }); + MaxValue = new ActivityGraph.RGB( + r: Math.Max(max.R, Math.Max(max.G, max.B)), + g: Math.Max(max.R, Math.Max(max.G, max.B)), + b: Math.Max(max.R, Math.Max(max.G, max.B))); + Console.WriteLine($"Max value is {MaxValue.R} {MaxValue.G} {MaxValue.B}"); + StateHasChanged(); + await Task.Yield(); } + private readonly struct StateEventEntry { + public required DateTime Timestamp { get; init; } + public required StateEventTransition State { get; init; } + public required StateEventResponse Event { get; init; } + public required StateEventResponse? Previous { get; init; } + + public void Deconstruct(out StateEventTransition transition, out StateEventResponse evt, out StateEventResponse? prev) { + transition = State; + evt = Event; + prev = Previous; + } + } + + private enum StateEventTransition : byte { + None, + Add, + Update, + Remove + } } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
index 3b68bfa..ce3513b 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,20 +73,20 @@ protected override async Task OnInitializedAsync() { log.CollectionChanged += (sender, args) => StateHasChanged(); - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); + var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); if (hs is null) return; rooms.CollectionChanged += (sender, args) => StateHasChanged(); - var sessions = await RMUStorage.GetAllTokens(); + var sessions = await sessionStore.GetAllSessions(); foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); - if (session is not null) { - var sessionRooms = await session.GetJoinedRooms(); + var homeserver = await sessionStore.GetHomeserver(userAuth.Key); + if (homeserver is not null) { + var sessionRooms = await homeserver.GetJoinedRooms(); foreach (var room in sessionRooms) { rooms.Add(room); } StateHasChanged(); - log.Add($"Got {sessionRooms.Count} rooms for {userAuth.UserId}"); + log.Add($"Got {sessionRooms.Count} rooms for {userAuth.Value.Auth.UserId}"); } } @@ -106,7 +106,7 @@ log.Add($"Done fetching members!"); - UserIDs.RemoveAll(x => sessions.Any(y => y.UserId == x)); + UserIDs.RemoveAll(x => sessions.Any(y => y.Value.Auth.UserId == x)); StateHasChanged(); Console.WriteLine("Rerendered!"); diff --git a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/InviteCounter.razor
index 8f4b4dd..2313884 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..a252e6b 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..b0d5a65 --- /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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + 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..ea39c9a 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..9e70687 --- /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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + 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..b62cf57 --- /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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + 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..5c5946f 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor
@@ -1,6 +1,8 @@ @page "/Tools/Moderation/InviteCounter" @using System.Collections.ObjectModel -@using LibMatrix.EventTypes.Spec.State +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State.RoomInfo +@using LibMatrix.Filters <h3>Invite counter</h3> <hr/> @@ -13,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> @@ -27,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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); if (hs is null) return; - + StateHasChanged(); Console.WriteLine("Rerendered!"); await base.OnInitializedAsync(); @@ -44,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 = [RoomMemberEventContent.EventId] }; + var events = room.GetManyMessagesAsync(limit: int.MaxValue, filter: filter.ToJson(ignoreNull: true, indent: false)); 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; - invites[evt.Sender]++; + if (content?.Membership != "invite") continue; + invites.TryAdd(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/Moderation/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor
index ea1e5f6..8fdad84 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..1ec3cd0 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
@@ -1,31 +1,162 @@ @page "/Tools/Moderation/MembershipHistory" +@using System.Collections.Frozen @using System.Collections.ObjectModel +@using System.Diagnostics +@using System.Text.Json +@using ArcaneLibs.Extensions @using LibMatrix -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo +@using LibMatrix.Filters +@{ + 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 = 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> + <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> + <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"> <option value="">All</option> @@ -44,92 +175,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 +298,289 @@ #region Filter bindings - private bool _chronologicalOrder = false; - - private bool ChronologicalOrder { - get => _chronologicalOrder; - set { - _chronologicalOrder = 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 _showJoins = true; + private bool ShowProfileUpdates { + get => field && DisambiguateProfileUpdates; + set; + } = true; - private bool ShowJoins { - get => _showJoins; + private bool ShowKicks { + 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 { - _showJoins = 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(); } - } - - private bool _showLeaves = true; + } = ""; - private bool ShowLeaves { - get => _showLeaves; - set { - _showLeaves = value; - StateHasChanged(); - } - } +#endregion - private bool _showUpdates = true; + private ObservableCollection<string> Log { get; set; } = new(); + private List<StateEventResponse> Memberships { get; set; } = []; + private AuthenticatedHomeserverGeneric Homeserver { get; set; } - private bool ShowUpdates { - get => _showUpdates; - set { - _showUpdates = value; - StateHasChanged(); - } - } + [Parameter, SupplyParameterFromQuery(Name = "room")] + public string RoomId { get; set; } = ""; - private bool _showKnocks = true; + protected override async Task OnInitializedAsync() { + Log.CollectionChanged += (sender, args) => StateHasChanged(); + Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + if (Homeserver is null) return; - private bool ShowKnocks { - get => _showKnocks; - set { - _showKnocks = value; - StateHasChanged(); - } + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + if (!string.IsNullOrWhiteSpace(RoomId)) + await Execute(); } - private bool _showInvites = true; + private async Task Execute() { + Memberships.Clear(); + var room = Homeserver.GetRoom(RoomId); + var filter = new SyncFilter.EventFilter() { Types = [RoomMemberEventContent.EventId] }; + var events = room.GetManyMessagesAsync(limit: int.MaxValue, filter: filter.ToJson(ignoreNull: true, indent: false)); + await foreach (var resp in events) { + var all = resp.State.Concat(resp.Chunk) + // ugly hack, because some users fuck around too much + .Select(x => { + if (x.RawContent?["displayname"]?.GetValueKind() != JsonValueKind.String) + x.RawContent?.Remove("displayname"); + if (x.RawContent?["avatar_url"]?.GetValueKind() is not JsonValueKind.String) + x.RawContent?.Remove("avatar_url"); + return x; + }); + Memberships.AddRange(all.Where(x => x.Type == RoomMemberEventContent.EventId)); - private bool ShowInvites { - get => _showInvites; - set { - _showInvites = value; - StateHasChanged(); + Log.Add($"Got {resp.State.Count} state and {resp.Chunk.Count} timeline events."); } - } - private bool _showKicks = true; + Log.Add("Reached end of timeline!"); - private bool ShowKicks { - get => _showKicks; - set { - _showKicks = value; - StateHasChanged(); - } + StateHasChanged(); } - private bool _showBans = true; + private readonly struct MembershipEntry { + public required MembershipTransition State { get; init; } + public required StateEventResponse Event { get; init; } + public required StateEventResponse? Previous { get; init; } - private bool ShowBans { - get => _showBans; - set { - _showBans = value; - StateHasChanged(); + public void Deconstruct(out MembershipTransition transition, out StateEventResponse evt, out StateEventResponse? prev) { + transition = State; + evt = Event; + prev = Previous; } } - - private string sender = ""; - - private string Sender { - get => sender; - set { - sender = value; - StateHasChanged(); - } + + private enum MembershipTransition : byte { + None, + Join, + Leave, + Knock, + Invite, + Ban, + + // disambiguated + ProfileUpdate, + Kick, + Unban, + InviteAccepted, + InviteRejected, + InviteRetracted, + KnockAccepted, + KnockRejected, + KnockRetracted } - - private string user = ""; - - private string User { - get => user; - set { - user = value; - StateHasChanged(); + + 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]; } } -#endregion - - private ObservableCollection<string> log { get; set; } = new(); - private List<StateEventResponse> Memberships { get; set; } = []; - 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(); - if (hs is null) return; + 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; + } - StateHasChanged(); - Console.WriteLine("Rerendered!"); - await base.OnInitializedAsync(); - if (!string.IsNullOrWhiteSpace(roomId)) - await Execute(); + 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 async Task Execute() { - Memberships.Clear(); - var room = hs.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"); + 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); } - StateHasChanged(); + 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..736e59a 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..c3cc09c 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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 sessionStore.GetAllSessions(); + var tasks = sessions.Select(async session => { + try { + var _hs = await sessionStore.GetHomeserver(session.Key); + 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.Value.Auth.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.Value.Auth.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,27 @@ } } + 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}", + { Membership: "knock", Reason: null } => $"Knocked at {time}", + { Membership: "knock", Reason: not null } => $"Knocked at {time} for {membership.Reason}", + _ => $"Unknown membership {membership.Membership}, sent at {time} by {state.Sender} 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..ac3c651 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..e5ffd5b 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); if (hs is null) return; - var sessions = await RMUStorage.GetAllTokens(); - foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); + var sessions = await sessionStore.GetAllSessions(); + foreach (var userAuth in sessions.Keys) { + var session = await sessionStore.GetHomeserver(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..c373a37 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); if (hs is null) return; - var sessions = await RMUStorage.GetAllTokens(); - foreach (var userAuth in sessions) { - var session = await RMUStorage.GetSession(userAuth); + var sessions = await sessionStore.GetAllSessions(); + foreach (var userAuth in sessions.Keys) { + var session = await sessionStore.GetHomeserver(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..a393d2e 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..4b8b7c2 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..ccd3e7b 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 Homeserver="@Homeserver" 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 Homeserver="@Homeserver" 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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true); 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..14cf2fc 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 ? 0 : 0; +}); try { builder.Configuration.AddJsonStream(await new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }.GetStreamAsync("/appsettings.json")); @@ -64,5 +78,6 @@ builder.Services.AddScoped<TieredStorageService>(x => ); builder.Services.AddRoryLibMatrixServices(); -builder.Services.AddScoped<RMUStorageWrapper>(); -await builder.Build().RunAsync(); \ No newline at end of file +builder.Services.AddScoped<RmuSessionStore>(); +// 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..eaf7a92 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,6 @@ protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - Homeserver ??= await RMUStorage.GetCurrentSessionOrNavigate(); if(Homeserver is null) return; await _semaphoreSlim.WaitAsync(); @@ -59,7 +58,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..b32735f 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,16 +9,15 @@ <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://matrix.to/#/%23mru%3Arory.gay?via=rory.gay&via=matrix.org&via=feline.support" target="_blank">Matrix</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> <article class="Content px-4"> @Body </article> - - </main> </div> -<UpdateAvailableDetector/> \ No newline at end of file +<UpdateAvailableDetector/> 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..822894a --- /dev/null +++ b/MatrixUtils.Web/Shared/MxcAvatar.razor
@@ -0,0 +1,49 @@ +<MxcImage Homeserver="@Homeserver" Uri="@MxcUri" style="@StyleString"/> + +@code { + private string _style; + + [Parameter] + public string? MxcUri { + get; + set { + if(field == value) return; + field = value; + // UriHasChanged(value); + StateHasChanged(); + } + } + + [Parameter] + public bool Circular { get; set; } + + [Parameter] + public int Size { get; set; } = 48; + + [Parameter] + public string SizeUnit { get; set; } = "px"; + + [Parameter] + public required 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 (string.IsNullOrWhiteSpace(value) || !value.StartsWith(Prefix)) { + // Console.WriteLine($"[MxcAvatar] UriHasChanged: {value} does not start with {Prefix}!"); + // return; + // } + // + // if (Homeserver is null) { + // Console.WriteLine($"[MxcAvatar] Homeserver is required for MxcAvatar! URI: {MxcUri}, Homeserver: {Homeserver?.ToString() ?? "null"}"); + // return; + // } + // + // Console.WriteLine($"[MxcAvatar] Homeserver: {Homeserver}"); + // StateHasChanged(); + // } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/MxcImage.razor b/MatrixUtils.Web/Shared/MxcImage.razor
index e651c3f..26609ee 100644 --- a/MatrixUtils.Web/Shared/MxcImage.razor +++ b/MatrixUtils.Web/Shared/MxcImage.razor
@@ -1,69 +1,69 @@ -<img src="@ResolvedUri" style="@StyleString"/> -@code { - private string _mxcUri; - private string _style; - private string _resolvedUri; +<AuthorizedImage src="@ResolvedUrl" AccessToken="@Homeserver?.AccessToken" style="@StyleString"/> +@code { [Parameter] - public string MxcUri { - get => _mxcUri ?? ""; + public string? Uri { + get; set { - Console.WriteLine($"New MXC uri: {value}"); - _mxcUri = value; + // Console.WriteLine($"New MXC uri: {value}"); + if (field == value) return; + field = value; UriHasChanged(value); } } + [Parameter] public bool Circular { get; set; } - + [Parameter] public int? Width { get; set; } - + [Parameter] public int? Height { get; set; } - + [Parameter] - public string Style { - get => _style; + public string? Style { + get; set { - _style = value; + field = value; StateHasChanged(); } } - + [Parameter] - public RemoteHomeserver? Homeserver { get; set; } + public required AuthenticatedHomeserverGeneric Homeserver { get; set; } - private string ResolvedUri { - get => _resolvedUri; + private string? ResolvedUrl { + get; set { - _resolvedUri = value; + field = value; StateHasChanged(); } } private string StyleString => $"{Style} {(Circular ? "border-radius: 50%;" : "")} {(Width.HasValue ? $"width: {Width}px;" : "")} {(Height.HasValue ? $"height: {Height}px;" : "")} 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; - } - 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]); + // private static readonly string Prefix = "mxc://"; + // private static readonly int PrefixLength = Prefix.Length; + + private async Task UriHasChanged(string? value) { + try { + if (string.IsNullOrWhiteSpace(value)) { + ResolvedUrl = null; + return; + } + + if (Homeserver is null) { + Console.WriteLine($"Homeserver is required for MxcImage! Uri: {value}, Homeserver: {Homeserver?.ToString() ?? "null"}"); + return; + } + + ResolvedUrl = await Homeserver.GetMediaUrlAsync(value); + // Console.WriteLine($"[MxcImage] Resolved URL: {ResolvedUrl}"); + StateHasChanged(); + } catch (Exception e) { + await Console.Error.WriteLineAsync($"Error resolving media URL: {e}"); } - ResolvedUri = Homeserver.ResolveMediaUri(value); - Console.WriteLine($"ResolvedUri: {ResolvedUri}"); } - // [Parameter] - // public string Class { get; set; } - } \ No newline at end of file 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..5819bee 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/RoomList.razor b/MatrixUtils.Web/Shared/RoomList.razor
index 42c5a9f..ba9cd69 100644 --- a/MatrixUtils.Web/Shared/RoomList.razor +++ b/MatrixUtils.Web/Shared/RoomList.razor
@@ -10,7 +10,7 @@ } else { @foreach (var category in RoomsWithTypes.OrderBy(x => x.Value.Count)) { - <RoomListCategory Category="@category" GlobalProfile="@GlobalProfile"></RoomListCategory> + <RoomListCategory Category="@category" GlobalProfile="@GlobalProfile" Homeserver="@Homeserver"></RoomListCategory> } } @@ -35,6 +35,9 @@ else { } [Parameter] + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + + [Parameter] public UserProfileResponse? GlobalProfile { get; set; } [Parameter] diff --git a/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor b/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
index 1f5ce89..1ab0a1a 100644 --- a/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor +++ b/MatrixUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
@@ -1,12 +1,12 @@ +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using MatrixUtils.Web.Classes.Constants -@using LibMatrix.EventTypes.Spec.State @using LibMatrix.Responses @using MatrixUtils.Abstractions <details open> <summary>@RoomType (@Rooms.Count)</summary> @foreach (var room in Rooms) { <div class="room-list-item"> - <RoomListItem RoomInfo="@room" ShowOwnProfile="@(RoomType == "Room")"></RoomListItem> + <RoomListItem RoomInfo="@room" ShowOwnProfile="@(RoomType == "Room")" Homeserver="@Homeserver"/> @* @if (RoomVersionDangerLevel(room) != 0 && *@ @* (room.StateEvents.FirstOrDefault(x=>x.Type == "m.room.power_levels")?.TypedContent is RoomPowerLevelEventContent powerLevels && powerLevels.UserHasPermission(Homeserver.UserId, "m.room.tombstone"))) { *@ @* <MatrixUtils.Web.Shared.SimpleComponents.LinkButton Color="@(RoomVersionDangerLevel(room) == 2 ? "#ff0000" : "#ff8800")" href="@($"/Rooms/Create?Import={room.Room.RoomId}")">Upgrade room</MatrixUtils.Web.Shared.SimpleComponents.LinkButton> *@ @@ -14,10 +14,11 @@ <LinkButton href="@($"/Rooms/{room.Room.RoomId}/Timeline")">View timeline</LinkButton> <LinkButton href="@($"/Rooms/{room.Room.RoomId}/State/View")">View state</LinkButton> <LinkButton href="@($"/Rooms/{room.Room.RoomId}/State/Edit")">Edit state</LinkButton> + <LinkButton href="@($"/Rooms/{room.Room.RoomId}/Upgrade")" Color="#888800">Upgrade/replace room</LinkButton> <LinkButton href="@($"/Tools/LeaveRoom?roomId={room.Room.RoomId}")" Color="#FF0000">Leave room</LinkButton> @if (room.CreationEventContent?.Type == "m.space") { - <RoomListSpace Space="@room"></RoomListSpace> + <RoomListSpace Space="@room" Homeserver="@Homeserver"/> } else if (room.CreationEventContent?.Type == "support.feline.policy.lists.msc.v1" || RoomType == "org.matrix.mjolnir.policy") { <LinkButton href="@($"/Rooms/{room.Room.RoomId}/Policies")">Manage policies</LinkButton> @@ -35,9 +36,9 @@ [Parameter] public UserProfileResponse? GlobalProfile { get; set; } - [CascadingParameter] - public AuthenticatedHomeserverGeneric Homeserver { get; set; } = null!; - + [Parameter] + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + private string RoomType => Category.Key; private List<RoomInfo> Rooms => Category.Value; diff --git a/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor b/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
index 6954990..27f0499 100644 --- a/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor +++ b/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
@@ -35,15 +35,18 @@ set => _breadcrumbs = value; } + [Parameter] + public required AuthenticatedHomeserverGeneric Homeserver { get; set; } + private ObservableCollection<RoomInfo> Children { get; set; } = new(); private Collection<RoomInfo> Unjoined { get; set; } = new(); protected override async Task OnInitializedAsync() { if (Breadcrumbs == null) throw new ArgumentNullException(nameof(Breadcrumbs)); + if (Homeserver is null) throw new ArgumentNullException(nameof(Homeserver)); await Task.Delay(Random.Shared.Next(1000, 10000)); var rooms = Space.Room.AsSpace.GetChildrenAsync(); - var hs = await RMUStorage.GetCurrentSessionOrNavigate(); - var joinedRooms = await hs.GetJoinedRooms(); + var joinedRooms = await Homeserver.GetJoinedRooms(); await foreach (var room in rooms) { if (Breadcrumbs.Contains(room.RoomId)) continue; var roomInfo = KnownRooms.FirstOrDefault(x => x.Room.RoomId == room.RoomId); @@ -51,10 +54,12 @@ roomInfo = new RoomInfo(room); KnownRooms.Add(roomInfo); } - if(joinedRooms.Any(x=>x.RoomId == room.RoomId)) + + if (joinedRooms.Any(x => x.RoomId == room.RoomId)) Children.Add(roomInfo); else Unjoined.Add(roomInfo); } + await base.OnInitializedAsync(); } diff --git a/MatrixUtils.Web/Shared/RoomListItem.razor b/MatrixUtils.Web/Shared/RoomListItem.razor
index bfaa900..2d85f64 100644 --- a/MatrixUtils.Web/Shared/RoomListItem.razor +++ b/MatrixUtils.Web/Shared/RoomListItem.razor
@@ -1,19 +1,26 @@ +@using ArcaneLibs @using LibMatrix -@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.EventTypes.Spec.State.RoomInfo @using LibMatrix.Responses @using MatrixUtils.Abstractions @using MatrixUtils.Web.Classes.Constants @if (RoomInfo is not null) { <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)"/> + <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;" : "")"/> *@ + + @if (!string.IsNullOrWhiteSpace(RoomInfo.RoomIcon)) { + <MxcAvatar Homeserver="@Homeserver" Circular="true" Size="32" MxcUri="@RoomInfo.RoomIcon"/> + } + else { + <img src="@Identicon" width="32" height="32" style="border-radius: 50%;"/> + } <div class="inlineBlock"> <span class="centerVertical">@RoomInfo.RoomName</span> @if (ChildContent is not null) { @@ -42,8 +49,6 @@ else { } } - - [Parameter] public bool ShowOwnProfile { get; set; } = false; @@ -61,27 +66,36 @@ else { OnParametersSetAsync(); } } + + [Parameter] + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } private bool HasOldRoomVersion { get; set; } = false; private bool HasDangerousRoomVersion { get; set; } = false; + private string Identicon { get; set; } + + private static SvgIdenticonGenerator _identiconGenerator = new SvgIdenticonGenerator(); + 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() { + if (RoomInfo is null) return; + Identicon = _identiconGenerator.GenerateAsDataUri(RoomInfo.Room.RoomId); + 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 +141,24 @@ else { protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - hs ??= await RMUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; + // hs ??= await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + // 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 +168,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 +180,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..5084807 100644 --- a/MatrixUtils.Web/Shared/UserListItem.razor +++ b/MatrixUtils.Web/Shared/UserListItem.razor
@@ -23,13 +23,14 @@ [Parameter] public string UserId { get; set; } - private AuthenticatedHomeserverGeneric _homeserver = null!; + [Parameter] + public AuthenticatedHomeserverGeneric _homeserver { get; set; } private SvgIdenticonGenerator _identiconGenerator = new(); protected override async Task OnInitializedAsync() { - _homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); - if (_homeserver is null) return; + // _homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + // if (_homeserver is null) return; if (User == null) { if (UserId == null) { diff --git a/MatrixUtils.Web/_Imports.razor b/MatrixUtils.Web/_Imports.razor
index 81c7874..b5a1316 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 RmuSessionStore sessionStore +@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/